首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Histogram of Oriented Gridients(HOG) 方向梯度直方图

Histogram of Oriented Gridients(HOG) 方向梯度直方图

作者头像
机器学习算法工程师
发布2018-03-06 12:23:56
1.2K0
发布2018-03-06 12:23:56
举报

作者 张旭 编辑 徐松

1. HOG简介

2. 数字图像梯度定义

3. HOG基本步骤

4. OpenCV实现HOG

5. 用KNN与HOG实现一个手写数字输入识别

1. HOG简介

方向梯度直方图(Histogram of Oriented Gradient, HOG)于2005年提出,是一种常用的特征提取方法,且HOG+SVM的方式在行人检测中有着优异的效果。经典的论文为《Histograms of oriented gradients for human detection》,这篇文章中,HOG就是用来做行人检测的。作者研究了行人检测的特征集问题,局部归一化的HOG描述子相比于现存的特征集有着更好的表现。HOG是大小统一的cell上进行梯度方向投影计算得到的,而且为了提高性能,还采用了重叠的局部对比度归一化。HOG描述子针对一个检测窗口,根据窗口中块的个数,块内cell的个数,以及cell内bins的个数串接得到最后的HOG描述子。

2. 数字图像梯度定义

在具体说明HOG算法之前,需要解释一下图像梯度的概念,在数字图像中,图像梯度信息包含梯度幅值(梯度大小)和梯度相位(梯度方向)大小,梯度幅值G(x,y)计算如公式(1)、(2)、(3)所示,其中Gx(x,y),Gy(x,y)分别为x,y方向上的分量。梯度相位θ(x,y)计算如公式(4)所示:

HOG流程图如下图所示:

(3)计算每个 cell 的特征描述符

在每个单元格(cell)里面,将其中包含的像素点的一维梯度投影到一定的方向上。把cell的角度范围均分成bin份,一般情况下bin=9。对每个像素点的梯度分别在这bin个方向进行加权投影之后,得到了各个方向的投影大小,然后对所有在同一个cell范围内的像素点的投影按照不同的方向进行累加计算,最后得出的就是该cell在这bin个方向上的特征值,即一个bin维的特征向量。cell的大小对接下来的分类是有关联的,所以需要选择合适大小的cell,根据现有的物体检测算法,为了取得较好的效果,一般都是选取4个或8个像素的大小。

(4)cell单元归一化

由于每一个cell计算出来的梯度直方图如果受到光照、阴影的干扰,相邻的cell可能也会呈现较大的差异,所以需要再次进行规范化处理,采用归一化的好处也是对干扰因素进一步的压缩。这里需要在样本图像上定义一个新的区块(block)来进行计算,这个块的范围定义为包含着几个连续的单元格。单元格归一化的过程是这样描述的,先求出每一个大的区块的特征值之和,即将块内包含的单元格的特征值相加,得到的结果也是该块的bin个特征值,然后是将同一个块内的每一个单元格的bin个不同方向的特征值除以上述值。一般来讲,因为检测窗口的滑动,是存在着重叠的块的。反过来说,每个单元格是被多个不同的块共享的。由于归一化是基于不同的块的,因此单独对每一个cell来说计算出来的值也不一样。也就是说对最后的结果而言,即便是同一个单元格的特征会多次出现在向量中,且每个值是不一样的。

(5)生成HOG描述子

将一个检测窗口中所有块的每一个cell单元内的bin个方向的投影大小串接结合成向量的形式就是该算法的输出的最终结果,即HOG 特征向量。特征维数的计算公式为:

其中为根据块滑动步长生成的块的个数,为一个块内cell的个数。

6. OpenCV实现HOG

OpenCV中,HOG被封装在了HOGDescriptor 类中,而且OpenCV提供了直接利用HOG+SVM进行多尺度行人检测的函数detectMultiScale(),在这里我们不介绍它,只说明如何利用HOG提取出特征,并把所有训练样本的特征组合成一个特征矩阵。

对于一个存在训练数据的路径下,比如D:\\data\\0文件夹下存放了连续命名的500张0数字图片,现在要把前300张拿出来作为label=0的traindata,特征为HOG,图片尺寸为20*20。那么我们可以用下面的代码组合特征:

6. OpenCV实现HOG

OpenCV中,HOG被封装在了HOGDescriptor 类中,而且OpenCV提供了直接利用HOG+SVM进行多尺度行人检测的函数detectMultiScale(),在这里我们不介绍它,只说明如何利用HOG提取出特征,并把所有训练样本的特征组合成一个特征矩阵。

对于一个存在训练数据的路径下,比如D:\\data\\0文件夹下存放了连续命名的500张0数字图片,现在要把前300张拿出来作为label=0的traindata,特征为HOG,图片尺寸为20*20。那么我们可以用下面的代码组合特征:

7. 用KNN与HOG实现一个手写数字输入识别

在上面的部分,我们用数字0举例生成了一张图像的HOG特征,特征维数为8100,在OpenCV3的安装文件路径/opencv/sources/samples/data/digits.png下,有这样一张图:

图片大小为1000*2000,有0-9的10个数字,每5行为一个数字,总共50行,共有5000个手写数字,每个数字块大小为20*20。 为了后续方便处理,我们先写一段小程序把这5000个图截取出来:

#define Posnum 300
int main()
{
char adpos[100];
HOGDescriptor hog(Size(20,20),Size(10,10),Size(2,2),Size(2,2),9);
int DescriptorDim;
Mat samFeatureMat;
Mat samLabelMat;
for (int i = 0;i < Posnum ;i++)
{
sprintf_s(adpos, "D:\\data\\0\\%d.jpg", i);
Mat src = imread(adpos);
vector<float> descriptors;
hog.compute(src,descriptors);
if ( i == 0)
{
DescriptorDim = descriptors.size();
samFeatureMat = Mat::zeros(Posnum , DescriptorDim, CV_32FC1);
samLabelMat = Mat::zeros(Posnum , 1, CV_32FC1);
}
for(int j=0; j<DescriptorDim; j++)
{
samFeatureMat.at<float>(i,j) = descriptors[j];
samLabelMat.at<float>(i,0) = 0;
}
}
return 0;
}

其中HOGDescriptor hog(Size(20,20),Size(10,10),Size(2,2),Size(2,2),9);即创建hog对象并利用构造函数对其初始化,构造函数为:

CV_WRAP HOGDescriptor() : winSize(64,128), blockSize(16,16), blockStride(8,8),
cellSize(8,8), nbins(9), derivAperture(1), winSigma(-1),
histogramNormType(HOGDescriptor::L2Hys), L2HysThreshold(0.2), gammaCorrection(true),
nlevels(HOGDescriptor::DEFAULT_NLEVELS)
{}
CV_WRAP HOGDescriptor(Size _winSize, Size _blockSize, Size _blockStride,
Size _cellSize, int _nbins, int _derivAperture=1, double _winSigma=-1,
int _histogramNormType=HOGDescriptor::L2Hys,
double _L2HysThreshold=0.2, bool _gammaCorrection=false,
int _nlevels=HOGDescriptor::DEFAULT_NLEVELS)

从构造函数可以看出,上述代码中检测窗口尺寸为20*20(和图像一样大),块尺寸为10*10,块步长为(2,2),cell尺寸为2*2,bin个数为9,那么计算一下描述子维数就是: 一个检测窗口中可以滑出36个块,一个块中可以划分25个cell,一个cell中产生9个方向,那么36*25*9=8100。

为考量HOG特征效果,我们设置一个简单的对比试验,分别用上述HOG特征(维度8100)与像素值特征(维度400)进行手写数字识别,分类算法选用KNN。

具体代码如下:

KNN_of_PixelValue:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/ml/ml.hpp>
using namespace std;
using namespace cv;
char ad[128]={0};
int main()
{
Mat traindata ,trainlabel;
int k=5,testnum=0,truenum=0;
//读取训练数据 4000张
for (int i = 0; i < 10; i++)
{
for (int j =0;j<400;j++)
{
sprintf_s(ad, "D:\\data\\%d\\%d.jpg",i,j);
Mat srcimage = imread(ad);
srcimage = srcimage.reshape(1,1);
traindata.push_back(srcimage);
trainlabel.push_back(i);
}
}
traindata.convertTo(traindata,CV_32F);
CvKNearest knn( traindata, trainlabel, cv::Mat(), false, k );
cv::Mat nearests( 1, k, CV_32F);
//读取测试数据 1000张
for (int i = 0; i < 10; i++)
{
for (int j =400;j<500;j++)
{
testnum++;
sprintf_s(ad, "D:\\data\\%d\\%d.jpg",i,j);
Mat testdata = imread(ad);
testdata = testdata.reshape(1,1);
testdata.convertTo(testdata,CV_32F);
int response = knn.find_nearest(testdata,k,0,0,&nearests,0);
if (response==i)
{
truenum++;
}
}
}
cout<<"测试总数"<<testnum<<endl;
cout<<"正确分类数"<<truenum<<endl;
cout<<"准确率:"<<(float)truenum/testnum*100<<"%"<<endl;
return 0;
}
KNN_of_HOG
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/ml/ml.hpp>
using namespace std;
using namespace cv;
char ad[128]={0};
int main()
{
Mat samFeatureMat ,samLabelMat;
int k=5,testnum=0,truenum=0;
HOGDescriptor hog(Size(20,20),Size(10,10),Size(2,2),Size(2,2),9);//利用构造函数,给对象赋值。
int DescriptorDim;//HOG描述子的维数
//读取训练数据 4000张
for (int i = 0; i < 10; i++)
{
for (int j =0;j<400;j++)
{
sprintf_s(ad, "D:\\data\\%d\\%d.jpg",i,j);
Mat srcimage = imread(ad);
vector<float> descriptors;//HOG描述子向量
hog.compute(srcimage,descriptors);
if ( i == 0&&j==0)
{
DescriptorDim = descriptors.size();
samFeatureMat = Mat::zeros(4000 , DescriptorDim, CV_32FC1);
samLabelMat = Mat::zeros(4000 , 1, CV_32FC1);
}
for(int k=0; k<DescriptorDim; k++)
{
samFeatureMat.at<float>(i*400+j,k) = descriptors[k];
samLabelMat.at<float>(i*400+j,0) = i;
}
}
}
samFeatureMat.convertTo(samFeatureMat,CV_32F);
CvKNearest knn( samFeatureMat, samLabelMat, cv::Mat(), false, k );
cv::Mat nearests( 1, k, CV_32F);
//读取测试数据 1000张
for (int i = 0; i < 10; i++)
{
for (int j =400;j<500;j++)
{
testnum++;
sprintf_s(ad, "D:\\data\\%d\\%d.jpg",i,j);
Mat testFeatureMat =Mat::zeros(1,DescriptorDim, CV_32FC1);
Mat testdata = imread(ad);
vector<float> descriptors;//HOG描述子向量
hog.compute(testdata,descriptors);
for(int k=0; k<DescriptorDim; k++)
{
testFeatureMat.at<float>(0,k) = descriptors[k];
}
testFeatureMat.convertTo(testFeatureMat,CV_32F);
int response = knn.find_nearest(testFeatureMat,k,0,0,&nearests,0);
if (response==i)
{
truenum++;
}
}

结果比较:


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-09-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 机器学习算法全栈工程师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档