一、影象直方圖的概念
影象直方圖是反映一個影象畫素分佈的統計表,其實橫座標代表了影象畫素的種類,可以是灰度的,也可以是彩色的。縱座標代表了每一種顏色值在影象中的畫素總數或者佔所有畫素個數的百分比。
影象是由畫素構成,因為反映畫素分佈的直方圖往往可以作為影象一個很重要的特徵。在實際工程中,影象直方圖在特徵提取、影象匹配等方面都有很好的應用。
二、利用OpenCV計算影象的直方圖
OpenCV中計算影象直方影象函式是calcHist,它的引數比較多,下面分析一下它的介面和用法。
void calcHist(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參數列明直方圖是否等距,最後一個引數與多影象下直方圖的顯示與儲存有關。
下面我們來計算一幅影象的灰度直方圖,彩色直方圖以及自定義的灰度分佈圖。
灰度直方圖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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; } |
彩色直方圖:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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; } |
不均勻直方圖,我們分別統計0-50,50-80,80-150,150-230,230-255區間的灰度分佈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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; } |
三、直方圖的顯示
從上面的例子中我們可以看出,直方圖計算得到的實際上是一個多維陣列,這並不夠直觀,我們希望能夠像在Excel中把相關資料通過表的形式表示出來。
下面通過劃線函式來把一個灰度直方圖顯示出來:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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; } |
四、直方圖變換
直方圖變換是影象處理中一個很重要的概念,影象直方圖可以反映出影象對比度,明暗程度等特徵,所以我們可以利用直方圖的變換進行影象畫面的調節。
直方圖變換在實際工程中的應用很廣,一些美化照片的軟體很多工具都是在影象的直方圖上作文章,如果有讀者對這這方面感興趣的推薦:http://www.cnblogs.com/Imageshop/
下面介紹兩個簡單的直方圖變換函式:直方圖拉伸與直方圖均衡化。
如果影象的灰度在直方圖上顯示集中在某一個區間,則說明影象色彩單一,我們可以將其擴充套件到更寬的灰度範圍內讓影象更有層次感。
變換函式:將影象的一種灰度值經過變換得到另一個灰度。
直方圖變換的核心就是變換函式,s=T(r),r是變換前的灰度值,s是變換後的灰度值,如要我們想將[a,b]區間的灰度變換到[0,255]範圍內,則變換函式是:T(r)=255*(r-a)/(b-a)。
我們在OpenCV中建立這樣一個變換函式:
1 2 3 4 5 6 7 8 9 10 11 12 |
// 建立一個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是影象中的最小灰度與最大灰度。我們可以從直方圖中求出:
1 2 3 4 5 6 7 8 9 10 11 |
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函式,把變換應用在直方圖上即可。
1 |
LUT(image,lut,result); |
第二個引數就像一個查詢表一樣,將原影象中的灰度按表查詢,然後把灰度值替換為表中對應的值。
有了上面灰度拉伸的例子就不難理解影象的直方圖均衡了,直方圖均衡化可以讓影象灰度分佈更加均勻,讓影象的對比度增強。
在OpenCV中直方圖均衡不用像灰度拉伸那樣先構造一個變換函式,它有直接對應的函式,當然你如果有興趣也可以去嘗試寫一下變換函式均衡化的變換原理會稍複雜一些,在OpenCV這個系列裡面,不會太多的介紹數字影象中的演算法,以後有機會再專門來討論。
1 2 3 4 5 6 7 8 9 10 |
int main() { Mat Image=imread("../cat.png"); cvtColor(Image,Image,CV_BGR2GRAY); Mat result; equalizeHist(Image,result); return 0; } |