圖像直方圖是反映圖像中像素分佈特性的統計表,一般顯示如下:
其中橫座標代表的是圖像像素的種類,或者說是灰度級,縱座標代表的是每一級灰度下像素數或者該灰度級下像素數在所有圖像總像素數總所佔的百分比。
直方圖反映了圖像像素的整體分佈,是圖像的一個很重要的特徵,直方圖處理也是很多空間域圖像處理的基礎,在特徵提取,圖像增強,圖像匹配等方面都佔有一席之地。
直觀上看,若直方圖的分量主要集中在左側低灰度級的區域,說明該圖像整體灰度偏低,欠曝或者環境昏暗就有可能造成灰度偏低;
若直方圖的分量主要集中在右側高灰度級的區域,說明圖像整體灰度偏高,過曝或環境光過於明亮就有可能造成灰度偏高;
若直方圖的分量在各個灰度級分佈均勻,佔據了整個灰度級,則說明該幅圖像對比度明顯,一般情況下,具有這種直方圖分佈的圖像的灰度細節比較豐富,圖像質量較好。
直方圖並不包含位置信息,相同的圖像一定對應同一個直方圖,而相同的直方圖有可能所對應的圖像千差萬別,一個明顯的例子是一幅選擇180°的圖像跟原圖像的直方圖分佈是一致的,但是圖像在表現上已經把圖像倒置了,是完全不同的兩幅圖像。
OpenCV中計算直方圖使用的是calcHist()函數,函數原型:
[cpp]
view plain
copy
print
?
- void calcHist( const Mat* images, int nimages,
- const int* channels, InputArray mask,
- OutputArray hist, int dims, const int* histSize,
- const float** ranges, bool uniform=true, bool accumulate=false );
第一個參數:const Mat類型的images指針,可以是單幅圖像或數組;
第二個參數:int型的nimages,是number of images的縮寫,表示 輸入數組的個數,單通道值爲1;
第三個參數:const int型的channesl,需要統計的通道 (dim)索引數,單通道灰度圖像爲0;
第四個參數:InputArray類型的Mat()掩碼,傳入Mat()表示未定義,不使用掩碼;
第五個參數:OutputArray類型的r_hist,儲存直方圖的矩陣;
第六個參數:int型的dims,直方圖維數;
第七個參數:const int型的histSize指針,表示直方圖的bin數目,即灰度級;
第八個參數:const float型的histRange指針,表示每個維度的取值範圍;
第九和第十個參數:uniform 和 accumulate,bin大小相同,清楚直方圖痕跡,分別是指爲true和false
直方圖opencv calcHist函數實現:
[cpp]
view plain
copy
print
?
- #include “core/core.hpp”
- #include “highgui/highgui.hpp”
- #include “imgproc/imgproc.hpp”
- using namespace cv;
- int main(int argc,char *argv[])
- {
- Mat image,imageGray,imageHist,imageNormalize;
- image=imread(argv[1]);
- if(!image.data)
- {
- return -1;
- }
- // RGB三通道圖像轉化成單通道灰度圖像
- // 也可以直接在imread函數裏第二個參數設爲0獲取灰度圖像
- cvtColor(image,imageGray,CV_RGB2GRAY);
- const int histSize=255; //定義灰度級數量
- float histR[]={0,255}; //定義每個灰度級下取值範圍
- const float *histRange=histR;
- //計算直方圖
- calcHist(&imageGray,1,0,Mat(),imageHist,1,&histSize,&histRange,true,false);
- //直方圖歸一化到範圍[0,histSize]
- normalize(imageHist,imageNormalize,0,histSize,NORM_MINMAX,-1,Mat());
- //創建直方圖畫布
- Mat imageShowHist(histSize,histR[1],CV_8UC3,Scalar(0,0,0));
- //分別畫出每個灰度級下的直方圖分佈
- for(int i=0;i<histSize;i++)
- {
- line(imageShowHist,Point(i,histR[1]),Point(i,histR[1]-cvRound(imageNormalize.at<float>(i))),Scalar(0,0,255),1,8,0);
- }
- imshow(“Moon”,image); imshow(“MoonGray”,imageGray);
- imshow(“Hist”,imageShowHist);
- waitKey();
- return 0;
- }
運行效果:
從直方圖上特性上分析,灰度級基本上在每個數量級上都有分佈,對比度和細節較爲明顯,但圖像整體稍微偏暗,對比灰度圖像,可以印證這些特性。
值得探討的是我覺得在opencv文檔裏的教程中對直方圖的計算應該有誤,原文鏈接:
例程代碼照抄如下:
[cpp]
view plain
copy
print
?
- #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( argv[1], 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;
- <span style=“color:#ff0000;”>int bin_w = cvRound( (double) hist_w/histSize );</span>
- 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++ )
- {
- // 加入了三個if語句,爲了比對直方圖,不是例程程序所有
- if(i==180)
- {
- imshow(“180”,histImage);
- }
- if(i==200)
- {
- imshow(“199”,histImage);
- }
- if(i==210)
- {
- imshow(“210”,histImage);
- }
- line( histImage, <span style=“color:#ff0000;”>Point( bin_w*(i-1), hist_h – cvRound(r_hist.at<float>(i-1)) )</span> ,
- 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;
- }
例程中所設直方圖灰度級爲255,直方圖畫布cols爲400, int bin_w = cvRound( (double) hist_w/histSize )計算出來值爲2;
這裏會有問題:當計算200灰度級下的直方圖時,Point( bin_w*(i-1), hist_h – cvRound(r_hist.at<float>(i-1)))所在的點已經超出了直方圖畫布的寬度400,意味着200~255灰度級的直方圖被截取捨棄了,直方圖並不完整。
以下是180,199和210灰度下直方圖對比:
可以看到,199灰階下已經繪製到了畫布的邊緣,其後的200~255灰階超出了畫布範圍,沒有再繪製出來,直方圖也不再有變化;
把200~255灰階補充完整的直方圖如下:
可以明顯看到綠色線代表的直方圖分佈,在200~255灰階下有一個下降的過程曲線,做了補充繪製。