背景
圖像的直方圖是衡量圖像像素分布的一種方式,可以通過分析像素分布,使用直方圖均衡化對圖像進行優化,讓圖像變的清晰。
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提供了一個簡單的計算數組集(通常是圖像或分割后的通道)的直方圖,步驟如下
參考材料:
如下完整代碼見 https://github.com/gityf/img-video/blob/master/opencv/hist.hpp
done.
祝玩的開心~
