精华内容
下载资源
问答
  • HOG特征

    万次阅读 多人点赞 2019-04-28 18:58:34
    01 什么是HOG特征 1.1 HOG特征简介 我们先来从字面入手分析一下HOG特征的名字。 HOG特征是图像的一种特征,图像的特征其实就是图像中某个区域的像素点在经过某种四则运算后所得到的结果。 它可以是一个具体的...

    01  什么是HOG特征

    1.1  HOG特征简介

    我们先来从字面入手分析一下HOG特征的名字。

    HOG特征是图像的一种特征,图像的特征其实就是图像中某个区域的像素点在经过某种四则运算后所得到的结果。

    它可以是一个具体的数值,可以是一个向量,可以是一个矩阵,当然他还可以是一个n维的数组。

    它远没有名字看上去那么神秘。。。。。

     

    说过了我认为的图像特征的定义,我们再来看今天的主角,HOG特征。 它的全名应该叫做Histogram Of Oriented Gradient , 中文是方向梯度直方图。 

    HOG 是由 Navneet Dalal & Bill Triggs 在 CVPR 2005发表的论文中提出来的,目的是为了更好的解决行人检测的问题。

    我们看一下百度百科上对于HOG特征的定义:  方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。HOG特征通过计算和统计图像局部区域的梯度方向直方图来构成特征。

     

    其实从名字上来看,我们应当不难理解HOG特征所描述的是什么,或者说他描述的是图像的哪种特征。

    方向梯度直方图   可以看出他描述的是关于梯度的一些东西,它的本质是个直方图。

    说到直方图,你脑海中最先想到的画面是什么呢?

    是它嘛?????

    反正,说到直方图,在我的脑海里最先想到的就是小学课本上关于概率的数学题的配图。。。。

    其实HOG特征所描述的和它相似。

    横轴取了一定间隔的值的范围,纵轴表示某个区间的值有多少个(频数)。

    给出一组数据(下图矩阵)让我们来简单的动手画一下它的直方图。

    我们首先要考虑的是X轴上的取值间隔问题,比如我们取10,那么数字0-9看成一类、10-19看成一类,如此类推。

    如图所示,0-9有{6、5、9} 3个,10-19有{12、17}两个。。。。。

    我们绘制出如下的直方图

    我们姑且称他为  一组随机数的直方图

    那么我们再来类比HOG(方向梯度直方图),是不是可以认为HOG特征统计的就是某个方向区间内的梯度呢?

    我们后面再来看

     

    1.2  HOG特征的主要思想:( 摘自百度百科 )

    在一副图像中,局部目标的表象和形状能够被梯度或边缘的方向密度分布很好地描述。其本质为:梯度的统计信息,而梯度主要存在于边缘的地方。

    1.3  HOG特征的优缺点

    与其他的特征描述方法相比,HOG有很多优点。

    首先,由于HOG是 在图像的局部方格单元上操作,所以它对图像几何的和光学的形变都能保持很好的不变性,这两种形变只会出现在更大的空间领域上。

    其次,在粗的空域抽样、精细 的方向抽样以及较强的局部光学归一化等条件下,只要行人大体上能够保持直立的姿势,可以容许行人有一些细微的肢体动作,这些细微的动作可以被忽略而不影响 检测效果。

    因此HOG特征是特别适合于做图像中的人体检测的 。

     

    02  HOG算法的实现

    2.1  预备部分

    名词解释

    在说具体的算法之前,要先对HOG特征中需要用到的一些名词做一定的了解。

    边看图边想象边理解吧。。。。

    图中我标出了序号,下面开始对号入座。。。。

    (1)  image 

    这个不需要过多的解释,就是整个输入。

    (2) Window

    (看了很多博客,少有提到这个概念,如果说错了可以留言让我改正)

    window就是包含你要检测的整个目标的一个窗口。  可以脑补一下上面的图其实是这样的一张图像,一个人站在一片开满了油菜花的田野里,嗯哼?就是这样。。。。你要检测行人,你就需要用这个window把你的行人给框住。因为window是整个HOG计算的最顶层,也就是说我们每次计算HOG特征,计算的并不是整幅图像的,而是一个window范围内的HOG特征。

    (      因为后续要用到对于size的计算,所以我们简单的提一下

    我们的window可以是任意尺寸的(arbitrary的),这里使用官方推荐的  64 x 128     )

    (3) block

    可以看出,block是window中的一个滑框。

    window的长和宽最好是block长宽的整数倍,  这里依旧使用官方推荐的16x 16

    (4) cell 

    cell 又是block的下一级了,这里注意,cell是不可滑动的

    size 依旧是官方推荐,,,,8x8

    (5) bin 

    bin在图中并没有给出,它代表的是直方图区间;

    上面我们在说到普通直方图绘制的时候提到过这个区间的问题,当时我们选择的是10,即0-9为一组。

    HOG叫做方向梯度直方图,这个直方图的X轴代表的是像素梯度的方向,所以我们也要考虑其区间问题。

    方向肯定是以角度来描述的,这个区间的意思就是说要将360度分为几份(或者是180度,后面会说到这个)。

    (  这里可能会有些费解,但是我实在是想不到什么好方法来描述了。。 )

    一般来说bin的值为9

     

    HOG特征的维度

    与我们之前说到的haar特征不同,HOG特征有很高的维度。其具体的计算方式如下:

    xxxxxxxxxxxxxx

    (   维度不能脱离图像的尺寸存在,我们图像的尺寸与cell,block的尺寸均为上述的官方推荐值   )

    对于最小的计算单元cell, 8x8尺寸的cell,其所产生的方向梯度直方图在最后会被转换成为一组单维向量,

    因为bin的值为9,所以每个cell产生9个特征。

    每4个cell 组成一个block,(注意,cell 是不滑动的)。

    对于64*128的图像而言,若将Block的滑动步长取为8,那么水平方向将有7个扫描窗口,垂直方向将有15个扫描窗口,

    共计105个扫描窗口。

     

    所以               一个64*128大小的图像最后得到的特征数为9*4*7*15=3780个。

     

     

    2.2  算法实现

    HOG特征的具体提取步骤如下

    (这是百度百科上扒下来的,可能有些不太好懂,后面我会结合我自己的一些理解。)

    1、色彩和伽马归一化

    为了减少光照因素的影响,首先需要将整个图像进行规范化(归一化)。在图像的纹理强度中,局部的表层曝光贡献的比重较大,所以,这种压缩处理能够有效地降低图像局部的阴影和光照变化。

    2、计算图像梯度

    计算图像横坐标和纵坐标方向的梯度,并据此计算每个像素位置的梯度方向值;求导操作不仅能够捕获轮廓,人影和一些纹理信息,还能进一步弱化光照的影响。

    最常用的方法是:简单地使用一个一维的离散微分模板在一个方向上或者同时在水平和垂直两个方向上对图像进行处理,更确切地说,这个方法需要使用滤波器核滤除图像中的色彩或变化剧烈的数据。

    3、构建方向的直方图

    细胞单元中的每一个像素点都为某个基于方向的直方图通道投票。投票是采取加权投票的方式,即每一票都是带有权值的,这个权值是根据该像素点的梯度幅度计算出来。可以采用幅值本身或者它的函数来表示这个权值,实际测试表明: 使用幅值来表示权值能获得最佳的效果,当然,也可以选择幅值的函数来表示,比如幅值的平方根、幅值的平方、幅值的截断形式等。细胞单元可以是矩形的,也可以是星形的。直方图通道是平均分布在0-1800(无向)或0-3600(有向)范围内。经研究发现,采用无向的梯度和9个直方图通道,能在行人检测试验中取得最佳的效果。

    4、将细胞单元组合成大的区间

    由于局部光照的变化以及前景-背景对比度的变化,使得梯度强度的变化范围非常大。这就需要对梯度强度做归一化。归一化能够进一步地对光照、阴影和边缘进行压缩。

    采取的办法是:把各个细胞单元组合成大的、空间上连通的区间。这样,HOG描述符就变成了由各区间所有细胞单元的直方图成分所组成的一个向量。这些区间是互有重叠的,这就意味着:每一个细胞单元的输出都多次作用于最终的描述器。

    区间有两个主要的几何形状——矩形区间(R-HOG)和环形区间(C-HOG)。R-HOG区间大体上是一些方形的格子,它可以有三个参数来表征:每个区间中细胞单元的数目、每个细胞单元中像素点的数目、每个细胞的直方图通道数目。

    5、收集HOG特征

    把提取的HOG特征输入到SVM分类器中,寻找一个最优超平面作为决策函数。

     

     

     

     

     

     

     

     

    自己的理解(与上面的算法实现步骤一一对应):

    首先是关于 色彩和伽马归一化  , 这一步应该算是一个图像的预处理过程,这里我们着重说HOG特征的提取部分,暂时略过这个预处理过程,这里我们只需要知道这一步的作用就OK了。

     

    关于计算图像梯度

    求方向梯度直方图(HOG)嘛,那自然不能少了梯度的计算了,这里的梯度指的是每一个像素的梯度。

    cell是计算直方图的基本单位,  我们从cell开始来入手分析HOG特征的实现。

    (这里的cell我们默认为8x8大小)我们计算cell中每个像素的梯度,

    要描述一个像素的梯度,需要从两方面入手,即幅值与方向。

    我们这里也使用模板来计算特征。

    首先用 [-1,0,1] 梯度算子对原图像做卷积运算,得到水平方向(以向右为正方向)的梯度分量 a 。

    然后用 [1,0,-1]T 梯度算子对原图像做卷积运算,得到竖直方向(以向上为正方向)的梯度分量 b 。

     

    幅值 =   根号下  (  a^2  +  b^2 )

    角度 =   arctan( a/b)

     

    之后我们要为这8x8个像素计算直方图。

    这次的x轴表示的是梯度的方向,我们将其定义为9个区间

    这里在区间定义上,我发现了两种方法:

    一种是在180度上,每20度定义一个;

    另一种是在360度上,每40度定义一个(但这40度并不是连续的,而是0-20 加上 180-200)。

     

    期初我以为其中有一个是错误的,但其实这两种都是可以的,

    直方图通道是平均分布在0-1800(无向)或0-3600(有向)范围内。经研究发现,采用无向的梯度和9个直方图通道,能在行人检测试验中取得最佳的效果。)-----节选自百度百科

     

    说了这么多,不如举个例子来的实在;

    譬如我们选取了一个size 为3x3的cell ,这个cell里有9个像素点,我们的bin(直方图区间)也为 9 个,

    (注意,这里两个取值虽然都是9,但是并没有一丝丝丝丝丝丝的关系,不要随便做出联想。)

    我们使用上述的模板计算出了梯度的幅值与方向

    幅值:  →        

     

     

    方向  :    →    

     

    各个区间先列出来,分别是:0-20、20-40、40-60、60-80、80-100、100-120、120-140、140-160、160-180,区间的右端点是不包含的。

    我们计算的时候先看方向矩阵,首先左上角的值是 90度,那就落在了 80-100 的区间,幅值矩阵对应位置的值是 6,因此 80-100 这个区间的 y 轴值加 6。再看方向矩阵的第一行第二列,也是 90,幅值矩阵对应值是 12,于是 80-100 这个区间再加 12,现在总的值是 18 了,相信到这里你已经看懂了,如此类推继续算下来,就可以得到这样一个直方图:

     

    我们将其称为cell的 descriptor(描述子)

                                                                         【关于bin的投影】

    【如果角度正好在这个bin区域的中心,则幅值大小取原值;若不在中心位置,则需要乘上一个夹角相关的函数】

     

     

    到这里就完成了cell  部分的计算了,接下来是它的上一层,也就是block的计算,,,,

    block是由多个cell所组成的,典型的组合方式是 2x2 个 cell 组成成一个 block,也就是跟图示的一样。

    我们知道每个 cell 上面都有一个 9 维的表示直方图大小的向量,那么一个 block 上就有 2x2x9 = 36维的向量。

    既然在cell中都已经做出方向梯度直方图了,那么block的作用是什么呢????

    其实,在block要做的操作就是把每一次选中的这 36 维向量做规范化(normalization),得到新的 36 维向量。

    规范化的方法有多种可选:

     

     

    将block内所有cell的descriptor串联起来,   便得到该block的 HOG特征描述子 descriptor。

     

    将window内的所有block的HOG特征descriptor串联起来就可以得到最终(你要检测的目标)的HOG特征descriptor了。

    这个就是最终的可供分类使用的特征向量了,你可以将它送入SVM中,从而得到最终的输出结果。

     

    文中的案例与部分章节参考于这为朋友的文章,万分感谢。

    https://www.jianshu.com/p/ed21c357ec12

     

     

     

     

     

     

     

     

     

    采用梯度幅值量级本身得到的检测效果最佳,而使用二值的边缘权值表示会严重降低效果。采用梯度幅值作为权重,可以使那些比较明显的边缘的方向信息对特征表达影响增大,这样比较合理,因为HOG特征主要就是依靠这些边缘纹理。

     

     

    展开全文
  • Hog特征

    2021-03-23 14:31:19
    Hog特征 方向梯度直方图(HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。HOG特征通过计算和统计图像局部区域的梯度方向直方图来构成特征。 主要思想是:在边缘具体位置未知的情况下,边缘...

    Hog特征

    方向梯度直方图(HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。HOG特征通过计算和统计图像局部区域的梯度方向直方图来构成特征。
    主要思想是:在边缘具体位置未知的情况下,边缘方向的分布也可以很好的表示目标的外形轮廓。图像中局部目标的表象和形状能够被梯度或边缘的方向密度分布很好地描述。
    其本质为:梯度的统计信息。梯度主要存在于边缘的地方。

    hog特征的算法步骤

    颜色空间归一化—>梯度计算—>梯度方向直方图—>重叠块直方图归一化—>HOG特征。

    第一步 颜色空间归一化
    ① 图像灰度化
    对于彩色图像,将RGB分量转化成灰度图像
    ② Gamma校正

    第二步 梯度计算
    分别在水平和竖直方向进行梯度计算

    第三步 梯度方向直方图
    构建梯度直方图的目的:为局部图像区域提供一个编码,能够保持对图像中目标的姿势和外观的弱敏感性。将图像分成若干个“单元格cell”,例如每个cell为8X8的像素大小。假设采用9个bin的直方图来统计这8X8个像素的梯度信息,即将cell的梯度方向0~180度(或0~360度,考虑了正负,signed)分成9个方向块(即9维特征向量),作为直方图的横轴,角度范围所对应的梯度值累加值作为直方图纵轴,每个bin的角度范围如下。
    bin的角度范围
    第四步 cell-block, 归一化梯度直方图
    由于局部光照的变化以及前景-背景对比度的变化,使得梯度强度的变化范围非常大,需要对梯度强度做归一化。把各个细胞单元组合成大的、空间上连通的区间(blocks)。
    这样,一个block内所有cell的特征向量串联起来便得到该block的HOG特征。
    这些区间互有重叠,每一个单元格的特征会以不同的结果多次出现在最后的特征向量中。我们将归一化之后的块描述符(向量)就称之为HOG描述符。
    在这里插入图片描述
    通常的HOG结构大致有三种:矩形HOG(简称为R-HOG),圆形HOG和中心环绕HOG。它们的单位都是Block(即块)。试验证明矩形HOG和圆形HOG的检测效果基本一致,而环绕形HOG效果相对差一些。
    在这里插入图片描述
    总之,HOG特征提取的过程:
    1、把样本图像分割为若干个像素的单元(cell),把梯度方向平均划分为9个区间(bin);
    2、在每个单元里面对所有像素的梯度方向在各个方向区间进行直方图统计,得到一个9维的特征向量;
    3、每相邻的4个单元构成一个块(block),把一个块内的特征向量联起来得到36维的特征向量,用块对样本图像进行扫描,扫描步长为一个单元;
    4、最后将所有块的特征串联起来,就得到了目标的HOG特征。

    hog的缺陷
    1、很难处理遮挡问题,人体姿势动作幅度过大或物体方向改变也不易检测(这个问题后来在DPM中采用可变形部件模型的方法得到了改善);
    2、跟SIFT相比,HOG没有选取主方向,也没有旋转梯度方向直方图,因而本身不具有旋转不变性(较大的方向变化),其旋转不变性是通过采用不同旋转方向的训练样本来实现的;
    3、跟SIFT相比,HOG本身不具有尺度不变性,其尺度不变性是通过缩放检测窗口图像的大小来实现的;
    4、 此外,由于梯度的性质,HOG对噪点相当敏感,在实际应用中,在Block和Cell划分之后,对于得到各个像区域中,有时候还会做一次高斯平滑去除噪点。

    代码实现:用到了opencv

    #include  <iostream>
    #include <opencv2/core/core.hpp>
    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/opencv.hpp>
    
    using namespace cv;
    using namespace std;
    
    Mat get_hogdescriptor_visual_image(Mat& origImg,vector<float>& descriptorValues,Size winSize,Size cellSize,int scaleFactor,double viz_factor)
    {
        Mat visual_image;
        resize(origImg, visual_image, Size(origImg.cols * scaleFactor, origImg.rows * scaleFactor));
    
        int gradientBinSize = 9;
        // 180度分成9份bin
        float radRangeForOneBin = 3.14 / (float)gradientBinSize;//每份bin的大小
    
        //准备cell的结构体,方向、梯度 
        //多少个cell
        int cells_in_x_dir = winSize.width / cellSize.width;
        int cells_in_y_dir = winSize.height / cellSize.height;
        int totalnrofcells = cells_in_x_dir * cells_in_y_dir;//特征向量有关数
    
        float*** gradientStrengths = new float** [cells_in_y_dir];
        int** cellUpdateCounter = new int* [cells_in_y_dir];
        //初始化置0
        for (int y = 0; y < cells_in_y_dir; y++)
        {
            gradientStrengths[y] = new float* [cells_in_x_dir];
            cellUpdateCounter[y] = new int[cells_in_x_dir];
            for (int x = 0; x < cells_in_x_dir; x++)
            {
                gradientStrengths[y][x] = new float[gradientBinSize];
                cellUpdateCounter[y][x] = 0;
    
                for (int bin = 0; bin < gradientBinSize; bin++)
                    gradientStrengths[y][x][bin] = 0.0;
            }
        }
    
        // block数=?-1
        // 最后一个block是特例
        int blocks_in_x_dir = cells_in_x_dir - 1;
        int blocks_in_y_dir = cells_in_y_dir - 1;
    
        // 每个cell的计算
        int descriptorDataIdx = 0;
        int cellx = 0;
        int celly = 0;
    
        for (int blockx = 0; blockx < blocks_in_x_dir; blockx++)
        {
            for (int blocky = 0; blocky < blocks_in_y_dir; blocky++)
            {
                // 一个block包括4个cell 
                for (int cellNr = 0; cellNr < 4; cellNr++)
                {
                    
                    int cellx = blockx;
                    int celly = blocky;
                    if (cellNr == 1) celly++;
                    if (cellNr == 2) cellx++;
                    if (cellNr == 3)
                    {
                        cellx++;
                        celly++;
                    }
                    //此cell已完结
                    for (int bin = 0; bin < gradientBinSize; bin++)
                    {
                        float gradientStrength = descriptorValues[descriptorDataIdx];
                        descriptorDataIdx++;
                        gradientStrengths[celly][cellx][bin] += gradientStrength;
                    } 
                    cellUpdateCounter[celly][cellx]++;
                } 
            }
        } 
    
    
        
        for (int celly = 0; celly < cells_in_y_dir; celly++)
        {
            for (int cellx = 0; cellx < cells_in_x_dir; cellx++)
            {
    
                float NrUpdatesForThisCell = (float)cellUpdateCounter[celly][cellx];
    
                // compute average gradient strenghts for each gradient bin direction  
                for (int bin = 0; bin < gradientBinSize; bin++)
                {
                    gradientStrengths[celly][cellx][bin] /= NrUpdatesForThisCell;
                }
            }
        }
    
    
        /*cout << "descriptorDataIdx = " << descriptorDataIdx << endl;*/
    
        // 可视化
        for (int celly = 0; celly < cells_in_y_dir; celly++)
        {
            for (int cellx = 0; cellx < cells_in_x_dir; cellx++)
            {
                int drawX = cellx * cellSize.width;
                int drawY = celly * cellSize.height;
    
                int mx = drawX + cellSize.width / 2;
                int my = drawY + cellSize.height / 2;
    
                rectangle(visual_image,
                    Point(drawX * scaleFactor, drawY * scaleFactor),
                    Point((drawX + cellSize.width) * scaleFactor,
                        (drawY + cellSize.height) * scaleFactor),
                    CV_RGB(100, 100, 100),
                    1);
    
                
                for (int bin = 0; bin < gradientBinSize; bin++)
                {
                    float currentGradStrength = gradientStrengths[celly][cellx][bin];
                    if (currentGradStrength == 0)
                        continue;
    
                    float currRad = bin * radRangeForOneBin + radRangeForOneBin / 2;
    
                    float dirVecX = cos(currRad);
                    float dirVecY = sin(currRad);
                    float maxVecLen = cellSize.width / 2;
                    float scale = viz_factor; 
                    float x1 = mx - dirVecX * currentGradStrength * maxVecLen * scale;
                    float y1 = my - dirVecY * currentGradStrength * maxVecLen * scale;
                    float x2 = mx + dirVecX * currentGradStrength * maxVecLen * scale;
                    float y2 = my + dirVecY * currentGradStrength * maxVecLen * scale;
                    line(visual_image,
                        Point(x1 * scaleFactor, y1 * scaleFactor),
                        Point(x2 * scaleFactor, y2 * scaleFactor),
                        CV_RGB(0, 0, 255),
                        1);
    
                }  
    
            } 
        } 
        //释放内存
        for (int y = 0; y < cells_in_y_dir; y++)
        {
            for (int x = 0; x < cells_in_x_dir; x++)
            {
                delete[] gradientStrengths[y][x];
            }
            delete[] gradientStrengths[y];
            delete[] cellUpdateCounter[y];
        }
        delete[] gradientStrengths;
        delete[] cellUpdateCounter;
    
        return visual_image;
    }
    
    
    int main()
    {
        HOGDescriptor hog;
           /* <1>win_size:检测窗口大小。
            <2>block_size:块大小,目前只支持Size(16, 16)。
            <3>block_stride:块的滑动步长,大小只支持是单元格cell_size大小的倍数。
            <4>cell_size:单元格的大小,目前只支持Size(8, 8)。
            <5>nbins:直方图bin的数量(投票箱的个数),目前每个单元格Cell只支持9个。
            <6>win_sigma:高斯滤波窗口的参数。
            <7>threshold_L2hys:块内直方图归一化类型L2 - Hys的归一化收缩率
            <8>gamma_correction:是否gamma校正
            <9>nlevels:检测窗口的最大数量*/
        hog.winSize = Size(512, 512);//hog子窗口大小
        vector<float> des;
        Mat src = imread("D://aaa.jpeg");
        //cvtColor(src, src, CV_32FC1);
        Mat dst;
        resize(src, dst, Size(512, 512));//保证512*512
        imshow("src", src);
    
        hog.compute(dst, des);//计算HOG特征向量
           /* <1>img:源图像。只支持CV_8UC1和CV_8UC4数据类型。
            <2>descriptors:返回的HOG特征向量,descriptors.size是HOG特征的维数。
            <3>winStride:窗口移动步长。
            <4>padding:扩充像素数。
            <5>locations:对于正样本可以直接取(0, 0), 负样本为随机产生合理坐标范围内的点坐标。*/
    
        cout << des.size() << endl;
        Mat background = Mat::zeros(Size(512, 512), CV_8UC1);
    
        Mat background_hog = get_hogdescriptor_visual_image(background, des, hog.winSize, hog.cellSize, 1, 2.0);
        imshow("HOG特征1", background_hog);
        imwrite("特征可视化1.jpg", background_hog);
        Mat src_hog = get_hogdescriptor_visual_image(src, des, hog.winSize, hog.cellSize, 1, 2.0);
        imshow("HOG特征2", src_hog);
        imwrite("特征可视化2.jpg", src_hog);
        waitKey();
        return 0;
    }
    
    
    
    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,243
精华内容 1,297
关键字:

hog特征