1、收集相关的资料
1.1、采用opencv_cascadetrain进行训练的步骤及注意事项
1.2、将haartraining 输出的数据stagen.xml 合并为 cascade.xml 文件 (使用了 convert_cascade.c) 48x48 注意中间是x(xyz的x)
1.3、 haartraing : sources\apps\目录下
---------------------------------------------------------------------------------------------------------------------------------------------------------------
void CascadeClassifier::detectMultiScale( const Mat& image, vector<Rect>& objects,
double scaleFactor, int minNeighbors,
int flags, Size minSize )
{
CV_Assert( scaleFactor > 1 && image.depth() == CV_8U );// 尺度比较大约1
if( empty() )
return;
if( !oldCascade.empty() )
{
MemStorage storage(cvCreateMemStorage(0));
CvMat _image = image;
CvSeq* _objects = cvHaarDetectObjects( &_image, oldCascade, storage, scaleFactor,
minNeighbors, flags, minSize );
vector<CvAvgComp> vecAvgComp;
Seq<CvAvgComp>(_objects).copyTo(vecAvgComp);
objects.resize(vecAvgComp.size());
std::transform(vecAvgComp.begin(), vecAvgComp.end(), objects.begin(), getRect());
return;
}
objects.clear();
Mat img = image, imgbuf(image.rows+1, image.cols+1, CV_8U);
if( img.channels() > 1 )// 若是多通道,转成灰度图像进行处理。即后续只支持灰度图像
{
Mat temp;
cvtColor(img, temp, CV_BGR2GRAY);
img = temp;// img贯彻后面,若是单通道直接指向image,否则指向变换后的灰度图像
}
int maxNumThreads = 1;
#ifdef _OPENMP
maxNumThreads = cv::getNumThreads(); //这个线程数用于将Vector分成多个,每个用于一个独立线程,方便后面合并
#endif
vector<vector<Rect> > rects( maxNumThreads );
vector<Rect>* rectsPtr = &rects[0];
vector<Ptr<FeatureEvaluator> > fevals( maxNumThreads );
fevals[0] = feval;// feval特征评估器指针,用于计算和存储特征
Ptr<FeatureEvaluator>* fevalsPtr = &fevals[0];
for( double factor = 1; ; factor *= scaleFactor )//一个尺度一个尺度计算
{
int stripCount, stripSize;
Size winSize( cvRound(origWinSize.width*factor), cvRound(origWinSize.height*factor) );//滑动窗口大小
Size sz( cvRound( img.cols/factor ), cvRound( img.rows/factor ) );//图像按这个系数缩放后大小
Size sz1( sz.width - origWinSize.width, sz.height - origWinSize.height );//图像大小-滑动窗口大小,用于计算滑动次数
if( sz1.width <= 0 || sz1.height <= 0 )//若是成立,说明图像大小比窗口还小
break;
if( winSize.width < minSize.width || winSize.height < minSize.height )//滑动窗口是否满足最小尺寸?
continue;
int yStep = factor > 2. ? 1 : 2;// 加大步长:在小尺度时,步长也小;在大尺度是,步长也加大
if( maxNumThreads > 1 )
{
stripCount = max(min(sz1.height/yStep, maxNumThreads*3), 1);//sz1.height/yStep:可以垂直滑动窗口几次
stripSize = (sz1.height + stripCount - 1)/stripCount;//stripCount:代表分了多少份,stripSize:每份大小
stripSize = (stripSize/yStep)*yStep; //? 可以理解取整吗??
}
else
{
stripCount = 1;
stripSize = sz1.height;
}
Mat img1( sz, CV_8U, imgbuf.data );
resize( img, img1, sz, 0, 0, CV_INTER_LINEAR );//将图像缩小
//Ptr<FeatureEvaluator> feval;是一个成员变量指针,指向了特征评估器
//setImage()方法根据输入图像计算其特征(Haar、LBP)
if( !feval->setImage( img1, origWinSize ) )// 计算Haar特征
break;
for( int i = 1; i < maxNumThreads; i++ )// Ptr<FeatureEvaluator> feval;
fevalsPtr[i] = feval->clone();// clone()复制全特征,并返回指针
#ifdef _OPENMP
#pragma omp parallel for num_threads(maxNumThreads) schedule(dynamic)
#endif
for( int i = 0; i < stripCount; i++ )//此处可以启动多线程
{
int threadIdx = cv::getThreadNum();
int y1 = i*stripSize, y2 = (i+1)*stripSize; //多线程是按高度进行分解的任务,每个线程负责一个区域
if( i == stripCount - 1 || y2 > sz1.height )
y2 = sz1.height;
Size ssz(sz1.width, y2 - y1);
for( int y = y1; y < y2; y += yStep ) // 先按行 //垂直滑动次数,为什么从y1开始,到y2结束呢?
for( int x = 0; x < ssz.width; x += yStep )//再按列(水平滑动);水平滑动次数
{ //runAt():The function returns 1 if the cascade classifier detects an object in the given location.
int r = runAt(fevalsPtr[threadIdx], Point(x,y));//runAt():Runs the detector at the specified point.
if( r > 0 )
rectsPtr[threadIdx].push_back(Rect(cvRound(x*factor), cvRound(y*factor),
winSize.width, winSize.height));
else if( r == 0 )
x += yStep;
}
}
}
for( vector< vector<Rect> >::const_iterator it = rects.begin(); it != rects.end(); it++ )//设计2层Vector的目的是为了支持多线程
objects.insert( objects.end(), it->begin(), it->end() );//将两层Vector合并到一层Vector中
groupRectangles( objects, minNeighbors, 0.2 );//合并重叠的矩形区域
}