一個圖像是由不同顔色的像素組成。像素值在圖像中的分布情況是這幅圖像的一個重要特征。我們将學習如何計算并使用直方圖來修改圖像的外觀。直方圖也可用于描述圖像的内容,并且檢測圖像中特定的對象或紋理。
1【計算圖像的直方圖】
圖像是由像素組成的,在一個單通道的灰階圖像中,每個像素的值介于0(黑色)~255(白色)之間。根據圖像的内容,我們會發現每個灰階值的像素數目是不同的。直方圖是一個簡單的表,它給出了一幅圖像或一組圖像中擁有給定數值的像素數量。是以,灰階圖像的直方圖有256個條目(容器)。0号容器給出值為0的像素個數,1号容器給出值為1的像素個數,以此類推。顯然,如果我們對直方圖的所有項求和,會得到像素的總數。直方圖也可以被歸一化,歸一化後的所有項之和等于1。在該情況下,每一項給出的都是擁有特定數值的像素在圖像中占的比例。
在opencv中計算直方圖,通過cv::calcHist函數。這是一個通用函數,可以計算任意像素類型的多通道圖像。下面代碼是實作一個單通道直方圖的簡單代碼實作。
cv::MatND CowisHistogram1D::getHistogram(const cv::Mat &image)
{
cv::MatND hist;
cv::calcHist(&image,//輸入的圖像
1, //計算幾張圖像
channels,//通道數量
cv::Mat(),//掩碼
hist, //傳回直方圖的矩陣
1, //通道數量
histSize,//項的數量
rang);//像素值的範圍
return hist;
}
擷取到直方圖的資料是一個矩陣,友善我們觀察,我們把資料畫出來,主要代碼如,運作的效果如下圖所示
cv::Mat CowisHistogram1D::gethistogramImage(const cv::Mat &image)
{
cv::MatND hist = getHistogram(image);
double maxVal = 0;
double minVal = 0;
cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);
cv::Mat histImg(histSize[0], histSize[0], CV_8U, cv::Scalar(255));
int hpt = static_cast<int>(0.9 * histSize[0]);
for(int h = 0; h < histSize[0]; h++)
{
float binVal = hist.at<float>(h);
int intensity = static_cast<int>(binVal * hpt / maxVal);
cv::line(histImg, cv::Point(h, histSize[0]),cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
}
return histImg;
}
從直方圖可以直覺的看到,灰階的中間位置存在一個大的峰值,同時有大量深色的像素。這兩組像素基本對應的是圖像的背景和前景。通過這兩組像素之間的過渡處進行門檻值可以證明這一點。有一個友善的函數cv::threshold函數。當需要使用一個門檻值來建立二值圖像時可以使用該函數。生成的圖像清晰的顯示了分段的背景與前景。
cv::calcHist有許多參數,大多數時候,我們的直方圖将是一個單通道或者三通道圖像。然而,該函數允許我們指定一個分布在幾個圖像中的多通道圖像。這就是為何一組圖像被作為該函數的輸入。第6個參數指定直方圖的次元,例如1指的是一維直方圖。直方圖計算中要考慮的通道列在一個數組中,它有指定的次元。在實作單通道時,預設使用的是通道0(第3個參數)。直方圖本身是通過每個次元的條目數量,每個次元的最小及最大值進行描述的,前者位于第7個參數,是一組整數,後者位于第8個參數,是一組每項包含兩個元素的數組。可以指定一個掩碼,指明哪些像素需要進行統計。還可以指定兩個額外的可選參數,都是布爾值。第一個表面直方圖是否歸一化的(預設是true),第2個值允許積累多個直方圖計算結果。如果這個參數為true,那麼圖像的像素統計值會加到輸入直方圖中目前的值上。需要主要的是,直方圖中的值是浮點數。
下面是計算三通道直方圖的代碼。
std::vector<cv::Mat> bgr_planes;
cv::split(image, bgr_planes);
cv::Mat hist1;
cv::calcHist(&bgr_planes[0], 1, channels, cv::Mat(), hist1, 1, histSize, ranges);
cv::Mat hist2;
cv::calcHist(&bgr_planes[1], 1, channels, cv::Mat(), hist2, 1, histSize, ranges);
cv::Mat hist3;
cv::calcHist(&bgr_planes[2], 1, channels, cv::Mat(), hist3, 1, histSize, ranges);
cv::Mat imageChann1(histSize[0], histSize[0], CV_8U, cv::Scalar(255));
cv::Mat imageChann2(histSize[1], histSize[1], CV_8U, cv::Scalar(255));
cv::Mat imageChann3(histSize[2], histSize[2], CV_8U, cv::Scalar(255));
double minVal = 0.;
double maxVal = 0.;
cv::minMaxLoc(hist1, &minVal, &maxVal, 0 ,0);
int hpt = static_cast<int>(0.9 * histSize[0]);
for(int i = 0; i < 3; i++)
{
for(int h = 0; h < histSize[0]; h++)
{
if(i == 0)
{
float binVal = hist1.at<float>(h);
int intensity = static_cast<int>(binVal * hpt / maxVal);
cv::line(imageChann1, cv::Point(h, histSize[0]),cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
}
else if(i == 1)
{
float binVal = hist2.at<float>(h);
int intensity = static_cast<int>(binVal * hpt / maxVal);
cv::line(imageChann2, cv::Point(h, histSize[0]), cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
}
else {
float binVal = hist3.at<float>(h);
int intensity = static_cast<int>(binVal * hpt / maxVal);
cv::line(imageChann3, cv::Point(h, histSize[0]), cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
}
}
}
cv::namedWindow("Channl1");
cv::namedWindow("Channl2");
cv::namedWindow("Channl3");
cv::imshow("Channl1", imageChann1);
cv::imshow("Channl2", imageChann2);
cv::imshow("Channl3", imageChann3);
運作結果如下