【转载】HOG源码解析二

原文链接:

https://blog.youkuaiyun.com/antter0510/article/details/20564627

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

[cpp]  view plain  copy
  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
  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. }  

### S32K3 Microcontroller DMA Configuration Tutorial #### Overview of DMA on the S32K3 Series The Direct Memory Access (DMA) feature within the S32K3 series allows data transfer between memory and peripherals without CPU intervention, improving system efficiency. The eDMA module supports multiple channels with flexible arbitration mechanisms to manage concurrent requests effectively[^1]. #### Key Components Involved in DMA Setup For configuring DMA operations specifically involving ADC interfaces: - **Hardware Units**: Two hardware units can be configured as `ADC0` and `ADC1`. Users should configure these based on their specific circuit requirements[^3]. - **Channel Management**: When using multiple ADC channels, only the last channel triggers a DMA request upon completion. After one major loop finishes executing, it automatically loads the next Transfer Control Descriptor (TCD)[^2]. #### Step-by-step Guide for Configuring DMA with ADC To set up DMA transfers from an ADC peripheral: 1. Initialize the ADC modules (`ADC0`, `ADC1`) according to project needs. 2. Configure TCD settings including source address, destination address, block size, etc., ensuring proper alignment with the desired buffer structure. 3. Set up interrupt handlers if necessary to handle events such as end-of-conversion or error conditions. 4. Enable DMA requests by setting appropriate bits in control registers associated with each ADC instance used. 5. Ensure that after completing a major loop, subsequent minor loops are correctly initialized via preloaded TCD entries. ```c // Example C code snippet showing basic setup steps void init_DMA_for_ADC(void){ // Assuming initialization functions exist for simplicity /* Initialize ADC */ ADC_Init(ADC0); /* Prepare TCD structures */ edma_config_t dmaConfig; EDMA_GetDefaultConfig(&dmaConfig); EDMA_Init(DMA_BASE_ADDR, &dmaConfig); /* Create and configure TCDs */ uint8_t tcdIndex = 0; // Choose available index EDMA_CreateHandle(&g_edmaHandle[tcdIndex], DMA_BASE_ADDR, tcdIndex); } ``` This example demonstrates initializing DMA alongside ADC configurations but does not cover all possible scenarios or optimizations which may vary depending on application specifics. --related questions-- 1. How do different types of DMA modes affect performance when interfacing with ADC? 2. What considerations must be taken into account while designing buffers for storing ADC samples through DMA? 3. Can you explain how priority levels influence DMA operation among competing peripherals like UART vs SPI? 4. In what ways could interrupts play a role during continuous acquisition mode enabled by DMA?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值