OpenCV成長之路(4):圖像直方圖
原文出處: Ronny 的博客(@RonnyYoung)
一、圖像直方圖的概念
圖像直方圖是反映一個圖像像素分布的統計表,其實橫坐標代表了圖像像素的種類,可以是灰度的,也可以是彩色的。縱坐標代表了每一種顏色值在圖像中的像素總數或者占所有像素個數的百分比。
圖像是由像素構成,因為反映像素分布的直方圖往往可以作為圖像一個很重要的特征。在實際工程中,圖像直方圖在特征提取、圖像匹配等方面都有很好的應用。
二、利用OpenCV計算圖像的直方圖
OpenCV中計算圖像直方圖像函數是calcHist,它的參數比較多,下面分析一下它的接口和用法。
voidcalcHist(const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, booluniform=true, bool accumulate=false)
const Mat* images:為輸入圖像的指針。
int nimages:要計算直方圖的圖像的個數。此函數可以為多圖像求直方圖,我們通常情況下都只作用于單一圖像,所以通常nimages=1。
const int* channels:圖像的通道,它是一個數組,如果是灰度圖像則channels[1]={0};如果是彩色圖像則channels[3]={0,1,2};如果是只是求彩色圖像第2個通道的直方圖,則channels[1]={1};
IuputArray mask:是一個遮罩圖像用于確定哪些點參與計算,實際應用中是個很好的參數,默認情況我們都設置為一個空圖像,即:Mat()。
OutArray hist:計算得到的直方圖
int dims:得到的直方圖的維數,灰度圖像為1維,彩色圖像為3維。
const int* histSize:直方圖橫坐標的區間數。如果是10,則它會橫坐標分為10份,然后統計每個區間的像素點總和。
const float** ranges:這是一個二維數組,用來指出每個區間的范圍。
后面兩個參數都有默認值,uniform參數表明直方圖是否等距,最后一個參數與多圖像下直方圖的顯示與存儲有關。
下面我們來計算一幅圖像的灰度直方圖,彩色直方圖以及自定義的灰度分布圖。
灰度直方圖:
int main()
{
Mat Image=imread("../cat.png");
cvtColor(Image,Image,CV_BGR2GRAY);
const int channels[1]={0};
const int histSize[1]={256};
float hranges[2]={0,255};
const float* ranges[1]={hranges};
MatND hist;
calcHist(&Image,1,channels,Mat(),hist,1,histSize,ranges);
return 0;
}</pre>
彩色直方圖:
int main()
{
Mat Image=imread("../cat.png");
const int channels[3]={0,1,2};
const int histSize[3]={256,256,256};
float hranges[2]={0,255};
const float* ranges[3]={hranges,hranges,hranges};
MatND hist;
calcHist(&Image,1,channels,Mat(),hist,3,histSize,ranges);
return 0;
}</pre>
不均勻直方圖,我們分別統計0-50,50-80,80-150,150-230,230-255區間的灰度分布:
int main()
{
Mat Image=imread("../cat.png");
cvtColor(Image,Image,CV_BGR2GRAY);
const int channels[1]={0};
int histSize[1]={5};
float hranges[6]={0,50,80,150,230,255};
const float* ranges[1]={hranges};
MatND hist;
calcHist(&Image,1,channels,Mat(),hist,1,histSize,ranges,false);
return 0;
}</pre>
三、直方圖的顯示
從上面的例子中我們可以看出,直方圖計算得到的實際上是一個多維數組,這并不夠直觀,我們希望能夠像在Excel中把相關數據通過表的形式表示出來。
下面通過劃線函數來把一個灰度直方圖顯示出來:
Mat getHistImg(const MatND& hist)
{
double maxVal=0;
double minVal=0;
//找到直方圖中的最大值和最小值
minMaxLoc(hist,&minVal,&maxVal,0,0);
int histSize=hist.rows;
Mat histImg(histSize,histSize,CV_8U,Scalar(255));
// 設置最大峰值為圖像高度的90%
int hpt=static_cast<int>(0.9*histSize);
for(int h=0;h<histSize;h++)
{
float binVal=hist.at<float>(h);
int intensity=static_cast<int>(binVal*hpt/maxVal);
line(histImg,Point(h,histSize),Point(h,histSize-intensity),Scalar::all(0));
}
return histImg;
}</pre>

四、直方圖變換
直方圖變換是圖像處理中一個很重要的概念,圖像直方圖可以反映出圖像對比度,明暗程度等特征,所以我們可以利用直方圖的變換進行圖像畫面的調節。
直方圖變換在實際工程中的應用很廣,一些美化照片的軟件很多工具都是在圖像的直方圖上作文章,如果有讀者對這這方面感興趣的推薦:http://www.cnblogs.com/Imageshop/
下面介紹兩個簡單的直方圖變換函數:直方圖拉伸與直方圖均衡化。
如果圖像的灰度在直方圖上顯示集中在某一個區間,則說明圖像色彩單一,我們可以將其擴展到更寬的灰度范圍內讓圖像更有層次感。
變換函數:將圖像的一種灰度值經過變換得到另一個灰度。
直方圖變換的核心就是變換函數,s=T(r),r是變換前的灰度值,s是變換后的灰度值,如要我們想將[a,b]區間的灰度變換到[0,255]范圍內,則變換函數是:T(r)=255*(r-a)/(b-a)。
我們在OpenCV中創建這樣一個變換函數:
// 創建一個1*256的矢量
Mat lut(1,256,CV_8U);
for(int i=0;i<256;i++)
{
if(lut.at<uchar>(i)<imin)
lut.at<uchar>(i)=0;
else if(lut.at<uchar>(i)>imax)
lut.at<uchar>(i)=255;
else
lut.at<uchar>(i)=static_cast<uchar>(
255.0*(i-imin)/(imax-imin)+0.5);
} 其中imax,imin是圖像中的最小灰度與最大灰度。我們可以從直方圖中求出:
int imax,imin;
for(imin=0;imin<256;imin++)
{
if(hist.at<uchar>(imin)>minValue)
break;
}
for(imax=255;imax>-1;imax--)
{
if(hist.at<uchar>(imax)>minValue)
break;
}
最后我們應用OpenCV中的LUT函數,把變換應用在直方圖上即可。
LUT(image,lut,result);
第二個參數就像一個查找表一樣,將原圖像中的灰度按表查找,然后把灰度值替換為表中對應的值。
有了上面灰度拉伸的例子就不難理解圖像的直方圖均衡了,直方圖均衡化可以讓圖像灰度分布更加均勻,讓圖像的對比度增強。
在OpenCV中直方圖均衡不用像灰度拉伸那樣先構造一個變換函數,它有直接對應的函數,當然你如果有興趣也可以去嘗試寫一下變換函數均衡化的變換原理會稍復雜一些,在OpenCV這個系列里面,不會太多的介紹數字圖像中的算法,以后有機會再專門來討論。
int main()
{
Mat Image=imread("../cat.png");
cvtColor(Image,Image,CV_BGR2GRAY);
Mat result;
equalizeHist(Image,result);
return 0;
}