直方圖(calcHist)
直方圖是一個影像像素的統計表,橫軸為影像中所有可能的像素值,假使為8位元圖,範圍即為0到255,縱軸為此橫軸強度的像素個數,直方圖可以被歸一化,歸一化後所有項和為一,在這種情況下,縱軸值表示此強度的像素佔影像的比例。
直方圖是影像的一個重要特性,我們可以從這看出強度分布狀況,像是否太暗或過曝,或者分布太過集中,進而評估影像的品質。在影像檢索時,直方圖可以當成是一段獨特的紋理,或者是獨特的物體,透過直方圖的比較協助我們搜尋物體。
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, bool uniform=true, bool accumulate=false)
• images:輸入圖,可以一個或多個圖,深度必須為CV_8U或CV_32F,可為任意通道數,但是每張圖的尺寸和深度必須相同。
• nimages:有幾張輸入圖。
• channels:直方圖通道清單。
• mask:可有可無的遮罩。
• hist:輸出的直方圖
• dims:直方圖維度,必須為正數且不能超過CV_MAX_DIMS(目前為32),假設為灰階圖的直方圖,每個像素只有強度資料,此時維度為1。
• histSize:直方圖橫軸(也稱bin)數目。
• ranges:直方圖的強度範圍,以8位元無負號的影像,就是[0,255]。
• uniform:各維度取值是否一致。
• accumulate:如果設定為true的話,在呼叫calcHist()這函式的時候,hist的內容不會被清掉,方便我們做多次的直方圖計算的累加。
繪製直方圖
OpenCV本身沒有提供繪製直方圖的函式,因此我們這邊自行撰寫drawHistImg()函式,輸入從得到的直方圖,輸出直方圖視覺化影像,流程如下:
- 找出輸入直方圖的最大值。
- 將最高點設定成輸出影像高度的0.9倍(也就是256*0.9)。
- 算出直方圖內各個像素強度,所對應的高度。
- 逐行畫出各強度的高度。
以下程式碼我們讀入一張圖,用calcHist()計算出直方圖,再用drawHistImg()將直方圖畫出:
#include <cstdio>
#include <opencv2/opencv.hpp>
using namespace cv;
void drawHistImg(const Mat &src, Mat &dst);
int main(){
Mat src = imread("lena.jpg",CV_LOAD_IMAGE_GRAYSCALE);
int histSize = 256;
float range[] = {0, 255} ;
const float* histRange = {range};
Mat histImg;
calcHist(&src, 1, 0, Mat(), histImg, 1, &histSize, &histRange);
Mat showHistImg(256,256,CV_8UC1,Scalar(255)); //把直方圖秀在一個256*256大的影像上
drawHistImg(histImg, showHistImg);
imshow("window1", src);
imshow("window2", showHistImg);
waitKey(0);
return 0;
}
void drawHistImg(const Mat &src, Mat &dst){
int histSize = 256;
float histMaxValue = 0;
for(int i=0; i<histSize; i++){
float tempValue = src.at<float>(i);
if(histMaxValue < tempValue){
histMaxValue = tempValue;
}
}
float scale = (0.9*256)/histMaxValue;
for(int i=0; i<histSize; i++){
int intensity = static_cast<int>(src.at<float>(i)*scale);
line(dst,Point(i,255),Point(i,255-intensity),Scalar(0));
}
}