logo资料库

OpenCV的HOG代码.pdf

第1页 / 共28页
第2页 / 共28页
第3页 / 共28页
第4页 / 共28页
第5页 / 共28页
第6页 / 共28页
第7页 / 共28页
第8页 / 共28页
资料共28页,剩余部分请下载后查看
double _L2HysThreshold=0.2, bool _histogramNormType=L2Hys, 大家好,由于我的毕业设计是“基于图像的手势识别”,因而对 HOG 算法和 SVM 算法有一定的研究,下面将我的学习心得和论文算法部分和大家分享。计算机视 觉是很有发展潜力的,希望大家共同分享,共同进步。 首先关于 HOG 算法: #include "_cvaux.h" /******************************************************************** ********************* struct CV_EXPORTS HOGDescriptor { public: enum { L2Hys=0 }; HOGDescriptor() : winSize(64,128), blockSize(16,16), blockStride(8,8), cellSize(8,8), nbins(9), derivAperture(1), winSigma(-1), histogramNormType(L2Hys), L2HysThreshold(0.2), gammaCorrection(true) {} HOGDescriptor(Size _winSize, Size _blockSize, Size _blockStride, Size _cellSize, int _nbins, int _derivAperture=1, double _winSigma=-1, int _gammaCorrection=false) : winSize(_winSize), cellSize(_cellSize), nbins(_nbins), derivAperture(_derivAperture), winSigma(_winSigma), histogramNormType(_histogramNormType), L2HysThreshold(_L2HysThreshold), gammaCorrection(_gammaCorrection) {} HOGDescriptor(const String& filename) { load(filename); } virtual ~HOGDescriptor() {} size_t getDescriptorSize() const; bool checkDetectorSize() const; double getWinSigma() const; virtual void setSVMDetector(const vector& _svmdetector); virtual bool load(const String& filename, const String& objname=String()); virtual void save(const String& filename, const String& objname=String()) const; blockSize(_blockSize), blockStride(_blockStride),
virtual void compute(const Mat& img, vector& descriptors, Size winStride=Size(), Size padding=Size(), const vector& locations=vector()) const; virtual void detect(const Mat& img, vector& foundLocations, double hitThreshold=0, Size winStride=Size(), Size padding=Size(), const vector& searchLocations=vector()) const; virtual void detectMultiScale(const Mat& img, vector& foundLocations, double hitThreshold=0, Size winStride=Size(), Size padding=Size(), double scale=1.05, int groupThreshold=2) const; //Mat& angleOfs,与后文 Mat& qangle 不一致,怀疑是笔误,由于 qangle 与 angleOfs 有不同含义,尽量改过来 virtual void computeGradient(const Mat& img, Mat& grad, Mat& angleOfs, Size paddingTL=Size(), Size paddingBR=Size()) const; static vector getDefaultPeopleDetector(); Size winSize;//窗口大小 Size blockSize;//Block 大小 Size blockStride;//block 每次移动宽度包括水平和垂直两个方向 Size cellSize;//Cell 单元大小 int nbins;//直方图 bin 数目 int derivAperture;//不知道什么用 double winSigma;//高斯函数的方差 int histogramNormType;//直方图归一化类型,具体见论文 double L2HysThreshold;//L2Hys 化中限制最大值为 0.2 bool gammaCorrection;//是否 Gamma 校正 vector svmDetector;//检测算子 }; ********************************************************************* *************/ namespace cv { size_t HOGDescriptor::getDescriptorSize() const { //检测数据的合理性
CV_Assert(blockSize.width % cellSize.width == 0 && blockSize.height % cellSize.height == 0); CV_Assert((winSize.width - blockSize.width) % blockStride.width == 0 && (winSize.height - blockSize.height) % blockStride.height == 0 ); //Descriptor 的大小 return (size_t)nbins* (blockSize.width/cellSize.width)* (blockSize.height/cellSize.height)* ((winSize.width - blockSize.width)/blockStride.width + 1)* ((winSize.height - blockSize.height)/blockStride.height + 1); //9*(16/8)*(16/8)*((64-16)/8+1)*((128-16)/8+1)=9*2*2*7*15=3780,实际上的检测 算子为 3781,多的 1 表示偏置 } double HOGDescriptor::getWinSigma() const { //winSigma 默认为-1,然而有下式知,实际上为 4;否则自己选择参数 return winSigma >= 0 ? winSigma : (blockSize.width + blockSize.height)/8.; } bool HOGDescriptor::checkDetectorSize() const { //size_t:unsigned int size_t detectorSize = svmDetector.size(), descriptorSize = getDescriptorSize(); //三种情况任意一种为 true 则表达式为 true,实际上是最后一种 return detectorSize == 0 || detectorSize == descriptorSize || detectorSize == descriptorSize + 1; } void HOGDescriptor::setSVMDetector(const vector& _svmDetector) { svmDetector = _svmDetector; CV_Assert( checkDetectorSize() ); } bool HOGDescriptor::load(const String& filename, const String& objname) { //XML/YML 文件存储 FileStorage fs(filename, FileStorage::READ); //objname 为空,!1=0,选择 fs.getFirstTopLevelNode();否则为 fs[objname] //注意到 FileStorage 中[]重载了:FileNode operator[](const string& nodename) (returns the top-level node by name )
FileNode obj = !objname.empty() ? fs[objname] : fs.getFirstTopLevelNode(); if( !obj.isMap() ) return false; FileNodeIterator it = obj["winSize"].begin(); it >> winSize.width >> winSize.height; it = obj["blockSize"].begin(); it >> blockSize.width >> blockSize.height; it = obj["blockStride"].begin(); it >> blockStride.width >> blockStride.height; it = obj["cellSize"].begin(); it >> cellSize.width >> cellSize.height; obj["nbins"] >> nbins; obj["derivAperture"] >> derivAperture; obj["winSigma"] >> winSigma; obj["histogramNormType"] >> histogramNormType; obj["L2HysThreshold"] >> L2HysThreshold; obj["gammaCorrection"] >> gammaCorrection; FileNode vecNode = obj["SVMDetector"]; if( vecNode.isSeq() ) { vecNode >> svmDetector; CV_Assert(checkDetectorSize()); } return true; } void HOGDescriptor::save(const String& filename, const String& objName) const { FileStorage fs(filename, FileStorage::WRITE); //空的对象名则取默认名,输出有一定格式,对象名后紧接{ fs << (!objName.empty() ? objName : FileStorage::getDefaultObjectName(filename)) << "{"; //之后依次为: fs << "winSize" << winSize << "blockSize" << blockSize << "blockStride" << blockStride << "cellSize" << cellSize << "nbins" << nbins << "derivAperture" << derivAperture << "winSigma" << getWinSigma() << "histogramNormType" << histogramNormType << "L2HysThreshold" << L2HysThreshold
<< "gammaCorrection" << gammaCorrection; if( !svmDetector.empty() ) fs << "SVMDetector" << "[:" << svmDetector << "]"; //注意还要输出"}" fs << "}"; } //img:原始图像 //grad:记录每个像素所属 bin 对应的权重的矩阵,为幅值乘以权值 //这个权值是关键,也很复杂:包括高斯权重,三次插值的权重,在本函数中先 值考虑幅值和相邻 bin 间的插值权重 //qangle:记录每个像素角度所属的 bin 序号的矩阵,均为 2 通道,为了线性插值 //paddingTL:Top 和 Left 扩充像素数 //paddingBR:类似同上 //功能:计算 img 经扩张后的图像中每个像素的梯度和角度 void HOGDescriptor::computeGradient(const Mat& img, Mat& grad, Mat& qangle, Size paddingTL, Size paddingBR) const { //先判断是否为单通道的灰度或者 3 通道的图像 CV_Assert( img.type() == CV_8U || img.type() == CV_8UC3 ); //计算 gradient 的图的大小,由 64*128==》112*160,则会产生 5*7=35 个窗口 (windowstride:8) //每个窗口 105 个 block,105*36=3780 维特征向量 //paddingTL.width=16,paddingTL.height=24 Size gradsize(img.cols + paddingTL.width + paddingBR.width, img.rows + paddingTL.height + paddingBR.height); //注意 grad 和 qangle 是 2 通道的矩阵,为 3D-trilinear 插值中的 orientation 维度, 另两维为坐标 x 与 y grad.create(gradsize, CV_32FC2); // qangle.create(gradsize, CV_8UC2); // [0..nbins-1] - quantized gradient orientation //wholeSize 为 parent matrix 大小,不是扩展后 gradsize 的大小 //roiofs 即为 img 在 parent matrix 中的偏置 //对于正样本 img=parent matrix;但对于负样本 img 是从 parent img 中抽取的 10 个随机位置 //至于 OpenCv 具体是怎么操作,使得 img 和 parent img 相联系,不是很了解 //wholeSize 与 roiofs 仅在 padding 时有用,可以不管,就认为传入的 img==parent img,是否是从 parent img 中取出无所谓 Size wholeSize; Point roiofs; img.locateROI(wholeSize, roiofs);
int i, x, y; int cn = img.channels(); //产生 1 行 256 列的向量,lut 为列向量头地址 Mat_ _lut(1, 256); const float* lut = &_lut(0,0); //gamma 校正,作者的编程思路很有意思 //初看不知道这怎么会与图像的 gamma 校正有关系,压根 img 都没出现,看到 后面大家会豁然开朗的 if( gammaCorrection ) for( i = 0; i < 256; i++ ) _lut(0,i) = std::sqrt((float)i); else for( i = 0; i < 256; i++ ) _lut(0,i) = (float)i; //开辟空间存 xmap 和 ymap,其中各占 gradsize.width+2 和 gradsize.height+2 空间 //+2 是为了计算 dx,dy 时用[-1,0,1]算子,即使在扩充图像中,其边缘计算梯度时还 是要再额外加一个像素的 //作者很喜欢直接用内存地址及之间的关系,初看是有点头大的 //另外再说说 xmap 与 ymap 的作用:其引入是因为 img 图像需要扩充到 gradsize 大小 //如果我们计算 img 中位于(-5,-6)像素时,需要将基于 img 的(-5,-6)坐标,映 射为基于 grad 和 qangle 的坐标(xmap,ymap) AutoBuffer mapbuf(gradsize.width + gradsize.height + 4); int* xmap = (int*)mapbuf + 1; int* ymap = xmap + gradsize.width + 2; // BORDER_REFLECT_101:(左插值)gfedcb|abcdefgh(原始像素)|gfedcba(右插值),一 种插值模式 const int borderType = (int)BORDER_REFLECT_101; //borderInterpolate 函数完成两项操作,一是利用插值扩充 img,二是返回 x-paddingTL.width+roiofs.x 映射后的坐标 xmap //例如,ximg=x(取 0)-paddingTL.width(取 24)+roiofs.x(取 0)=-24 ==>xmap[0]=0 // 即 img 中 x=-24, 映 射 到 grad 中 xmap=0, 并 且 存 在 xmap[0] 中 , 至 于 borderInterpolate 的具体操作可以不必细究 for( x = -1; x < gradsize.width + 1; x++ ) xmap[x] = borderInterpolate(x - paddingTL.width + roiofs.x, wholeSize.width, borderType); for( y = -1; y < gradsize.height + 1; y++ ) ymap[y] = borderInterpolate(y - paddingTL.height + roiofs.y,
wholeSize.height, borderType); // x- & y- derivatives for the whole row // 由于后面的循环是以行为单位,每次循环内存重复使用,所以只要记录一行 的信息而不是整个矩阵 int width = gradsize.width; AutoBuffer _dbuf(width*4); float* dbuf = _dbuf; //注意到内存的连续性方便之后的编程 Mat Dx(1, width, CV_32F, dbuf); Mat Dy(1, width, CV_32F, dbuf + width); Mat Mag(1, width, CV_32F, dbuf + width*2); Mat Angle(1, width, CV_32F, dbuf + width*3); int _nbins = nbins; float angleScale = (float)(_nbins/CV_PI);//9/pi for( y = 0; y < gradsize.height; y++ ) { //指向每行的第一个元素,img.data 为矩阵的第一个元素地址 const uchar* imgPtr = img.data + img.step*ymap[y]; const uchar* prevPtr = img.data + img.step*ymap[y-1]; const uchar* nextPtr = img.data + img.step*ymap[y+1]; float* gradPtr = (float*)grad.ptr(y); uchar* qanglePtr = (uchar*)qangle.ptr(y); //1 通道 if( cn == 1 ) { for( x = 0; x < width; x++ ) { int x1 = xmap[x]; //imgPtr 指向 img 第 y 行首元素,imgPtr[x]即表示第(x,y)像素,其亮度值位于 0~255, 对应 lut[0]~lut[255] //即若像素亮度为 120,则对应 lut[120],若有 gamma 校正,lut[120]=sqrt(120) //由于补充了虚拟像素,即在 imgPtr[-1]无法表示 gradsize 中-1 位置元素,而需要 有个转换 //imgPtr[-1-paddingTL.width+roiofs.x],即 imgPtr[xmap[-1]],即 gradsize 中-1 位置元 素为 img 中 xmap[-1]位置的元素 dbuf[x] = (float)(lut[imgPtr[xmap[x+1]]] - lut[imgPtr[xmap[x-1]]]); //由于内存的连续性,隔 width,即存 Dy
dbuf[width + x] = (float)(lut[nextPtr[x1]] - lut[prevPtr[x1]]); } } else //3 通道,3 通道中取最大值 { for( x = 0; x < width; x++ ) { int x1 = xmap[x]*3; const uchar* p2 = imgPtr + xmap[x+1]*3; const uchar* p0 = imgPtr + xmap[x-1]*3; float dx0, dy0, dx, dy, mag0, mag; dx0 = lut[p2[2]] - lut[p0[2]]; dy0 = lut[nextPtr[x1+2]] - lut[prevPtr[x1+2]]; mag0 = dx0*dx0 + dy0*dy0; dx = lut[p2[1]] - lut[p0[1]]; dy = lut[nextPtr[x1+1]] - lut[prevPtr[x1+1]]; mag = dx*dx + dy*dy; if( mag0 < mag ) { dx0 = dx; dy0 = dy; mag0 = mag; } dx = lut[p2[0]] - lut[p0[0]]; dy = lut[nextPtr[x1]] - lut[prevPtr[x1]]; mag = dx*dx + dy*dy; if( mag0 < mag ) { dx0 = dx; dy0 = dy; mag0 = mag; } dbuf[x] = dx0; dbuf[x+width] = dy0; } }
分享到:
收藏