OpenCV 【二十四】直方图均衡化,直方图计算,直方图对比

目录

1.直方图均衡化¶

1.1 原理

1.2 直方图均衡化

1.3 直方图均衡化原理

1.4 代码实例

1.5 运行效果

2. 直方图计算¶

2.1 目标

2.2 直方图

2.3 代码实例

2.4 运行结果

3 直方图对比¶

3.1 目标

3.2 原理

3.3 代码

3.4 运行结果


1.直方图均衡化

  • 什么是图像的直方图和为什么图像的直方图很有用

  • 用OpenCV函数 equalizeHist 对图像进行直方图均衡化

1.1 原理

  • 直方图是图像中像素强度分布的图形表达方式.

  • 它统计了每一个强度值所具有的像素个数.

../../../../../_images/Histogram_Equalization_Theory_0.jpg

1.2 直方图均衡化

  • 直方图均衡化是通过拉伸像素强度分布范围来增强图像对比度的一种方法.

  • 说得更清楚一些, 以上面的直方图为例, 你可以看到像素主要集中在中间的一些强度值上. 直方图均衡化要做的就是 拉伸 这个范围. 见下面左图: 绿圈圈出了 少有像素分布其上的 强度值. 对其应用均衡化后, 得到了中间图所示的直方图. 均衡化的图像见下面右图.

1.3 直方图均衡化原理

OpenCV 【九】——calcHist ——图像直方图统计

  • 1.4 代码实例

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
​
using namespace cv;
using namespace std;
​
/**  @function main */
int main( int argc, char** argv )
{
  Mat src, dst;
​
  char* source_window = "Source image";
  char* equalized_window = "Equalized Image";
​
  /// 加载源图像
  src = imread( argv[1], 1 );
​
  if( !src.data )
    { cout<<"Usage: ./Histogram_Demo <path_to_image>"<<endl;
      return -1;}
​
  /// 转为灰度图
  cvtColor( src, src, CV_BGR2GRAY );
​
  /// 应用直方图均衡化
  equalizeHist( src, dst );
​
  /// 显示结果
  namedWindow( source_window, CV_WINDOW_AUTOSIZE );
  namedWindow( equalized_window, CV_WINDOW_AUTOSIZE );
​
  imshow( source_window, src );
  imshow( equalized_window, dst );
​
  /// 等待用户按键退出程序
  waitKey(0);
​
  return 0;
}

 

1.5 运行效果

直方图均衡化

 

2. 直方图计算

2.1 目标

  • 如何使用OpenCV函数 split 将图像分割成单通道数组。

  • 如何使用OpenCV函数 calcHist 计算图像阵列的直方图。

  • 如何使用OpenCV函数 normalize 归一化数组。

2.2 直方图

详见OpenCV 【九】——calcHist ——图像直方图统计

  • 让我们再来搞清楚直方图的一些具体细节:

    1. dims: 需要统计的特征的数目, 在上例中, dims = 1 因为我们仅仅统计了灰度值(灰度图像)。
    2. bins: 每个特征空间 子区段 的数目,在上例中, bins = 16
    3. range: 每个特征空间的取值范围,在上例中, range = [0,255]
  • 怎样去统计两个特征呢? 在这种情况下, 直方图就是3维的了,x轴和y轴分别代表一个特征, z轴是掉入 

../../../../../_images/Histogram_Calculation_Theory_Hist1.jpg

  •  组合中的样本数目。 同样的方法适用于更高维的情形 (当然会变得很复杂)。

2.3 代码实例

  • 本程序做什么?

    • 装载一张图像

    • 使用函数 split 将载入的图像分割成 R, G, B 单通道图像

    • 调用函数 calcHist 计算各单通道图像的直方图

    • 在一个窗口叠加显示3张直方图

  • 下载代码: 点击 这里

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
​
using namespace std;
using namespace cv;
​
/** @函数 main */
int main(int argc, char** argv)
{
    Mat src, dst;
​
    /// 装载图像
    src = imread("C:\\Users\\guoqi\\Desktop\\ch7\\4.jpg", 1);
​
    if (!src.data)
    {
        return -1;
    }
​
    /// 分割成3个单通道图像 ( R, G 和 B )
    vector<Mat> rgb_planes;
    split(src, rgb_planes);
​
    /// 设定bin数目
    int histSize = 255;
​
    /// 设定取值范围 ( R,G,B) )
    float range[] = { 0, 255 };
    const float* histRange = { range };
​
    bool uniform = true; bool accumulate = false;
​
    Mat r_hist, g_hist, b_hist;
​
    /// 计算直方图:
    calcHist(&rgb_planes[0], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);
    calcHist(&rgb_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
    calcHist(&rgb_planes[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);
​
    // 创建直方图画布
    int hist_w = 400; int hist_h = 400;
    int bin_w = cvRound((double)hist_w / histSize);
​
    Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
​
    /// 将直方图归一化到范围 [ 0, histImage.rows ]
    normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
​
    /// 在直方图画布上画出直方图
    for (int i = 1; i < histSize; i++)
    {
        line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
            Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))),
            Scalar(0, 0, 255), 2, 8, 0);
        line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
            Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))),
            Scalar(0, 255, 0), 2, 8, 0);
        line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
            Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))),
            Scalar(255, 0, 0), 2, 8, 0);
    }
​
    /// 显示直方图
    namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE);
    imshow("calcHist Demo", histImage);
​
    waitKey(0);
    return 0;
}

 

2.4 运行结果

 

3 直方图对比

HSV 颜色空间

基于上述理由,在图像处理中使用较多的是 HSV 颜色空间,它比 RGB 更接近人们对彩色的感知经验。非常直观地表达颜色的色调、鲜艳程度和明暗程度,方便进行颜色的对比。

在 HSV 颜色空间下,比 BGR 更容易跟踪某种颜色的物体,常用于分割指定颜色的物体。

HSV 表达彩色图像的方式由三个部分组成:

  • Hue(色调、色相)

  • Saturation(饱和度、色彩纯净度)

  • Value(明度)

用下面这个圆柱体来表示 HSV 颜色空间,圆柱体的横截面可以看做是一个极座标系 ,H 用极座标的极角表示,S 用极座标的极轴长度表示,V 用圆柱中轴的高度表示。

img

 

Hue 用角度度量,取值范围为0~360°,表示色彩信息,即所处的光谱颜色的位置。,表示如下:

img

 

颜色圆环上所有的颜色都是光谱上的颜色,从红色开始按逆时针方向旋转,Hue=0 表示红色,Hue=120 表示绿色,Hue=240 表示蓝色等等。

在 GRB中 颜色由三个值共同决定,比如黄色为即 (255,255,0);在HSV中,黄色只由一个值决定,Hue=60即可。

HSV 圆柱体的半边横截面(Hue=60):

img

 

其中Saturation*水平方向表示饱和度,饱和度表示颜色接近光谱色的程度饱和度越高,说明颜色越深,越接近光谱色饱和度越低,说明颜色越浅,越接近白色。饱和度为0表示纯白色。取值范围为0~100%,值越大,颜色越饱和。**

Value(明度):竖直方向表示明度,决定颜色空间中颜色的明暗程度,明度越高,表示颜色越明亮,范围是 0-100%。明度为0表示纯黑色(此时颜色最暗)。

可以通俗理解为:

在Hue一定的情况下,饱和度减小,就是往光谱色中添加白色,光谱色所占的比例也在减小,饱和度减为0,表示光谱色所占的比例为零,导致整个颜色呈现白色。

明度减小,就是往光谱色中添加黑色,光谱色所占的比例也在减小,明度减为0,表示光谱色所占的比例为零,导致整个颜色呈现黑色。

HSV 对用户来说是一种比较直观的颜色模型。我们可以很轻松地得到单一颜色,即指定颜色角H,并让V=S=1,然后通过向其中加入黑色和白色来得到我们需要的颜色。增加黑色可以减小V而S不变,同样增加白色可以减小S而V不变。例如,要得到深蓝色,V=0.4 S=1 H=240度。要得到浅蓝色,V=1 S=0.4 H=240度。

HSV 的拉伸对比度增强就是对 S 和 V 两个分量进行归一化(min-max normalize)即可,H 保持不变。

RGB颜色空间更加面向于工业,而HSV更加面向于用户,大多数做图像识别这一块的都会运用HSV颜色空间,因为HSV颜色空间表达起来更加直观!

3.1 目标

  • 如何使用OpenCV函数 compareHist 产生一个表达两个直方图的相似度的数值。

  • 如何使用不同的对比标准来对直方图进行比较。

3.2 原理

  • H_{1} and H_{2} ), 首先必须要选择一个衡量直方图相似度的 对比标准 (d(H_{1}, H_{2})) 。

  • OpenCV 函数 compareHist 执行了具体的直方图对比的任务。该函数提供了4种对比标准来计算相似度:

    1. 要比较两个直方图( H_{1} and H_{2} ), 首先必须要选择一个衡量直方图相似度的 对比标准 (d(H_{1}, H_{2})) 。

    2. OpenCV 函数 compareHist 执行了具体的直方图对比的任务。该函数提供了4种对比标准来计算相似度:

      1. Correlation ( CV_COMP_CORREL )

        d(H_1,H_2) =  \frac{\sum_I (H_1(I) - \bar{H_1}) (H_2(I) - \bar{H_2})}{\sqrt{\sum_I(H_1(I) - \bar{H_1})^2 \sum_I(H_2(I) - \bar{H_2})^2}}

        其中

        \bar{H_k} =  \frac{1}{N} \sum _J H_k(J)

        N 是直方图中bin的数目。

    3. Chi-Square ( CV_COMP_CHISQR )

      d(H_1,H_2) =  \sum _I  \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)+H_2(I)}

    4. Intersection ( CV_COMP_INTERSECT )

    5. d(H_1,H_2) =  \sum _I  \min (H_1(I), H_2(I))

    6. Bhattacharyya 距离( CV_COMP_BHATTACHARYYA )

      d(H_1,H_2) =  \sqrt{1 - \frac{1}{\sqrt{\bar{H_1} \bar{H_2} N^2}} \sum_I \sqrt{H_1(I) \cdot H_2(I)}}

3.3 代码

  • 本程序做什么?

    • 装载一张 基准图像 和 两张 测试图像 进行对比。

    • 产生一张取自 基准图像 下半部的图像。

    • 将图像转换到HSV格式。

    • 计算所有图像的H-S直方图,并归一化以便对比。

    • 基准图像 直方图与 两张测试图像直方图,基准图像半身像直方图,以及基准图像本身的直方图分别作对比。

    • 显示计算所得的直方图相似度数值。

  • 下载代码: 点击 这里

  • 代码一瞥:

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
​
using namespace std;
using namespace cv;
​
/** @函数 main */
int main( int argc, char** argv )
{
  Mat src_base, hsv_base;
  Mat src_test1, hsv_test1;
  Mat src_test2, hsv_test2;
  Mat hsv_half_down;
​
  /// 装载三张背景环境不同的图像
  if( argc < 4 )
    { printf("** Error. Usage: ./compareHist_Demo <image_settings0> <image_setting1> <image_settings2>\n");
      return -1;
    }
​
  src_base = imread( argv[1], 1 );
  src_test1 = imread( argv[2], 1 );
  src_test2 = imread( argv[3], 1 );
​
  /// 转换到 HSV
  cvtColor( src_base, hsv_base, CV_BGR2HSV );
  cvtColor( src_test1, hsv_test1, CV_BGR2HSV );
  cvtColor( src_test2, hsv_test2, CV_BGR2HSV );
​
  hsv_half_down = hsv_base( Range( hsv_base.rows/2, hsv_base.rows - 1 ), Range( 0, hsv_base.cols - 1 ) );
​
  /// 对hue通道使用30个bin,对saturatoin通道使用32个bin
  int h_bins = 50; int s_bins = 60;
  int histSize[] = { h_bins, s_bins };
​
  // hue的取值范围从0到256, saturation取值范围从0到180
  float h_ranges[] = { 0, 256 };
  float s_ranges[] = { 0, 180 };
​
  const float* ranges[] = { h_ranges, s_ranges };
​
  // 使用第0和第1通道
  int channels[] = { 0, 1 };
​
  /// 直方图
  MatND hist_base;
  MatND hist_half_down;
  MatND hist_test1;
  MatND hist_test2;
​
  /// 计算HSV图像的直方图
  calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );
  normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() );
​
  calcHist( &hsv_half_down, 1, channels, Mat(), hist_half_down, 2, histSize, ranges, true, false );
  normalize( hist_half_down, hist_half_down, 0, 1, NORM_MINMAX, -1, Mat() );
​
  calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false );
  normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() );
​
  calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false );
  normalize( hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat() );
​
  ///应用不同的直方图对比方法
  for( int i = 0; i < 4; i++ )
     { int compare_method = i;
       double base_base = compareHist( hist_base, hist_base, compare_method );
       double base_half = compareHist( hist_base, hist_half_down, compare_method );
       double base_test1 = compareHist( hist_base, hist_test1, compare_method );
       double base_test2 = compareHist( hist_base, hist_test2, compare_method );
​
       printf( " Method [%d] Perfect, Base-Half, Base-Test(1), Base-Test(2) : %f, %f, %f, %f \n", i, base_base, base_half , base_test1, base_test2 );
     }
​
  printf( "Done \n" );
​
  return 0;
 }

 

3.4 运行结果

  1. 第一张为基准图像,其余两张为测试图像。同时我们会将基准图像与它自身及其半身图像进行对比。

  2. 我们应该会预料到当将基准图像直方图及其自身进行对比时会产生完美的匹配, 当与来源于同一样的背景环境的半身图对比时应该会有比较高的相似度, 当与来自不同亮度光照条件的其余两张测试图像对比时匹配度应该不是很好:

  3. 下面显示的是结果数值:

对比标准 基准 - 基准 基准 - 半身 基准 - 测试1 基准 - 测试2
Correlation 1.000000 0.930766 0.182073 0.120447
Chi-square 0.000000 4.940466 21.184536 49.273437
Intersection 24.391548 14.959809 3.889029 5.775088
Bhattacharyya 0.000000 0.222609 0.646576 0.801869

对于 CorrelationIntersection 标准, 值越大相似度越大。因此可以看到对于采用这两个方法的对比,基准 - 基准 的对比结果值是最大的, 而 基准 - 半身 的匹配则是第二好(跟我们预测的一致)。而另外两种对比标准,则是结果越小相似度越大。 我们可以观察到基准图像直方图与两张测试图像直方图的匹配是最差的,这再一次印证了我们的预测。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章