原地址: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特征的計算