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

 

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