Opencv2.4学习::HOG特征

HOG特征


一、什么是HOG特征

  • 是关于目标区域梯度方向的特征
  • 是一个向量

 

二、如何提取HOG特征

  • 图片归一化处理,减弱光线、阴影等影响
  • 图像梯度计算,一般用卷积方法,水平模板为[-1,0,1],竖直模板为[-1,0,1]T,看到这个,很容易联想到边缘检测,实际上,这个梯度很大程度上就代表了图像的边缘轮廓信息
  • 统计梯度方向,将目标窗口(win:64*128)继续细分为块(block:16*16),而块又继续细分为细胞窗口(cell:8*8)。
  • 块之间仅相差8个像素,那么目标窗口内一共划分【(64/16)*2-1 】*【(128/16)*2-1】=7*15=105个块block,
  • 而每个块里面又可划分为2*2=4个细胞cell。
  • 计算每个细胞的梯度方向直方图,假定该直方图为9个bin,那么,对于整个目标窗口(我们手动框定的部分),就可以提取到9*4*105=3780个bin,将每个bin的值按顺序排列形成一个3780维的向量,这个向量,即为目标区域的HOG特征
  • 一个网上流传的流程图如下:

 

 

三、 窗口(win)、块(block)、细胞(cell)与像素的关系

  • 图中蓝色框是我们的目标区域,也就是我们总共要提取hog特征的区域,假设他的size为(64*128)
  • 图中黄色框为一个块(block),size为(16*16)【一般来说这个block的宽、高都为win窗口可以整除的数】
  • 图中红色框为一个细胞(cell),size为(8.8)【那么可以看到,一个block里面可以放4个cell
  • cell里面的一个方格为1个像素

 

四、特征向量维度数目的计算

 

  • 一般设定黄色框(block)每次只沿x或y移动一半size,即8个像素
  • 那么,对于蓝色框(win),一共可分为[(64/8)-1]*[(128/8)-1]=7*15=105个block
  • 那么,对于蓝色框(win),一共可分为105*4=420个cell
  • 我们对每个cell都提取一个梯度直方图,这个梯度直方图有9个bin
  • 那么, 对于蓝色框(win),一共就有420*9=3780个bin
  • 如果不知道直方图是什么,看一下这个就明白了

 

五、关于梯度直方图的计算(重点)

(1)图像卷积

图像梯度一般利用图像与梯度算子卷积实现,关于这部分内容,可以参考Opencv2.4学习::图像卷积

(2)梯度算子

HOG特征提取所用的梯度算子为:水平方向 【-1,0,1】 , 垂直方向 【-1,0,1】T

(3)对于像素I(x,y)的梯度信息为:

水平梯度:G_{x}(x,y)=I(x+1,y)-I(x-1,y)

垂直梯度:G_{y}(x,y)=I(x,y+1)-I(x,y-1)

梯度幅值:|G(x,y)|=\sqrt{G_{x}^{2}(x,y)+G_{y}^{2}(x,y)}

梯度方向 :\alpha (x,y)=\arctan \frac{G_{y}(x,y)}{G_{x}(x,y)}*\frac{180}{\pi }  ,0^{\circ}<\alpha (x,y)<=180^{\circ}

(4)当我们获得了一个cell的所有像素的梯度信息之后,如何生成cell梯度直方图

1)划分区域,对180度划分9个区域,其中0,20】,20,40】。。。。。类推即可

2)制定落入规则,如果我们知道I(x,y)像素的梯度方向为15度时,可将他划分到【0,20】这个区域(或者叫bin),当梯度方向为25度时,可直接将他划分到【20,40】区域,或者按比例划分到【0,20】和【20,40】这两个区域之中。一般这个规则可 根据实际实际情况制定。

3)直方图bin数值计算: 假如区域为【0,20】为bin[0],当某个像素的梯度方向恰好落在这个区域时,那么bin[0]加上该像素梯度幅值即可,即bin[0]+=|G(x,y)|,即bin[0]+=\sqrt{G_{x}^{2}(x,y)+G_{y}^{2}(x,y)}

4)对所有像素遍历,并且将该像素梯度幅值加到对应的bin上,即生成关于cell的梯度直方图 

 

六、 生成HOG特征向量

(1)对每个block的直方图进行归一化

一个block有4个cell,这个操作就是将这4个cell的直方图信息4*9=36个bins串接起来,形成一个36维向量,然后进行归一化。这个归一化方法一般采用L2归一化;

(2)直方图串接形成HOG特征向量

将每个block归一化完成之后的直方图串接起来,一共有105*36=3780个bins,那么,这个HOG特征向量是一个3780维的向量。

 

六、代码实现

  • 代码中利用积分图对上述计算过程实现了一定的简化
#include <iostream>
#include<stdlib.h>
#include<stdio.h>
#include<string>
using namespace std;
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/opencv.hpp>
using namespace cv;

#define NBINS 9
#define THETA 20
#define R 4
#define BLOCKSIZE 16
#define CELLSIZE 8
#define BLOCKSTEP 8
//计算积分图
std::vector<Mat> CalculateIntegralHOG(Mat &srcMat)
{
    //cvtColor(srcMat,srcMat,CV_BGR2GRAY);
    //sobel边缘检测
    Mat sobelMatX,sobelMatY;
    Sobel(srcMat,sobelMatX,CV_32F,1,0);
    Sobel(srcMat,sobelMatY,CV_32F,0,1);
    std::vector<Mat> bins(NBINS);
    for(int i=0;i<NBINS;i++){
        bins[i]=Mat::zeros(srcMat.size(),CV_32F);//每个bins储存一副与原图尺寸一样的32FMat

    }
    Mat magnMat,angleMat;

    //貌似是直接利用X方向的梯度,和 Y方向的梯度,直接得到了幅值图和角度图
    //幅值图每个像素点的计算公式为:magnMat(x,y)=((sobelMatX(x,y)^2+sobelMatY(x,y)^2)^0.5
    //角度图每个像素点的计算公式为:angleMat(x,y)=atan2(sobelMatX(x,y),sobelMatY(x,y))*180/PI
    //atan2(y,x)求的是y/x的反正切,其返回值为[-pi,+pi]之间的一个数
    //所以角度图的每个像素点的值为角度,而不是弧度制
    cartToPolar(sobelMatX,sobelMatY,magnMat,angleMat,true);

    //下面操作的意义是,将角度图的每个像素值限制在0-180度范围内,因为30度和210度是同一个方向
    //if angleMat(x,y)<0 ,then angleMat(x,y)+=180;
    cv::add(angleMat,Scalar(180),angleMat,angleMat<0);
    //if angleMat(x,y)>180 ,then angleMat(x,y)-=180;
    cv::add(angleMat,Scalar(-180),angleMat,angleMat>=180);
    //将180度分为9份,每份20度
    angleMat/=THETA;

    //计算每个bin的幅值
    for(int y=0;y<srcMat.rows;y++){
        for(int x=0;x<srcMat.cols;x++){
            int ind=angleMat.at<float>(y,x);//取整数
            //当(x,y)这个像素的角度属于哪个0-20,20-40....160-180 哪个范围时,
            //就将像素的梯度幅值加到对应的bin
            bins[ind].at<float>(y,x)+=magnMat.at<float>(y,x);
        }
    }
    //上面得到了0-20,20-40....160-180 9个区间下的图,
    //即srcMat图像上梯度角度为0-20的像素点的梯度幅值被赋值到bins[0]的对应像素点上,以此类推

    std::vector<Mat> integrals(NBINS);
    for(int i=0;i<NBINS;i++){
        //计算积分图
        //积分图意义:integrals[i](X,Y)=bins[i](x<X,y<Y);
        //就是说,在积分图integrals[i]的某个像素(X,Y)的值=
        //图bins[i]中矩形范围Rect(0,0,X,Y)内所有像素点的值之和

        //那么对于整个图bins[i]的所有像素点之和=integrals[i](width,height),即等于右下角的像素点值
        integral(bins[i],integrals[i]);

        //到这里,如果想要获取srcMat图指定区域Rect(Xs,Ys,Width,Height)的HOG特征,只需
        //i遍历9次:
        //HOG_Bin[i]=integrals[i](Xs+Width,Ys+Height)-integrals[i](Xs+Width,Ys)-
        //integrals[i](Xs,Ys+Height)+integrals[i](Xs,Ys);
    }
    return integrals;
}

//计算单个cell的HOG特征
void cacHOGinCell(Mat &HOGCellMat,Rect roi,std::vector<Mat> &integrals,int cell_id)
{
    int x0=roi.x;
    int y0=roi.y;
    int x1=x0+roi.width;
    int y1=y0+roi.height;
    //Mat hist(Size(NBINS,1),CV_32F);
    for(int i=0;i<NBINS;i++){
        //获取指定区域的HOG特征 有多少个BIN就遍历多少次
        Mat integral=integrals[i];
        float a=integral.at<double>(y1,x1);
        float b=integral.at<double>(y0,x1);
        float c=integral.at<double>(y1,x0);
        float d=integral.at<double>(y0,x0);
        //HOGCellMat 这个Mat只有一行,列数为一个块block里面bin的数量=blocksize*blocksize*NBINS
        HOGCellMat.at<float>(0,i+cell_id*NBINS)=a-b-c+d;
    }
    //return hist;
}

//计算单个BLOCK的HOG特征
cv::Mat cacHOGinBlock(Point block_pt_start,std::vector<Mat> &integrals)
{
    Mat hist(Size(NBINS*(BLOCKSIZE/CELLSIZE)*(BLOCKSIZE/CELLSIZE),1),CV_32F);
    int cell_bins_num=NBINS;
    for(int i=0;i<BLOCKSIZE/CELLSIZE;i++){
        for(int j=0;j<BLOCKSIZE/CELLSIZE;j++){
            Rect roi(block_pt_start.x+j*CELLSIZE,block_pt_start.y+i*CELLSIZE,CELLSIZE,CELLSIZE);
            cacHOGinCell(hist,roi,integrals,i*BLOCKSIZE/CELLSIZE+j);
//            for(int k=0;k<cell_bins_num;k++){
//                hist.at<float>(0,k+(i*BLOCKSIZE/CELLSIZE+j)*cell_bins_num)=
//                        histinCell.at<float>(0,k);
//            }
        }
    }

    //归一化到L2
    normalize(hist,hist,1,0,NORM_L2);
    return hist;
}

//计算目标区域的HOG特征
cv::Mat cacHOGinWin(Rect roi_win,std::vector<Mat>&integrals)
{
    int block_bins_num=NBINS*(BLOCKSIZE/CELLSIZE)*(BLOCKSIZE/CELLSIZE);
    Rect roi_resize=Rect(roi_win.x,roi_win.y,int(roi_win.width/8)*8,int(roi_win.height/8)*8);
    Mat hist(Size(block_bins_num*
                  (roi_resize.width/CELLSIZE-1)*(roi_resize.height/CELLSIZE-1),1),CV_32F);
    int size=0;
    for(int i=0;i<roi_resize.height/CELLSIZE-1;i++){
        for(int j=0;j<roi_resize.width/CELLSIZE-1;j++){
            Point block_pt_start=Point(roi_win.x+j*CELLSIZE,roi_win.y+i*CELLSIZE);
            Mat histinBlock=cacHOGinBlock(block_pt_start,integrals);

            for(int k=0;k<block_bins_num;k++){
                hist.at<float>(0,k+((i*(roi_resize.width/CELLSIZE-1)+j)*block_bins_num))=
                        histinBlock.at<float>(0,k);
//                if(hist.at<float>(0,k+(i*(roi_resize.width/CELLSIZE-1)+j)*block_bins_num)>1)
//                    cout<<histinBlock.at<float>(0,k)<<endl;
            }
        }
    }
    return hist;
}

int main()
{
    Mat srcImage=imread("//home//msi//obama.jpg");
    if(!srcImage.data){
        cout<<"failed to read"<<endl;
        system("pause");
        return -1;
    }
    Mat srcGray;
    cvtColor(srcImage,srcGray,CV_BGR2GRAY);
    //要提取HOG特征向量的区域
    Rect testR(10,10,64,128);
    //生成积分图
    std::vector<Mat> integrals=CalculateIntegralHOG(srcGray);
    //计算HOG特征
    Mat hist=cacHOGinWin(testR,integrals);
    //输出
    for(int i=0;i<hist.cols;i++){
        printf("第%d维:%f\n",i,hist.at<float>(0,i));
    }
    return 0;

}

运行测试:


参考文章:

https://blog.csdn.net/liyuqian199695/article/details/53835989

https://blog.csdn.net/yuan1125/article/details/70878104

https://www.jianshu.com/p/ed21c357ec12

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章