【從零學習OpenCV 4】影像直方圖繪製

  • 2019 年 12 月 13 日
  • 筆記

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

影像直方圖是影像處理中非常重要的像素統計結果,影像直方圖不再表徵任何的影像紋理資訊,而是對影像像素的統計。由於同一物體無論是旋轉還是平移在影像中都具有相同的灰度值,因此直方圖具有平移不變性、放縮不變性等優點,因此可以用來查看影像整體的變化形式,例如影像是否過暗、影像像素灰度值主要集中在哪些範圍等,在特定的條件下也可以利用影像直方圖進行影像的識別,例如對數字的識別。

影像直方圖簡單來說就是統計影像中每個灰度值的個數,之後將影像灰度值作為橫軸,以灰度值個數或者灰度值所佔比率作為縱軸繪製的統計圖。通過直方圖可以看出影像中哪些灰度值數目較多,哪些較少,可以通過一定的方法將灰度值較為集中的區域映射到較為稀疏的區域,從而使得影像在像素灰度值上分布更加符合期望狀態。通常情況下,像素灰度值代表亮暗程度,因此通過影像直方圖可以分析影像亮暗對比度,並調整影像的亮暗程度。

在OpenCV 4中只提供了影像直方圖的統計函數calcHist(),該函數能夠統計出影像中每個灰度值的個數,但是對於直方圖的繪製需要使用者自行繪製。我們首先學習統計灰度值數目的函數calcHist()的使用,該函數的原型在程式碼清單4-1中給出。

程式碼清單4-1 calcHist()函數原型  1.  void cv::calcHist(const Mat * images,  2.                        int  nimages,  3.                        const int * channels,  4.                        InputArray mask,  5.                        OutputArray hist,  6.                        int  dims,  7.                        const int * histSize,  8.                        const float ** ranges,  9.                        bool  uniform = true,  10.                        bool  accumulate = false  11.                        )
  • images:待統計直方圖的影像數組,數組中所有的影像應具有相同的尺寸和數據類型,並且數據類型只能是CV_8U、CV_16U和CV_32F三種中的一種,但是不同影像的通道數可以不同。
  • nimages:輸入的影像數量
  • channels:需要統計的通道索引數組,第一個影像的通道索引從0到images[0].channels()-1,第二個影像通道索引從images[0].channels()到images[0].channels()+ images[1].channels()-1,以此類推。
  • mask:可選的操作掩碼,如果是空矩陣則表示影像中所有位置的像素都計入直方圖中,如果矩陣不為空,則必須與輸入影像尺寸相同且數據類型為CV_8U。
  • hist:輸出的統計直方圖結果,是一個dims維度的數組。
  • dims:需要計算直方圖的維度,必須是整數,並且不能大於CV_MAX_DIMS,在OpenCV 4.0和OpenCV 4.1版本中為32。
  • histSize:存放每個維度直方圖的數組的尺寸。
  • ranges:每個影像通道中灰度值的取值範圍。
  • uniform:直方圖是否均勻的標誌符,默認狀態下為均勻(true)。
  • accumulate:是否累積統計直方圖的標誌,如果累積(true),則統計新影像的直方圖時之前影像的統計結果不會被清除,該同能主要用於統計多個影像整體的直方圖。

該函數用於統計影像中每個灰度值像素的個數,例如統計一張CV_8UC1的影像,需要統計灰度值從0到255中每一個灰度值在影像中的像素個數,如果某個灰度值在影像中沒有,那麼該灰度值的統計結果就是0。由於該函數具有較多的參數,並且每個參數都較為複雜,因此作者建議讀者在使用該函數時只統計單通道影像的灰度值分布,對於多通道影像可以將影像每個通道分離後再進行統計。

為了使讀者更加了解函數的使用方法,我們在程式碼清單4-2中提供了繪製灰度影像的影像直方圖的示常式序。在程式中我們首先使用calcHist()函數統計灰度影像裡面每個灰度值的數目,之後通過不斷繪製矩形的方式實現直方圖的繪製。由於影像中部分灰度值像素數目較多,因此我們將每個灰度值數目縮小了20倍後再進行繪製,繪製的直方圖在圖4-1中所示。在程式中我們使用了OpenCV 4提供的四捨五入的取整函數cvRound(),該函數輸入參數為double類型的變數,返回值為對該變數四捨五入後的int型數值。

程式碼清單4-2 myCalHist.cpp繪製影像直方圖  1.  #include <opencv2opencv.hpp>  2.  #include <iostream>  3.  4.  using namespace cv;  5.  using namespace std;  6.  7.  int main()  8. {  9.    Mat img = imread("apple.jpg");  10.    if (img.empty())  11.    {  12.      cout << "請確認影像文件名稱是否正確" << endl;  13.      return -1;  14.    }  15.    Mat gray;  16.    cvtColor(img, gray, COLOR_BGR2GRAY);  17.    //設置提取直方圖的相關變數  18.    Mat hist; //用於存放直方圖計算結果  19.    const int channels[1] = { 0 }; //通道索引  20.    float inRanges[2] = { 0,255 };  21.    const float* ranges[1] = { inRanges }; //像素灰度值範圍  22.    const int bins[1] = { 256 }; //直方圖的維度,其實就是像素灰度值的最大值  23.    calcHist(&img, 1, channels, Mat(), hist, 1, bins, ranges); //計算影像直方圖  24.    //準備繪製直方圖  25.    int hist_w = 512;  26.    int hist_h = 400;  27.    int width = 2;  28.    Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);  29.    for (int i = 1; i <= hist.rows; i++)  30.    {  31.      rectangle(histImage, Point(width*(i - 1), hist_h - 1),  32.        Point(width*i - 1, hist_h - cvRound(hist.at<float>(i - 1) / 20)),  33.        Scalar(255, 255, 255), -1);  34.    }  35.    namedWindow("histImage", WINDOW_AUTOSIZE);  36.    imshow("histImage", histImage);  37.    imshow("gray", gray);  38.    waitKey(0);  39.    return 0;  40.  }

圖4-1 myCalHist.cpp程式運行結果