【從零學習OpenCV 4】直方圖歸一化

  • 2019 年 12 月 13 日
  • 筆記

經過幾個月的努力,小白終於完成了市面上第一本OpenCV 4入門書籍《從零學習OpenCV 4》。為了更讓小夥伴更早的了解最新版的OpenCV 4,小白與出版社溝通,提前在公眾號上連載部分內容,請持續關注小白。

前面我們完成了對一張影像像素灰度值的統計,並成功繪製了影像的直方圖。但是由於繪製直方圖的影像高度小於某些灰度值統計的數目,因此我們在繪製直方圖時將所有的數據都縮小為原來的二十分之一之後再進行繪製,目的就是為了能夠將直方圖完整的繪製在影像中。如果換一張影像的直方圖統計結果或者將直方圖繪製到一個尺寸更小的影像中時,可能需要將統計數據縮小為原來的三十分之一、五十分之一甚至更低。數據縮小比例與統計結果、將要繪製直方圖影像的尺寸相關,因此每次繪製時都需要計算數據縮小的比例。另外,由於像素灰度值統計的數目與影像的尺寸具有直接關係,如果以灰度值數目作為最終統計結果,那麼一張影像經過尺寸放縮後的兩張影像的直方圖將會有巨大的差異,然而直方圖可以用來表示影像的明亮程度,從理論上講通過縮放的兩張影像將具有大致相似的直方圖分布特性,因此用灰度值的數目作為統計結果具有一定的局限性。

影像的像素灰度值統計結果主要目的之一就是查看某個灰度值在所有像素中所佔的比例,因此可以用每個灰度值像素的數目佔一幅影像中所有像素數目的比例來表示某個灰度值數目的多少,即將統計結果再除以影像中像素個數。這種方式可以保證每個灰度值的統計結果都是0到100%之間的數據,實現統計結果的歸一化,但是這種方式也存在一個弊端,就是再CV_8U類型的影像中,灰度值有256個等級,平均每個像素的灰度值所佔比例為0.39%,這個比例非常低,因此為了更直觀的繪製影像直方圖,常需要將比例擴大一定的倍數後再繪製影像。另一種常用的歸一化方式是尋找統計結果中最大數值,把所有結果除以這個最大的數值,以實現將所有數據都縮放到0到1之間。

針對上面這兩種歸一化方式,OpenCV 4提供了normalize()函數實現多種形式的歸一化功能,該函數的函數原型在程式碼清單4-3中給出。

程式碼清單4-3 normalize()函數原型  1.  void cv::normalize(InputArray src,  2.                         InputOutputArray dst,  3.                         double  alpha = 1,  4.                         double   beta = 0,  5.                         int  norm_type = NORM_L2,  6.                         int  dtype = -1,  7.                         InputArray mask = noArray()  8.                         )
  • src:輸入數組矩陣。
  • dst:輸入與src相同大小的數組矩陣。
  • alpha:在範圍歸一化的情況下,歸一化到下限邊界的標準值
  • beta:範圍歸一化時的上限範圍,它不用於標準規範化。
  • norm_type:歸一化過程中數據範數種類標誌,常用可選擇參數在表4-1中給出
  • dtype:輸出數據類型選擇標誌,如果為負數,則輸出數據與src擁有相同的類型,否則與src具有相同的通道數和數據類型。
  • mask:掩碼矩陣。

該函數輸入一個存放數據的矩陣,通過參數alpha設置將數據縮放到的最大範圍,然後通過norm_type參數選擇計算範數的種類,之後將輸入矩陣中的每個數據分別除以求取的範數數值,最後得到縮放的結果。輸出結果是一個CV_32F類型的矩陣,可以將輸入矩陣作為輸出矩陣,或者重新定義一個新的矩陣用於存放輸出結果。該函數的第五個參數用於選擇計算數據範數的種類,常用的可選擇參數以及計算範數的公式都在表4-1中給出。計算不同的範數,最後的結果也不相同,例如選擇NORM_L1標誌,輸出結果為每個灰度值所佔的比例;選擇NORM_INF參數,輸出結果為除以數據中最大值,將所有的數據歸一化到0到1之間。

表4-1 normalize()函數歸一化常用標誌參數

標誌參數

為了了解歸一化函數normalize()的作用,在程式碼清單4-4中給出了通過不同方式歸一化數組

的計算結果,並且分別用灰度值所佔比例和除以數據最大值的方式對影像直方圖進行歸一化操作。為了更加直觀的展現歸一化後的結果,我們將每個灰度值所佔比例放大了30倍,並將繪製直方圖的影像高度作為1進行繪製直方圖,最終結果在圖4-3給出,根據結果顯示,無論是否進行歸一化,或者採用那種歸一化方法,直方圖的分布特性都不會改變。

程式碼清單4-4 myNormalize.cpp直方圖歸一化操作  1.  #include <opencv2opencv.hpp>  2.  #include <iostream>  3.  4.  using namespace cv;  5.  using namespace std;  6.  7.  int main()  8. {  9.    system("color F0"); //更改輸出介面顏色  10.    vector<double> positiveData = { 2.0, 8.0, 10.0 };  11.    vector<double> normalized_L1, normalized_L2, normalized_Inf, normalized_L2SQR;  12.    //測試不同歸一化方法  13.    normalize(positiveData, normalized_L1, 1.0, 0.0, NORM_L1); //絕對值求和歸一化  14.    cout <<"normalized_L1=["<< normalized_L1[0]<<", "  15.      << normalized_L1[1]<<", "<< normalized_L1[2] <<"]"<< endl;  16.    normalize(positiveData, normalized_L2, 1.0, 0.0, NORM_L2); //模長歸一化  17.    cout << "normalized_L2=[" << normalized_L2[0] << ", "  18.      << normalized_L2[1] << ", " << normalized_L2[2] << "]" << endl;  19.    normalize(positiveData, normalized_Inf, 1.0, 0.0, NORM_INF); //最大值歸一化  20.    cout << "normalized_Inf=[" << normalized_Inf[0] << ", "  21.      << normalized_Inf[1] << ", " << normalized_Inf[2] << "]" << endl;  22.    normalize(positiveData, normalized_L2SQR, 1.0, 0.0, NORM_MINMAX); //偏移歸一化  23.    cout << "normalized_MINMAX=[" << normalized_L2SQR[0] << ", "  24.      << normalized_L2SQR[1] << ", " << normalized_L2SQR[2] << "]" << endl;  25.    //將影像直方圖歸一化  26.    Mat img = imread("apple.jpg");  27.    if (img.empty())  28.    {  29.      cout << "請確認影像文件名稱是否正確" << endl;  30.      return -1;  31.    }  32.    Mat gray,hist;  33.    cvtColor(img, gray, COLOR_BGR2GRAY);  34.    const int channels[1] = { 0 };  35.    float inRanges[2] = { 0,255 };  36.    const float* ranges[1] = { inRanges };  37.    const int bins[1] = { 256 };  38.    calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);  39.    int hist_w = 512;  40.    int hist_h = 400;  41.    int width = 2;  42.    Mat histImage_L1 = Mat::zeros(hist_h, hist_w, CV_8UC3);  43.    Mat histImage_Inf = Mat::zeros(hist_h, hist_w, CV_8UC3);  44.    Mat hist_L1, hist_Inf;  45.    normalize(hist, hist_L1, 1, 0, NORM_L1,-1, Mat());  46.    for (int i = 1; i <= hist_L1.rows; i++)  47.    {  48.      rectangle(histImage_L1, Point(width*(i - 1), hist_h - 1),  49.        Point(width*i - 1, hist_h - cvRound(30*hist_h*hist_L1.at<float>(i-1))-1),  50.        Scalar(255, 255, 255), -1);  51.    }  52.    normalize(hist, hist_Inf, 1, 0, NORM_INF, -1, Mat());  53.    for (int i = 1; i <= hist_Inf.rows; i++)  54.    {  55.      rectangle(histImage_Inf, Point(width*(i - 1), hist_h - 1),  56.        Point(width*i - 1, hist_h - cvRound(hist_h*hist_Inf.at<float>(i-1)) - 1),  57.        Scalar(255, 255, 255), -1);  58.    }  59.    imshow("histImage_L1", histImage_L1);  60.    imshow("histImage_Inf", histImage_Inf);  61.    waitKey(0);  62.    return 0;  63.  }

圖4-2 myNormalize.cpp程式中對數組歸一化結果

圖4-3 myNormalize.cpp程式對影像直方圖的歸一化結果

Tags: