分类器是如何做检测的?——CascadeClassifier中的detectMultiScale函数解读

原地址:http://blog.csdn.net/delltdk/article/details/9186875

在进入detectMultiScal函数之前,首先需要对CascadeClassifier做初始化。

1.     初始化——read函数

CascadeClassifier的初始化很简单:

cv::CascadeClassifier classifier;

classifier.load(“cascade.xml”); //这里的xml是训练得到的分类器xml

 

CascadeClassifier类中既有load也有read函数,二者是相同的,load将引用read函数。

1.1   xml的结构

训练得到的分类器以xml形式保存,整体上它包括stageType、featureType、height、width、stageParams、featureParams、stages、features几个节点。

 

图1. 分类器的Xml文件整体结构

除stages和features外,其他主要是一些分类器的参数。

Stages中包含15个stage(训练程序设定),每个stage中包含多个weakClassifiers,而每个weakClassifier中又包含一个internalNodes和一个leafValues。internalNodes中四个变量代表一个node,分别为node中的left/right标记、特征池中的ID和threshold。leafValues中两个变量代表一个node,分别为left leaf的值和right leaf的值。

 

图2. 分类器的Xml文件具体结构

而features是分类器的特征池,每个特征包含一个矩形和要提取的特征序号(0~35)。

 

图3. features的具体结构

1.2   read的过程

下面是read代码,主要包括从xml中获取两部分内容:data和featureEvaluator的读取。

bool CascadeClassifier::read(constFileNode&root)

{

    if( !data.read(root) )//data成员变量的读取

        return false;

 

    // load features---特征的读取

    featureEvaluator= FeatureEvaluator::create(data.featureType);

    FileNodefn =root[CC_FEATURES];

    if( fn.empty() )

        return false;

 

    return featureEvaluator->read(fn);

}

1.2.1         data成员变量的读取

data的读取中同样可以分为两部分:分类器参数读取和stage分类树的建立。

首先是参数部分的获取。

static constfloatTHRESHOLD_EPS= 1e-5f;

 

// load stage params

// stageType为BOOST类型

    string stageTypeStr = (string)root[CC_STAGE_TYPE];

    if( stageTypeStr == CC_BOOST)

        stageType= BOOST;

    else

        return false;

    // 这里以HOG特征分类器为例,featureType=2(HOG)

    string featureTypeStr = (string)root[CC_FEATURE_TYPE];

    if( featureTypeStr == CC_HAAR)

        featureType= FeatureEvaluator::HAAR;

    else if( featureTypeStr== CC_LBP )

        featureType= FeatureEvaluator::LBP;

    else if( featureTypeStr== CC_HOG )

        featureType= FeatureEvaluator::HOG;

 

    else

        return false;

    //检测窗口的最小size,也就是正样本的size

    origWinSize.width = (int)root[CC_WIDTH];

    origWinSize.height = (int)root[CC_HEIGHT];

    CV_Assert(origWinSize.height> 0 &&origWinSize.width > 0 );

    //我训练得到的HOG分类器为true,还不清楚这里的意思

    isStumpBased= (int)(root[CC_STAGE_PARAMS][CC_MAX_DEPTH])== 1 ?true : false;

 

// load feature params

// 载入特征参数,HOG分类器下包括两个参数:maxCatCount和featSize,featSize很透明,就是特征的种类数,这里为36,是指每个block中4个cell、每个cell9个梯度方向的直方图。例如特征号为3时,计算的是当前窗口中划分为4个cell后第一个cell中所有点在120°方向(可能是,这要视起始角度而定)上分量的和,然后经过归一化后的值。对于第二个参数maxCatCount,这里为0,尚不清楚(这是指代表一个弱分类器的树的类别数量,用来计算一棵树的节点大小也就是nodeStep)

    FileNode fn = root[CC_FEATURE_PARAMS];

    if( fn.empty() )

        return false;

 

    ncategories= fn[CC_MAX_CAT_COUNT];

    int subsetSize = (ncategories+ 31)/32,

       nodeStep = 3 + ( ncategories>0 ? subsetSize: 1 );

至此分类器参数读取完毕。

 

接下来是建立分类树,也就是stage部分的载入。

// load stages

    fn = root[CC_STAGES];

    if( fn.empty() )

        return false;

 

    stages.reserve(fn.size());//stages包含15个节点,fn.size()==15

    classifiers.clear();

    nodes.clear();

 

    FileNodeIteratorit =fn.begin(),it_end=fn.end();

 

    for( int si = 0; it != it_end; si++, ++it )//遍历stages

    {

        FileNodefns = *it;

        Stagestage;//stage结构中包含threshold、ntrees和first三个变量

        stage.threshold = (float)fns[CC_STAGE_THRESHOLD]-THRESHOLD_EPS;

        fns= fns[CC_WEAK_CLASSIFIERS];

        if(fns.empty())

            returnfalse;

        stage.ntrees = (int)fns.size();

        stage.first = (int)classifiers.size();//ntrees和first指出该stage中包含的树的数目和起始位置

        stages.push_back(stage);//stage被保存在stage的vector(也就是stages)中

        classifiers.reserve(stages[si].first +stages[si].ntrees);//相应地扩展classifiers的空间,它存储的是这些stage中的weak classifiers,也就是weak trees

 

        FileNodeIteratorit1 =fns.begin(),it1_end=fns.end();//遍历weak classifier

        for( ; it1 != it1_end;++it1 )// weaktrees

        {

            FileNodefnw = *it1;

            FileNodeinternalNodes =fnw[CC_INTERNAL_NODES];

            FileNodeleafValues =fnw[CC_LEAF_VALUES];

            if(internalNodes.empty()||leafValues.empty())

                returnfalse;

 

            DTreetree;

            tree.nodeCount = (int)internalNodes.size()/nodeStep;

            classifiers.push_back(tree);//一个弱分类器或者说一个weak tree中只包含一个int变量,用它在classifiers中的位置和自身来指出它所包含的node个数

 

            nodes.reserve(nodes.size() +tree.nodeCount);

            leaves.reserve(leaves.size() +leafValues.size());//扩展存储node和leaves的vector结构空间

            if(subsetSize > 0 )

                subsets.reserve(subsets.size() +tree.nodeCount*subsetSize);

 

            FileNodeIteratorinternalNodesIter =internalNodes.begin(),internalNodesEnd=internalNodes.end();

//遍历nodes

            for(; internalNodesIter != internalNodesEnd; )//nodes

            {

                DTreeNodenode;//一个node中包含left、right、threshold和featureIdx四个变量。其中left和right是其对应的代号,left=0,right=-1;featureIdx指的是整个分类器中使用的特征池中某个特征的ID,比如共有108个特征,那么featureIdx就在0~107之间;threshold是node的。同时可以看到这里的HOG分类器中每个弱分类器仅包含一个node,也就是仅对某一个特征做判断,而不是多个特征的集合

                node.left = (int)*internalNodesIter; ++internalNodesIter;

                node.right = (int)*internalNodesIter; ++internalNodesIter;

                node.featureIdx = (int)*internalNodesIter; ++internalNodesIter;

                if(subsetSize > 0 )

                {

                    for(intj = 0;j <subsetSize;j++, ++internalNodesIter)

                        subsets.push_back((int)*internalNodesIter);

                    node.threshold = 0.f;

                }

                else

                {

                    node.threshold = (float)*internalNodesIter; ++internalNodesIter;

                }

                nodes.push_back(node);//得到的node将保存在它的vector结构nodes中

            }

 

            internalNodesIter=leafValues.begin(),internalNodesEnd =leafValues.end();

 

            for(; internalNodesIter != internalNodesEnd; ++internalNodesIter)// leaves

                leaves.push_back((float)*internalNodesIter);//leaves中保存相应每个node的left leaf和right leaf的值,因为每个weak tree只有一个node也就分别只有一个left leaf和right leaf,这些将保存在leaves中

        }

    }

通过stage树的建立可以看出最终是获取stages、classifiers、nodes和leaves四个vector变量。其中的nodes和leaves共同组成一系列有序节点,而classifiers中的变量则是在这些节点中查询来构成一个由弱分类器组,它仅仅是把这些弱分类器组合在一起,最后stages中每一个stage也就是一个强分类器,它在classifiers中查询得到自己所属的弱分类器都有哪些,从而构成一个强分类器的基础。

1.2.2         featureEvaluator的读取

完成data部分的载入后,接下来就是特征计算器(featureEvaluator)的载入了。上面每一个node中都会计算特征池中的某一个特征,这个特征以featureIdx出现在node中。现在来看看这些featureIdx背后的内容。

首先要创建某种特征类型的特征计算器,这里支持的是Haar、LBP和HOG三种。

featureEvaluator =FeatureEvaluator::create(data.featureType);

create中生成一个HaarEvaluator/LBPEvaluator/HOGEvaluator对象并返回指针而已。那HOGEvaluators中包含什么内容呢?

这里暂不提其他成员,先介绍一个vector<Feature>的指针 features,也就是存储了一系列Feature对象:

struct Feature

    {

        Feature();

        float calc( int offset )const;

        void updatePtrs( const vector<Mat>&_hist,constMat &_normSum);

        bool read( const FileNode&node); 

 

        enum { CELL_NUM = 4, BIN_NUM= 9 };

 

        Rectrect[CELL_NUM];

        int featComponent; //componentindex from 0 to 35

        const float* pF[4]; //for feature calculation

        const float* pN[4]; //for normalization calculation

};

这里的vector<Feature>将是计算特征的核心,并且featureEvaluator的读入部分主要就是对这个vector变量的内容作初始化,因此在此展示一下。

 

 featureEvaluator创建之后在xml中的features节点下开始读入。

bool HOGEvaluator::read( const FileNode& node)

{

    features->resize(node.size());//node.size()为整个分类器中使用到的特征数量,以我训练的HOG分类器为例包含108个特征

    featuresPtr= &(*features)[0];

    FileNodeIteratorit =node.begin(),it_end=node.end();

    for(inti = 0;it !=it_end;++it,i++)

    {

        if(!featuresPtr[i].read(*it))//遍历所有features并读入到featureEvaluator的features中

            returnfalse;

    }

    return true;

}

Feature的读入程序:

bool HOGEvaluator::Feature :: read(const FileNode&node )

{

    FileNodernode =node[CC_RECT];//rect节点下包括一个矩形和一个特征类型号featComponent

    FileNodeIteratorit =rnode.begin();

    it>> rect[0].x>> rect[0].y>> rect[0].width>> rect[0].height>> featComponent;//featComponent范围在[0,35],36类特征中的一个

    rect[1].x =rect[0].x +rect[0].width;

    rect[1].y =rect[0].y;

    rect[2].x =rect[0].x;

    rect[2].y =rect[0].y +rect[0].height;

    rect[3].x =rect[0].x +rect[0].width;

    rect[3].y =rect[0].y +rect[0].height;

    rect[1].width =rect[2].width =rect[3].width =rect[0].width;

rect[1].height=rect[2].height=rect[3].height=rect[0].height;

//xml中的rect存储的矩形信息与4个矩形之间的关系如下图4所示

 

    return true;

}

 

图4. Rect数组与xml中矩形的关系

这样经过特征读取这一步后,获得了一个特征池,池中每一个特征表示在图中某个矩形位置提取ID为0到35的某个特征量。

 

1.3   read的结果

read的结果一是初始化了分类器的特征类型、最小检测窗口size等参数;二是建立级联的分类器树;三是提取了xml中的特征池。

2.     detectMultiscale函数

在load分类器之后,可以调用该函数对一幅图像做多尺度检测。

2.1   函数自身

//输入参数:image—Mat类型的图像

           objects—检测得到的矩形

           rejectLevels—如果不符合特征的矩形,返回级联分类器中符合的强分类器数

           levelWeights—

          scaleFactor—图像缩放因子

           minNeighbors—

          flags—

           minObjectSize—最小检测窗口大小

           maxObjectSize—最大检测窗口大小

           outputRejectLevels—是否输出rejectLevels和levelWeights,默认为false

voidCascadeClassifier::detectMultiScale(constMat&image,vector<Rect>&objects,vector<int>&rejectLevels,vector<double>&levelWeights,doublescaleFactor,intminNeighbors,intflags,SizeminObjectSize,SizemaxObjectSize,booloutputRejectLevels)

{

    const double GROUP_EPS =0.2;

 

    CV_Assert(scaleFactor > 1 &&image.depth()==CV_8U );//256灰度级且当前缩放因子大于1

 

    if( empty() )//没有载入

        return;

 

    if( isOldFormatCascade() )//这里是指haarTraining得到的分类器或者老版本的OpenCV,我不确定,但是这里可以跳过,因为训练与检测所使用的OpenCV版本是一致的

    {

        MemStoragestorage(cvCreateMemStorage(0));

        CvMat_image =image;

        CvSeq*_objects=cvHaarDetectObjectsForROC(&_image,oldCascade,storage,rejectLevels,levelWeights,scaleFactor,

                                              minNeighbors, flags,minObjectSize,maxObjectSize,outputRejectLevels );

        vector<CvAvgComp>vecAvgComp;

        Seq<CvAvgComp>(_objects).copyTo(vecAvgComp);

        objects.resize(vecAvgComp.size());

        std::transform(vecAvgComp.begin(),vecAvgComp.end(),objects.begin(),getRect());

        return;

    }

 

    objects.clear();

//mask的应用尚不清楚

    if (!maskGenerator.empty()){

        maskGenerator->initializeMask(image);

    }

 

 

    if( maxObjectSize.height== 0 || maxObjectSize.width == 0 )//很明显不能为0

        maxObjectSize= image.size();//默认最大检测size为图像size

 

    Mat grayImage = image;

    if( grayImage.channels()> 1 )//如果是三通道转换为灰度图

    {

        Mat temp;

        cvtColor(grayImage,temp,CV_BGR2GRAY);

        grayImage= temp;

    }

 

    Mat imageBuffer(image.rows + 1,image.cols + 1,CV_8U);

    vector<Rect>candidates;//每个尺度下的图像的检测结果装在该vector中

 

    for( double factor = 1;; factor *= scaleFactor)//对每个尺度下图像检测

    {

        SizeoriginalWindowSize =getOriginalWindowSize();//最小检测窗口size

 

        SizewindowSize(cvRound(originalWindowSize.width*factor),cvRound(originalWindowSize.height*factor) );//当前检测窗口size

        SizescaledImageSize(cvRound(grayImage.cols/factor ),cvRound(grayImage.rows/factor ) );//缩放后图像size

        SizeprocessingRectSize(scaledImageSize.width -originalWindowSize.width + 1,scaledImageSize.height-originalWindowSize.height + 1 );//滑动窗口在宽和高上的滑动距离

 

        if( processingRectSize.width<= 0 || processingRectSize.height <= 0 )

            break;

        if( windowSize.width> maxObjectSize.width|| windowSize.height> maxObjectSize.height)

            break;

        if( windowSize.width< minObjectSize.width|| windowSize.height< minObjectSize.height)

            continue;

 

        Mat scaledImage( scaledImageSize,CV_8U,imageBuffer.data );

        resize(grayImage,scaledImage,scaledImageSize, 0, 0,CV_INTER_LINEAR );//将灰度图resize到scaledImage中,size为当前尺度下的缩放图像

 

        int yStep;//滑动窗口的滑动步长,x和y方向上相同

        if( getFeatureType() == cv::FeatureEvaluator::HOG)

        {

            yStep= 4;

        }

        else

        {

            yStep= factor > 2. ? 1 : 2;//当缩放比例比较大时,滑动步长减小

        }

 

        int stripCount, stripSize;

 

    #ifdef HAVE_TBB

        const intPTS_PER_THREAD = 1000;

        stripCount =((processingRectSize.width/yStep)*(processingRectSize.height + yStep-1)/yStep +PTS_PER_THREAD/2)/PTS_PER_THREAD;

        stripCount =std::min(std::max(stripCount, 1), 100);

        stripSize =(((processingRectSize.height + stripCount - 1)/stripCount +yStep-1)/yStep)*yStep;

    #else

        stripCount= 1;

        stripSize= processingRectSize.height;//y方向上的滑动距离

    #endif

 

        if( !detectSingleScale(scaledImage,stripCount,processingRectSize,stripSize,yStep,factor,candidates,

            rejectLevels,levelWeights,outputRejectLevels) )//对单尺度图像做检测

            break;

    }

 

 

    objects.resize(candidates.size());

    std::copy(candidates.begin(),candidates.end(),objects.begin());//将每个尺度下的检测结果copy到输出vector中

 

    if( outputRejectLevels )//默认为false,不输出rejectLevels

    {

        groupRectangles(objects,rejectLevels,levelWeights,minNeighbors,GROUP_EPS );

    }

    else

    {

        groupRectangles(objects,minNeighbors,GROUP_EPS );//尚未去看

    }

}

 

可以看到detectMultiscale只是对detectSingleScale做了一次多尺度的封装。在单一尺度的图像中detectSingleScale是如何检测的呢?

2.2  detectSingleScale函数

//函数参数设置可以参见detectMultiScale函数

boolCascadeClassifier::detectSingleScale(constMat&image,intstripCount,SizeprocessingRectSize,intstripSize,intyStep,doublefactor,vector<Rect>&candidates,vector<int>&levels,vector<double>&weights,booloutputRejectLevels)

{

    if( !featureEvaluator->setImage(image,data.origWinSize ) )//setImage函数为特征计算做准备,

        return false;

 

    Mat currentMask;

    if (!maskGenerator.empty()){

        currentMask=maskGenerator->generateMask(image);

    }//仍然不解mask的应用,好像没用到?

 

    ConcurrentRectVectorconcurrentCandidates;//在每个平行粒子中访问的检测输出空间

    vector<int>rejectLevels;

    vector<double>levelWeights;

    if( outputRejectLevels )//这里选择的默认false,不返回

    {

       parallel_for(BlockedRange(0,stripCount),CascadeClassifierInvoker(*this,processingRectSize,stripSize,yStep,factor,

            concurrentCandidates,rejectLevels,levelWeights,true,currentMask));

        levels.insert(levels.end(),rejectLevels.begin(),rejectLevels.end() );

        weights.insert(weights.end(),levelWeights.begin(),levelWeights.end() );

    }

    else

    {

        parallel_for(BlockedRange(0,stripCount),CascadeClassifierInvoker(*this,processingRectSize,stripSize,yStep,factor,concurrentCandidates,rejectLevels,levelWeights,false,currentMask));//这里是检测过程中的关键,使用parallel_for是为了TBB加速中使用,生成stripCount个平行线程(每个线程生成一个CascadeClassifierInvoker),在每个CascadeClassifierInvoker中对当前图像做一次检测,这是TBB利用多线程做的加速计算

    }

    candidates.insert(candidates.end(),concurrentCandidates.begin(),concurrentCandidates.end() );//将检测结果加入到输出中

 

    return true;

}

2.2.1         featureEvaluators的setImage函数

   此处仍以HOG为例,其他两个特征的计算可能与之有所不同。

bool HOGEvaluator::setImage( const Mat& image,Size winSize)

{

    int rows = image.rows + 1;

    int cols = image.cols + 1;

    origWinSize= winSize;//最小检测窗口size

    if( image.cols <origWinSize.width||image.rows<origWinSize.height)

        return false;

    hist.clear();//hist为存储Mat类型的vector

    for( int bin = 0; bin < Feature::BIN_NUM;bin++)//BIN_NUM=9,梯度方向分为9个,所以统计得到的Mat个数应当为9个

    {

        hist.push_back(Mat(rows,cols,CV_32FC1) );

    }

    normSum.create(rows,cols,CV_32FC1);//归一化的norm存储空间

 

    integralHistogram(image,hist,normSum,Feature::BIN_NUM );//计算归一化后的直方图

 

    size_t featIdx, featCount= features->size();

//遍历更新特征池中每个特征的HOG特征计算所需要的矩形四个顶点上对应积分图的指针

    for( featIdx = 0; featIdx< featCount; featIdx++)

    {

        featuresPtr[featIdx].updatePtrs(hist,normSum);

    }

    return true;

}

这里的updatePtrs函数是要根据梯度直方图和归一图来更新每个Feature中保存的四个指针,例如某Feature在xml中的形式为0 0 8 8 13,那么它所在的矩形就是cvRect(0,0,16,16),同时featComponent=13,binIdx=featComponent%9=4,cellIdx=featComponent/9=1.那么这个特征就是要计算矩形(8,0,8,8)中梯度方向160°方向上的分量总和。要计算这个特征我们只需要在hist中的第4个Mat中查找出矩形四个顶点上的值就可以了。而Feature中的四个float型指针正是指向hist中这四个值的指针。UpdatePtrs的作用就是要更新这四个指针。具体程序如下:

inline voidHOGEvaluator::Feature ::updatePtrs(constvector<Mat> &_hist,constMat&_normSum )

{

    int binIdx = featComponent% BIN_NUM;//计算要更新的角度

    int cellIdx = featComponent/ BIN_NUM;//计算要更新的cell是哪一个

    Rect normRect = Rect(rect[0].x,rect[0].y,2*rect[0].width,2*rect[0].height);

 

    const float* featBuf = (constfloat*)_hist[binIdx].data;

    size_t featStep = _hist[0].step /sizeof(featBuf[0]);

 

    const float* normBuf = (constfloat*)_normSum.data;

    size_t normStep = _normSum.step /sizeof(normBuf[0]);

 

    CV_SUM_PTRS(pF[0],pF[1],pF[2],pF[3],featBuf,rect[cellIdx],featStep);//更新四个直方积分图中的指针

    CV_SUM_PTRS(pN[0],pN[1],pN[2],pN[3],normBuf,normRect,normStep );//更新四个归一图中的指针

}

2.2.2         CascadeClassifierInvoker类的实例化

每个线程中会生成该类的一个对象,但是这里没有做TBB加速,因而是单线程。该对象的operator中对当前缩放尺度下的图像以滑窗形式扫描,在每个点上做分类器级联检测;如果有TBB加速,每个对象仅检测一行,通过多行一起扫描来加速。

void operator()(constBlockedRange&range)const

    {

        Ptr<FeatureEvaluator>evaluator=classifier->featureEvaluator->clone();//复制featureEvaluator的指针

 

        SizewinSize(cvRound(classifier->data.origWinSize.width*scalingFactor),cvRound(classifier->data.origWinSize.height*scalingFactor));//当前检测窗口的size,其实这里是通过缩放图像来做的,而不是窗口大小的改变

 

        int y1 = range.begin() *stripSize;//range的变化范围为[0,1)

        int y2 = min(range.end() *stripSize,processingRectSize.height);//y方向上的行数不可能超过滑动距离

        for( int y = y1;y <y2;y +=yStep )//遍历所有行

        {

            for(intx = 0;x <processingRectSize.width;x +=yStep )//遍历一行

            {

//依然是尚未搞懂的mask

                if( (!mask.empty())&& (mask.at<uchar>(Point(x,y))==0)) {

                    continue;

                }

 

                doublegypWeight;

                intresult =classifier->runAt(evaluator,Point(x,y),gypWeight);//在当前点提取每个stage中的特征并检验是否满足分类器,result是通过的stage个数的相反数,如果全部通过则为1

                if(rejectLevels )//默认为false

                {

                    if(result == 1 )

                        result =  -(int)classifier->data.stages.size();

                    if(classifier->data.stages.size() +result < 4 )

                    {

                        rectangles->push_back(Rect(cvRound(x*scalingFactor),cvRound(y*scalingFactor),winSize.width,winSize.height));

                        rejectLevels->push_back(-result);

                        levelWeights->push_back(gypWeight);

                    }

                }

                elseif(result> 0 )

                    rectangles->push_back(Rect(cvRound(x*scalingFactor),cvRound(y*scalingFactor),winSize.width,winSize.height));

                if(result == 0 )//保存当前的窗口

                    x+= yStep;

            }

          }

  }

这个程序中唯一需要解释的是CascadeClassifier::runAt函数。对于isStumpBased=true的HOG分类器,返回的结果是predictOrderedStump<HOGEvaluator>(*this, evaluator, weight )this指针是当前CascadeClassifier的指针,evaluator是featureEvaluator的指针,weight为double类型。predictOrderedStump函数如下:

 

template<classFEval>

inline intpredictOrderedStump(CascadeClassifier&cascade,Ptr<FeatureEvaluator> &_featureEvaluator,double&sum)

{

    int nodeOfs = 0, leafOfs= 0;//node和leaf的整体序号

    FEval&featureEvaluator = (FEval&)*_featureEvaluator;

    float* cascadeLeaves = &cascade.data.leaves[0];//定义指向leaves首地址的指针

    CascadeClassifier::Data::DTreeNode*cascadeNodes = &cascade.data.nodes[0];//定义指向nodes首地址的指针

    CascadeClassifier::Data::Stage*cascadeStages = &cascade.data.stages[0];//定义指向stages首地址的指针

 

    int nstages = (int)cascade.data.stages.size();

    for( int stageIdx = 0; stageIdx < nstages;stageIdx++ )

    {

        CascadeClassifier::Data::Stage&stage =cascadeStages[stageIdx];//遍历每个stage

        sum= 0.0;//该stage中的叶节点的和

 

        int ntrees = stage.ntrees;

        for( int i = 0; i < ntrees; i++, nodeOfs++,leafOfs+= 2 )

        {

            CascadeClassifier::Data::DTreeNode&node =cascadeNodes[nodeOfs];//获取当前stage的各个node

            doublevalue =featureEvaluator(node.featureIdx);//这里node的featureIdx指出要计算的是哪一个特征,也就是xml中的哪一个rect,在生成一个HOGEvaluator时就会在operator中根据传入的featureIdx计算特征值,引用到HOGEvaluator中的calc函数

            sum+= cascadeLeaves[ value< node.threshold? leafOfs : leafOfs+ 1 ];//根据node中的threshold得到左叶子或者右叶子的值,加到该stage中的总和

        }

 

        if( sum < stage.threshold )//如果总和大于stage的threshold则通过,小于则退出,并返回当前stage的相反数

            return-stageIdx;

    }

    return 1;

}

Feature中的calc很简单,因为前面已经更新了四个对应于矩形顶点处积分图的指针已经被更新,归一图中的指针也已经被更新。

这里表达的计算如下图所示:

 

图5. 积分图计算示意

要计算D中的值,在积分图中四个顶点的指针所指向的内容分别为A,A+B,A+C和A+B+C+D。因此中间两项与其余两项的差就是要求的D区域了。其中的offset变量是根据滑动窗口的位置确定的,代表上图中D矩形的左上顶点在全图中的位置。程序如下:

首先由如下定义

#define CALC_SUM_(p0,p1,p2,p3,offset)

    ((p0)[offset] - (p1)[offset] - (p2)[offset] + (p3)[offset])  

   

#define CALC_SUM(rect,offset)CALC_SUM_((rect)[0], (rect)[1],(rect)[2], (rect)[3],offset)

然后是Feature中的calc函数

inline floatHOGEvaluator::Feature ::calc(intoffset )const

{

    float res = CALC_SUM(pF,offset);

    float normFactor = CALC_SUM(pN,offset);

    res = (res > 0.001f) ? (res/ (normFactor + 0.001f) ) : 0.f;

    return res;

}

 

编后语: 此处均以HOG特征为例,有关Haar特征和LBP特征的计算部分,可参见

分类器是如何做检测的?——【续】检测中Haar和LBP特征的计算

转载于:https://www.cnblogs.com/lanye/p/3599375.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/377248.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

abs std::abs_ABS的完整形式是什么?

abs std::absABS&#xff1a;防抱死制动系统 (ABS: Anti-lock Braking System) ABS is an abbreviation of the Anti-lock Braking System. It is a safety anti-skid braking system that is used on a variety of aircraft, automobiles and other land vehicles, such as mo…

汉字速查使用方法简介

《汉字速查》&#xff08;HanziSearcher&#xff09;是一个支持全汉字字典和词典的检索工具。其界面如下所示。 界面上方为工具栏。 左方为字典和词典检索栏。 右方在启动时显示版权信息和作者的联系方式&#xff0c;在执行检索时&#xff0c;显示检索结果。 检索方法 汉字速查…

android jni示例_Android服务示例

android jni示例A service is a component that runs in the background for supporting different types of operations that are long running. The user is not interacted with these. These perform task even if application is destroyed. Examples include handling of…

空间换时间,把递归的时间复杂度降低到O(2n)

递归算法的时间复杂度除非只有前两项&#xff0c;否则都不是线性的&#xff0c;并且相当耗费内存。我们用最常见的的fibonacci数列来说明&#xff1a; function fibonacci(n){if( n 0 || n 1){return n;} else {return fibonacci(n - 1) fibonacci(n - 2);} } 这是一种最常见…

《MySQL——给长字符串加索引》

对于长字符串&#xff0c;可用如下方式建立索引&#xff1a; &#xff08;1&#xff09;前缀索引 &#xff08;2&#xff09;字符串倒叙前缀索引 &#xff08;3&#xff09;添加hash字段并在hash字段上加索引 &#xff08;4&#xff09;字段拆分&#xff08;一个字段可拆分为两…

傻瓜教你看清MVC内部执行流程之ViewData数据传输,轻松学MVC--①目了然篇(待续)

1.首先在执行到Controller里面的action(方法)时,执行到最后会调用一个View()-->此方法是Controller的一个方法 源代码: View Code protected internal ViewResult View(){return View(null /* viewName */, null /* masterName */, null /* model */);} 2.然后继续调用自己…

poj 1088

题目&#xff1a;http://poj.org/problem?id1088 记忆化搜索&#xff0c;dp[r][c] max(dp[r - 1][c] , dp[r 1][c] , dp[r][c - 1] , dp[r][c 1]) 1 ( if (题目给的条件满足&#xff09;&#xff09; View Code 1 using namespace std;2 typedef long long ll;3 const in…

《MySQL——order by逻辑(全字段排序与rowid排序)》

创建一个表&#xff0c;然后使用查询语句&#xff1a; 查询城市是“杭州”的所有人名字&#xff0c;并且按照姓名排序返回前 1000 个人的姓名、年龄 create table t (id int(11) not null,city vachar(16) not null,name vachar(16) not null,age vachar(16) not null,addr va…

HTML5 video

摘要&#xff1a;本文主要介绍HTML5 video在android2.2中实现的主要架构和程序流程。 一、实现HTML5 video主要的类 1&#xff0e; 主要类结构及介绍 图1中绿色类为java类&#xff0c;其余为c类&#xff0c;下面是各个类的具体介绍: (1) HTMLElement类不是最上层类&#xff0c…

明源面试

明源面试&#xff0c;笔试题目如下 一、SQL测试题 1 有两张表 根据给出的SQL语句&#xff0c;写出返回的行数分别是多少&#xff1f;为了形象直观的显示&#xff0c;我给出了sql语句执行结果。 A 学生表 B分数表 新题目 select a.* from a inner join b on a.idb.id; …

肯德基收银系统模式_肯德基的完整形式是什么?

肯德基收银系统模式肯德基&#xff1a;肯塔基炸鸡 (KFC: Kentucky Fried Chicken) KFC is an abbreviation of "Kentucky Fried Chicken". It is a fast-food restaurant chain whose specialty is known for fried chicken because of its specialization in it. It…

泛型(CSDN转载)

函数的参数不同叫多态&#xff0c;函数的参数类型可以不确定吗&#xff1f; 函数的返回值只能是一个吗&#xff1f;函数的返回值可以不确定吗&#xff1f; 泛型是一种特殊的类型&#xff0c;它把指定类型的工作推迟到客户端代码声明并实例化类或方法的时候进行。 下面是两个经典…

pvr波形是什么意思_PVR的完整形式是什么?

pvr波形是什么意思PVR&#xff1a;Priya村路演 (PVR: Priya Village Roadshow) PVR is an abbreviation of Priya Village Roadshow. It is one of the biggest and leading multiplex cinema chains in India. PVR是Priya Village Roadshow的缩写 。 它是印度最大和领先的多元…

《MySQL——查询长时间不返回的三种原因与查询慢的原因》

目录查询长时间不返回等MDL锁等flush等行锁查询慢构造一张表&#xff0c;表有两个字段id和c&#xff0c;再里面插入了10万行记录 create table t (id int(11) not null,c int(11) default null,primary key (id) ) engine InnoDB;delimiter ;; create procedure idata() begi…

《MySQL——幻读与next-key lock与间隙锁带来的死锁》

create table t (id int(11) not null,c int(11) default null,d int(11) default null,primary key (id),key c (c) ) engine InnoDB;insert into t values(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);该表除了主键id&#xff0c;还有索引c。 问下面的语句…

css 阴影 效果_CSS阴影效果

css 阴影 效果CSS中的阴影效果 (Shadow Effects in CSS) It is always good to make our web pages stylish and beautiful, web pages that would catch users eyes instantly but one gets confused as to how to style his or her web page. The confusion is quite legit t…

《MySQL——加锁规则(待补全,有些没看懂)》

catalog加锁规则等值查询间隙锁非唯一索引等值锁主键索引范围锁非唯一索引范围锁唯一索引范围锁 bug非唯一索引上存在"等值"的例子limit语句加锁关于死锁总结 1、查询过程中访问到的对象才会加锁&#xff0c;而加锁的基本单位是next-key lock&#xff08;前开后闭&am…

PHP环境搭建:Windows 7下安装配置PHP+Apache+Mysql环境教程

这两天刚装好Windows 7&#xff0c;碰巧前段时间有朋友问我Windows下如何安装搭建PHP环境&#xff0c;所以打算勤劳下&#xff0c;手动一步步搭建PHP环境&#xff0c;暂且不使用PHP环境搭建软件了&#xff0c;在此详细图解在Windows 7下安装配置PHPApacheMysql环境的教程&#…

《MySQL—— 业务高峰期的性能问题的紧急处理的手段 》

catalog短连接风暴先处理占着连接但是不工作地线程减少连接过程的消耗慢查询性能问题索引没有设计好语句没写好选错索引QPS突增问题短连接风暴 正常的短连接&#xff1a; 执行很少sql语句就断开&#xff0c;下次需要的时候再重连。MySQL建立连接的过程成本很高&#xff0c;包含…

《MySQL——redo log 与 binlog 写入机制》

目录binlog写入机制redo log写入机制组提交机制实现大量的TPS理解WAL机制如何提升IO性能瓶颈WAL机制告诉我们&#xff1a;只要redo log与binlog保证持久化到磁盘里&#xff0c;就能确保MySQL异常重启后&#xff0c;数据可以恢复。 下面主要记录一下MySQL写入binlog和redo log的…