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 標準, 值越大相似度越大。因此可以看到對於採用這兩個方法的對比,基準 - 基準 的對比結果值是最大的, 而 基準 - 半身 的匹配則是第二好(跟我們預測的一致)。而另外兩種對比標準,則是結果越小相似度越大。 我們可以觀察到基準圖像直方圖與兩張測試圖像直方圖的匹配是最差的,這再一次印證了我們的預測。

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