手撕Hog 特征值提取——一种通俗的Hog特征提取方法
Hog特征是什么。这里就不赘述了。
不,还是说下吧。
Hog特征是一种梯度特征。主要是对于图像边缘的描述。有点那种,一眼看过去是什么样的那种感觉。
严格的还是看CVPR 2005 年的那篇paper吧。用Hog特征结合SVM,做行人检测的。
这里摘抄下我觉得写得不错的一段。
HOG特征提取方法就是将一个image:
1. 灰度化(将图像看做一个x,y,z(灰度)的三维图像)
2. 划分成小cells(2*2)
3. 计算每个cell中每个pixel的gradient(即orientation)
4. 统计每个cell的梯度直方图(不同梯度的个数),即可形成每个cell的descriptor
灰度图就是 (R+G+B)/3
Cell 待会再说。先说说如何计算图像梯度。
图像梯度听起来很玄学。其实很弱鸡。
它是这样的:
x方向的梯度:其实就是某一像素相邻左右两边的差值。
y方向的梯度:就是某一像素上下相邻两像素的差值。
没错,这就是梯度了。。
然后是某一像素的梯度幅值如上公式所示。
梯度方向也是用的到的。
对于上面,我是通过矩阵来实现。
定义一个梯度矩阵如下:
0 -1 0 0 0 0
1 0 -1 0 0 0
0 -1 0 1 0 0
0 0 -1 0 1 0
0 0 0 -1 0 1
0 0 0 0 -1 0
注意观察这个矩阵。叫它A吧。
那么假设图像是一个mxn的矩阵I
那么对x的梯度计算就是 Dot(I,A) 其中A为nxn
对y的梯度计算就是Dot(A,I) 其中A 为mxm
然后如上公式计算各个值。这样Hog其实就做了一半多了。(而且,这两个矩阵的点乘其实是耗时的主要部分)
//这个是我生成梯度矩阵的代码:
Mat_<float> GenerateGradientMatrix(int rank)
{
Mat_<float> result =Mat_<float>(rank, rank);
for (int i = 0; i < rank; i++)
{
for (int j = 0; j < rank; j++)
{
result(i, j) = 0;
}
if (i!=rank-1)
{
result(i, i + 1) = 1;
}
if (i!=0)
{
result(i, i - 1) = -1;
}
}
return result;
}
//在Hog特征中,另外一个概念是Cell, Cell是什么?
Cell 其实就是组成图片的小图片。把一张图片,划分成几个块。一般这个块是nxn大小的。
这样比如你是3*3,那么就有9个像素属于这个Cell。
//这里引用一下: http://www.open-open.com/lib/view/open1440832074794.html
为每个细胞单元构建梯度方向直方图
第三步的目的是为局部图像区域提供一个编码,同时能够保持对图像中人体对象的姿势和外观的弱敏感性。
我们将图像分成若干个“单元格cell”,例如每个cell为66个像素。假设我们采用9个bin的直方图来统计这66个像素的梯度信息。也 就是将cell的梯度方向360度分成9个方向块,如图所示:例如:如果这个像素的梯度方向是20-40度,直方图第2个bin的计数就加一,这样,对 cell内每个像素用梯度方向在直方图中进行加权投影(映射到固定的角度范围),就可以得到这个cell的梯度方向直方图了,就是该cell对应的9维特 征向量(因为有9个bin)。
像素梯度方向用到了,那么梯度大小呢?梯度大小就是作为投影的权值的。例如说:这个像素的梯度方向是20-40度,然后它的梯度大小是2(假设啊),那么直方图第2个bin的计数就不是加一了,而是加二(假设啊)。

细胞单元可以是矩形的(rectangular),也可以是星形的(radial)。
//
这就是Cell了。
我写码的时候把Cell 写成Bin了。我的Bin 定义如下:
struct Bin
{
Bin()
{
for (int i = 0; i < 360/Angle; i++)
{
bins[i] = 0;
}
}
float bins[360 / Angle];
};
里面的那只数组就是用来统计每个梯度方向上的权值大小。
这个是把梯度值和梯度方向两个矩阵填充到Cells里的代码:
Bin** FillInBin(Mat_<float>& length,Mat_<float>& direction,int& binRows,int& binCols)
{
binRows = 1 + length.rows / CellSize;
binCols = 1 + length.cols / CellSize;
Bin **result = new Bin*[1 + length.rows / CellSize];
for (int i = 0;i<1 + length.rows / CellSize;i++)
result[i] = new Bin[1 + length.cols / CellSize];
for (int i = 0; i < length.rows; i++)
{
for (int j = 0; j < length.cols; j++)
{
int tempDir = direction(i, j);
if (tempDir<0)
{
if (tempDir>-999)
{
tempDir = 360.0f + tempDir;
}
else
{
tempDir = 0;
}
}
result[i / CellSize][ j / CellSize].bins[ (int)(tempDir / 90)] += length(i, j);
}
}
for (int i = 0; i < binRows; i++)
{
for (int j = 0; j < binCols; j++)
{
for (int k = 0; k < 360/Angle; k++)
{
result[i][j].bins[k] /= (0.0007*(360 * CellSize*CellSize / (360.0 / Angle)));
}
}
}
return result;
}
//这时,每个Cell里已经充满了每个像素的贡献。
此时对这个Cell做一个正规化。那么其实Hog,已经讲完了。。
等等,那么Block呢》
Block就是一个滑动窗口,比Cell更大的一个块。而且允许重叠。每个Block由一堆Cell构成。更多的就是再对Block做一个正规化。进一步消除光照等噪声。
//最后贴上全部代码:
全部写在一个cpp 里便于copy and paste
#include <iostream>
#include <opencv\cv.hpp>
#include <math.h>
#include <vector>
#define CellSize 20
#define Angle 40
#define PI 3.1415926
using namespace std;
using namespace cv;
struct Bin
{
Bin()
{
for (int i = 0; i < 360/Angle; i++)
{
bins[i] = 0;
}
}
float bins[360 / Angle];
};
Mat_<float> DrawBin(Bin** bin,const int& binRows,const int& binCols)
{
Mat_<float> result = Mat_<float>(binRows*CellSize, binCols*CellSize);
for (int i = 0; i < result.rows; i++)
{
for (int j = 0; j < result.cols; j++)
{
result(i, j) = 0;
}
}
for (int i = 0; i < binRows; i++)
{
for (int j = 0; j < binCols; j++)
{
//for each angle
int angleIndex = 0;
for (int l = 0; l < 360 / Angle; l++)
{
if (bin[i][j].bins[l]>bin[i][j].bins[angleIndex])
{
angleIndex = l;
}
}
//from a cell start to end
for (int k = 0; k < CellSize; k++)
{
int ro = k + i*CellSize;
float x = k - CellSize / 2;
int y = (int)tanf(angleIndex*(PI*Angle / 360.0))*x;
if (-CellSize/2<y&&y<CellSize/2)
{
int co = y + CellSize / 2 + j*CellSize;
if (co>0&&co<result.cols&&ro>0&&ro<result.rows)
{
int tempValue = bin[i][j].bins[angleIndex];
result(ro, co) = tempValue;
}
}
}
}
}
return result;
}
Bin** FillInBin(Mat_<float>& length,Mat_<float>& direction,int& binRows,int& binCols)
{
binRows = 1 + length.rows / CellSize;
binCols = 1 + length.cols / CellSize;
Bin **result = new Bin*[1 + length.rows / CellSize];
for (int i = 0;i<1 + length.rows / CellSize;i++)
result[i] = new Bin[1 + length.cols / CellSize];
for (int i = 0; i < length.rows; i++)
{
for (int j = 0; j < length.cols; j++)
{
int tempDir = direction(i, j);
if (tempDir<0)
{
if (tempDir>-999)
{
tempDir = 360.0f + tempDir;
}
else
{
tempDir = 0;
}
}
result[i / CellSize][ j / CellSize].bins[ (int)(tempDir / 90)] += length(i, j);
}
}
for (int i = 0; i < binRows; i++)
{
for (int j = 0; j < binCols; j++)
{
for (int k = 0; k < 360/Angle; k++)
{
result[i][j].bins[k] /= (0.0007*(360 * CellSize*CellSize / (360.0 / Angle)));
}
}
}
return result;
}
Mat_<float> GenerateGradientMatrix(int rank)
{
Mat_<float> result =Mat_<float>(rank, rank);
for (int i = 0; i < rank; i++)
{
for (int j = 0; j < rank; j++)
{
result(i, j) = 0;
}
if (i!=rank-1)
{
result(i, i + 1) = 1;
}
if (i!=0)
{
result(i, i - 1) = -1;
}
}
return result;
}
Mat_<float> Clean(Mat_<float>& result)
{
Mat_<float> source = result.clone();
for (int i = 0; i < source.cols*source.rows; i++)
{
if (source.data[i]>255)
{
source.data[i] = 255;
}
if (source.data[i]<0)
{
source.data[i] = 0;
}
}
return source;
}
Mat_<float> ReverseImg(Mat_<float>& source)
{
Mat_<float> target = Mat_<float>(source.rows, source.cols);
for (int i = 0; i < target.rows; i++)
{
for (int j = 0; j < target.cols; j++)
{
target(i, j) = -source(i, j) + 255;
}
}
return target;
}
Mat_<float> GetDirection(Mat_<float>& _x, Mat_<float>& _y)
{
Mat_<float> result = Mat_<float>(_x.rows, _x.cols);
for (int i = 0; i < result.rows; i++)
{
for (int j = 0; j < result.cols; j++)
{
result(i, j) = 360.0*atanf(_y(i, j) / _x(i, j)) / PI;
}
}
return result;
}
Mat_<float> SquarSumSqrt(Mat_<float>& ma, Mat_<float>& mb)
{
Mat_<float> result = Mat_<float>(ma.rows, ma.cols);
for (int i = 0; i < result.rows; i++)
{
for (int j = 0; j < result.cols; j++)
{
result(i, j) = sqrtf( ma(i, j)*ma(i, j)+mb(i,j)*mb(i,j));
}
}
return result;
}
int main(int argc, char* argv)
{
Mat_<float> img = imread("E:/照片/264.jpg", 0);
Mat_<float> right = GenerateGradientMatrix(img.cols);
Mat_<float> left = GenerateGradientMatrix(img.rows);
Mat_<float> _x = img*right;
Mat_<float> _y = left*img;
Mat_<float> _length = SquarSumSqrt(_x, _y);
Mat_<float> _direction = GetDirection(_x, _y);
int binRows;
int binCols;
Bin** myBin = FillInBin(_length, _direction,binRows,binCols);
Mat_<float> draw = DrawBin(myBin,binRows,binCols);
for (int i = 0; i < binRows; i++)
{
delete[] myBin[i];
myBin[i] = NULL;
}
delete[] myBin;
imshow("Hog_CPP",(Mat_<uchar>)draw);
waitKey(0);
system("pause");
return 0;
}
