zoukankan      html  css  js  c++  java
  • OpenCV_颜色直方图的计算、显示、处理、对比及反向投影

    首先介绍一下直方图
    一.用带权重的样本统计直方图
    直方图Histogram,是一种常见的概率分布的非参数(区别于高斯分布,泊松分布等用参数表达概率密度的方法)表达方法。直方图可以看成概率密度分布的离散化表达方法。它的计算很简单,是一种投票的方法,就是每个样本往对应的小盒子(bin)里投一票。假设N个样本数据x量化为1~M之间的整数,那么Hist是M维数组,对应的直方图计算方法如下:

    //initializing
    for i=1:M
        Hist[i] = 0;
    end
    //voting
    for  i = 1:N
        Hist[x[i]] += 1 ;
    end
    为了表示成概率分布,需要Hist数组和为1:
    //normalize
    for  j = 1:M
        Hist[j] = Hist[j]/sum(Hist) ; //sum(Hist) = Hist[1] + Hist[2] + ... + Hist[M];
    end
    在这里sum(Hist)等于样本个数N。以上“投票+归一化”两个步骤,其实可以和成一步,即每个样本x[i]有一个对应的权重w[i]=1/N,投票的时候往小盒子里放的是权重:
     
    //initializing
    for i=1:M
        Hist[i] = 0;
    end
    //voting
    for  i = 1:N
        Hist[x[i]] += w[i] ;
    end
     
    这样得到数组Hist里的每个值都是一个0-1之间的小数,表示当x取这个值(数组对应的下标)时候的概率。
     
    二.样本权重的变化影响直方图形状
    如果想改变一个概率分布取曲线的形状,比如,在训练的时候要强调x[i]=3的样本,一种办法是增加这种样本的个数,使Hist[3]变大,另一种方法是增加和它对应的样本的权重w[i],让某些样本产生“一个顶俩”的效果,这两种方式都能起到改变概率分布的作用。
     
    对人脸检测这类基于图像的目标检测方法,需要搜集大量样本进行离线的训练,样本要尽可能覆盖所有可能的情况,从而使所提取特征的概率分布具有代表性,当样本数趋向于无穷大的时候,概率分布就趋向于真实的分布情况。但通常在实际中,样本数总是有限的,这时就可以通过增加某些比较重要的样本的权重,来达到看起来是增加了某些样本数量的效果
     
    另外,直方图数组Hist的某个bin中的样本权重的改变,不应该仅仅影响当前的bin,也应该对周围相邻的bin造成影响。比如,在统计一个地区人群的身高分布的时候,如果只有一个样本x=1.65m,我们可以在Hist数组中对应1.6<=x<1.7这一个bin里投上一票,但是1.5<=x<1.6和1.7<=x<1.8两个bin的得票为0。其实不应该这样,根据身高应该是连续分布的这一假设,从样本x=1.65m可以安全地推断出世上也应该有身高1.59和1.71的人,这样左右两个相邻的bin的值就不应为0,应该也受到x=1.65m这个样本的影响,这种影响可以假设是服从高斯分布,即越远的bin受影响越小。这其实是另一种常见的概率密度估计的非参数方法,kernel density estimation(KDE)。我们的人脸检测应用由于样本足够多,所以没有考虑这一方法。也就是说,每个特征值在投票的时候,只对一个bin起作用,不影响相邻的那两个。

    颜色直方图直观的显示了图像在色彩空间的分布状况,本文将讨论在OpenCv中跟直方图相关的一些基本操作,包括:计算、显示、处理、对比及反向投影.

    直方图的计算、显示、处理
      OpenCv中,可以用方法cvCalcHist方法计算图像的直方图。不过值得注意的是,该方法的第一个参数image是一个指向数组的IplImage* 类型指针。这允许利用多个图像通道。对于多通道图像(如HSV或者RGB),在调用函数cvCalcHist()之前,先要调用函数cvSplit()将图像分为单通道,此方法与宏cvCvtPixtoPlane等同,然后再选择需要参与直方图计算的通道。下面有几段计算直方图的代码,分别计算单通道(红色)直方图、色调和饱和度直方图。

    如何在OpenCV中绘制一个直方图?下面是有关于绘制直方图实验的学习总结:

    1.cvCreateHist(dim,&size,flag,**range,uniform):

       dim为hist的维度;size为总分区间数;flag为标记图像是什么类型,是CV_HIST_ARRAY,还是CV_HIST_TREE,然后就可以选择如何保存数据,如果CV_HIST_ARRAY,就用CvMatND(多维密集数组);如果CV_HIST_TREE,就使用CvSparseMat(多维稀疏数组);range为每个维度的范围;uniform为归一化标识,这个还不是很好理解。

    2.IplImage中有一个属性可以设置图像的原点坐标,0代表左上角,1代表左下角。(image->origin)

    3.cvCalcHist(**imge,*hist):

       image为统计图像,hist为直方图结构体

    4.获取了直方图信息后,可以通过直方图结构体直接访问内部信息:

      hist->type;hist->thresh[i][0]和hist->thresh[i][1]分别存储上阀值和下阀值。如果CV_HIST_ARRAY的话,可以通过((CvMatND*)hist->bins)->data.fl[i]获取统计值。不过OpenCV已经提供了一个很好的方法去查询,cvQueryHistValue_*D(hist,d...);

    5.可以通过CV_RGB(R,G,B)的宏设置RGB颜色。

    6.直方图的绘制,使用cvQueryHistValue_*D去获取统计值,然后绘制在一个创建好的图像中。可以通过cvLine或者cvRectangle方法。

    7.还可以通过设置range的范围来获取有需要的区域,如果这样的话,最好在画直方图的时候,计算好每个区间的width值。

    8.cvGetMinMaxValue(hist,*min,*max,*minindex,*maxindex):

      min和max必须为float类型,minindex和maxindex为最小最大值的位置

    9.cvRound方法:四舍五入

    10.从HSV颜色转换为RGB颜色:

      IplImage* hsv_color = cvCreateImage(cvSize(1,1),8,3);
      IplImage* rgb_color = cvCreateImage(cvSize(1,1),8,3); 

      cvSet2D(hsv_color,0,0,cvScalar(h*180.f/h_bins,s*255.f/s_bins,255,0));
      cvCvtColor(hsv_color,rgb_color,CV_HSV2BGR);
      CvScalar color = cvGet2D(rgb_color,0,0);

    具体代码如下:

    View Code
      1 #include "stdafx.h"
    2 #include <iostream>
    3 using namespace std;
    4
    5
    6 #ifdef _CH_
    7 #pragma package <opencv>
    8 #endif
    9
    10 #include "cv.h"
    11 #include "highgui.h"
    12
    13
    14 //色相饱和度直方图
    15 void CalcHistHs()
    16 {
    17 IplImage* img_source;
    18
    19 if (img_source = cvLoadImage("flower.jpg",1))
    20 {
    21 IplImage* hsv = cvCreateImage(cvGetSize(img_source),8,3);
    22
    23 //rgb色彩空间转换到hsv色彩空间
    24 cvCvtColor(img_source,hsv,CV_BGR2HSV);
    25 cvNamedWindow( "hsv", 1 );
    26 cvShowImage( "hsv", hsv );
    27
    28 IplImage* h_plane = cvCreateImage(cvGetSize(img_source),8,1);
    29 IplImage* s_plane = cvCreateImage(cvGetSize(img_source),8,1);
    30 IplImage* v_plane = cvCreateImage(cvGetSize(img_source),8,1);
    31
    32 IplImage* planes[] ={h_plane,s_plane};
    33 //分割为单通道图像
    34 cvCvtPixToPlane(hsv,h_plane,s_plane,v_plane,0);
    35 cvNamedWindow( "h_plane", 1 );
    36 cvShowImage( "h_plane", h_plane );
    37 cvNamedWindow( "s_plane", 1 );
    38 cvShowImage( "s_plane", s_plane );
    39 cvNamedWindow( "v_plane", 1 );
    40 cvShowImage( "v_plane", v_plane );
    41
    42
    43 //build the histogram and compute its contents
    44
    45 /** H 分量划分为30个等级,S分量划分为32个等级 */
    46 int h_bins =30, s_bins = 32;
    47
    48 CvHistogram* hist;
    49
    50 {
    51 int hist_size[] = {h_bins,s_bins};
    52 float h_ranges[] = {0,180}; /* hue varies from 0 (~0°red) to 180 (~360°red again) */
    53
    54 float s_ranges[] = {0,255}; /* saturation varies from 0 (black-gray-white) to 255 (pure spectrum color) */
    55
    56 float* ranges[] = {h_ranges,s_ranges};
    57
    58 /** 创建直方图,二维, 每个维度上均分 */
    59 hist = cvCreateHist(
    60 2, // int dims
    61 hist_size, // int* sizes
    62 CV_HIST_ARRAY, // int type
    63 ranges, // float** ranges
    64 1 //uniform
    65 );
    66 }
    67
    68 // 根据H,S两个平面数据统计直方图
    69 cvCalcHist( planes, hist, 0, 0 );
    70 //归一化处理
    71 cvNormalizeHist(hist,1.0);
    72 /** 获取直方图统计的最大值,用于动态显示直方图 */
    73 float max_value;
    74 cvGetMinMaxHistValue( hist, 0, &max_value, 0, 0 );
    75
    76 int scale=10;
    77 //创建直方图图像
    78 /** 设置直方图显示图像 */
    79 int height = 240;
    80 int width = (h_bins*s_bins*6);
    81 IplImage* hist_img = cvCreateImage( cvSize(width,height), 8, 3 );
    82 cvZero( hist_img );
    83
    84 /** 用来进行HSV到RGB颜色转换的临时单位图像 */
    85 IplImage * hsv_color = cvCreateImage(cvSize(1,1),8,3);
    86 IplImage * rgb_color = cvCreateImage(cvSize(1,1),8,3);
    87 int bin_w = width / (h_bins * s_bins);
    88 for(int h = 0; h < h_bins; h++)
    89 {
    90 for(int s = 0; s < s_bins; s++)
    91 {
    92 int i = h*s_bins + s;
    93 /** 获得直方图中的统计次数,计算显示在图像中的高度 */
    94 float bin_val = cvQueryHistValue_2D( hist, h, s );
    95 int intensity = cvRound(bin_val*height/max_value);
    96
    97 /** 获得当前直方图代表的颜色,转换成RGB用于绘制 */
    98 cvSet2D(hsv_color,0,0,cvScalar(h*180.f / h_bins,s*255.f/s_bins,255,0));
    99 cvCvtColor(hsv_color,rgb_color,CV_HSV2BGR);
    100 CvScalar color = cvGet2D(rgb_color,0,0);
    101
    102 cvRectangle( hist_img, cvPoint(i*bin_w,height),
    103 cvPoint((i+1)*bin_w,height - intensity),
    104 color, -1, 8, 0 );
    105 }
    106 }
    107
    108 cvNamedWindow( "Source", 1 );
    109 cvShowImage( "Source", img_source );
    110
    111 cvNamedWindow( "H-S Histogram", 1 );
    112 cvShowImage( "H-S Histogram", hist_img );
    113
    114
    115
    116
    117 }
    118
    119 }
    120
    121 void CalcHistRgb()
    122 {
    123 IplImage* img_source;
    124
    125 if (img_source = cvLoadImage("flower.jpg",1))
    126 {
    127 IplImage* RedChannel = cvCreateImage( cvGetSize(img_source), 8, 1);
    128 IplImage* GreenChannel = cvCreateImage( cvGetSize(img_source), 8, 1);
    129 IplImage* BlueChannel = cvCreateImage( cvGetSize(img_source), 8, 1);
    130 IplImage* alphaChannel = cvCreateImage( cvGetSize(img_source), 8, 1);
    131 IplImage* gray_plane = cvCreateImage(cvGetSize(img_source),8,1);
    132
    133
    134 //分割为单通道图像
    135 cvCvtPixToPlane(img_source,BlueChannel,GreenChannel,RedChannel,0);
    136 // 显示图像
    137 cvNamedWindow( "RedChannel", 1 );
    138 cvNamedWindow( "GreenChannel", 1 );
    139 cvNamedWindow( "BlueChannel", 1 );
    140 cvNamedWindow( "lphaChannel", 1 );
    141
    142 cvShowImage( "RedChannel", RedChannel );
    143 cvShowImage( "GreenChannel", GreenChannel );
    144 cvShowImage( "BlueChannel", BlueChannel );
    145 cvShowImage( "lphaChannel", alphaChannel );
    146
    147
    148 cvCvtColor(img_source,gray_plane,CV_BGR2GRAY);
    149 cvNamedWindow("GrayPlane",1);
    150 cvShowImage("GrayPlane",gray_plane);
    151 //OpenCV中不管是Windows中Load的还是摄像头取得的都是BGR顺序排列的
    152
    153 //然后为这四幅图创建对应的直方图结构。
    154 int hist_size = 100;
    155
    156 int hist_height = 100;
    157
    158 float range[] = {0,255};
    159
    160 float* ranges[]={range};
    161
    162 CvHistogram* r_hist = cvCreateHist(1,&hist_size,CV_HIST_ARRAY,ranges,1);
    163
    164 CvHistogram* g_hist = cvCreateHist(1,&hist_size,CV_HIST_ARRAY,ranges,1);
    165
    166 CvHistogram* b_hist = cvCreateHist(1,&hist_size,CV_HIST_ARRAY,ranges,1);
    167
    168 CvHistogram* gray_hist = cvCreateHist(1,&hist_size,CV_HIST_ARRAY,ranges,1);
    169
    170 //接下来计算直方图,创建用于显示直方图的图像,略去了一部分重复代码,以下也是
    171
    172 cvCalcHist(&RedChannel,r_hist,0,0);
    173 cvCalcHist(&GreenChannel,g_hist,0,0);
    174 cvCalcHist(&BlueChannel,b_hist,0,0);
    175 cvCalcHist(&gray_plane,gray_hist,0,0);
    176 cvNormalizeHist(gray_hist,1.0);
    177 cvNormalizeHist(r_hist,1.0);
    178 cvNormalizeHist(g_hist,1.0);
    179 cvNormalizeHist(b_hist,1.0);
    180
    181 int scale = 2;
    182
    183 IplImage* hist_image = cvCreateImage(cvSize(hist_size*scale,hist_height*4),8,3);
    184
    185 cvZero(hist_image);
    186
    187 //然后开始显示,这里对直方图进行了标准化处理,不然的话无法观察到明显的变化。
    188
    189 float r_max_value = 0;
    190 float g_max_value = 0;
    191 float b_max_value = 0;
    192 float gray_max_value = 0;
    193 cvGetMinMaxHistValue(r_hist, 0,&r_max_value,0,0);
    194 cvGetMinMaxHistValue(g_hist, 0,&g_max_value,0,0);
    195 cvGetMinMaxHistValue(b_hist, 0,&b_max_value,0,0);
    196 cvGetMinMaxHistValue(b_hist, 0,&gray_max_value,0,0);
    197 for(int i=0;i<hist_size;i++)
    198 {
    199
    200 float r_bin_val = cvQueryHistValue_1D(r_hist,i);
    201
    202 int r_intensity = cvRound(r_bin_val*hist_height/r_max_value);
    203 cvRectangle(
    204 hist_image,
    205 cvPoint(i*scale,hist_height-1),
    206 cvPoint((i+1)*scale - 1, hist_height - r_intensity),
    207 CV_RGB(255,0,0));
    208
    209 float g_bin_val=cvQueryHistValue_1D(g_hist,i);
    210 int g_intensity = cvRound(g_bin_val*hist_height/g_max_value);
    211 cvRectangle(
    212 hist_image,
    213 cvPoint(i*scale,2*hist_height-1),
    214 cvPoint((i+1)*scale - 1, 2*hist_height - g_intensity),
    215 CV_RGB(0,255,0));
    216
    217 float b_bin_val = cvQueryHistValue_1D(b_hist,i);
    218 int b_intensity = cvRound(b_bin_val*hist_height/b_max_value);
    219 cvRectangle(
    220 hist_image,
    221 cvPoint(i*scale,3*hist_height-1),
    222 cvPoint((i+1)*scale - 1, 3*hist_height - b_intensity),
    223 CV_RGB(0,0,255));
    224
    225 float gray_bin_val = cvQueryHistValue_1D(gray_hist,i);
    226 int gray_intensity = cvRound(gray_bin_val*hist_height/gray_max_value);
    227 cvRectangle(
    228 hist_image,
    229 cvPoint(i*scale,4*hist_height-1),
    230 cvPoint((i+1)*scale - 1, 4*hist_height - gray_intensity),
    231 CV_RGB(100,100,100));
    232
    233 }
    234 cvNamedWindow( "Source", 1 );
    235 cvShowImage( "Source", img_source );
    236
    237 cvNamedWindow( "RGB_Histogram", 1 );
    238 cvShowImage( "RGB_Histogram", hist_image );
    239
    240
    241 }
    242
    243 }
    244
    245 int main()
    246 {
    247 CalcHistRgb();
    248 CalcHistHs();
    249 cvWaitKey(0);
    250 }

    直方图对比

    OpenCv提供了5种对比直方图的方式:CORREL(相关)、CHISQR(卡方)、INTERSECT(相交)、BHATTACHARYYA、EMD(最小工作距离),其中CHISQR速度最快,EMD速度最慢且有诸多限制,但是EMD的效果最好。世界总是充满了矛盾,而我们的工作就是化解矛盾,把每种方式都测试一下,找到正好能解决问题并且速度最优的方法。
     需要注意的是:EMD方式要求先将直方图转换成矩阵:
    EMD方法会占用很很很大量的内存,在使用前请注意直方图的维数及区间数目,不然会出现内存不足的异常。关于这点,请参看我的另一篇文章《关于使用cvCalcEMD2计算两个直方图间最小工作距离的限制(Why cvCalcEMD2 Throw Insufficient Memory Exception)》。还有一点值得注意的是,不同的对比方式对待结果的方式很不一样,结果越大不一定说明匹配度更高,具体请参看《学习OpenCv》这本书的相关章节。

    对于直方图的相关和相交对比,结果值越大(即亮度较高)的地方表示匹配程度越高;
    对于直方图的卡方、Bhattacharyya、EMD对比,结果值越小(即越黑暗)的地方表示匹配程度越高。

    陆地移动距离EMD

    光线能引起图像颜色值的漂移,尽管这些漂移没有改变颜色直方图的形状,但是这些漂移引起了颜色值位置的变化,从而导致前述匹配策略失效。如果利用颜色直方图的距离测量来代替直方图的匹配策略,那么我们仍然可以像直方图对比一样对比两个直方图的距离,即使第二个直方图发生漂移,也能找到最小的距离度量。

    陆地移动距离时一种度量准则,它实际上度量的是怎样将一个直方图的形状转变为另一个直方图的形状,包括移动直方图的部分或者全部到一个新的位置,可以在任何维的直方图上进行这种度量。

    0表示最精确匹配,半匹配是成功将直方图的一半转换,将左边直方图的一半转换到下一个直方图。最终移动整个直方图到右边需要整个单位的距离(即将模板直方图转换为完全不匹配直方图)。1表示完全不匹配。

    具体代码如下:

    View Code
     1 int main()
    2 {
    3 /*CalcHistHs("flower.jpg");
    4 CvHistogram* hist1 = CalcHistRgb("flower.jpg");
    5 CvHistogram* hist2 = CalcHistRgb("flower3.jpg");
    6
    7 double result = cvCompareHist(hist1,hist2,CV_COMP_CORREL);
    8 cout<< result;
    9
    10 cvWaitKey(0);*/
    11
    12 IplImage *src = cvLoadImage("flower.jpg");
    13 IplImage *hsv = cvCreateImage(cvGetSize(src),8,3);
    14 cvCvtColor(src,hsv,CV_BGR2HSV);// change RGB of src to HSV of hsv
    15 IplImage *h_plane = cvCreateImage(cvGetSize(src),8,1);
    16 IplImage *s_plane = cvCreateImage(cvGetSize(src),8,1);
    17 IplImage *v_plane = cvCreateImage(cvGetSize(src),8,1);
    18 IplImage *planes[]={h_plane,s_plane};
    19 cvSplit(hsv,h_plane,s_plane,v_plane,0);//split HSV to 3 channal
    20
    21 //求得直方图
    22 int h_bins=30,s_bins=32;
    23 CvHistogram *hist1,*hist2;
    24 int size[]={h_bins,s_bins};
    25 float h_ranges[]={0,180};
    26 float s_ranges[]={0,255};
    27 float *ranges[]={h_ranges,s_ranges};
    28
    29 hist1=cvCreateHist(2,size,CV_HIST_ARRAY,ranges,1);
    30 cvCalcHist(planes,hist1,0,0);//只能一个通道一个通道的写入直方图,所以上面分成H、S、V
    31 cvNormalizeHist(hist1,1.0);//归一化直方图,使所有块的值加起来为1
    32 hist2=cvCreateHist(2,size,CV_HIST_ARRAY,ranges,1);
    33 cvCalcHist(planes,hist2,0,0);
    34 cvNormalizeHist(hist2,1.0);
    35
    36 //求得signature用于比较直方图
    37 CvMat *sig1,*sig2;
    38 int numrows=h_bins*s_bins;
    39 sig1=cvCreateMat(numrows,3,CV_32FC1);//由于是2维图像直方图,所以只需保存每个点的(值,横坐标,纵坐标)三个数,一共numrows个点
    40 sig2=cvCreateMat(numrows,3,CV_32FC1);
    41 for(int h=0;h<h_bins;h++)
    42 {
    43 for(int s=0;s<s_bins;s++)
    44 {
    45 float bin_val=cvQueryHistValue_2D(hist1,h,s);
    46 cvSet2D(sig1,h*s_bins+s,0,cvScalar(bin_val));
    47 cvSet2D(sig1,h*s_bins+s,1,cvScalar(h));
    48 cvSet2D(sig1,h*s_bins+s,2,cvScalar(s));
    49 bin_val=cvQueryHistValue_2D(hist2,h,s);
    50 cvSet2D(sig2,h*s_bins+s,0,cvScalar(bin_val));
    51 cvSet2D(sig2,h*s_bins+s,1,cvScalar(h));
    52 cvSet2D(sig2,h*s_bins+s,2,cvScalar(s));
    53 }
    54 }
    55 float emd=cvCalcEMD2(sig1,sig2,CV_DIST_L2);
    56 cout<<emd;//可以清楚的看出因为我对比同一个图像直方图,所以得数0是对的,完全一样
    57 cvWaitKey(0);
    58 return 0;
    59
    60
    61 }


    直方图的反向投影

    我对于反向投影的理解是通过颜色直方图,检测图片中的某个像素点的颜色是否位于直方图中,若位于则将其加亮(阀值化?),通过对图像的检测,得出结果图像。如果照着我这个理解的话,那么对同一物体的颜色的取样点越多,那么越能更好的找出目标图形(当然,这很可能也会带来一些不需要的点,那么这个方法应该对于对比度较大的图像比较有效……)
    换个说法就是在反向投影中,直方图的作用在于提供一个比较标准,即对于要检测的图像来说,需要给它提供一个用于识别出它需要被识别出的部分的直方图。

    1.反向投影的作用是什么?
        反向投影用于在输入图像(通常较大)中查找特定图像(通常较小或者仅1个像素,以下将其称为模板图像)最匹配的点或者区域,也就是定位模板图像出现在输入图像的位置。
    2.反向投影如何查找(工作)?
        查找的方式就是不断的在输入图像中切割跟模板图像大小一致的图像块,并用直方图对比的方式与模板图像进行比较。

    假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:
    (1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;
    (2)生成临时图像的直方图;
    (3)用临时图像的直方图和模板图像的直方图对比,对比结果记为c;
    (4)直方图对比结果c,就是结果图像(0,0)处的像素值;
    (5)切割输入图像从(0,1)至(10,11)的临时图像,对比直方图,并记录到结果图像;
    (6)重复(1)~(5)步直到输入图像的右下角。

    直方图反向投影示意图

    3.反向投影的结果是什么?
        反向投影的结果包含了:以每个输入图像像素点为起点的直方图对比结果。可以把它看成是一个二维的浮点型数组,二维矩阵,或者单通道的浮点型图像。
    4.特殊情况怎么样?
        如果输入图像和模板图像一样大,那么反向投影相当于直方图对比。如果输入图像比模板图像还小,直接罢工~~。
    5.使用时有什么要注意的地方?
        需要注意的地方比较多,我们对照反向投影函数来说:
        void cvCalcBackProjectPatch(
            IplImage** image,   /*输入图像:是一个单通道图像数组,而非实际图像*/
            CvArr* dst,         /*输出结果:是一个单通道32位浮点图像,它的宽度为W-w+1,高度为H-h+1,这里的W和H是输入图像的宽度和高度,w和h是模板图像的宽度和高度*/
            CvSize patch_size,  /*模板图像的大小:宽度和高度*/
            CvHistogram* hist,  /*模板图像的直方图:直方图的维数和输入图像的个数相同,并且次序要一致;例如:输入图像包含色调和饱和度,那么直方图的第0维是色调,第1维是饱和度*/
            int method,         /*对比方式:跟直方图对比中的方式类似,可以是:CORREL(相关)、CHISQR(卡方)、INTERSECT(相交)、BHATTACHARYYA*/
            float factor        /*归一化因子,一般都设置成1,否则很可能会出错;中文、英文以及各路转载的文档都错了,这个参数的实际类型是double,而非float,我看了源代码才搞定这个地方*/
        );
        还有最需要注意的地方:这个函数的执行效率非常的低,在使用之前尤其需要注意图像的大小,直方图的维数,对比方式。如果说对比单个直方图对现在的电脑来说是清风拂面,那么反向投影是狂风海啸。对于1010x1010的RGB输入图像,10x10的模板图像,需要生成1百万次3维直方图,对比1百万次3维直方图。

     下面为具体代码:

    View Code
      1 #include <cv.h>
    2 #include <highgui.h>
    3
    4
    5 int main()
    6 {
    7 IplImage* src = cvLoadImage("./sample1.jpg");
    8 if (src != NULL)
    9 {
    10 IplImage* hsv = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
    11 //将bgr图像转化为hsv图像
    12 cvCvtColor(src, hsv, CV_BGR2HSV);
    13
    14
    15 //创建3个单通道的图像,用于将hsv图像分离为3个通道
    16 IplImage* h = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
    17 IplImage* s = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
    18 IplImage* v = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
    19 IplImage* planes[] = {h, s};
    20 //将hsv图像分离
    21 cvCvtPixToPlane(hsv, h, s, v, 0);
    22
    23
    24 //在h通道上设定30个划分度,在s通道上设定32个划分度
    25 int h_bins(30), s_bins(32);
    26 CvHistogram* hist;
    27 {
    28 int hist_size[] = {h_bins, s_bins};
    29 float h_ranges[] = {0, 180};
    30 float s_ranges[] = {0, 255};
    31 //将h通道上的范围,即[0,180],以及s通道上的范围,即[0,255]组织成函数cvCreateHist所需
    32 //的形式
    33 float* ranges[] = {h_ranges, s_ranges};
    34 //创建一个2维的、h维上划分度为30、s维上划分度为32的均分直方图
    35 hist = cvCreateHist(2, hist_size, CV_HIST_ARRAY, ranges, 1);
    36 }
    37 //通过源图像上的h与s通道上图像数据统计出直方图
    38 cvCalcHist(planes, hist, 0, 0);
    39 //获取得到的直方图的所有数据的和
    40 CvScalar sc = cvSum(hist->bins);
    41 float sum = sc.val[0] + sc.val[1] + sc.val[2] + sc.val[3];
    42 //归一化直方图
    43 cvNormalizeHist(hist, 1.0);
    44 //scale:每一个划分在用于显示的图像上所占的像素点数
    45 int scale = 10;
    46 IplImage* img = cvCreateImage(cvSize(h_bins * scale, s_bins * scale), IPL_DEPTH_8U, 3);
    47 cvZero(img);
    48
    49
    50 //为什么要求最大值呢?由于归一化之后的图像中的像素点的值都不会大于1,那么在人眼看来都是黑
    51 //色的一片,没法通过图像来直观的感受直方图了,所以在下边会通过将直方图的值和直方图中的最大
    52 //值做除法,再乘以255,使人眼能看出直方图中数值的变化……如下代码对应直方图2
    53 float max_value = 0;
    54 cvGetMinMaxHistValue(hist, NULL, &max_value, 0, 0);
    55 for (int h = 0; h < h_bins; ++h)
    56 {
    57 for (int s = 0; s < s_bins; ++s)
    58 {
    59 //访问直方图在关于h与s所确定的颜色的数值
    60 float bin_val = cvQueryHistValue_2D(hist, h, s);
    61 int intensity = cvRound(bin_val * 255 / max_value);
    62 cvRectangle(img,
    63 cvPoint(h * scale, s * scale),
    64 cvPoint((h + 1) * scale - 1, (s + 1) * scale - 1),
    65 CV_RGB(intensity, intensity, intensity),
    66 CV_FILLED);
    67 }
    68 }
    69
    70
    71 //如下代码对应直方图1
    72 int height = 240;
    73 int width = (h_bins*s_bins*6);
    74 IplImage* hist_img = cvCreateImage( cvSize(width,height), 8, 3 );
    75 cvZero( hist_img );
    76
    77 IplImage * hsv_color = cvCreateImage(cvSize(1,1),8,3);
    78 IplImage * rgb_color = cvCreateImage(cvSize(1,1),8,3);
    79 int bin_w = width / (h_bins * s_bins);
    80 for(int h = 0; h < h_bins; h++)
    81 {
    82 for(int s = 0; s < s_bins; s++)
    83 {
    84 int i = h*s_bins + s;
    85
    86 float bin_val = cvQueryHistValue_2D( hist, h, s );
    87 int intensity = cvRound(bin_val*height/max_value);
    88
    89 cvSet2D(hsv_color,0,0,cvScalar(h*180.f / h_bins,s*255.f/s_bins,255,0));
    90 //hsv颜色转化为bgr颜色
    91 cvCvtColor(hsv_color,rgb_color,CV_HSV2BGR);
    92 CvScalar color = cvGet2D(rgb_color,0,0);
    93 //IplImage类型指定的图像原点在左上角,所以要实现图中的效果,矩形的两个顶角应像下边
    94 //代码中设置
    95 cvRectangle( hist_img, cvPoint(i*bin_w,height),
    96 cvPoint((i+1)*bin_w,height - intensity),
    97 color, -1, 8, 0 );
    98 }
    99 }
    100 cvNamedWindow( "H-S Histogram", 1 );
    101 cvShowImage( "H-S Histogram", hist_img );
    102 cvNamedWindow("src");
    103 cvShowImage("src", src);
    104 cvNamedWindow("img");
    105 cvShowImage("img", img);
    106
    107
    108 IplImage* img2 = cvLoadImage("./sample2.jpg");
    109 IplImage* hsv2 = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 3);
    110 cvCvtColor(img2, hsv2, CV_BGR2HSV);
    111 IplImage* h2 = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1);
    112 IplImage* s2 = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1);
    113 IplImage* v2 = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1);
    114 IplImage* planes2[] = {h2, s2};
    115 cvCvtPixToPlane(hsv2, h2, s2, v2, 0);
    116 //cvCvtPixToPlane(img2, h2, s2, v2, 0);
    117 IplImage* backProject = cvCreateImage(cvGetSize(img2), IPL_DEPTH_8U, 1);
    118 cvSetZero(backProject);
    119 cvNormalizeHist(hist, sum);
    120 cvCalcBackProject(planes2, backProject, hist);
    121
    122
    123 cvNamedWindow("img2");
    124 cvShowImage("img2", img2);
    125 cvNamedWindow("back project");
    126 cvShowImage("back project", backProject);
    127
    128
    129 //释放资源
    130 cvWaitKey(0);
    131 cvDestroyAllWindows();
    132 cvReleaseImage(&src);
    133 cvReleaseImage(&img);
    134 cvReleaseImage(&backProject);
    135 cvReleaseImage(&img2);
    136 cvReleaseHist(&hist);
    137 }
    138
    139
    140 return 0;
    141 }

    模板匹配(Match Template) 修改版

    模板匹配的工作方式
        模板匹配的工作方式跟直方图的反向投影基本一样,大致过程是这样的:通过在输入图像上滑动图像块对实际的图像块和输入图像进行匹配。
        假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:
      (1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;(改者注:其实每次匹配都是在模板的中心点对应的位置来给像素赋值,即第一次比较应该是将模板的(temp.width/2,temp.height/2)中心点开始的1/4面积同输入图像进行比较,匹配得到的结果c保存到模板中心点所在像素值中,具体参照《Learning OpenCV》,所以最终用来保留匹配结果的图像大小应该是size =  (images->width - patch_size.x + 1,images->height - patch_size.y + 1))

      (2)用临时图像和模板图像进行对比,对比结果记为c;
      (3)对比结果c,就是结果图像(0,0)处的像素值;
      (4)切割输入图像从(0,1)至(10,11)的临时图像,对比,并记录到结果图像;
      (5)重复(1)~(4)步直到输入图像的右下角。
        大家可以看到,直方图反向投影对比的是直方图,而模板匹配对比的是图像的像素值;模板匹配比直方图反向投影速度要快一些,但是我个人认为直方图反向投影的鲁棒性会更好。

    模板匹配的匹配方式
        在OpenCv和EmguCv中支持以下6种对比方式:
        CV_TM_SQDIFF 平方差匹配法:该方法采用平方差来进行匹配;最好的匹配值为0;匹配越差,匹配值越大。
        CV_TM_CCORR 相关匹配法:该方法采用乘法操作;数值越大表明匹配程度越好。
        CV_TM_CCOEFF 相关系数匹配法:1表示完美的匹配;-1表示最差的匹配。
        CV_TM_SQDIFF_NORMED 归一化平方差匹配法
        CV_TM_CCORR_NORMED 归一化相关匹配法
        CV_TM_CCOEFF_NORMED 归一化相关系数匹配法
        根据我的测试结果来看,上述几种匹配方式需要的计算时间比较接近(跟《学习OpenCv》书上说的不同),我们可以选择一个能适应场景的匹配方式。

    具体代码:

    View Code
     1 int main( int argc, char** argv ) {
    2
    3 IplImage *src, *templ,*ftmp[6]; //ftmp is what to display on
    4 int i;
    5
    6 //Read in the template to be used for matching:
    7 if((templ=cvLoadImage("flower1.jpg", 1))== 0) {
    8 printf("Error on reading template /n");
    9 return(-1);
    10 }
    11
    12 //Read in the source image to be searched:
    13 if((src=cvLoadImage("flower.jpg", 1))== 0) {
    14 printf("Error on reading src image /n");
    15 return(-1);
    16 }
    17
    18 int patchx = templ->width;
    19 int patchy = templ->height;
    20 int iwidth = src->width - patchx + 1;
    21 int iheight = src->height - patchy + 1;
    22 for(i=0; i<6; ++i){
    23 ftmp[i] = cvCreateImage( cvSize(iwidth,iheight),32,1);
    24 }
    25
    26 //DO THE MATCHING OF THE TEMPLATE WITH THE IMAGE
    27 for(i=0; i<6; ++i){
    28 cvMatchTemplate( src, templ, ftmp[i], i);
    29 // double min,max;
    30 // cvMinMaxLoc(ftmp,&min,&max);
    31 cvNormalize(ftmp[i],ftmp[i],1,0,CV_MINMAX);
    32 }
    33 //DISPLAY
    34 cvNamedWindow( "Template", 0 );
    35 cvShowImage( "Template", templ );
    36 cvNamedWindow( "Image", 0 );
    37 cvShowImage( "Image", src );
    38
    39 cvNamedWindow( "SQDIFF", 0 );
    40 cvShowImage( "SQDIFF", ftmp[0] );
    41
    42 cvNamedWindow( "SQDIFF_NORMED", 0 );
    43 cvShowImage( "SQDIFF_NORMED", ftmp[1] );
    44
    45 cvNamedWindow( "CCORR", 0 );
    46 cvShowImage( "CCORR", ftmp[2] );
    47
    48 cvNamedWindow( "CCORR_NORMED", 0 );
    49 cvShowImage( "CCORR_NORMED", ftmp[3] );
    50
    51 cvNamedWindow( "CCOEFF", 0 );
    52 cvShowImage( "CCOEFF", ftmp[4] );
    53
    54 cvNamedWindow( "CCOEFF_NORMED", 0 );
    55 cvShowImage( "CCOEFF_NORMED", ftmp[5] );
    56
    57
    58 //LET USER VIEW RESULTS:
    59 cvWaitKey(0);
    60
    61 return 0;
    62 }

    感谢您耐心看完本文,希望对您有所帮助。



  • 相关阅读:
    windows10使用记录
    DevOps理论与实践总结
    工资调整
    Node程序debug小记
    基于游标的分页接口实现
    util.promisify 的那些事儿
    如何编写 Typescript 声明文件
    async语法升级踩坑小记
    使用TS+Sequelize实现更简洁的CRUD
    使用 TypeScript 改造构建工具及测试用例
  • 原文地址:https://www.cnblogs.com/slysky/p/2210745.html
Copyright © 2011-2022 走看看