分類器是如何做檢測的?——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.pswp.cn/news/377248.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/377248.shtml
英文地址,請注明出處:http://en.pswp.cn/news/377248.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

<MySQL>何時使用普通索引,何時使用唯一索引

如果能夠保證業務代碼不會寫入重復數據&#xff0c;就可以繼續往下看。 如果業務不能保證&#xff0c;那么必須創建唯一索引。 關于查詢能力 普通索引和唯一索引在查詢能力上是沒有很大差別的。 如&#xff1a;select id from T where k5 1、普通索引查找到滿足條件的第一個記…

Web版OutLook,利用POP接收郵件服務器郵件

一直想做一個Web版的OutLook&#xff0c;所以才萌生這個想法&#xff0c;其實以前也接觸過這方面的東西。于是上網找了找&#xff0c;漫天的都是Jmail來接收&#xff0c;好吧&#xff0c;既然大家都在用我也就下載下來試試了。 什么&#xff0c;怎么總是報錯呢&#xff1f;原來…

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…

ubuntu 使用

shell 命令歷史搜索 &#xff1a; ctrl r使能 session 選擇界面&#xff1a;安裝gnome-session-fallback安裝lwqq轉載于:https://www.cnblogs.com/JonnyLulu/p/3600263.html

漢字速查使用方法簡介

《漢字速查》&#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…

《MySQL——選錯索引,該如何做》

如果不斷地刪除歷史數據和新增數據&#xff0c;MySQL有時會選錯索引。 選擇索引是優化器的工作&#xff0c;優化器優化時會考慮的因素&#xff1a;掃描行數、是否需要排序、是否使用臨時表 MySQL通過統計索引上的基數&#xff0c;作為索引的區分度。 統計方法時采樣統計&#x…

LPWSTR 類型的實參與const.char *類型形參不兼容

CString csPlus; CString csSummand; m_PlusNumber.GetWindowTextW(csPlus); m_Summand.GetWindowTextW(csSummand); int nPlus atoi(csPlus.GetBuffer(0)); //將編輯框文本轉換成整數// int nPlus atoi(strcpy(csPlus.GetBuffer(10),"aa")); csPlus.ReleaseBu…

空間換時間,把遞歸的時間復雜度降低到O(2n)

遞歸算法的時間復雜度除非只有前兩項&#xff0c;否則都不是線性的&#xff0c;并且相當耗費內存。我們用最常見的的fibonacci數列來說明&#xff1a; function fibonacci(n){if( n 0 || n 1){return n;} else {return fibonacci(n - 1) fibonacci(n - 2);} } 這是一種最常見…

scala char_Scala中的Char數據類型

scala charScala Char數據類型 (Scala Char Data Type) Character (char) in Scala is a data type that is equivalent to 16-bit unsigned integer. The character data type stores a single character. It can be an alphabet, numbers, symbols, etc. The character takes…

《MySQL——給長字符串加索引》

對于長字符串&#xff0c;可用如下方式建立索引&#xff1a; &#xff08;1&#xff09;前綴索引 &#xff08;2&#xff09;字符串倒敘前綴索引 &#xff08;3&#xff09;添加hash字段并在hash字段上加索引 &#xff08;4&#xff09;字段拆分&#xff08;一個字段可拆分為兩…

[藍橋杯歷屆試題] 歐拉與雞蛋

大數學家歐拉在集市上遇到了本村的兩個農婦&#xff0c;每人跨著個空籃子。她們和歐拉打招呼說兩人剛剛賣完了所有的雞蛋。 歐拉隨便問&#xff1a;“賣了多少雞蛋呢&#xff1f;” 不料一個說&#xff1a;“我們兩人自己賣自己的&#xff0c;一共賣了150個雞蛋&#xff0c;雖然…

Python元組練習

Here, we are covering following Python tuple exercises, 在這里&#xff0c;我們將介紹以下Python元組練習 &#xff0c; Creating & printing a tuple 創建和打印元組 Unpacking the tuple into strings 將元組解包成字符串 Create a tuple containing the letters of…

傻瓜教你看清MVC內部執行流程之ViewData數據傳輸,輕松學MVC--①目了然篇(待續)

1.首先在執行到Controller里面的action(方法)時,執行到最后會調用一個View()-->此方法是Controller的一個方法 源代碼: View Code protected internal ViewResult View(){return View(null /* viewName */, null /* masterName */, null /* model */);} 2.然后繼續調用自己…

《MySQL——count()邏輯》

count()用法 count()語義&#xff1a;該函數為一個聚合函數&#xff0c;對于返回的結果集一行行地判斷&#xff0c;如果count函數地參數不是NULL&#xff0c;累計值就加1&#xff0c;否則不加。最后返回累計值。 所以count(*),count(主鍵id)和count(1)都表示返回滿足條件地結果…

phpmailer 發送郵件

<?php /* 可用新浪和網易郵箱測試成功&#xff0c;但QQ不成功&#xff01; 下載 phpmailer 解壓 http://phpmailer.worxware.com/要注意郵件服務器的端口號&#xff0c;默認是 25 不用修改&#xff0c;如果不是則要修改如下&#xff0c;在$mail->IsSMTP() ;下一行加上 $…

靜態負載均衡和動態負載均衡_動態負載平衡

靜態負載均衡和動態負載均衡動態負載平衡 (Dynamic Load Balancing) The algorithm monitors changes on the system workload and redistributes the work accordingly. 該算法監視系統工作負載的變化并相應地重新分配工作。 This algorithm works on three strategies: 該算…

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…

ruby 生成哈希值_哈希== Ruby中的運算符

ruby 生成哈希值In the last article, we have seen how we can compare two hash objects with the help of < operator? "<" method is a public instance method defined in Rubys library. 在上一篇文章中&#xff0c;我們看到了如何在<運算符的幫助下…