opencv之HOG源代码注释

【原文:http://blog.csdn.net/antter0510/article/details/20564627

在阅读的过程中主要参考tornadomeet的博文,在这里表示感谢。同时在阅读的过程中也发现了其中的一些不足,在我的注释中会一一指出。由于本人能力有限,对源代码的理解还存在不足,比如usecache部分,还有weight的计算过程都没有进行深究。由于代码本身过长,所以会另外写一篇文章对代码进行分析。下面进入正题,首先是对HOGDescriptor结构体的声明部分进行简单的注释。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. struct CV_EXPORTS_W HOGDescriptor  
  2. {  
  3. public:  
  4.     enum { L2Hys=0 };  
  5.     enum { DEFAULT_NLEVELS=64 };  
  6.   
  7.     CV_WRAP HOGDescriptor() : winSize(64,128), blockSize(16,16), blockStride(8,8),  
  8.         cellSize(8,8), nbins(9), derivAperture(1), winSigma(-1),  
  9.         histogramNormType(HOGDescriptor::L2Hys), L2HysThreshold(0.2), gammaCorrection(true),  
  10.         nlevels(HOGDescriptor::DEFAULT_NLEVELS)  
  11.     {}  
  12.   
  13.     CV_WRAP HOGDescriptor(Size _winSize, Size _blockSize, Size _blockStride,  
  14.                   Size _cellSize, int _nbins, int _derivAperture=1, double _winSigma=-1,  
  15.                   int _histogramNormType=HOGDescriptor::L2Hys,  
  16.                   double _L2HysThreshold=0.2, bool _gammaCorrection=false,  
  17.                   int _nlevels=HOGDescriptor::DEFAULT_NLEVELS)  
  18.     : winSize(_winSize), blockSize(_blockSize), blockStride(_blockStride), cellSize(_cellSize),  
  19.     nbins(_nbins), derivAperture(_derivAperture), winSigma(_winSigma),  
  20.     histogramNormType(_histogramNormType), L2HysThreshold(_L2HysThreshold),  
  21.     gammaCorrection(_gammaCorrection), nlevels(_nlevels)  
  22.     {}  
  23.     //含参及无参构造函数  
  24.     //可以导入文本文件进行初始化  
  25.     CV_WRAP HOGDescriptor(const String& filename)  
  26.     {  
  27.         load(filename);//这里load函数为加载filename中第一个节点信息  
  28.     }  
  29.   
  30.     HOGDescriptor(const HOGDescriptor& d)  
  31.     {  
  32.         d.copyTo(*this);//将d中的描述算子参数赋值给当前描述子  
  33.     }  
  34.   
  35.     virtual ~HOGDescriptor() {}  
  36.   
  37.     //size_t是一个long unsigned int型  
  38.     CV_WRAP size_t getDescriptorSize() const;//获取描述算子维度  
  39.     CV_WRAP bool checkDetectorSize() const;//检查描述算子维数是否合法  
  40.     CV_WRAP double getWinSigma() const;  
  41.   
  42.     //virtual为虚函数,在指针或引用时起函数多态作用  
  43.     CV_WRAP virtual void setSVMDetector(InputArray _svmdetector);//设定SVM向量  
  44.   
  45.     virtual bool read(FileNode& fn);//将节点fn的描述算子参数信息读入,其中可能包含了SVMVec  
  46.     virtual void write(FileStorage& fs, const String& objname) const;//将参数信息写入以objname命名的目标节点中  
  47.   
  48.     CV_WRAP virtual bool load(const String& filename, const String& objname=String());//加载filename中名为objname的节点信息  
  49.     CV_WRAP virtual void save(const String& filename, const String& objname=String()) const;//保存当前参数  
  50.     virtual void copyTo(HOGDescriptor& c) const;//拷贝  
  51.     //计算输入原始图像img的所有扫描窗口或指定扫描窗口(由locations指定)的描述算子集合,并存放在descriptors中  
  52.  CV_WRAP virtual void compute(const Mat& img, CV_OUT vector<float>& descriptors, Size winStride=Size(), Size padding=Size(), const vector<Point>& locations=vector<Point>()) const;  
  53.   
  54.     //对输入原始图像img的整体或部分(由searchLocations指定)进行扫描,并记录检测到含有行人的扫描窗口的矩形信息(由foundLocations保存)及置信度(weights)  
  55.     CV_WRAP virtual void detect(const Mat& img, CV_OUT vector<Point>& foundLocations,  
  56.                         CV_OUT vector<double>& weights,  
  57.                         double hitThreshold=0, Size winStride=Size(),  
  58.                         Size padding=Size(),  
  59.                         const vector<Point>& searchLocations=vector<Point>()) const;  
  60.     //不含weight  
  61.     virtual void detect(const Mat& img, CV_OUT vector<Point>& foundLocations,  
  62.                         double hitThreshold=0, Size winStride=Size(),  
  63.                         Size padding=Size(),  
  64.                         const vector<Point>& searchLocations=vector<Point>()) const;  
  65.     //多尺度扫描,通过对原始图像img进行尺寸变换来实现尺度检测过程  
  66.     CV_WRAP virtual void detectMultiScale(const Mat& img, CV_OUT vector<Rect>& foundLocations,  
  67.                                   CV_OUT vector<double>& foundWeights, double hitThreshold=0,  
  68.                                   Size winStride=Size(), Size padding=Size(), double scale=1.05,  
  69.                                   double finalThreshold=2.0,bool useMeanshiftGrouping = falseconst;  
  70.     //不含weight  
  71.     virtual void detectMultiScale(const Mat& img, CV_OUT vector<Rect>& foundLocations,  
  72.                                   double hitThreshold=0, Size winStride=Size(),  
  73.                                   Size padding=Size(), double scale=1.05,  
  74.                                   double finalThreshold=2.0, bool useMeanshiftGrouping = falseconst;  
  75.     //计算原始输入图像img的梯度幅度图grad,梯度方向图qangle  
  76.     //这里作者出现一个小小的失误,声明变量中的angleOfs,其实应该是qangle  
  77.     CV_WRAP virtual void computeGradient(const Mat& img, CV_OUT Mat& grad, CV_OUT Mat& angleOfs,  
  78.                                  Size paddingTL=Size(), Size paddingBR=Size()) const;  
  79.   
  80.     CV_WRAP static vector<float> getDefaultPeopleDetector();  
  81.     CV_WRAP static vector<float> getDaimlerPeopleDetector();//获得两个检测器?  
  82.   
  83.     CV_PROP Size winSize;//扫描窗口大小,默认(64*128)  
  84.     CV_PROP Size blockSize;//block块大小,默认为(16*16)  
  85.     CV_PROP Size blockStride;//block块每次移动距离,默认为(8*8)  
  86.     CV_PROP Size cellSize;//block中每个cell的大小,默认(8*8)  
  87.     CV_PROP int nbins;//每个cell最终产生梯度直方图的bin个数,默认为9  
  88.     CV_PROP int derivAperture;//不了解  
  89.     CV_PROP double winSigma;//高斯  
  90.     CV_PROP int histogramNormType;//归一化类型?  
  91.     CV_PROP double L2HysThreshold;//归一化操作阈值?  
  92.     CV_PROP bool gammaCorrection;//是否进行伽马校正  
  93.     CV_PROP vector<float> svmDetector;//svm检测器  
  94.     CV_PROP int nlevels;//金字塔层数  
  95. };  


具体的实现细节:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. namespace cv  
  2. {  
  3. //获取描述算子大小  
  4. size_t HOGDescriptor::getDescriptorSize() const  
  5. {  
  6.     CV_Assert(blockSize.width % cellSize.width == 0 &&  
  7.         blockSize.height % cellSize.height == 0);  
  8.     CV_Assert((winSize.width - blockSize.width) % blockStride.width == 0 &&  
  9.         (winSize.height - blockSize.height) % blockStride.height == 0 );  
  10.     return (size_t)nbins*  
  11.         (blockSize.width/cellSize.width)*  
  12.         (blockSize.height/cellSize.height)*  
  13.         ((winSize.width - blockSize.width)/blockStride.width + 1)*  
  14.         ((winSize.height - blockSize.height)/blockStride.height + 1);  
  15.         //9*(16/8)*(16/8)*((64-16)/8+1)*((128-16)/8+1)=9*2*2*7*15=3780,实际上的检测算子为3781,多一维表示偏置  
  16. }  
  17. //获得Sigma值  
  18. double HOGDescriptor::getWinSigma() const  
  19. {  
  20.     return winSigma >= 0 ? winSigma : (blockSize.width + blockSize.height)/8.;  
  21. }  
  22. //检查探测器detector维度:可以为0;descriptor维度;descriptor维度加一  
  23. bool HOGDescriptor::checkDetectorSize() const  
  24. {  
  25.     size_t detectorSize = svmDetector.size(), descriptorSize = getDescriptorSize();  
  26.     return detectorSize == 0 ||  
  27.         detectorSize == descriptorSize ||  
  28.         detectorSize == descriptorSize + 1;  
  29. }  
  30. //根据传递参数设定svmDetector内容,并检查detector尺寸  
  31. void HOGDescriptor::setSVMDetector(InputArray _svmDetector)  
  32. {  
  33.     _svmDetector.getMat().convertTo(svmDetector, CV_32F);  
  34.     CV_Assert( checkDetectorSize() );  
  35. }  
  36.   
  37. #define CV_TYPE_NAME_HOG_DESCRIPTOR "opencv-object-detector-hog"  
  38.   
  39. //读操作,根据fileNode内容对当前HOGdescriptor参数进行设定,其中包可能括svmDetector的设定  
  40. bool HOGDescriptor::read(FileNode& obj)  
  41. {  
  42.     if( !obj.isMap() )  
  43.         return false;  
  44.     FileNodeIterator it = obj["winSize"].begin();  
  45.     it >> winSize.width >> winSize.height;  
  46.     it = obj["blockSize"].begin();  
  47.     it >> blockSize.width >> blockSize.height;  
  48.     it = obj["blockStride"].begin();  
  49.     it >> blockStride.width >> blockStride.height;  
  50.     it = obj["cellSize"].begin();  
  51.     it >> cellSize.width >> cellSize.height;  
  52.     obj["nbins"] >> nbins;  
  53.     obj["derivAperture"] >> derivAperture;  
  54.     obj["winSigma"] >> winSigma;  
  55.     obj["histogramNormType"] >> histogramNormType;  
  56.     obj["L2HysThreshold"] >> L2HysThreshold;  
  57.     obj["gammaCorrection"] >> gammaCorrection;  
  58.   
  59.     FileNode vecNode = obj["SVMDetector"];  
  60.     if( vecNode.isSeq() )  
  61.     {  
  62.         vecNode >> svmDetector;  
  63.         CV_Assert(checkDetectorSize());  
  64.     }  
  65.     return true;  
  66. }  
  67.   
  68. //将当前HOGDescriptor的参数内容写入文件进行保存,并使用objname进行区分。其中可能包括svmDetector内容  
  69. void HOGDescriptor::write(FileStorage& fs, const String& objName) const  
  70. {  
  71.     if( !objName.empty() )  
  72.         fs << objName;  
  73.   
  74.     fs << "{" CV_TYPE_NAME_HOG_DESCRIPTOR  
  75.     << "winSize" << winSize  
  76.     << "blockSize" << blockSize  
  77.     << "blockStride" << blockStride  
  78.     << "cellSize" << cellSize  
  79.     << "nbins" << nbins  
  80.     << "derivAperture" << derivAperture  
  81.     << "winSigma" << getWinSigma()  
  82.     << "histogramNormType" << histogramNormType  
  83.     << "L2HysThreshold" << L2HysThreshold  
  84.     << "gammaCorrection" << gammaCorrection;  
  85.     if( !svmDetector.empty() )  
  86.         fs << "SVMDetector" << "[:" << svmDetector << "]";  
  87.     fs << "}";  
  88. }  
  89. //在文件中读取名为objname节点的信息(信息内容为descriptor参数)  
  90. bool HOGDescriptor::load(const String& filename, const String& objname)  
  91. {  
  92.     FileStorage fs(filename, FileStorage::READ);  
  93.     FileNode obj = !objname.empty() ? fs[objname] : fs.getFirstTopLevelNode();  
  94.     return read(obj);  
  95. }  
  96. //将当前descriptor参数写入名为objname的目标节点  
  97. void HOGDescriptor::save(const String& filename, const String& objName) const  
  98. {  
  99.     FileStorage fs(filename, FileStorage::WRITE);  
  100.     write(fs, !objName.empty() ? objName : FileStorage::getDefaultObjectName(filename));  
  101. }  
  102. //由上可以判定出该fileStorage的结构:objname---param;objname---param;...一个文件中可以含有多组参数,并能够实现随机读取  
  103.   
  104.   
  105. //复制操作,将当前数据复制给descriptor c  
  106. void HOGDescriptor::copyTo(HOGDescriptor& c) const  
  107. {  
  108.     c.winSize = winSize;  
  109.     c.blockSize = blockSize;  
  110.     c.blockStride = blockStride;  
  111.     c.cellSize = cellSize;  
  112.     c.nbins = nbins;  
  113.     c.derivAperture = derivAperture;  
  114.     c.winSigma = winSigma;  
  115.     c.histogramNormType = histogramNormType;  
  116.     c.L2HysThreshold = L2HysThreshold;  
  117.     c.gammaCorrection = gammaCorrection;  
  118.     c.svmDetector = svmDetector;  
  119. }  
  120.   
  121. //计算图像img的梯度幅度图像grad和梯度方向图像qangle.  
  122. //img原始输入图像  
  123. //grad、qangle保存计算结果  
  124. //paddingTL(TopLeft)为需要在原图像img左上角扩增的尺寸,  
  125. //paddingBR(BottomRight)为需要在img图像右下角扩增的尺寸。  
  126. //确定每个像素对应的梯度幅度值及方向  
  127. void HOGDescriptor::computeGradient(const Mat& img, Mat& grad, Mat& qangle,  
  128.                                     Size paddingTL, Size paddingBR) const  
  129. {  
  130.     CV_Assert( img.type() == CV_8U || img.type() == CV_8UC3 );//判定img数据类型  
  131.   
  132.     Size gradsize(img.cols + paddingTL.width + paddingBR.width,  
  133.                   img.rows + paddingTL.height + paddingBR.height);//确定梯度图的尺寸大小,原始图像大小加扩展部分  
  134.     grad.create(gradsize, CV_32FC2);  //  
  135.     qangle.create(gradsize, CV_8UC2); // 均为2通道  
  136.     Size wholeSize;  
  137.     Point roiofs;  
  138.     img.locateROI(wholeSize, roiofs);  
  139.     //locateROI的作用是,定位当前矩阵(可能是原始矩阵中的部分)在原始矩阵中的位置,  
  140.     //whloeSize表示原始矩阵的尺寸,roiofs表示当前矩阵在原始矩阵的相对位置。  
  141.     //这里假设img为独立存在,则wholeSize表示img大小,ofs为(0,0)这里的img是整幅图像,而不是滑动窗口内图像  
  142.   
  143.     int i, x, y;  
  144.     int cn = img.channels();//获得img的通道数  
  145.   
  146.     Mat_<float> _lut(1, 256);  
  147.     const float* lut = &_lut(0,0);//获得指向_lut初始位置的指针  
  148.   
  149.     //对_lut进行初始化操作,这里分两种情形:gamma校正or not,  
  150.     if( gammaCorrection )  
  151.         for( i = 0; i < 256; i++ )  
  152.             _lut(0,i) = std::sqrt((float)i);//所谓校正就是开平方,lut(0,i)取值范围缩小  
  153.     else  
  154.         for( i = 0; i < 256; i++ )  
  155.             _lut(0,i) = (float)i;  
  156.   
  157.     //在opencv的core.hpp里面有AutoBuffer<>()函数,该函数为自动分配一段指定大小的内存,并且可以指定内存中数据的类型。  
  158.     AutoBuffer<int> mapbuf(gradsize.width + gradsize.height + 4);  
  159.     //这里开辟空间大小为梯度图宽+高+4,类型为整型  
  160.   
  161.     int* xmap = (int*)mapbuf + 1;//两个指针,xmap指向mapBuf的头部加一,基本上是width范围内  
  162.     int* ymap = xmap + gradsize.width + 2;//ymap指向hight范围内  
  163.     //简单了解xmap,ymap指向位置 mapBuf :[_[xmap...]_ _[ymap...]_](前者长度为 width,后者长度为 height)  
  164.     const int borderType = (int)BORDER_REFLECT_101;  
  165.     //确定边界扩充类型为BORDER_REFLECT_101,扩充格式如右:BORDER_REFLECT_101   gfedcb|abcdefgh|gfedcba  
  166.   
  167.     /*int borderInterpolate(int p, int len, int borderType) 
  168.       该函数的作用是已知扩展后像素位置,反推该像素在原始图像中的位置,这里的位置的相对坐标原点都是原始图像中的左上方位置 
  169.       p表示在扩展后沿某坐标轴像素位置(img坐标原点),len表示沿该坐标轴原始图像宽度,borderType表示扩展类型 
  170.     */  
  171.     //xmap中内容指向x方向扩展图像grad像素位置(下标)对应原始图像的位置(值),  
  172.     //ymap 指向y方向扩张图像中像素位置对应原始图像中的位置  
  173.     for( x = -1; x < gradsize.width + 1; x++ )  
  174.         xmap[x] = borderInterpolate(x - paddingTL.width + roiofs.x,  
  175.                         wholeSize.width, borderType) - roiofs.x;  
  176.     for( y = -1; y < gradsize.height + 1; y++ )  
  177.         ymap[y] = borderInterpolate(y - paddingTL.height + roiofs.y,  
  178.                         wholeSize.height, borderType) - roiofs.y;  
  179.     //mapBuf最终内容是位置一一对应关系,grad中位置(grad图像坐标原点)--->img中原始位置(原始img图像坐标原点),目的是方便查找  
  180.     //x = -1、width在扩展图像grad中也是不存在的。这里-1的作用就是在y == 0 的时候能够取到其perPixel(grad图像中)  
  181.   
  182.     int width = gradsize.width;  
  183.     AutoBuffer<float> _dbuf(width*4);//创建新的内存单元,用来存储单步计算得得到的 dx、dy、mag、angle  
  184.     float* dbuf = _dbuf  
  185.     //Dx为水平梯度,Dy为垂直梯度,Mag为梯度幅度,Angle为梯度方向,可知在内存中是连续存在的  
  186.     //缓存,暂时存放数据  
  187.     Mat Dx(1, width, CV_32F, dbuf);  
  188.     Mat Dy(1, width, CV_32F, dbuf + width);  
  189.     Mat Mag(1, width, CV_32F, dbuf + width*2);  
  190.     Mat Angle(1, width, CV_32F, dbuf + width*3);//非常不错的编程技巧,对内存单元的灵活把握  
  191.   
  192.     int _nbins = nbins;//得到bin值,默认为9  
  193.     float angleScale = (float)(_nbins/CV_PI);//确定角度值变换系数,用来将得到的角度划分到9个bin中  
  194. #ifdef HAVE_IPP  
  195.     Mat lutimg(img.rows,img.cols,CV_MAKETYPE(CV_32F,cn));  
  196.     Mat hidxs(1, width, CV_32F);  
  197.     Ipp32f* pHidxs  = (Ipp32f*)hidxs.data;  
  198.     Ipp32f* pAngles = (Ipp32f*)Angle.data;  
  199.   
  200.     IppiSize roiSize;  
  201.     roiSize.width = img.cols;  
  202.     roiSize.height = img.rows;  
  203.   
  204.     for( y = 0; y < roiSize.height; y++ )  
  205.     {  
  206.        const uchar* imgPtr = img.data + y*img.step;  
  207.        float* imglutPtr = (float*)(lutimg.data + y*lutimg.step);  
  208.   
  209.        for( x = 0; x < roiSize.width*cn; x++ )  
  210.        {  
  211.           imglutPtr[x] = lut[imgPtr[x]];  
  212.        }  
  213.     }  
  214.   
  215. #endif  
  216.     //按行遍历计算梯度图  
  217.     for( y = 0; y < gradsize.height; y++ )  
  218.     {  
  219. #ifdef HAVE_IPP  
  220.         const float* imgPtr  = (float*)(lutimg.data + lutimg.step*ymap[y]);  
  221.         const float* prevPtr = (float*)(lutimg.data + lutimg.step*ymap[y-1]);  
  222.         const float* nextPtr = (float*)(lutimg.data + lutimg.step*ymap[y+1]);  
  223. #else  
  224.         //获得原始img图像中对应像素,垂直方向上一个像素、下一个像素,简单的图像遍历  
  225.         const uchar* imgPtr  = img.data + img.step*ymap[y];  
  226.         const uchar* prevPtr = img.data + img.step*ymap[y-1];  
  227.         const uchar* nextPtr = img.data + img.step*ymap[y+1];//通过简单的映射表得到梯度图像中每个像素对应的原始img像素  
  228. #endif  
  229.         //获得grad图像当前像素指针,获得梯度方向当前像素指针  
  230.         float* gradPtr = (float*)grad.ptr(y);  
  231.         uchar* qanglePtr = (uchar*)qangle.ptr(y);  
  232.   
  233.         if( cn == 1 )//如果原始img图像为单通道图像  
  234.         {  
  235.             for( x = 0; x < width; x++ )//遍历当前行所有列像素,并将计算结果保存到dbuf中,水平梯度,垂直梯度  
  236.             {  
  237.                 int x1 = xmap[x];//获得x位置对应原始图像中位置x1  
  238. #ifdef HAVE_IPP  
  239.                 dbuf[x] = (float)(imgPtr[xmap[x+1]] - imgPtr[xmap[x-1]]);  
  240.                 dbuf[width + x] = (float)(nextPtr[x1] - prevPtr[x1]);  
  241. #else  
  242.                 //计算水平梯度值  
  243.                 //xmap[x+1]得到原始图像中右侧像素位置,imgPtr[xmap[x+1]]得到该像素的像素值(0~255)  
  244.                 //lut[...]得到float型值,可以进行gamma校正,存在一一映射关系  
  245.                 //dx = [-1 0 +1]  
  246.                 dbuf[x] = (float)(lut[imgPtr[xmap[x+1]]] - lut[imgPtr[xmap[x-1]]]);//Dx  
  247.                 //计算垂直梯度值  
  248.                 //同理dy = [-1 0 +1]T  
  249.                 dbuf[width + x] = (float)(lut[nextPtr[x1]] - lut[prevPtr[x1]]);//Dy  
  250. #endif  
  251.             }  
  252.         }  
  253.         else//原始图像为多通道时  
  254.         {  
  255.             for( x = 0; x < width; x++ )  
  256.             {  
  257.                 int x1 = xmap[x]*3;  
  258.                 float dx0, dy0, dx, dy, mag0, mag;  
  259. #ifdef HAVE_IPP  
  260.                 const float* p2 = imgPtr + xmap[x+1]*3;  
  261.                 const float* p0 = imgPtr + xmap[x-1]*3;  
  262.   
  263.                 dx0 = p2[2] - p0[2];  
  264.                 dy0 = nextPtr[x1+2] - prevPtr[x1+2];  
  265.                 mag0 = dx0*dx0 + dy0*dy0;  
  266.   
  267.                 dx = p2[1] - p0[1];  
  268.                 dy = nextPtr[x1+1] - prevPtr[x1+1];  
  269.                 mag = dx*dx + dy*dy;  
  270.   
  271.                 if( mag0 < mag )  
  272.                 {  
  273.                     dx0 = dx;  
  274.                     dy0 = dy;  
  275.                     mag0 = mag;  
  276.                 }  
  277.   
  278.                 dx = p2[0] - p0[0];  
  279.                 dy = nextPtr[x1] - prevPtr[x1];  
  280.                 mag = dx*dx + dy*dy;  
  281. #else  
  282.                 const uchar* p2 = imgPtr + xmap[x+1]*3;  
  283.                 const uchar* p0 = imgPtr + xmap[x-1]*3;  
  284.   
  285.                 dx0 = lut[p2[2]] - lut[p0[2]];  
  286.                 dy0 = lut[nextPtr[x1+2]] - lut[prevPtr[x1+2]];  
  287.                 mag0 = dx0*dx0 + dy0*dy0;  
  288.   
  289.                 dx = lut[p2[1]] - lut[p0[1]];  
  290.                 dy = lut[nextPtr[x1+1]] - lut[prevPtr[x1+1]];  
  291.                 mag = dx*dx + dy*dy;  
  292.   
  293.                 if( mag0 < mag )  
  294.                 {  
  295.                     dx0 = dx;  
  296.                     dy0 = dy;  
  297.                     mag0 = mag;  
  298.                 }  
  299.   
  300.                 dx = lut[p2[0]] - lut[p0[0]];  
  301.                 dy = lut[nextPtr[x1]] - lut[prevPtr[x1]];  
  302.                 mag = dx*dx + dy*dy;  
  303.  #endif  
  304.                 if( mag0 < mag )  
  305.                 {  
  306.                     dx0 = dx;  
  307.                     dy0 = dy;  
  308.                     mag0 = mag;  
  309.                 }  
  310.   
  311.                 dbuf[x] = dx0;  
  312.                 dbuf[x+width] = dy0;  
  313.                 //最终dbuf中存放的是较大的值,  
  314.                 //三个通道分别计算dx、dy、mag,通过比较mag的值,将最大的一组Dx、Dy进行保存  
  315.             }  
  316.         }  
  317. #ifdef HAVE_IPP  
  318.         ippsCartToPolar_32f((const Ipp32f*)Dx.data, (const Ipp32f*)Dy.data, (Ipp32f*)Mag.data, pAngles, width);  
  319.         for( x = 0; x < width; x++ )  
  320.         {  
  321.            if(pAngles[x] < 0.f)  
  322.              pAngles[x] += (Ipp32f)(CV_PI*2.);  
  323.         }  
  324.   
  325.         ippsNormalize_32f(pAngles, pAngles, width, 0.5f/angleScale, 1.f/angleScale);  
  326.         ippsFloor_32f(pAngles,(Ipp32f*)hidxs.data,width);  
  327.         ippsSub_32f_I((Ipp32f*)hidxs.data,pAngles,width);  
  328.         ippsMul_32f_I((Ipp32f*)Mag.data,pAngles,width);  
  329.   
  330.         ippsSub_32f_I(pAngles,(Ipp32f*)Mag.data,width);  
  331.         ippsRealToCplx_32f((Ipp32f*)Mag.data,pAngles,(Ipp32fc*)gradPtr,width);  
  332. #else  
  333.         //计算二维向量的幅值及角度值,这里的Dx、Dy表示的该行所有像素经由模板计算的到的所有值  
  334.         //mag = sqrt(Dx*Dx + Dy*Dy) angle = atan2(Dy,Dx)*180 / pi  
  335.         //参数false指明这里的Angle得到结果为弧度表示,取值范围是[0,2*pi](tornadomeet中的一个失误,导致其后面的hidx等值的计算均出现问题)  
  336.         cartToPolar( Dx, Dy, Mag, Angle, false );  
  337.         //所有计算值都已经保存到dbuf中了  
  338.   
  339. #endif  
  340.         //遍历该行中的所有像素,将角度值进行划分为九个单元,  
  341.         for( x = 0; x < width; x++ )  
  342.         {  
  343. #ifdef HAVE_IPP  
  344.             int hidx = (int)pHidxs[x];  
  345. #else  
  346.             float mag = dbuf[x+width*2], angle = dbuf[x+width*3]*angleScale - 0.5f; //得到某个像素对应幅值与角度值(-0.5<angle<17.5)  
  347.   
  348.             int hidx = cvFloor(angle);//hidx = {-1,...17},向下取整  
  349.             angle -= hidx;//angle表示与下端bin的弧度值之差  
  350.             gradPtr[x*2] = mag*(1.f - angle);  
  351.             gradPtr[x*2+1] = mag*angle;  
  352.             //利用角度的小数部分作为权重,对梯度幅值重分配  
  353. #endif  
  354.             if( hidx < 0 )  
  355.                 hidx += _nbins;  
  356.             else if( hidx >= _nbins )  
  357.                 hidx -= _nbins;  
  358.             //这里的hidx的求解结果原始为{-1,0,1...8,9,10...16,17}->{8,0,1...8,0,1...7,8}  
  359.             //也就是将[0,2*pi]范围弧度值,分配到9个单元格中  
  360.             assert( (unsigned)hidx < (unsigned)_nbins );//这里的hidx值必定是小于9的  
  361.             qanglePtr[x*2] = (uchar)hidx;  
  362.             hidx++;  
  363.             hidx &= hidx < _nbins ? -1 : 0;  
  364.             //与-1相与为其本身,与0相与为0,因而这里的hidx自加一之后,如果没有超出则保留,超出则置为零  
  365.             qanglePtr[x*2+1] = (uchar)hidx;  
  366.         }  
  367.     }  
  368.     //完成对整个原始img图像的梯度幅值计算,梯度方向值计算  
  369.     //每个像素对应两个梯度值与方向值。根据角度的位置关系,将梯度值信息分配给相邻的两个bins  
  370. }  
  371.   
  372.   
  373. //这里HOGCache结构体的作用是,针对特定输入的img,及block、cell参数  
  374. //计算得到一个普通滑动窗口内所有block的信息,block产生直方图位置,block在滑动窗口内的相对位置  
  375. //单个block中所有像素的对不同cell产生直方图的贡献情况,分三种情况讨论  
  376. //进而在之后的描述算子(直方图)的计算过程中,直接对单个像素套用blockData、pixData信息得到其对应直方图的值  
  377. struct HOGCache  
  378. {  
  379.     struct BlockData//存储block数据内容,1个BlockData结构体是对应的一个block数据  
  380.     {  
  381.         BlockData() : histOfs(0), imgOffset() {}  
  382.         int histOfs;//histOfs表示当前block对整个滑动窗口内HOG描述算子的对应的描述向量的起始位置  
  383.         Point imgOffset;//imgOffset表示为当前block在滑动窗口图片中的相对坐标(当然是指左上角坐标)  
  384.     };  
  385.   
  386.     struct PixData//存取某个像素内容,1个PixData结构体是对应的block中1个像素点的数据  
  387.     {  
  388.         //这里的注释与tornadomeet中给出的含义解释也不同  
  389.         size_t gradOfs, qangleOfs;//gradOfs表示当前像素相对所属block起始位置 在grad图像中的偏移量  
  390.                             //同理qangle  
  391.         int histOfs[4];//histOfs[]//这里指的是当前pixel所贡献cell产生向量相对其所属block向量起始位置的偏移量 (贡献cell最多有4个)  
  392.                     //通过该值可以很直接的确定当前像素对应幅值,方向值的最终归属  
  393.         float histWeights[4];//histWeight[]贡献权重??  
  394.         float gradWeight;//gradWeight表示该点本身由于处在block中位置的不同因而对梯度直方图贡献也不同,  
  395.                          //其权值按照二维高斯分布(以block中心为二维高斯的中心)来决定??  
  396.   
  397.     };  
  398.   
  399.     HOGCache();  
  400.     //含参构造函数,内部调用了init函数  
  401.     HOGCache(const HOGDescriptor* descriptor,  
  402.         const Mat& img, Size paddingTL, Size paddingBR,  
  403.         bool useCache, Size cacheStride);  
  404.     virtual ~HOGCache() {};  
  405.     //完成对HOGCache的初始化工作,细节之后进行讨论  
  406.     virtual void init(const HOGDescriptor* descriptor,  
  407.         const Mat& img, Size paddingTL, Size paddingBR,  
  408.         bool useCache, Size cacheStride);  
  409.   
  410.     Size windowsInImage(Size imageSize, Size winStride) const;//得到单幅图像中扫描窗口的个数  
  411.     Rect getWindow(Size imageSize, Size winStride, int idx) const;//确定idx索引得到的扫描窗口具体信息,返回内容为一个矩阵  
  412.   
  413.     const float* getBlock(Point pt, float* buf);//获得指向block的直方图  
  414.     virtual void normalizeBlockHistogram(float* histogram) const;//归一化block直方图  
  415.   
  416.     vector<PixData> pixData;//存数所有像素的数据  
  417.     vector<BlockData> blockData;//存储单个滑动窗口内所有block的数据  
  418.   
  419.     bool useCache;//标志是否使用缓存  
  420.     vector<int> ymaxCached;//与缓存使用相关,暂时不考虑  
  421.     Size winSize, cacheStride;//winSize,扫描窗口大小;  
  422.     Size nblocks, ncells;//单个滑动窗口内block个数,单个block中cell的个数  
  423.     int blockHistogramSize;//单个block计算得到描述算子维数  
  424.     int count1, count2, count4;//统计一个block中不同类型像素的个数  
  425.     Point imgoffset;//偏移量  
  426.     Mat_<float> blockCache;//与cache相关不先考虑  
  427.     Mat_<uchar> blockCacheFlags;//不先考虑  
  428.   
  429.     Mat grad, qangle;//存储梯度幅度图,梯度方向图  
  430.     const HOGDescriptor* descriptor;//HOG  
  431. };  
  432.   
  433.   
  434. HOGCache::HOGCache()  
  435. {  
  436.     useCache = false;  
  437.     blockHistogramSize = count1 = count2 = count4 = 0;  
  438.     descriptor = 0;  
  439. }  
  440.   
  441. //_useCache == 0  
  442. HOGCache::HOGCache(const HOGDescriptor* _descriptor,  
  443.         const Mat& _img, Size _paddingTL, Size _paddingBR,  
  444.         bool _useCache, Size _cacheStride)  
  445. {  
  446.     init(_descriptor, _img, _paddingTL, _paddingBR, _useCache, _cacheStride);  
  447. }  
  448.   
  449. //对缓存结构体进行初始化操作  
  450. //完成对原始img图像的梯度图grad,方向图qangle的计算工作  
  451. //初始化工作,定位了在一个扫描窗口中每一个block对应整体特征向量初始位置和其在扫描窗口中的相对位置  
  452. //另外明确了一个block中每个像素对应产生贡献的cell,及其分别相应的权重  
  453. void HOGCache::init(const HOGDescriptor* _descriptor,  
  454.         const Mat& _img, Size _paddingTL, Size _paddingBR,  
  455.         bool _useCache, Size _cacheStride)  
  456. {  
  457.     descriptor = _descriptor;  
  458.     cacheStride = _cacheStride;//缓存一次移动的距离??有待进一步精确描述,为winStride与blockStride的最大公约数,  
  459.     useCache = _useCache;//这里的参数输入为0,表示不启用,暂时先不予考虑  
  460.     //这里的grad及qangle都是descriptor结构体内部变量  
  461.     descriptor->computeGradient(_img, grad, qangle, _paddingTL, _paddingBR);//计算当前图像img的梯度方向矩阵  
  462.     //经过计算得到当前img的每个像素的梯度幅度值与方向值(各有两个)  
  463.   
  464.     imgoffset = _paddingTL;//原始img图像填充的尺寸大小  
  465.   
  466.     winSize = descriptor->winSize;//扫描窗口大小(64*128)  
  467.     Size blockSize = descriptor->blockSize;//当前descriptor的block大小,(16*16)  
  468.     Size blockStride = descriptor->blockStride;//表示block每次跨越像素个数(x方向,y方向)  
  469.     Size cellSize = descriptor->cellSize;//cell大小(8*8)  
  470.     Size winSize = descriptor->winSize;//窗口大小(64*128),与上方的winSize重复了吧?  
  471.     int i, j, nbins = descriptor->nbins;//9  
  472.     int rawBlockSize = blockSize.width*blockSize.height;//block内像素个数  
  473.   
  474.     nblocks = Size((winSize.width - blockSize.width)/blockStride.width + 1,  
  475.                    (winSize.height - blockSize.height)/blockStride.height + 1);//一个扫描窗口内包含block个数(7,15)存在重叠  
  476.     ncells = Size(blockSize.width/cellSize.width, blockSize.height/cellSize.height);//一个block中包含cell个数(2,2)  
  477.     blockHistogramSize = ncells.width*ncells.height*nbins;//一个block生成特征向量维数(2*2*9每个cell生成一个直方图,每个直方图含有9个bins)  
  478.   
  479.     if( useCache )//这里的使用缓存的含义是?跳过  
  480.     {  
  481.         Size cacheSize((grad.cols - blockSize.width)/cacheStride.width+1,  
  482.                        (winSize.height/cacheStride.height)+1);//设置缓存大小  
  483.         blockCache.create(cacheSize.height, cacheSize.width*blockHistogramSize);  
  484.         blockCacheFlags.create(cacheSize);  
  485.         size_t i, cacheRows = blockCache.rows;  
  486.         ymaxCached.resize(cacheRows);  
  487.         for( i = 0; i < cacheRows; i++ )  
  488.             ymaxCached[i] = -1;  
  489.     }  
  490.   
  491.     //weights为一个尺寸为blockSize的二维高斯表,下面的代码就是计算二维高斯的系数  
  492.     //作用是参与计算block内像素权重  
  493.     Mat_<float> weights(blockSize);//(16*16)大小  
  494.     float sigma = (float)descriptor->getWinSigma();  
  495.     float scale = 1.f/(sigma*sigma*2);  
  496.     for(i = 0; i < blockSize.height; i++)  
  497.         for(j = 0; j < blockSize.width; j++)  
  498.         {  
  499.             float di = i - blockSize.height*0.5f;  
  500.             float dj = j - blockSize.width*0.5f;  
  501.             weights(i,j) = std::exp(-(di*di + dj*dj)*scale);  
  502.         }  
  503.   
  504.     blockData.resize(nblocks.width*nblocks.height);//共保存width*height数目的block数据,这里的resize有可能重新申请内存空间  
  505.     //这里是简单的一个扫描窗口内含有的还有的block个数  
  506.     pixData.resize(rawBlockSize*3);//rawBlockSize表示block中像素个数,这里*3表示不是三通道而是划分为三类像素区分存放  
  507.                                    //这里申请的内存远远超过所需,但为了方便计算,需先如此申请,后再进行压缩  
  508.   
  509.     //主要是因为这里得到的值,不是梯度方向值,仅仅是用于计算该扫描窗口参与计算描述算子的像素的计算公式。  
  510.     //之后将扫描窗口值进行带入,才得到最终的descriptor。很有可能,  
  511.   
  512.     // Initialize 2 lookup tables, pixData & blockData.//初始化两个查找表:pixData&blockData  
  513.     // Here is why://原因如下  
  514.     //  
  515.     // The detection algorithm runs in 4 nested loops (at each pyramid layer):  
  516.     //  loop over the windows within the input image  
  517.     //    loop over the blocks within each window  
  518.     //      loop over the cells within each block  
  519.     //        loop over the pixels in each cell  
  520.   
  521.     // As each of the loops runs over a 2-dimensional array,  
  522.     // we could get 8(!) nested loops in total, which is very-very slow.  
  523.     // To speed the things up, we do the following:  
  524.     //   1. loop over windows is unrolled in the HOGDescriptor::{compute|detect} methods;  
  525.     //         inside we compute the current search window using getWindow() method.  
  526.     //         Yes, it involves some overhead (function call + couple of divisions),  
  527.     //         but it's tiny in fact.  
  528.     //   2. loop over the blocks is also unrolled. Inside we use pre-computed blockData[j]  
  529.     //         to set up gradient and histogram pointers.  
  530.     //   3. loops over cells and pixels in each cell are merged  
  531.     //       (since there is no overlap between cells, each pixel in the block is processed once)  
  532.     //      and also unrolled. Inside we use PixData[k] to access the gradient values and  
  533.     //      update the histogram  
  534.   
  535.     // 检测过程在每个金字塔层进行四次嵌套查询(暂时没有看出金字塔来),  
  536.     // 利用滑动窗口扫描整幅图像,在滑动窗口内循环遍历每个block,在block中遍历每个cells,在cells中遍历每个像素  
  537.     // 每次检测循环过程都要处理2维数组,这也就是说要进行8次嵌套,这是非常耗时的  
  538.     //   利用HOGDescriptor用的compute|detect方法循环遍历每个窗口,在内部利用getWindow方法计算得到当前窗口值,设计一些开销,但是只是很少一部分  
  539.     //   遍历cell和pixel的方法融合到一起,由于各个cell之间没有重叠,因而各个pixel仅仅计算一次  
  540.     //   同样unrolled,利用PixData[k]得到梯度值,并更新直方图  
  541.     //   遍历block的方法也同样是unrolled的,利用预先计算你的blockData去进一步计算梯度和直方图指针  
  542.   
  543.     //针对block个体,其中包含4个cell,对pixData进行初始化操作,  
  544.     count1 = count2 = count4 = 0;//记录对不同数目cell做出贡献的像素个数  
  545.     //遍历每个像素  
  546.   
  547.     //遍历扫描窗口内某个block  
  548.     //计算单个block中的内所有像素的pixData值  
  549.     //对单个block进行区域划分如下:  
  550.     //{[A][B] [C][D]}  
  551.     //{[E][F] [G][H]}  
  552.     //  
  553.     //{[I][J] [K][L]}  
  554.     //{[M][N] [O][P]}    //参考tornadomeet文章内容  
  555.   
  556.     for( j = 0; j < blockSize.width; j++ )//blockSize.width == 16  
  557.         for( i = 0; i < blockSize.height; i++ )//blockSize.height == 16  
  558.         {  
  559.             PixData* data = 0;//新建PixData指针  
  560.             float cellX = (j+0.5f)/cellSize.width - 0.5f;//cellSize.width == 8  
  561.             int icellX0 = cvFloor(cellX);  
  562.             int icellX1 = icellX0 + 1;  
  563.             cellX -= icellX0;//差值  
  564.             //j = [0,3] icellX0 = -1,icellX1 = 0;  
  565.             //j = [4,11] icellX0 = 0;icellX1 = 1  
  566.             //j = [12,15] icellX0 = 1,icell1 = 2  
  567.   
  568.             float cellY = (i+0.5f)/cellSize.height - 0.5f;  
  569.             int icellY0 = cvFloor(cellY);  
  570.             int icellY1 = icellY0 + 1;  
  571.             cellY -= icellY0;  
  572.             //i = [0,3]  icellY0 = -1,icellY1 = 0  
  573.             //i = [4,11] icellY0 = 0,icellY1 = 1  
  574.             //i = [12,15] icellY0 = 1,icellY1 = 2  
  575.   
  576.             //cellY表示差值  
  577.             //ncells(2,2),宽高均为2  
  578.             if( (unsigned)icellX0 < (unsigned)ncells.width &&  
  579.                 (unsigned)icellX1 < (unsigned)ncells.width )  
  580.             {  
  581.                 if( (unsigned)icellY0 < (unsigned)ncells.height &&  
  582.                     (unsigned)icellY1 < (unsigned)ncells.height )  
  583.                 {  
  584.                     //区域 F、G、J、K  
  585.                     //注意这里的unsigned,这里满足该约束条件的只能是icellX0 == 0;icellY0 == 0  
  586.                     //当前区域内像素对四个cell值均有影响  
  587.                     //需要明确的是,无论怎样,最终的结果仍然是每个cell产生一个九维向量,一个block共4*9 = 36维特征向量  
  588.                     //ncells.height == 2  
  589.                     data = &pixData[rawBlockSize*2 + (count4++)];//跳过前两类,直接对第三类(4)进行赋值操作  
  590.                     data->histOfs[0] = (icellX0*ncells.height + icellY0)*nbins;//0*nbins  
  591.                     data->histWeights[0] = (1.f - cellX)*(1.f - cellY);//权重,比较巧妙的计算,节省很多繁琐的过程  
  592.                     data->histOfs[1] = (icellX1*ncells.height + icellY0)*nbins;//2*nbins  
  593.                     data->histWeights[1] = cellX*(1.f - cellY);  
  594.                     data->histOfs[2] = (icellX0*ncells.height + icellY1)*nbins;//1*bins  
  595.                     data->histWeights[2] = (1.f - cellX)*cellY;  
  596.                     data->histOfs[3] = (icellX1*ncells.height + icellY1)*nbins;//(2 + 1)*bins  
  597.                     data->histWeights[3] = cellX*cellY;  
  598.                     //histOfs表示当前像素对哪个直方图做出贡献,histWeight表示对 对应直方图做出贡献的权重  
  599.                     //其他依次类推  
  600.   
  601.                 }  
  602.                 else  
  603.                 {  
  604.                     //区域B、C、N、O  
  605.                     data = &pixData[rawBlockSize + (count2++)];  
  606.                     if( (unsigned)icellY0 < (unsigned)ncells.height )//unsigned(-1) > 2  
  607.                     {  
  608.                         //N、O  
  609.                         icellY1 = icellY0;//icellY1 = 1,原值为2  
  610.                         cellY = 1.f - cellY;  
  611.                     }  
  612.                     data->histOfs[0] = (icellX0*ncells.height + icellY1)*nbins;  
  613.                     data->histWeights[0] = (1.f - cellX)*cellY;  
  614.                     data->histOfs[1] = (icellX1*ncells.height + icellY1)*nbins;  
  615.                     data->histWeights[1] = cellX*cellY;  
  616.                     //设定两类权重  
  617.                     data->histOfs[2] = data->histOfs[3] = 0;  
  618.                     data->histWeights[2] = data->histWeights[3] = 0;  
  619.                 }  
  620.             }  
  621.             else  
  622.             {  
  623.                 if( (unsigned)icellX0 < (unsigned)ncells.width )//icellX0 == 1  
  624.                 {  
  625.                     icellX1 = icellX0;  
  626.                     cellX = 1.f - cellX;  
  627.                 }  
  628.   
  629.                 if( (unsigned)icellY0 < (unsigned)ncells.height &&  
  630.                     (unsigned)icellY1 < (unsigned)ncells.height )  
  631.                 {  
  632.                     //区域E、H、I、L  
  633.                     data = &pixData[rawBlockSize + (count2++)];  
  634.                     data->histOfs[0] = (icellX1*ncells.height + icellY0)*nbins;  
  635.                     data->histWeights[0] = cellX*(1.f - cellY);  
  636.                     data->histOfs[1] = (icellX1*ncells.height + icellY1)*nbins;  
  637.                     data->histWeights[1] = cellX*cellY;  
  638.                     data->histOfs[2] = data->histOfs[3] = 0;  
  639.                     data->histWeights[2] = data->histWeights[3] = 0;  
  640.                 }  
  641.                 else  
  642.                 {  
  643.                     //区域A、D、M、P  
  644.                     data = &pixData[count1++];  
  645.                     if( (unsigned)icellY0 < (unsigned)ncells.height )  
  646.                     {  
  647.                         icellY1 = icellY0;  
  648.                         cellY = 1.f - cellY;  
  649.                     }  
  650.                     data->histOfs[0] = (icellX1*ncells.height + icellY1)*nbins;  
  651.                     data->histWeights[0] = cellX*cellY;  
  652.                     //近对自身所在cell产生影响  
  653.                     data->histOfs[1] = data->histOfs[2] = data->histOfs[3] = 0;  
  654.                     data->histWeights[1] = data->histWeights[2] = data->histWeights[3] = 0;  
  655.                 }  
  656.             }  
  657.             data->gradOfs = (grad.cols*i + j)*2;//tornadomeet给出的内容表示怀疑  
  658.             data->qangleOfs = (qangle.cols*i + j)*2;//具体含义是,当前坐标(i,j)在grad/qangle图中相对block左上角坐标的位置偏移量  
  659.             data->gradWeight = weights(i,j);//权重,比较容易理解  
  660.         }//for循环结束,完成对pixData的赋值工作,明确一个block中每个像素负责的bins,及其贡献权重  
  661.   
  662.     assert( count1 + count2 + count4 == rawBlockSize );//最终保证每个像素均参与处理,其总和应为rawBlockSize  
  663.     // defragment pixData//碎片整理,保证连续性,也就是对其进行移动  
  664.     for( j = 0; j < count2; j++ )  
  665.         pixData[j + count1] = pixData[j + rawBlockSize];  
  666.     for( j = 0; j < count4; j++ )  
  667.         pixData[j + count1 + count2] = pixData[j + rawBlockSize*2];  
  668.     count2 += count1;  
  669.     count4 += count2;//记录各自的起始位置  
  670.   
  671.     // initialize blockData  
  672.     for( j = 0; j < nblocks.width; j++ )  
  673.         for( i = 0; i < nblocks.height; i++ )  
  674.         {  
  675.             BlockData& data = blockData[j*nblocks.height + i];//得到第(i,j)个block的地址  
  676.             data.histOfs = (j*nblocks.height + i)*blockHistogramSize;//定位当前block产生的描述算子在扫描窗口整体描述算子中的起始位置  
  677.             data.imgOffset = Point(j*blockStride.width,i*blockStride.height);//定位其在扫描窗口内的相对坐标  
  678.         }  
  679. }  
  680.   
  681. //pt为该block在原始img图像中的左上角坐标(可以用来确定其梯度方向值等等),buf为当前block所贡献直方图的起始位置  
  682. //很奇怪的是这里的参数buf指向的内容和最终程序返回的内容是同一个内容吧?解答:在利用cache处理的时候会出现不同的值  
  683. const float* HOGCache::getBlock(Point pt, float* buf)  
  684. {  
  685.     float* blockHist = buf;//得到指向直方图位置的指针  
  686.     assert(descriptor != 0);  
  687.   
  688.     Size blockSize = descriptor->blockSize;//block块的大小(16*16)  
  689.     pt += imgoffset;//pt+填充偏移量,表示当前block在扩展图像中的坐标位置(grad/qangle)  
  690.   
  691.     //验证pt满足约束条件,不能超出grad下边界-XX距离  
  692.     CV_Assert( (unsigned)pt.x <= (unsigned)(grad.cols - blockSize.width) &&  
  693.                (unsigned)pt.y <= (unsigned)(grad.rows - blockSize.height) );  
  694.   
  695.     //使用缓存,暂时不考虑,跳过  
  696.     if( useCache )  
  697.     {  
  698.         CV_Assert( pt.x % cacheStride.width == 0 &&  
  699.                    pt.y % cacheStride.height == 0 );  
  700.         Point cacheIdx(pt.x/cacheStride.width,  
  701.                       (pt.y/cacheStride.height) % blockCache.rows);  
  702.         if( pt.y != ymaxCached[cacheIdx.y] )  
  703.         {  
  704.             Mat_<uchar> cacheRow = blockCacheFlags.row(cacheIdx.y);  
  705.             cacheRow = (uchar)0;  
  706.             ymaxCached[cacheIdx.y] = pt.y;  
  707.         }  
  708.   
  709.         blockHist = &blockCache[cacheIdx.y][cacheIdx.x*blockHistogramSize];  
  710.         uchar& computedFlag = blockCacheFlags(cacheIdx.y, cacheIdx.x);  
  711.         if( computedFlag != 0 )  
  712.             return blockHist;  
  713.         computedFlag = (uchar)1; // set it at once, before actual computing  
  714.     }  
  715.     //分别得到影响不同数量cell的像素个数  
  716.     int k, C1 = count1, C2 = count2, C4 = count4;  
  717.   
  718.     //获得grad、qangle中指向该block位置的指针  
  719.     //step:是一个数组,定义了矩阵的布局step[0]表示第一位的长度,step[1]表示第二维的长度,以此类推  
  720.     const float* gradPtr = (const float*)(grad.data + grad.step*pt.y) + pt.x*2;//*2 的原因是每个单元均为2维元素  
  721.     const uchar* qanglePtr = qangle.data + qangle.step*pt.y + pt.x*2;  
  722.     CV_Assert( blockHist != 0 );  
  723.   
  724. #ifdef HAVE_IPP  
  725.     ippsZero_32f(blockHist,blockHistogramSize);  
  726. #else  
  727.     for( k = 0; k < blockHistogramSize; k++ )  
  728.         blockHist[k] = 0.f;//对当前直方图进行初始化操作,初始化为0.f  
  729. #endif  
  730.   
  731.     const PixData* _pixData = &pixData[0];//获得pixData的指针  
  732.     //pixData的存储方式是连续存放的[...C1...C2...C4],所以可以经由k值依次读取,可以完成对一个block中所有像素的遍历  
  733.     //先对影响个数为1的像素进行统计,也就是四个角的区域  
  734.     for( k = 0; k < C1; k++ )  
  735.     {  
  736.         const PixData& pk = _pixData[k];  
  737.         const float* a = gradPtr + pk.gradOfs;//这里的gradPtr指向的是当前block对应grad中的起始位置,与gradOfs进行相加,得到  
  738.                                               //当前像素在grad中的位置信息,这里因为不再是i、j遍历,所以用这种方式得到当前像素的位置信息  
  739.         float w = pk.gradWeight*pk.histWeights[0];//权重的计算,因为只有一个影响cell,所以只需一次计算  
  740.         const uchar* h = qanglePtr + pk.qangleOfs;  
  741.         int h0 = h[0], h1 = h[1];//得到两个bin方向值  
  742.         float* hist = blockHist + pk.histOfs[0];//确定当前像素影响的cell  
  743.         float t0 = hist[h0] + a[0]*w;//累加,对应不同bin值  
  744.         float t1 = hist[h1] + a[1]*w;//累加  
  745.         hist[h0] = t0; hist[h1] = t1;//对影响cell的对应的直方图进行赋值  
  746.     }  
  747.   
  748.     for( ; k < C2; k++ )//类似计算  
  749.     {  
  750.         const PixData& pk = _pixData[k];  
  751.         const float* a = gradPtr + pk.gradOfs;  
  752.         float w, t0, t1, a0 = a[0], a1 = a[1];  
  753.         const uchar* h = qanglePtr + pk.qangleOfs;  
  754.         int h0 = h[0], h1 = h[1];  
  755.   
  756.         float* hist = blockHist + pk.histOfs[0];  
  757.         w = pk.gradWeight*pk.histWeights[0];  
  758.         t0 = hist[h0] + a0*w;  
  759.         t1 = hist[h1] + a1*w;  
  760.         hist[h0] = t0; hist[h1] = t1;  
  761.   
  762.         hist = blockHist + pk.histOfs[1];  
  763.         w = pk.gradWeight*pk.histWeights[1];  
  764.         t0 = hist[h0] + a0*w;  
  765.         t1 = hist[h1] + a1*w;  
  766.         hist[h0] = t0; hist[h1] = t1;  
  767.     }  
  768.   
  769.     for( ; k < C4; k++ )//类似计算  
  770.     {  
  771.         const PixData& pk = _pixData[k];  
  772.         const float* a = gradPtr + pk.gradOfs;  
  773.         float w, t0, t1, a0 = a[0], a1 = a[1];  
  774.         const uchar* h = qanglePtr + pk.qangleOfs;  
  775.         int h0 = h[0], h1 = h[1];  
  776.   
  777.         float* hist = blockHist + pk.histOfs[0];  
  778.         w = pk.gradWeight*pk.histWeights[0];  
  779.         t0 = hist[h0] + a0*w;  
  780.         t1 = hist[h1] + a1*w;  
  781.         hist[h0] = t0; hist[h1] = t1;  
  782.   
  783.         hist = blockHist + pk.histOfs[1];  
  784.         w = pk.gradWeight*pk.histWeights[1];  
  785.         t0 = hist[h0] + a0*w;  
  786.         t1 = hist[h1] + a1*w;  
  787.         hist[h0] = t0; hist[h1] = t1;  
  788.   
  789.         hist = blockHist + pk.histOfs[2];  
  790.         w = pk.gradWeight*pk.histWeights[2];  
  791.         t0 = hist[h0] + a0*w;  
  792.         t1 = hist[h1] + a1*w;  
  793.         hist[h0] = t0; hist[h1] = t1;  
  794.   
  795.         hist = blockHist + pk.histOfs[3];  
  796.         w = pk.gradWeight*pk.histWeights[3];  
  797.         t0 = hist[h0] + a0*w;  
  798.         t1 = hist[h1] + a1*w;  
  799.         hist[h0] = t0; hist[h1] = t1;  
  800.     }  
  801.   
  802.     normalizeBlockHistogram(blockHist);//对生成的blockHist进行归一化处理  
  803.   
  804.     return blockHist;//将最终得到的block直方图返回  
  805. }  
  806.   
  807. //对block梯度方向直方图进行归一化处理,两次处理..  
  808. void HOGCache::normalizeBlockHistogram(float* _hist) const  
  809. {  
  810.     float* hist = &_hist[0];  
  811. #ifdef HAVE_IPP  
  812.     size_t sz = blockHistogramSize;  
  813. #else  
  814.     size_t i, sz = blockHistogramSize;//blockHistogramSize表示直方图所含维数  
  815. #endif  
  816.   
  817.     float sum = 0;  
  818. #ifdef HAVE_IPP  
  819.     ippsDotProd_32f(hist,hist,sz,&sum);  
  820. #else  
  821.     for( i = 0; i < sz; i++ )  
  822.         sum += hist[i]*hist[i];//平方和?  
  823. #endif  
  824.   
  825.     float scale = 1.f/(std::sqrt(sum)+sz*0.1f), thresh = (float)descriptor->L2HysThreshold;  
  826.     //获得变换系数,及最大阈值  
  827. #ifdef HAVE_IPP  
  828.     ippsMulC_32f_I(scale,hist,sz);  
  829.     ippsThreshold_32f_I( hist, sz, thresh, ippCmpGreater );  
  830.     ippsDotProd_32f(hist,hist,sz,&sum);  
  831. #else  
  832.     for( i = 0, sum = 0; i < sz; i++ )  
  833.     {  
  834.         hist[i] = std::min(hist[i]*scale, thresh);//在第一次的基础上继续求解平方和  
  835.         sum += hist[i]*hist[i];  
  836.     }  
  837. #endif  
  838.   
  839.     scale = 1.f/(std::sqrt(sum)+1e-3f);  
  840. #ifdef HAVE_IPP  
  841.     ippsMulC_32f_I(scale,hist,sz);  
  842. #else  
  843.     for( i = 0; i < sz; i++ )  
  844.         hist[i] *= scale;//直接乘以系数,得到最终的归一化结果  
  845. #endif  
  846. }  
  847.   
  848. //得到单幅图像中扫描窗口的个数  
  849. Size HOGCache::windowsInImage(Size imageSize, Size winStride) const  
  850. {  
  851.     return Size((imageSize.width - winSize.width)/winStride.width + 1,  
  852.                 (imageSize.height - winSize.height)/winStride.height + 1);  
  853. }  
  854.   
  855. //确定idx索引得到的扫描窗口具体位置  
  856. Rect HOGCache::getWindow(Size imageSize, Size winStride, int idx) const  
  857. {  
  858.     int nwindowsX = (imageSize.width - winSize.width)/winStride.width + 1;//x轴方向排列扫描窗口个数  
  859.     int y = idx / nwindowsX;//得到索引idx扫描窗口的y轴坐标  
  860.     int x = idx - nwindowsX*y;//得到索引idx扫描窗口的x轴坐标  
  861.     return Rect( x*winStride.width, y*winStride.height, winSize.width, winSize.height );  
  862.     //确定idx个扫描串口所在的位置,主要是左上角坐标,其宽度与高度已经确定  
  863. }  
  864.   
  865. //HOGCache结束..  
  866. //HOGCache是一次计算得到直方图计算公式,之后利用grad、qangle套用公式则可以得到每个扫描窗口的特征向量  
  867.   
  868.   
  869. //这里计算的最终结果是输入图像中 所有扫描窗口或若干指定扫描窗口(locations决定)的特征向量的集合  
  870. //img,输入图像;descriptors;计算得到的特征向量,winStride,扫描窗口每次移动位置;padding,填充区域大小  
  871. //locations,定位若干个扫描窗口进行特征向量的计算  
  872. void HOGDescriptor::compute(const Mat& img, vector<float>& descriptors,  
  873.                             Size winStride, Size padding,  
  874.                             const vector<Point>& locations) const  
  875. {  
  876.     if( winStride == Size() )  
  877.         winStride = cellSize;//如果winStride的为空,则将其设定为cellSize,这里的winStride是滑动窗口每次移动的距离  
  878.     //gcd(x,y)求两数的最大公约数,  
  879.     Size cacheStride(gcd(winStride.width, blockStride.width),  
  880.                      gcd(winStride.height, blockStride.height));//缓存每次移动距离,在useCache == 0的情形下,并没有发挥作用,  
  881.     size_t nwindows = locations.size();//确定扫描窗口的个数  
  882.     //alignSize(m, n)返回n的倍数大于等于m的最小值eg.alignSize(7,3) == 9  
  883.     padding.width = (int)alignSize(std::max(padding.width, 0), cacheStride.width);  
  884.     padding.height = (int)alignSize(std::max(padding.height, 0), cacheStride.height);  
  885.   
  886.     Size paddedImgSize(img.cols + padding.width*2, img.rows + padding.height*2);//确定填充后img的尺寸,这里的填充是为了计算梯度值  
  887.   
  888.     //对原始图像进行缓冲结构体的计算,  
  889.     //每次都要进行一次计算,不是一种浪费么?== 这里的compute的函数是在怎样的情形下进行调用的还不清楚,也许只调用一次呢  
  890.     HOGCache cache(this, img, padding, padding, nwindows == 0, cacheStride);//得到HOG缓冲体  
  891.     //完成梯度方向图的计算,并对blockData及pixData进行初始化操作,  
  892.   
  893.     if( !nwindows )//如果nwindows == 0,则表示locations为空,则对所有扫描窗口进行计算  
  894.         nwindows = cache.windowsInImage(paddedImgSize, winStride).area();//整幅图像中包含扫描窗口个数  
  895.   
  896.     const HOGCache::BlockData* blockData = &cache.blockData[0];//获得缓冲体内block指针  
  897.   
  898.     int nblocks = cache.nblocks.area();//单个扫描窗口内包含block的个数  
  899.     int blockHistogramSize = cache.blockHistogramSize;//单个block贡献直方图维数  
  900.     size_t dsize = getDescriptorSize();//获得一个滑动窗口内特征向量大小  
  901.     descriptors.resize(dsize*nwindows);//整幅原始img图像所包含特征向量维数(若干个扫描窗口的特征向量集合)  
  902.   
  903.     forsize_t i = 0; i < nwindows; i++ )//对每个扫描窗口进行计算,这里要逐一计算特征向量  
  904.     {  
  905.         float* descriptor = &descriptors[i*dsize];//得到该扫描窗口对应特征向量在descriptors中的起始位置,  
  906.         Point pt0;  
  907.         if( !locations.empty() )//非空,表示存在若干个指定位置的扫描窗口  
  908.         {  
  909.             pt0 = locations[i];  
  910.             //不满足约束条件 继续,不进行处理  
  911.             if( pt0.x < -padding.width || pt0.x > img.cols + padding.width - winSize.width ||  
  912.                 pt0.y < -padding.height || pt0.y > img.rows + padding.height - winSize.height )  
  913.                 continue;  
  914.         }  
  915.         else//并未指定,则依次遍历  
  916.         {  
  917.             pt0 = cache.getWindow(paddedImgSize, winStride, (int)i).tl() - Point(padding);//第i个扫描窗口在原始img图像中的左上坐标  
  918.             CV_Assert(pt0.x % cacheStride.width == 0 && pt0.y % cacheStride.height == 0);  
  919.         }  
  920.         //pt0,表示扫描窗口在原始img图像中的位置  
  921.         //针对单个扫描窗口内,计算其每个block直方图值,  
  922.         forint j = 0; j < nblocks; j++ )  
  923.         {  
  924.             const HOGCache::BlockData& bj = blockData[j];//定位当前block在blockData中的位置  
  925.             Point pt = pt0 + bj.imgOffset;//该block在原始img中的左上角坐标:扫描窗口在img中坐标+block在扫描窗口中坐标  
  926.   
  927.             float* dst = descriptor + bj.histOfs;//当前block贡献直方图的起始位置  
  928.             const float* src = cache.getBlock(pt, dst);//计算当前block直方图,并得到指向指针  
  929.             if( src != dst )//一般情况下是应该相等的,什么时候会出现不相等的情形,利用cache进行加速的时候  
  930. #ifdef HAVE_IPP  
  931.                ippsCopy_32f(src,dst,blockHistogramSize);  
  932. #else  
  933.                 forint k = 0; k < blockHistogramSize; k++ )  
  934.                     dst[k] = src[k];//利用计算得到的直方图对最终直方图(descriptors)中的对应区域进行赋值操作。  
  935. #endif  
  936.         }  
  937.     }  
  938.     //对所有扫描窗口进行计算特征向量并保存到descriptors中  
  939. }  
  940.   
  941. //检测过程,是对单幅图像进行的检测过程,可以利用这里的locations做点文章  
  942. //无论怎样这里的grad、qangle值 还有blockdata、pixData都是要进行计算的,  
  943. //可以进一步优化的地方在于对于blockData、pixData的计算结果进行保存,在相同尺寸的原始图像中不需要再次进行计算  
  944. //img待检测图像;hits存储检测到行人的扫描窗口的左上角坐标,  
  945. //weights存储检测到行人的扫描窗口的s值  
  946. //hitThreshold检测阈值;winstride,padding,locations  
  947. void HOGDescriptor::detect(const Mat& img,  
  948.     vector<Point>& hits, vector<double>& weights, double hitThreshold,   
  949.     Size winStride, Size padding, const vector<Point>& locations) const  
  950. {  
  951.     hits.clear();  
  952.     if( svmDetector.empty() )  
  953.         return;  
  954.   
  955.     if( winStride == Size() )  
  956.         winStride = cellSize;  
  957.     Size cacheStride(gcd(winStride.width, blockStride.width),  
  958.                      gcd(winStride.height, blockStride.height));  
  959.     size_t nwindows = locations.size();  
  960.     //alignSize(m, n)返回n的倍数大于等于m的最小值eg.alignSize(7,3) == 9  
  961.     padding.width = (int)alignSize(std::max(padding.width, 0), cacheStride.width);  
  962.     padding.height = (int)alignSize(std::max(padding.height, 0), cacheStride.height);  
  963.     Size paddedImgSize(img.cols + padding.width*2, img.rows + padding.height*2);//确定填充后图像尺寸  
  964.   
  965.     HOGCache cache(this, img, padding, padding, nwindows == 0, cacheStride);  
  966.     //完成梯度图,方向图的计算及blockData、pixData的初始化工作  
  967.   
  968.     if( !nwindows )  
  969.         nwindows = cache.windowsInImage(paddedImgSize, winStride).area();  
  970.   
  971.     const HOGCache::BlockData* blockData = &cache.blockData[0];  
  972.   
  973.     int nblocks = cache.nblocks.area();//一个扫描窗口中block的个数  
  974.     int blockHistogramSize = cache.blockHistogramSize;//一个block中特征向量的维数  
  975.     size_t dsize = getDescriptorSize();//一个扫描窗口中特征向量的维数  
  976.   
  977.     double rho = svmDetector.size() > dsize ? svmDetector[dsize] : 0;//暂时不了解含义  
  978.     vector<float> blockHist(blockHistogramSize);//确定维数的block直方图  
  979.   
  980.     forsize_t i = 0; i < nwindows; i++ )//对所有待处理扫描窗口进行计算  
  981.     {  
  982.         Point pt0;  
  983.         if( !locations.empty() )//locations非空  
  984.         {  
  985.             pt0 = locations[i];  
  986.             //不满足约束条件  
  987.             if( pt0.x < -padding.width || pt0.x > img.cols + padding.width - winSize.width ||  
  988.                 pt0.y < -padding.height || pt0.y > img.rows + padding.height - winSize.height )  
  989.                 continue;  
  990.         }  
  991.         else  
  992.         {  
  993.             pt0 = cache.getWindow(paddedImgSize, winStride, (int)i).tl() - Point(padding);  
  994.             CV_Assert(pt0.x % cacheStride.width == 0 && pt0.y % cacheStride.height == 0);  
  995.         }  
  996.         double s = rho;  
  997.         const float* svmVec = &svmDetector[0];//指向检测器  
  998. #ifdef HAVE_IPP  
  999.         int j;  
  1000. #else  
  1001.         int j, k;  
  1002. #endif  
  1003.         //对当前指定扫描窗口进行计算,对包含的每个block进行一个判别过程  
  1004.         for( j = 0; j < nblocks; j++, svmVec += blockHistogramSize )  
  1005.         {  
  1006.             const HOGCache::BlockData& bj = blockData[j];//一个扫描窗口中第j个block的信息  
  1007.             Point pt = pt0 + bj.imgOffset;//当前block在原始img图像中的位置  
  1008.   
  1009.             const float* vec = cache.getBlock(pt, &blockHist[0]);//得到当前block的直方图  
  1010. #ifdef HAVE_IPP  
  1011.             Ipp32f partSum;  
  1012.             ippsDotProd_32f(vec,svmVec,blockHistogramSize,&partSum);  
  1013.             s += (double)partSum;  
  1014. #else  
  1015.             //每个产生的block描述算子分别参与分类判别  
  1016.             for( k = 0; k <= blockHistogramSize - 4; k += 4 )  
  1017.                 s += vec[k]*svmVec[k] + vec[k+1]*svmVec[k+1] +  
  1018.                     vec[k+2]*svmVec[k+2] + vec[k+3]*svmVec[k+3];  
  1019.             for( ; k < blockHistogramSize; k++ )  
  1020.                 s += vec[k]*svmVec[k];  
  1021.             //需要了解的是这里个svmVec的内容是什么,与特征向量同等维度的向量值,然后呢  
  1022.             //s = vec * svmVec  
  1023.             //设定4是为了减少循环次数,  
  1024.             //又需要明确svmVec的产生过程,这里先暂时放在这里,只需要知道S = vec * svmVec,之后与阈值hitThreshold进行判断,足矣,  
  1025. #endif  
  1026.         }  
  1027.         if( s >= hitThreshold )//超过既定阈值,说明包含行人  
  1028.         {  
  1029.             hits.push_back(pt0);  
  1030.             weights.push_back(s);  
  1031.         }  
  1032.     }  
  1033. }  
  1034.   
  1035. //不保留weights的检测过程  
  1036. void HOGDescriptor::detect(const Mat& img, vector<Point>& hits, double hitThreshold,   
  1037.                            Size winStride, Size padding, const vector<Point>& locations) const  
  1038. {  
  1039.     vector<double> weightsV;  
  1040.     detect(img, hits, weightsV, hitThreshold, winStride, padding, locations);  
  1041. }  
  1042.   
  1043. //这个结构体的作用又是什么呢?在之后的detectMultiScale中会用到  
  1044.   
  1045. struct HOGInvoker//构造一个供parallel_for使用的循环结构体  
  1046. {  
  1047.     //出现金字塔相关内容了:levelScale  
  1048.     HOGInvoker( const HOGDescriptor* _hog, const Mat& _img,  
  1049.                 double _hitThreshold, Size _winStride, Size _padding,  
  1050.                 const double* _levelScale, ConcurrentRectVector* _vec,   
  1051.                 ConcurrentDoubleVector* _weights=0, ConcurrentDoubleVector* _scales=0 )   
  1052.     {  
  1053.         hog = _hog;  
  1054.         img = _img;  
  1055.         hitThreshold = _hitThreshold;  
  1056.         winStride = _winStride;  
  1057.         padding = _padding;  
  1058.         levelScale = _levelScale;  
  1059.         vec = _vec;  
  1060.         weights = _weights;  
  1061.         scales = _scales;  
  1062.     }  
  1063.   
  1064.     void operator()( const BlockedRange& range ) const  
  1065.     {  
  1066.         int i, i1 = range.begin(), i2 = range.end();  
  1067.         double minScale = i1 > 0 ? levelScale[i1] : i2 > 1 ? levelScale[i1+1] : std::max(img.cols, img.rows);  
  1068.         //最小尺度的计算,  
  1069.         /*if i1 > 0  
  1070.          *  minScale = levelScale[i1];  
  1071.          *else if i2 > 1  
  1072.          *  minScale = levelScale[i1 + 1]  
  1073.          *else  
  1074.          *  minScale = max(img.cols,img.rows)??  
  1075.          **/  
  1076.         Size maxSz(cvCeil(img.cols/minScale), cvCeil(img.rows/minScale));//得到最大可能内存空间?  
  1077.         Mat smallerImgBuf(maxSz, img.type());//创建内存空间,  
  1078.         vector<Point> locations;//定位  
  1079.         vector<double> hitsWeights;//  
  1080.   
  1081.         for( i = i1; i < i2; i++ )  
  1082.         {  
  1083.             double scale = levelScale[i];//当前变化比例  
  1084.             Size sz(cvRound(img.cols/scale), cvRound(img.rows/scale));//变化后的图像尺寸  
  1085.             Mat smallerImg(sz, img.type(), smallerImgBuf.data);//从buf中划分适当的内存空间给当前smallerImg  
  1086.             if( sz == img.size() )//对原始img图像进行比例变化,得到smallerImg  
  1087.                 smallerImg = Mat(sz, img.type(), img.data, img.step);  
  1088.             else  
  1089.                 resize(img, smallerImg, sz);//进行尺度变换  
  1090.             //对变化后的samllerImg进行detect操作  
  1091.             hog->detect(smallerImg, locations, hitsWeights, hitThreshold, winStride, padding);  
  1092.             //这里的location,hitWeight保存检测含有行人的扫描窗口的相关信息  
  1093.             Size scaledWinSize = Size(cvRound(hog->winSize.width*scale), cvRound(hog->winSize.height*scale));  
  1094.             //得到当前尺度下扫描窗口(64*128)对应变换前原始img尺寸大小  
  1095.             forsize_t j = 0; j < locations.size(); j++ )  
  1096.             {  
  1097.                 vec->push_back(Rect(cvRound(locations[j].x*scale),  
  1098.                                     cvRound(locations[j].y*scale),  
  1099.                                     scaledWinSize.width, scaledWinSize.height));  
  1100.                 //保存期在原始图像中的位置信息  
  1101.                 if (scales) {  
  1102.                     scales->push_back(scale);//保存比例信息  
  1103.                 }  
  1104.             }  
  1105.               
  1106.             if (weights && (!hitsWeights.empty()))  
  1107.             {  
  1108.                 for (size_t j = 0; j < locations.size(); j++)  
  1109.                 {  
  1110.                     weights->push_back(hitsWeights[j]);//如果detect过程保留了weight,则进一步保存weight信息  
  1111.                 }  
  1112.             }          
  1113.         }  
  1114.     }  
  1115.   
  1116.     const HOGDescriptor* hog;//descriptor  
  1117.     Mat img;//待检测图像  
  1118.     double hitThreshold;//svm检测阈值  
  1119.     Size winStride;//窗口移动距离  
  1120.     Size padding;//填充尺寸  
  1121.     const double* levelScale;//&levelScale[0]  
  1122.     ConcurrentRectVector* vec;//得到扫描窗口在原始img图像中位置  
  1123.     ConcurrentDoubleVector* weights;//权重  
  1124.     ConcurrentDoubleVector* scales;//每个保留窗口对应scale值  
  1125. };  
  1126.   
  1127. //重点看一下如何解决多尺度问题  
  1128. //scale0表示多尺度之间的变化系数,scale0 <= 1时,表示无多尺度变化  
  1129. void HOGDescriptor::detectMultiScale(  
  1130.     const Mat& img, vector<Rect>& foundLocations, vector<double>& foundWeights,  
  1131.     double hitThreshold, Size winStride, Size padding,  
  1132.     double scale0, double finalThreshold, bool useMeanshiftGrouping) const    
  1133. {  
  1134.     double scale = 1.;  
  1135.     int levels = 0;  
  1136.   
  1137.     vector<double> levelScale;  
  1138.     //这里可以看到scale的值不断变大,也就是说这里的尺度变化时对原始img图像不断等比例缩小的过程,也就是所谓的金字塔了  
  1139.     //原始图像比例不断变化,而扫描窗口尺寸保持不变  
  1140.     for( levels = 0; levels < nlevels; levels++ )  
  1141.     {  
  1142.         levelScale.push_back(scale);//记录每个level上的变化尺度,当变化结果不满足一个扫描窗口大小时,提前结束  
  1143.         //cvRound返回最接近参数的整数  
  1144.         if( cvRound(img.cols/scale) < winSize.width ||  
  1145.             cvRound(img.rows/scale) < winSize.height ||  
  1146.             scale0 <= 1 )  
  1147.             break;  
  1148.         scale *= scale0;//scale0 > 0,因而scale的数值成倍增加,同样意味着img的尺寸成倍缩小  
  1149.     }  
  1150.     levels = std::max(levels, 1);//确定可以变化的层数,(不一定是参数中给定的nlevels)  
  1151.     levelScale.resize(levels);//对levelScale尺寸进行确定,  
  1152.   
  1153.     ConcurrentRectVector allCandidates;//保留所有检测到的扫描窗口的矩形框信息  
  1154.     ConcurrentDoubleVector tempScales;//保留检测到扫描窗口对应的尺度信息  
  1155.     ConcurrentDoubleVector tempWeights;//保留检测到的扫面窗口时的svm求和结果  
  1156.     vector<double> foundScales;//  
  1157.     //parallel_for 循环访问一个索引范围,并在每次迭代时以并行方式执行用户提供的函数  
  1158.     //并行处理每个尺度的计算过程  
  1159.     parallel_for(BlockedRange(0, (int)levelScale.size()),  
  1160.                  HOGInvoker(this, img, hitThreshold, winStride, padding,  
  1161.                             &levelScale[0], &allCandidates, &tempWeights, &tempScales));  
  1162.   
  1163.     std::copy(tempScales.begin(), tempScales.end(), back_inserter(foundScales));  
  1164.     foundLocations.clear();  
  1165.     std::copy(allCandidates.begin(), allCandidates.end(), back_inserter(foundLocations));  
  1166.     foundWeights.clear();  
  1167.     std::copy(tempWeights.begin(), tempWeights.end(), back_inserter(foundWeights));  
  1168.     //将得到的临时数据插入foundXX中保存  
  1169.     if ( useMeanshiftGrouping )  
  1170.     {  
  1171.         //领用meanshift对矩形框进行聚类操作  
  1172.         groupRectangles_meanshift(foundLocations, foundWeights, foundScales, finalThreshold, winSize);  
  1173.     }  
  1174.     else  
  1175.     {  
  1176.         //对矩形框进行简单聚类  
  1177.         groupRectangles(foundLocations, (int)finalThreshold, 0.2);  
  1178.     }  
  1179. }  
  1180.   
  1181. void HOGDescriptor::detectMultiScale(const Mat& img, vector<Rect>& foundLocations,   
  1182.                                      double hitThreshold, Size winStride, Size padding,  
  1183.                                      double scale0, double finalThreshold, bool useMeanshiftGrouping) const    
  1184. {  
  1185.     vector<double> foundWeights;  
  1186.     detectMultiScale(img, foundLocations, foundWeights, hitThreshold, winStride,   
  1187.                      padding, scale0, finalThreshold, useMeanshiftGrouping);  
  1188. }  

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值