您当前的位置: 首页 >  opencv
  • 2浏览

    0关注

    483博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

OpenCV源码解析:多尺度检测detectMultiScale

高精度计算机视觉 发布时间:2018-08-24 10:07:34 ,浏览量:2

准备工作

本例是一个汽车外形的识别程序。

源码下载:https://download.csdn.net/download/tanmx219/10623808

xml分类器及检测原理

训练得到的xml分类器文件内容如下所示,




  BOOST
  LBP
  24
  48
  
    GAB
    9.9500000476837158e-01
    5.0000000000000000e-01
    9.4999999999999996e-01
    1
    100
  
    256
    1
  10
  
    
    
      3
      -1.0521354675292969e+00
      
        

一般的参数都不难理解,我这里只讲几个比较难理解的参数,

程序中的subsets是指分类器中每个特征树下面的internalNodes节点,

表示这一层有几个弱分类器
表示这一层的threshold,这个threshold要跟所有弱分类器的输出之和比较后然后决定这层的输出时0还是1

internalNodes 表示的是这个弱分类器,以一个实际的internalNodes为例,


          
            0 -1 4 1438712153 -216989541 -1056911107 -721088487
            277360815 1535640456 706631871 1068310687
          
            -8.9545452594757080e-01 7.0357143878936768e-01

其中 0 和 -1表示的是叶节点的索引(leafindex);

后面的那个数值4是predictCategoricalStump函数中的stump.featureIdx,他表示的是这个节点属于哪个feature,即feature的索引,可利用这个索引跟输入图像的序号快速定位输入图像的积分图然后求得这个索引对应的特征值(3.0以前的老版本中对应的是Node->split->var_idx)

后面那8个数是CvDTreeSplit的subset的内容(subsets[0]~subsets[7]),算出来的特征值会跟这个subset里的特定子集比较来看是不是属于这个子集。

leafValues表示左右child的值,二叉树如果向左分裂就是左值,向右分裂就是右值。

从上面图可以看出,OpenCV由弱分类器“并联”组成强分类器,而由强分类器“串联”组成级联分类器。

为了检测到不同大小的目标,一般有两种做法:逐步缩小图像;或者,逐步放大检测窗口。缩小图像就是把图像长宽同时按照一定比例(默认1.1 or 1.2)逐步缩小,然后检测;放大检测窗口是把检测窗口长宽按照一定比例逐步放大,这时位于检测窗口内的特征也会对应放大,然后检测。在默认的情况下,OpenCV是采取逐步缩小的情况,如下图所示,最先检测的图片是底部那张大图。

然后,对应每张图,级联分类器的大小固定的检测窗口器开始遍历图像,以便在图像找到位置不同的目标。对照程序来看,这个固定的大小就是上图的红色框,大小是XML分类器中规定的参数决定的,

24
48

这样,为了找到图像中不同位置的目标,需要逐次移动检测窗口,随着检测窗口的移动,窗口中的特征相应也随着窗口移动,这样就可以遍历到图像中的每一个位置,完成所有的特征检测。

main主程序

如下所示

#include 
#pragma comment(lib, "opencv_world341d.lib")
​
using namespace cv;
using namespace std;
​
int main(int argc, char** argv)
{
​
    String TEST_DIR = "data_file\\test";
    String CAR_CXML = "data_file\\cascade.xml";
    String NAME_WIN = "Entrenar OpenCV";
​
    CascadeClassifier car_detector;
    // read all the data
    if (!car_detector.load(CAR_CXML)) { cout attr = (const char**)(chunk + 1);
                count = 0;
                // 如果链表中还没节点(内容),则该chunk就是第一个,如果已经有了节点,
                // 就把chunk作为下一个节点
                if( !last )
                    first = last = chunk;
                else
                    last = last->next = chunk; 
            }
            last->attr[count*2] = attrname->str.ptr; // last->attr[count*2] 指针指向tag的内存
        }

        if( last ) // 读取value(!last表示标签名称tag, last表示是value值)
        {
            CvFileNode stub;

            // 跳过空格和一些无效字符
            if( *ptr != '=' )
            {
                ptr = icvXMLSkipSpaces( fs, ptr, CV_XML_INSIDE_TAG );
                if( *ptr != '=' )
                    CV_PARSE_ERROR( "Attribute name should be followed by \'=\'" );
            }

            // 确认值是放在双引号之中的
            c = *++ptr;
            if( c != '\"' && c != '\'' )
            {
                ptr = icvXMLSkipSpaces( fs, ptr, CV_XML_INSIDE_TAG );
                if( *ptr != '\"' && *ptr != '\'' )
                    CV_PARSE_ERROR( "Attribute value should be put into single or double quotes" );
            }

            // 取value值,读以的结果保存在stub->data.str
            ptr = icvXMLParseValue( fs, ptr, &stub, CV_NODE_STRING );
            assert( stub.tag == CV_NODE_STRING );
            // 注意,这里last->attr取得的tag和value,只不过是把last->attr的指针指向数据的内存
            last->attr[count*2+1] = stub.data.str.ptr;
            count++;
        }

        c = *ptr;
        have_space = cv_isspace(c) || c == '\0';

        if( c != '>' )
        {
            ptr = icvXMLSkipSpaces( fs, ptr, CV_XML_INSIDE_TAG );
            c = *ptr;
        }

        if( c == '>' )
        {
            if( tag_type == CV_XML_HEADER_TAG )
                CV_PARSE_ERROR( "Invalid closing tag for             
关注
打赏
1661664439
查看更多评论
0.0403s