OpenCV函式解讀之groupRectangles
阿新 • • 發佈:2019-01-28
不管新版本的CascadeClassifier,還是老版本的HAAR檢測函式cvHaarDetectObjects,都使用了groupRectangles函式進行視窗的組合,其函式原型有以下幾個:
CV_EXPORTS void groupRectangles(CV_OUT CV_IN_OUT vector<Rect>& rectList, int groupThreshold, double eps=0.2);
CV_EXPORTS_W void groupRectangles(CV_OUT CV_IN_OUT vector<Rect>& rectList, CV_OUT vector<int>& weights, int groupThreshold, double eps=0.2);
CV_EXPORTS void groupRectangles( vector<Rect>& rectList, int groupThreshold, double eps, vector<int>* weights, vector<double>* levelWeights );
CV_EXPORTS void groupRectangles(vector<Rect>& rectList, vector<int>& rejectLevels,
vector<double>& levelWeights, int groupThreshold, double eps=0.2);
CV_EXPORTS void groupRectangles_meanshift(vector<Rect>& rectList, vector<double>& foundWeights, vector<double>& foundScales,
double detectThreshold = 0.0, Size winDetSize = Size(64, 128));
最後一個函式新增mean shift進行組合聚類,下面針對groupRectangles函式進行說明(前三個函式都呼叫了引數最多的第四個函式實現):
rectList:帶組合的視窗,即作為輸入又作為輸出
rejectLevels:通過分類器的stage數,一般不小於stage總數-4,也就是weights
levelWeights:通過上述stage數的輸出權重,也就是通過的stage數的所有node之和,裡面即包含left_val又right_val,同一個node只包含其中的一個
groupThreshold:組合閾值,當沒有輸入rejectLevels的時候,當待合併的視窗數大於該閾值的時候才可能進行合併,否則放棄;當輸入rejectLevels的時候,當前組合下通過檢測的stage最大值數大於該閾值的時候才可能進行合併,否則放棄
eps:待合併的兩個視窗的相關性,從矩形所在位置的畫素差值考慮,當eps為0的時候不進行合併,直接返回
該函式的內部執行流程
1) 當組合閾值groupThreshold小於等於0的時候,如果輸出weights,則weights中返回與rectList同樣個數個1,函式直接返回,不進行合併操作
2) 呼叫partition函式對rectList中的矩形進行分類
vector<int> labels;
int nclasses = partition(rectList, labels, SimilarRects(eps));
其中nclasses表示組合類別,labels表示每個rect屬於哪個類別的,相似度計算使用SimilarRects類
值得一提的是,該函式的呼叫必須輸入不相交的計算方法,在groupRectangles函式中使用SimilarRects計算相似度,輸入引數為eps,相似的矩形是要被分為同一類的
SimilarRect中計算相似度的方法:
inline bool operator()(const Rect& r1, const Rect& r2) const
{
// delta為最小長寬的eps倍
double delta = eps*(std::min(r1.width, r2.width) + std::min(r1.height, r2.height))*0.5;
// 如果矩形的四個頂點的位置差別都小於delta,則表示相似的矩形
return std::abs(r1.x - r2.x) <= delta &&
std::abs(r1.y - r2.y) <= delta &&
std::abs(r1.x + r1.width - r2.x - r2.width) <= delta &&
std::abs(r1.y + r1.height - r2.y - r2.height) <= delta;
}
3) 組合分到同一類別的矩形並儲存當前類別下通過stage的最大值以及最大的權重
for( i = 0; i < nlabels; i++ )
{
int cls = labels[i];
rrects[cls].x += rectList[i].x;
rrects[cls].y += rectList[i].y;
rrects[cls].width += rectList[i].width;
rrects[cls].height += rectList[i].height;
rweights[cls]++;
}
for( i = 0; i < nclasses; i++ )
{
Rect r = rrects[i];
float s = 1.f/rweights[i];
rrects[i] = Rect(saturate_cast<int>(r.x*s),
saturate_cast<int>(r.y*s),
saturate_cast<int>(r.width*s),
saturate_cast<int>(r.height*s));
}
for( i = 0; i < nlabels; i++ )
{
int cls = labels[i];
if( (*weights)[i] > rejectLevels[cls] )
{
rejectLevels[cls] = (*weights)[i];
rejectWeights[cls] = (*levelWeights)[i];
}
else if( ( (*weights)[i] == rejectLevels[cls] ) && ( (*levelWeights)[i] > rejectWeights[cls] ) )
rejectWeights[cls] = (*levelWeights)[i];
}
4) 按照groupThreshold合併規則,以及是否存在包含關係輸出合併後的矩形
for( i = 0; i < nclasses; i++ )
{
Rect r1 = rrects[i];
int n1 = levelWeights ? rejectLevels[i] : rweights[i];
double w1 = rejectWeights[i];
// 合併的矩形數小於等於組合閾值不進行輸出
if( n1 <= groupThreshold )
continue;
// filter out small face rectangles inside large rectangles
for( j = 0; j < nclasses; j++ )
{
int n2 = rweights[j];
if( j == i || n2 <= groupThreshold )
continue;
Rect r2 = rrects[j];
int dx = saturate_cast<int>( r2.width * eps );
int dy = saturate_cast<int>( r2.height * eps );
// 當r1在r2的內部的時候,停止
if( i != j &&
r1.x >= r2.x - dx &&
r1.y >= r2.y - dy &&
r1.x + r1.width <= r2.x + r2.width + dx &&
r1.y + r1.height <= r2.y + r2.height + dy &&
(n2 > std::max(3, n1) || n1 < 3) )
break;
}
// r1不在r2的內部時j才可能等於nclasses
if( j == nclasses )
{
rectList.push_back(r1);
if( weights )
weights->push_back(n1);
if( levelWeights )
levelWeights->push_back(w1);
}
}