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

作者 张旭 编辑 徐松

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++;
}
}

结果比较:


原文发布于微信公众号 - 机器学习算法全栈工程师(Jeemy110)

原文发表时间:2017-09-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AI研习社

实时识别字母:深度学习和 OpenCV 应用搭建实用教程

这是一个关于如何构建深度学习应用程序的教程,该应用程序可以实时识别由感兴趣的对象(在这个案例中为瓶盖)写出的字母。

531
来自专栏人工智能LeadAI

解析Tensorflow官方PTB模型的demo

01 seq2seq代码案例解读 RNN 模型作为一个可以学习时间序列的模型被认为是深度学习中比较重要的一类模型。在Tensorflow的官方教程中,有两个与...

3988
来自专栏人工智能LeadAI

宠物狗图片分类之迁移学习代码笔记

本文主要是总结之前零零散散抽出时间做的百度西交大狗狗图片分类竞赛题目 竞赛.目前本人已经彻底排到了50名后面,,,也没有想到什么办法去调优,并且平时也忙没时间再...

571
来自专栏目标检测和深度学习

从零开始PyTorch项目:YOLO v3目标检测实现

在过去几个月中,我一直在实验室中研究提升目标检测的方法。在这之中我获得的最大启发就是意识到:学习目标检测的最佳方法就是自己动手实现这些算法,而这正是本教程引导你...

19111
来自专栏机器之心

从零开始PyTorch项目:YOLO v3目标检测实现

选自Medium 作者:Ayoosh Kathuria 机器之心编译 目标检测是深度学习近期发展过程中受益最多的领域。随着技术的进步,人们已经开发出了很多用于目...

8555
来自专栏吉浦迅科技

深度学习GeForce GTX 1080/Titan X(Maxwell)/ Titan X (Pascal)比较

【新智元导读】深度学习计算该买哪款GPU,选择哪个平台?这篇文章为你提供对比指南。 购买用于运行深度学习算法的硬件时,我们常常找不到任何有用的基准,唯一的选择是...

5335
来自专栏新智元

【干货】深度学习三大硬件+四大学习库基准测试对比,指标全面呈现

【新智元导读】深度学习计算该买哪款GPU,选择哪个平台?这篇文章为你提供对比指南。 购买用于运行深度学习算法的硬件时,我们常常找不到任何有用的基准,唯一的选择是...

41415
来自专栏云时之间

深度学习与TensorFlow:VGG论文复现

上一篇文章我们介绍了下VGG这一个经典的深度学习模型,今天便让我们通过使用VGG开源的VGG16模型去复现一下该论文.

3413
来自专栏mathor

“达观杯”文本智能处理挑战赛

 由于提供的数据集较大,一般运行时间再10到15分钟之间,基础电脑配置在4核8G的样子(越消耗内存在6.2G),因此,一般可能会遇到内存溢出的错误

342
来自专栏新智元

PyTorch 最新版发布:API 变动,增加新特征,多项运算和加载速度提升

【新智元导读】PyTorch 发布了最新版,API 有一些变动,增加了一系列新的特征,多项运算或加载速度提升,而且修改了大量bug。官方文档也提供了一些示例。 ...

4677

扫描关注云+社区