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)的梯度信息爲:
水平梯度:
垂直梯度:
梯度幅值:
梯度方向 : ,
(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]加上該像素梯度幅值即可,即,即
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