SVM+HOG 之牛奶盒检测(二、HOG)

方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。
它通过计算和统计图像局部区域的梯度方向直方图来构成特征。
Hog特征结合SVM分类器在图像识别中应用非常广泛,尤其是传统机器学习中比较成功的行人检测算法。当然现在都是深度学习的天下了。
核心思想: 图像中,局部目标的表象和形状能够被梯度或边缘的方向密度分布很好地描述。本质是梯度的统计信息,而梯度主要存在于边缘的地方。

具体流程: 图像(image)->滑动检测窗口(win)->图像块(block)->细胞单元(cell)
1、将图像灰度化;
2、采用Gamma校正对输入图像进行颜色空间的标准化(归一化);目的是调节图像的对比度,降低图像局部的阴影和光照变化所造成的影响,同时可以抑制噪音的干扰,得到一幅图像(image)

3、计算图像每个像素的梯度(包括大小和方向);主要是为了捕获轮廓信息,同时进一步弱化光照的干扰。

4、选择滑动窗口(win)

5、在滑动窗口中选择一块(block)

6、在块中划分cell单元(cell)

7、统计每个cell的梯度直方图(不同梯度的个数),即可形成每个cell的descriptor描述子(描述向量);

8、将滑动窗口(win)内的所有块(block)的HOG特征descriptor串联起来就可以得到该滑动窗口的的HOG特征descriptor了。

9、如此按滑动步长一步步移动滑动窗口,直到图像的结束。


hdck.png
记在笔记本上的手写笔记.jpg

如何计算一个细胞(cell)中的梯度方向直方图呢?

统计图像梯度直方图.png

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

这是HOG特征descriptor的直观显示:
原图:


原图.jpg

HOG直观显示:


hogvisualize.jpg

将其放大可以看到那些cell中的descriptor


fd.jpg

这是直观显示HOG特征的代码
注意,图像的尺寸必须是偶数x偶数(也可能是整数x整数),不然程序运行会出错!!

#include <opencv2/opencv.hpp>    
#include <cstdio>    
#include <cstdlib>    
#include <Windows.h>    
//#include "opencvtest.h"  

using namespace std;
using namespace cv;

// HOGDescriptor visual_imagealizer  
// adapted for arbitrary size of feature sets and training images  
Mat get_hogdescriptor_visual_image(Mat& origImg,
    vector<float>& descriptorValues,//hog特征向量  
    Size winSize,//图片窗口大小  
    Size cellSize,
    int scaleFactor,//缩放背景图像的比例  
    double viz_factor)//缩放hog特征的线长比例  
{
    Mat visual_image;//最后可视化的图像大小  
    resize(origImg, visual_image, Size(origImg.cols*scaleFactor, origImg.rows*scaleFactor));

    int gradientBinSize = 9;
    // dividing 180° into 9 bins, how large (in rad) is one bin?  
    float radRangeForOneBin = 3.14 / (float)gradientBinSize; //pi=3.14对应180°  

    // prepare data structure: 9 orientation / gradient strenghts for each cell  
    int cells_in_x_dir = winSize.width / cellSize.width;//x方向上的cell个数  
    int cells_in_y_dir = winSize.height / cellSize.height;//y方向上的cell个数  
    int totalnrofcells = cells_in_x_dir * cells_in_y_dir;//cell的总个数  
    //注意此处三维数组的定义格式  
    //int ***b;  
    //int a[2][3][4];  
    //int (*b)[3][4] = a;  
    //gradientStrengths[cells_in_y_dir][cells_in_x_dir][9]  
    float*** gradientStrengths = new float**[cells_in_y_dir];
    int** cellUpdateCounter = new int*[cells_in_y_dir];
    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;//把每个cell的9个bin对应的梯度强度都初始化为0  
        }
    }

    // nr of blocks = nr of cells - 1  
    // since there is a new block on each cell (overlapping blocks!) but the last one  
    //相当于blockstride = (8,8)  
    int blocks_in_x_dir = cells_in_x_dir - 1;
    int blocks_in_y_dir = cells_in_y_dir - 1;

    // compute gradient strengths per 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++)
        {
            // 4 cells per block ...  
            for (int cellNr = 0; cellNr<4; cellNr++)
            {
                // compute corresponding cell nr  
                int cellx = blockx;
                int celly = blocky;
                if (cellNr == 1) celly++;
                if (cellNr == 2) cellx++;
                if (cellNr == 3)
                {
                    cellx++;
                    celly++;
                }

                for (int bin = 0; bin<gradientBinSize; bin++)
                {
                    float gradientStrength = descriptorValues[descriptorDataIdx];
                    descriptorDataIdx++;

                    gradientStrengths[celly][cellx][bin] += gradientStrength;//因为C是按行存储  

                } // for (all bins)  


                // note: overlapping blocks lead to multiple updates of this sum!  
                // we therefore keep track how often a cell was updated,  
                // to compute average gradient strengths  
                cellUpdateCounter[celly][cellx]++;//由于block之间有重叠,所以要记录哪些cell被多次计算了  

            } // for (all cells)  


        } // for (all block x pos)  
    } // for (all block y pos)  


    // compute average gradient strengths  
    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 << "winSize = " << winSize << endl;
    cout << "cellSize = " << cellSize << endl;
    cout << "blockSize = " << cellSize * 2 << endl;
    cout << "blockNum = " << blocks_in_x_dir << "×" << blocks_in_y_dir << endl;
    cout << "descriptorDataIdx = " << descriptorDataIdx << endl;

    // draw cells  
    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(0, 0, 0),//cell框线的颜色  
                1);

            // draw in each cell all 9 gradient strengths  
            for (int bin = 0; bin<gradientBinSize; bin++)
            {
                float currentGradStrength = gradientStrengths[celly][cellx][bin];

                // no line to draw?  
                if (currentGradStrength == 0)
                    continue;

                float currRad = bin * radRangeForOneBin + radRangeForOneBin / 2;//取每个bin里的中间值,如10°,30°,...,170°.  

                float dirVecX = cos(currRad);
                float dirVecY = sin(currRad);
                float maxVecLen = cellSize.width / 2;
                float scale = viz_factor; // just a visual_imagealization scale,  
                // to see the lines better  

                // compute line coordinates  
                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;

                // draw gradient visual_imagealization  
                line(visual_image,
                    Point(x1*scaleFactor, y1*scaleFactor),
                    Point(x2*scaleFactor, y2*scaleFactor),
                    CV_RGB(255, 255, 255),//HOG可视化的cell的颜色  
                    1);

            } // for (all bins)  

        } // for (cellx)  
    } // for (celly)  
 
    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;//返回最终的HOG可视化图像  

}


int main()
{

    HOGDescriptor hog;//使用的是默认的hog参数  

    Mat src = imread("1.jpg");//注意这里边的双斜杠
    int src_width = src.cols;
    int src_height = src.rows;
    int width = src_width;
    int height = src_height;
    hog.winSize = Size(width, height);
    vector<float> des;//HOG特征向量  

    Mat dst;
    resize(src, dst, Size(width, height));//规范图像尺寸  
    imshow("src", src);
    hog.compute(dst, des);//计算hog特征  
    Mat background = Mat::zeros(Size(width, height), CV_8UC1);//设置黑色背景图,因为要用白色绘制hog特征  

    Mat d = get_hogdescriptor_visual_image(background, des, hog.winSize, hog.cellSize, 3, 2.5);
    imshow("dst", d);
    imwrite("hogvisualize.jpg", d);
    waitKey();

    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352