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 );//合并重叠的矩形区域 }