3、OpenCV中的HOG算法来源:
HOG特征提取是统计梯度直方图特征。具体来说就是将梯度方向(0->360°)划分为9个区间,将图像化为16x16的若干个block,每个block再化为4个cell(8x8)。对每一个cell,算出每一像素点的梯度方向和模,按梯度方向增加对应bin的值,最终综合N个cell的梯度直方图形成一个高维描述子向量。实际实现的时候会有各种插值。
图像中像素点(x,y)的梯度为:
作者也尝试了其他一些更复杂的模板,如3×3 Sobel 模板,或对角线模板(diagonal masks),但是在这个行人检测的实验中,这些复杂模板的表现都较差,所以作者的结论是:模板越简单,效果反而越好。
(4)将图像分割为小的Cell单元格
(5)为每个单元格构建梯度方向直方图【重点】
这步的目的是:统计局部图像梯度信息并进行量化(或称为编码),得到局部图像区域的特征描述向量。同时能够保持对图像中人体对象的姿势和外观的弱敏感性。
我们将图像分成若干个“单元格cell”,例如每个cell为8*8个像素(可以是矩形的(rectangular),也可以是星形的(radial))。假设我们采用9个bin的直方图来统计这8*8个像素的梯度信息。也就是将cell的梯度方向360度分成9个方向块,如图所示:例如:如果这个像素的梯度方向是20-40度,直方图第2个bin的计数就加一,这样,对cell内每个像素用梯度方向在直方图中进行加权投影(映射到固定的角度范围),就可以得到这个cell的梯度方向直方图了,就是该cell对应的9维特征向量(因为有9个bin)。
像素梯度方向用到了,那么梯度大小呢?梯度大小就是作为投影的权值的。例如说:这个像素的梯度方向是20-40度,然后它的梯度大小是2(假设啊),那么直方图第2个bin的计数就不是加一了,而是加二(假设啊)。
单元格Cell中的每一个像素点都为某个基于方向的直方图通道(orientation-based histogram channel)投票。投票是采取加权投票(weighted voting)的方式,即每一票都是带权值的,这个权值是根据该像素点的梯度幅度计算出来。可以采用幅值本身或者它的函数来表示这个权值,实际测试表明: 使用幅值来表示权值能获得最佳的效果,当然,也可以选择幅值的函数来表示,比如幅值的平方根(square root)、幅值的平方(square of the gradient magnitude)、幅值的截断形式(clipped version of the magnitude)等。根据Dalal等人论文的测试结果,采用梯度幅值量级本身得到的检测效果最佳,使用量级的平方根会轻微降低检测结果,而使用二值的边缘权值表示会严重降低效果。
其中,加权采用三线性插值(链接为详细说明的博文)方法,即将当前像素的梯度方向大小、像素在cell中的x坐标与y坐标这三个值来作为插值权重,而被用来插入的值为像素的梯度幅值。采用三线性插值的好处在于:避免了梯度方向直方图在cell边界和梯度方向量化的bin边界处的突然变化。
方法:
(6-1)将多个临近的cell组合成一个block块,然后求其梯度方向直方图向量;
(6-2)采用L2-Norm with Hysteresis threshold方式进行归一化,即将直方图向量中bin值的最大值限制为0.2以下,然后再重新归一化一次;
注意:block之间的是“共享”的,也即是说,一个cell会被多个block“共享”。另外,每个“cell”在被归一化时都是“block”independent的,也就是说每个cell在其所属的block中都会被归一化一次,得到一个vector。这就意味着:每一个单元格的特征会以不同的结果多次出现在最后的特征向量中。
(6-3)四种归一化方法:
作者采用了四中不同的方法对区间进行归一化,并对结果进行了比较。引入v表示一个还没有被归一 化的向量,它包含了给定区间(block)的所有直方图信息。| | vk | |表示v的k阶范数,这里的k去1、2。用e表示一个很小的常数。这时,归一化因子可以表示如下:
L2-norm:
L1-norm:
L1-sqrt:
L2-Hys:它可以通过先进行L2-norm,对结果进行截短(clipping)(即值被限制为v - 0.2v之间),然后再重新归一化得到。
作者发现:采用L2- Hys,L2-norm 和 L1-sqrt方式所取得的效果是一样的,L1-norm稍微表现出一点点不可靠性。但是对于没有被归一化的数据来说,这四种方法都表现出来显着的改进。
(6-4)区间(块)有两个主要的几何形状——矩形区间(R-HOG)和环形区间(C-HOG)。
A、R-HOG区间(blocks):大体上是一些方形的格子,它可以有三个参数来表征:每个区间中细胞单元的数目、每个细胞单元中像素点的数目、每个细胞的直方图通道数目。例如:行人检测的最佳参数设置是:3×3细胞/区间、6×6像素/细胞、9个直方图通道。则一块的特征数为:3*3*9;作者还发现,对于R-HOG,在对直方图做处理之前,给每个区间(block)加一个高斯空域窗口(Gaussian spatial window)是非常必要的,因为这样可以降低边缘的周围像素点(pixels around the edge)的权重。R-HOG是各区间被组合起来用于对空域信息进行编码(are used in conjunction to encode spatial form information)。
B、C-HOG区间(blocks):有两种不同的形式,它们的区别在于:一个的中心细胞是完整的,一个的中心细胞是被分割的。如右图所示:
作者发现C-HOG的这两种形式都能取得相同的效果。C-HOG区间(blocks)可以用四个参数来表征:角度盒子的个数(number of angular bins)、半径盒子个数(number of radial bins)、中心盒子的半径(radius of the center bin)、半径的伸展因子(expansion factor for the radius)。通过实验,对于行人检测,最佳的参数设置为:4个角度盒子、2个半径盒子、中心盒子半径为4个像素、伸展因子为2。前面提到过,对于R-HOG,中间加一个高斯空域窗口是非常有必要的,但对于C-HOG,这显得没有必要。C-HOG看起来很像基于形状上下文(Shape Contexts)的方法,但不同之处是:C-HOG的区间中包含的细胞单元有多个方向通道(orientation channels),而基于形状上下文的方法仅仅只用到了一个单一的边缘存在数(edge presence count)。
(6-5)HOG描述符(不同于OpenCV定义):我们将归一化之后的块描述符(向量)就称之为HOG描述符。
(6-6)块划分带来的问题:块与块之间是相互独立的吗?
答:通常的将某个变量范围固定划分为几个区域,由于边界变量与相邻区域也有相关性,所以变量只对一个区域进行投影而对相邻区域完全无关时会对其他区域产生混叠效应。
分块之间的相关性问题的解决:
方案一:块重叠,重复统计计算
在重叠方式中,块与块之间的边缘点被重复根据权重投影到各自相邻块(block)中,从而一定模糊了块与块之间的边界,处于块边缘部分的像素点也能够给相邻块中的方向梯度直方图提供一定贡献,从而达到关联块与块之间的关系的作用。Datal对于块和块之间相互重叠程度对人体目标检测识别率影响也做了实验分析。
方案二:线性插值权重分配
有些文献采用的不是块与块重叠的方法,而是采用线性插值的方法来削弱混叠效应。这种方法的主要思想是每个Block都对临近的Block都有影响,这种影响,我们可以以一种加权方式附加上去。
基于线性插值的基本思想,对于上图四个方向(横纵两个45度斜角方向)个进行一次线性插值就可以达到权重分配目的。下面介绍一维线性插值。假设x1和x2是x块相邻两块的中心,且x1<x<x2。对w(即权重,一般可直接采用该block的直方图值即h(x))进行线性插值的方法如下式:
其中b在横纵方向取块间隔,而在斜45度方向则可采用sqrt(2)倍的块间隔。
将所有“block”的HOG描述符组合在一起,形成最终的feature vector,该feature vector就描述了detect window的图像内容。
C++: gpu::HOGDescriptor::HOGDescriptor(Size win_size=Size(64, 128),
C++: size_t gpu::HOGDescriptor::getDescriptorSize() const
C++: size_t gpu::HOGDescriptor::getBlockHistogramSize() const
C++: void gpu::HOGDescriptor::setSVMDetector(const vector<float>& detector)
C++: static vector<float> gpu::HOGDescriptor::getDefaultPeopleDetector()
C++: static vector<float> gpu::HOGDescriptor::getPeopleDetector48x96()
C++: static vector<float> gpu::HOGDescriptor::getPeopleDetector64x128()
C++: void gpu::HOGDescriptor::detect(const GpuMat& img,
(3)参数注释
C++: void gpu::HOGDescriptor::detectMultiScale(const GpuMat& img,
(3)参数注释
<1> 得到层数levels
某图片(530,402)为例,lg(402/128)/lg1.05=23.4 则得到层数为24
<2>循环levels次,每次执行内容如下
HOGThreadData& tdata = threadData[getThreadNum()];
Mat smallerImg(sz, img.type(), tdata.smallerImgBuf.data);
<3>循环中调用以下核心函数
detect(smallerImg, tdata.locations, hitThreshold, winStride, padding);
其参数分别为,该比例下图像、返回结果列表、门槛值、步长、margin
该函数内容如下:
(a)得到补齐图像尺寸paddedImgSize
(b)创建类的对象HOGCache cache(this, img, padding, padding, nwindows == 0, cacheStride); 在创建过程中,首先初始化HOGCache::init,包括:计算梯度descriptor->computeGradient、得到块的个数105、每块参数个数36。
(c)获得窗口个数nwindows,以第一层为例,其窗口数为(530+32*2-64)/8+ (402+32*2-128)/8+1 =67*43=2881,其中(32,32)为winStride参数, 也可用(24,16)
(d)在每个窗口执行循环,内容如下:
在105个块中执行循环,每个块内容为:通过getblock函数计算HOG特征并 归一化,36个数分别与算子中对应数进行相应运算;判断105个块的总和 s >= hitThreshold 则认为检测到目标
C++: void gpu::HOGDescriptor::getDescriptors(const GpuMat& img,
(3)参数注释
DESCR_FORMAT_ROW_BY_ROW - 行存储。DESCR_FORMAT_COL_BY_COL - 列存储。
11、computeGradient 函数
(3)参数注释
void HOGDescriptor::compute(const Mat& img,
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/gpu/gpu.hpp> #include <stdio.h>
using namespace cv;
int main(int argc, char** argv)
{
Mat img;
vector<Rect> found;
img = imread(argv[1]);
if(argc != 2 || !img.data)
{
printf(“没有图片\n”);
return -1;
}
HOGDescriptor defaultHog;
defaultHog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());
//进行检测
defaultHog.detectMultiScale(img, found);
//画长方形,框出行人
for(int i = 0; i < found.size(); i++)
{
Rect r = found[i];
rectangle(img, r.tl(), r.br(), Scalar(0, 0, 255), 3);
}
namedWindow(“检测行人”, CV_WINDOW_AUTOSIZE);
imshow(“检测行人”, img);
waitKey(0);
</span><span style="color: #0000ff;">return</span> <span style="color: #800080;">0</span><span style="color: #000000;">;
}
对每一张图片调用hog.compute(img, descriptors,Size(8,8), Size(0,0));可以生成hog descriptors,把它保存到文件中for(int j=0;j<3780;j++)fprintf(f,"%f,",descriptors[j]);
来源:CSDN
作者:码灵薯
链接:https://blog.csdn.net/geduo_feng/article/details/103523739