[EmguCV|C#]使用CvInvoke自己绘制色彩直方图-直方图(Hitsogram)系列(4)

过年结束了,虽然还是学生所以其实还有两个礼拜的假期,不过为了不让自己发慌,趁著假期多利用充实自己,所以提早回到开工状态,而这次总算要把一直说的自己动手绘制猜色直方图文章写出。

在很之前[EmguCV|WinForm] 使用EmguCV内建直方图工具绘制直方图(Histogram)-直方图(Histogram)系列 (1) 篇中,可以透过EmguCV内建的HistogramBox与HistogramViewer来帮助我们呈现一张图像的直方图资讯

不过有时后,我们仍希望可以直接用颜色来呈现一张图像的色彩分布,而这篇便是要来实践这部分。


前言


过年结束了,虽然还是学生所以其实还有两个礼拜的假期,不过为了不让自己发慌,趁著假期多利用充实自己,所以提早回到开工状态,而这次总算要把一直说的自己动手绘制猜色直方图文章写出。

在很之前[EmguCV|WinForm] 使用EmguCV内建直方图工具绘制直方图(Histogram)-直方图(Histogram)系列 (1) 篇中,可以透过EmguCV内建的HistogramBox与HistogramViewer来帮助我们呈现一张图像的直方图资讯

不过有时后,我们仍希望可以直接用颜色来呈现一张图像的色彩分布,而这篇便是要来实践这部分。

PS:建议先看过[EmguCV|C#]使用EmguCV的DenseHistogram类计算与纪录图像直方图-直方图(Histogram)系列(2)

绘制值方图


1.绘制的呈现结果

这篇文章会教导如何手动绘制完如下值方图图像:

hsv_h_histogram

上述的图为hue色相的色彩分布图,因为我把Bin值设定为16,所以量化后的颜色区间的变化比较简单-至于Bin是什么可以参考先前的文章 [EmguCV|C#]使用EmguCV的DenseHistogram类计算与纪录图像直方图-直方图(Histogram)系列(2)

不过由于2维能呈现的资讯比较少,所以在绘制这样的直方图图像时,我是采用HSV色彩空间,因为HSV色彩空间可以把色相分离出来,纯看色彩的基本颜色(可见光),而原先的RGB色彩空间由于颜色是混在一起,所以难以呈现(如下RGB色彩空间示意图)

RGB_color_solid_cube

另外本篇的绘制会搭配[EmguCV|C#]使用EmguCV的CvInvoke计算值方图(Histogram)-直方图(Histogram)系列(3)文章,使用CvInvoke来计算图像的值方图资讯到DenseHistogarm,那么接下来我们来看一下如何绘制吧

2.绘制的方式

我们在做值方图的绘制时,其实是把计算好的DenseHistogarm数据拿取出来,一一取得各个Bin所计算好的资讯,并把它绘制到一张Image 型态的图像上,所以最后呈现时,其实是在呈现一张图片。

这边我们先来看绘制Hue色相的直方图:

1.初始化参数

这边我们需要在乎的是max_value ,此变量是用来存放直方图中所找到的最高累积区块的数值,例如上述的粉笔图像的值方图中,左边红色的区块所累积的颜色是最多,假设他是300,表示在这张图中,有300个落在这个红色区块中的颜色。

而这个数值可以用来做为之后绘制到图上时,用来计算出个颜色区块显示在图像上的座标位置;h_bins 则是取得我们之前计算时所设定的颜色区间设定区块。

float max_value = 0.0f;
int[] a1 = new int[100];
int[] b1 = new int[100];
float ax = 0;
int h_bins = histDense.BinDimension[0].Size;

2.取得直方图的最大与最小累积值的区块(Bin)

CvInvoke.cvGetMinMaxHistValue(histDense, ref ax, ref max_value, a1, b1);

3.设定一个要把直方图数据绘制到的图像的宽高,并初始化

//设定值方图图像显示的宽高
int height = 240;
int width = 800;
IntPtr hist_img = CvInvoke.cvCreateImage(new System.Drawing.Size(width, height), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 3);
CvInvoke.cvZero(hist_img);

4.计算出显示在图像上的bin宽度

这是为了让我们原先所设定的Bin数量可以合理的显示在一张我们所想要设定的宽度图像上,例如我们这边打算显示到800*240图像上,因为宽是800,为了可以让我所设定的50个bins合理的完整显示,所以要做个计算

int bin_w = width / (h_bins); //h_bin设定为50

5.依序取得值方图中的每个Bin区块数据并计算危险是到图像上的位置与颜色

依序寻访每一个Bin区块,并撷取bin的数值(颜色区块的累积值),并计算如果显示到240高的图像上,要显示的位置以及要显示的颜色

for (int h = 0; h < h_bins; h++)
{

       / 取得直方图的统计数据,计算值方图中的所有颜色中最高统计的数值,作为实际显示时的图像高 */
      //取得值方图的数值位置,以便之后存成文件
      double bin_val = CvInvoke.cvQueryHistValue_1D(histDense, h);

      //计算取得的bin值要显示在240高的图像上时的位置
      int intensity = (int)System.Math.Round(bin_val * height / max_value);

      / 取得现在抓取的直方图的hue颜色,并为了显示成图像可观看,转换成RGB色彩 */
      CvInvoke.cvRectangle(hist_img, new System.Drawing.Point(h * bin_w, height),
                       new System.Drawing.Point((h + 1) * bin_w, height - intensity),
                       HueToBgr(h * 180.0d / h_bins), -1, Emgu.CV.CvEnum.LINE_TYPE.EIGHT_CONNECTED, 0);

}

其中,HueToBgr的方法如下:

用来将色相的数值转换成RGB颜色

/// 
       /// hue色相转换成rgb color
       /// 
       /// 
       /// 
  
  
       private static MCvScalar HueToBgr(double hue)
       {
           int[] rgb = new int[3];
           int p, sector;
           int[,] sector_data = { { 0, 2, 1 }, { 1, 2, 0 }, { 1, 0, 2 }, { 2, 0, 1 }, { 2, 1, 0 }, { 0, 1, 2 } };
           hue *= 0.033333333333333333333333333333333f;
           sector = (int)Math.Floor(hue);
           p = (int)Math.Round(255 * (hue - sector));
           //p ^= sector & 1 ? 255 : 0;
           if ((sector & 1) == 1) p ^= 255;
           else p ^= 0;
           rgb[sector_data[sector, 0]] = 255;
           rgb[sector_data[sector, 1]] = 0;
           rgb[sector_data[sector, 2]] = p;
           MCvScalar scalar = new MCvScalar(rgb[2], rgb[1], rgb[0], 0);
           return scalar;
       }

6.把得到的值方图图像转换为EmguCV使用的image 型态

原先我们在计算时所用的存放型态是Intptr,可以参考[EmguCV|OpenCV|C#] 转换支持存取OpenCV Iplmage的IntPtr型态为EmguCV保存图像的型态所写的内容

以下是其中一种方式:

Image
  
  
   
    hist_emgu_img = new Image
   
   
    
    (new System.Drawing.Size(width, height));
CvInvoke.cvCopy(hist_img, hist_emgu_img.Ptr, IntPtr.Zero);
return hist_emgu_img;
   
   
  
  

3.绘制HS直方图的方法

这边我们来快速带一下如何绘制HS直方图,至于为什么会需要用到S(饱和度)呢?

有可能我们会希望透过饱和度增加可以分析有亮度时,影像的辨识稳定度,不过如果分太细,因为细致度增加过头了,反而只会让辨识下降。

1.初始化参数

这边多了s_bin,因为我们也会需要看到饱和度的变化

float max_value = 0.0f;
int[] a1 = new int[100];
int[] b1 = new int[100];
float ax = 0;
int h_bins = histDense.BinDimension[0].Size;
int s_bins = histDense.BinDimension[1].Size;

2.取得直方图的最大与最小累积值的区块(Bin)

CvInvoke.cvGetMinMaxHistValue(histDense, ref ax, ref max_value, a1, b1);

3.设定一个要把直方图数据绘制到的图像的宽高,并初始化

因为除了显示色相的Bin之外,我们还需要显示饱和度,所以可能原先的宽会超过,如果超过在而外计算一个刚好的宽做为显示的影像大小

//设定值方图图像显示的宽高
int height = 300;
int width;
//如果设定的bins超过窗口设定的显示范围,另外给予可以符合用额外的弹出窗口显示的值,因为要同时看到h与s的bin值,显示的图像宽可能会太宽
if (h_bins * s_bins > 800)
{
       width = h_bins * s_bins * 2;
}
else
{
      width = 800;
}

IntPtr hist_img = CvInvoke.cvCreateImage(new System.Drawing.Size(width, height), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 3);
CvInvoke.cvZero(hist_img);

4.初始化用来存放把色相值方图的数据,转换成要显示出来的RGB数值空间

因为我们原先的直方图是Hsv空间,但是当我们要绘制到图像来看时,因为所记录的RGB空间(Image 型态),为了能够做正确的显示,所以我们之后需要做一个色彩空间转换

//用来存放从Hsv转回RGB图像时用的空间
IntPtr hsv_color = CvInvoke.cvCreateImage(new System.Drawing.Size(1, 1), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 3);
IntPtr rgb_color = CvInvoke.cvCreateImage(new System.Drawing.Size(1, 1), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 3);

5.计算出显示在图像上的bin宽度

int bin_w = width / (h_bins * s_bins);

6.依序取得值方图中的每个Bin区块数据并计算危险是到图像上的位置与颜色

for (int h = 0; h < h_bins; h++)
{
       for (int s = 0; s < s_bins; s++)
      {
            int i = h * s_bins + s;

           / 取得直方图的统计数据,计算值方图中的所有颜色中最高统计的数值,作为实际显示时的图像高 */
            //取得值方图的数值位置,以便之后存成文件
           double bin_val = CvInvoke.cvQueryHistValue_2D(histDense, h, s);
           int intensity = (int)System.Math.Round(bin_val * height / max_value);

            / 取得现在抓取的直方图的hue颜色,并为了显示成图像可观看,转换成RGB色彩 */
            CvInvoke.cvSet2D(hsv_color, 0, 0, new Emgu.CV.Structure.MCvScalar(h * 180.0f / h_bins, s * 255.0f / s_bins, 255, 0)); //这边用来计算色相与饱和度的统计数据转换到图像上 hsv_color的数值
            CvInvoke.cvCvtColor(hsv_color, rgb_color, COLOR_CONVERSION.CV_HSV2BGR);   //在把hsv颜色空间转换为RGB
            Emgu.CV.Structure.MCvScalar color = CvInvoke.cvGet2D(rgb_color, 0, 0);
            CvInvoke.cvRectangle(hist_img, new System.Drawing.Point(i * bin_w, height), new System.Drawing.Point((i + 1) * bin_w, height - intensity), color, -1, Emgu.CV.CvEnum.LINE_TYPE.EIGHT_CONNECTED, 0);
      }
}

7.把得到的值方图图像转换为EmguCV使用的image 型态

原先我们在计算时所用的存放型态是Intptr,可以参考[EmguCV|OpenCV|C#] 转换支持存取OpenCV Iplmage的IntPtr型态为EmguCV保存图像的型态所写的内容

以下是其中一种方式:

Image
  
  
   
    hist_emgu_img = new Image
   
   
    
    (new System.Drawing.Size(width, height));
CvInvoke.cvCopy(hist_img, hist_emgu_img.Ptr, IntPtr.Zero);
return hist_emgu_img;

   
   
  
  

显示结果

hsv_hs_histogram

心得


希望这篇可以帮助想要呈现色彩的人一个帮助=)

也是给自己一个纪录

程序范例档在这

参考数据:

颜色直方图的计算、显示、处理、对比及反向投影(How to Use Histogram? Calculate, Show, Process, Compare and BackProject)


文章中的叙述如有观念不正确错误的部分,欢迎告知指正 谢谢 =)

另外要转载请附上出处 感谢

原文:大专栏  [EmguCV|C#]使用CvInvoke自己绘制色彩直方图-直方图(Hitsogram)系列(4)


猜你喜欢

转载自www.cnblogs.com/petewell/p/11457738.html