【影像處理】基於OpenCV實現影像直方圖的原理

Mr.YF發表於2021-10-08

背景

影像的直方圖是衡量影像畫素分佈的一種方式,可以通過分析畫素分佈,使用直方圖均衡化對影像進行優化,讓影像變的清晰。

opencv官方對影像直方圖的定義如下:

  • 直方圖是影像中畫素強度分佈的圖形表達方式.
  • 它統計了每一個強度值所具有的畫素個數.

一、直方圖計算的原理

一副影像實際上就是一個數字矩陣。

3x3的灰度影像由9個畫素組成,每個畫素都取值0-255中的一個值,0表示黑色,255表示白色,中間值是介於黑色和白色之間的灰度值。

如下以一個高度為3,寬度為3的圖片為例說明直方圖的計算。

  • 定義一個255大小的陣列,用於儲存灰度值出現的次數
  • 遍歷影像的每一個元素,將畫素的灰度值出現的次數統計到對應的灰度次數中
  • 將灰度值次數統計陣列進行歸一化處理(歸一化到0-255範圍內,便於繪圖使用)
  • 將歸一化的灰度次數進行繪圖展示

如下圖是計算直方圖的過程。

 

 

 

二、直方圖計算步驟

根據直方圖計算的原理,如下我們就開始動手寫一個計算影像直方圖程式碼實現。

1. 載入影像

載入影像,並顯示

    cv::Mat rawImage = cv::imread("demo1/leopard2.png", cv::IMREAD_ANYCOLOR);
    cv::imshow("rawImage", rawImage);

影像顯示影像(我喜歡的那個小豹子)

2. 定義統計影像三個通道灰度值出現次數和歸一化數的陣列

定義並初始化次數陣列,按照灰度值255,用於統計每個畫素灰度值出現的次數。

        int histSize = 255;
        int histValues[3][255] = {};
        int histNormalizeValues[3][255] = {};
        for (int k = 0; k < histSize; ++k) {
            histValues[0][k] = 0;
            histValues[1][k] = 0;
            histValues[2][k] = 0;
            histNormalizeValues[0][k] = 0;
            histNormalizeValues[1][k] = 0;
            histNormalizeValues[2][k] = 0;
        }

3. 遍歷影像,計算三個通道灰度值出現的次數

彩色影像由BGR三個通道構成,分別計算統計這三個通道的灰度值次數

       cv::Vec3b rgbPixel;
        // 遍歷影像,統計BGR三個通道的影像的灰度值出現的次數
        for (int i = 0; i < rgbImage.rows; ++i) {
            for (int j = 0; j < rgbImage.cols; ++j) {
                // B G R
                rgbPixel = rgbImage.at<cv::Vec3b>(i, j);
                histValues[2][rgbPixel[2]] += 1;
                histValues[1][rgbPixel[1]] += 1;
                histValues[0][rgbPixel[0]] += 1;
            }
        }

4. 將上一步影像灰度值次數歸一化到0-255之間

歸一化方法的演算法見之前的文章 https://www.cnblogs.com/voipman/p/5046153.html

        // 把如上的統計值歸一化到0-255範圍內
        calcNormalize(histValues[0], histNormalizeValues[0]);
        calcNormalize(histValues[1], histNormalizeValues[1]);
        calcNormalize(histValues[2], histNormalizeValues[2]);

歸一化程式碼實現

    /**
     * 計算一個陣列的歸一化,此處歸一化到0-255之間
     * @param srcValues
     * @param dstValues
     */
    void calcNormalize(int srcValues[255], int dstValues[255]) {
        int minValue = srcValues[0];
        int maxValue = srcValues[0];

        for (int i = 1; i < 255; ++i) {
            if (minValue > srcValues[i]) {
                minValue = srcValues[i];
            }
            if (maxValue < srcValues[i]) {
                maxValue = srcValues[i];
            }
        }
        int minMaxDiff = maxValue - minValue;
        for (int j = 0; j < 255; ++j) {
            dstValues[j] = static_cast<int>((float)(srcValues[j] - minValue) / (float)minMaxDiff * 255.);
        }
    }

5. 繪製直方圖到頁面

如下劃線程式碼邏輯是畫出3條線,分別是藍綠紅三條,每一條線連線前後兩個點,依次連線0-254點形成對應的線。

        // 建立直方圖畫布
        int hist_w = 400; int hist_h = 400;
        int bin_w = cvRound( (double) hist_w/histSize );

        cv::Mat histImage( hist_w, hist_h, CV_8UC3, cv::Scalar( 255,255,255) );
        // 把三個通道的直方圖歸一化資料繪製在直方圖上
        for (int i = 1; i < histSize; ++i) {
            cv::line(histImage,
                    cv::Point(bin_w * (i-1), hist_h - cvRound(histNormalizeValues[0][i-1])),
                    cv::Point(bin_w * (i), hist_h - cvRound(histNormalizeValues[0][i])),
                    cv::Scalar(0, 0, 255), 2,cv::LINE_AA, 0);
            cv::line(histImage,
                    cv::Point(bin_w * (i-1), hist_h - cvRound(histNormalizeValues[1][i-1])),
                    cv::Point(bin_w * (i), hist_h - cvRound(histNormalizeValues[1][i])),
                    cv::Scalar(0, 255, 0), 2,cv::LINE_AA, 0);
            cv::line(histImage,
                    cv::Point(bin_w * (i-1), hist_h - cvRound(histNormalizeValues[2][i-1])),
                    cv::Point(bin_w * (i), hist_h - cvRound(histNormalizeValues[2][i])),
                    cv::Scalar(255, 0, 0), 2,cv::LINE_AA, 0);
        }
        cv::imshow("histImage", histImage);

繪圖中的繪線邏輯如下圖中的綠線線段所示(連線前後兩個點形成對應的線段):

6. 繪製直方圖顯示

 

直方圖結果解析和說明:

  • 從這個直方圖可以看出原始影像三個通道的資料都比較集中
  • 紅色通道的資料集中在中間130左右,太黑和太白的資料比較少。
  • 綠色通道的資料集中在180左右,兩邊資料比較少。
  • 藍色通道的資料集中在210作用的數值內,黑色的資料很少。

 

影像優化

使用直方圖均衡化演算法對影像進行均衡處理

    void EqualizeHist(cv::Mat &rgbImage) {
        std::vector<cv::Mat> rgbImages;
        cv::split(rgbImage, rgbImages);
        /// 應用直方圖均衡化

        cv::Mat dstR, dstG, dstB;
        equalizeHist(rgbImages[0], dstB);
        equalizeHist(rgbImages[1], dstG);
        equalizeHist(rgbImages[2], dstR);

        std::vector<cv::Mat> grayHistImages;
        grayHistImages.push_back(dstB);
        grayHistImages.push_back(dstG);
        grayHistImages.push_back(dstR);
        cv::merge(grayHistImages, rgbImage);
    }

  

對影像做了直方圖均衡化處理後的效果如下:

影像分析:

  • 影像看起來黑白分明,小豹子影像很清晰。

經過直方圖均衡化處理後的影像,重新計算直方圖,觀察灰度值分佈

 

 

影像分析:

  • 均衡化後的直方圖均勻的分佈在0-255之間。

 

OpenCV提供了一個簡單的計算陣列集(通常是影像或分割後的通道)的直方圖,步驟如下

  • cv::split拆分影像到多個通道
  • 使用計算直方圖函式 calcHist 計算影像的直方圖
  • 使用函式 cv::normalize 歸一化陣列
  • 使用cv::line繪製直方圖

 

參考材料:

opencv直方圖均衡化處理 

opencv直方圖計算

如下完整程式碼見 https://github.com/gityf/img-video/blob/master/opencv/hist.hpp

 

done.

祝玩的開心~

相關文章