OpenCV影象增強:直方圖拉伸和直方圖均衡化
直方圖反映了影象中畫素值的分佈情況,很多時候,影象的視覺缺陷可以根據影象的直方圖來分析。比如直方圖太窄,說明影象使用的灰度值範圍太窄;比如直方圖有一個很強烈的峰值,說明影象部分灰度值的使用頻率比其他強度值要高得多。
所以,可以通過直方圖資訊來修改影象的灰度值。如果將一種灰度修改為另一種灰度,那麼這意味著這種改變不是針對某些畫素的,而是整體性的,新的顏色值只與當前畫素的顏色值相關。這種關係,通常可以用一個對映函式來表示,稱之為查詢表。
對於灰度圖,查詢表是一個一維陣列,包含256個專案。陣列下標(0-255)代表當前灰度,下標對應的值代表當前灰度修改後的值。
OpenCV裡的查詢表函式cv::LUT(),
//Performs a look-up table transform of an array
void LUT(InputArray src, InputArray lut, OutputArray dst, int interpolation=0 )
下面介紹的直方圖拉伸和直方圖均衡化操作都會用到查詢表。程式中直方圖的計算呼叫了自定義的類函式,參見OpenCV計算和顯示影象直方圖(其實將一些演算法封裝為類很簡單,方便了呼叫和擴充套件)
直方圖拉伸
//直方圖拉伸
cv::Mat stretch(const cv::Mat& image,int minvalue=0 )
{
Histogram1D h;
cv::Mat hist = h.getHistogram(image);
//找到直方圖的左邊限值
int imin = 0;
for (; imin < h.getHistSize()[0];imin++)
{
if (hist.at<float>(imin)>minvalue)
{
break;
}
}
//找到直方圖的右邊限值
int imax = h.getHistSize()[0]-1;
for (; imax >= 0; imax--)
{
if (hist.at<float>(imax)>minvalue)
{
break;
}
}
//建立查詢表
cv::Mat lut(1, 256, CV_8U);
//構建查詢表
for (int i = 0; i < 256;i++)
{
if (i < imin)
lut.at<uchar>(i) = 0;
else if (i>imax)
lut.at<uchar>(i) = 255;
else
{
lut.at<uchar>(i) = cvRound(255.0*(i - imin) / (imax - imin));
}
}
cv::Mat result = Histogram1D::applyLookUp(image,lut);
return result;
}
至於Histogram1D::applyLookUp
只是cv::LUT簡單的重新封裝,具體如下
cv::Mat Histogram1D::applyLookUp(const cv::Mat& image, const cv::Mat& lookup)
{
cv::Mat result;
cv::LUT(image,lookup,result);
return result;
}
執行效果:
源影象vs拉伸直方圖後的影象
源影象直方圖vs拉伸後的直方圖
直方圖均衡化
OpenCV提供了一個易用的函式,用於直方圖均衡化處理,
//Equalizes the histogram of a grayscale image.
void equalizeHist(InputArray src, OutputArray dst)
如果只是使用,而非自己弄清楚內部的實現,閱讀可以到此為止了^-^。
實現原理是:一個完全均衡的直方圖,意味著所有箱子包含的畫素數量是相同的。其中一個必要條件就是,50%畫素的強度值小於128,25%畫素的強度值小於64,依次類推。所以p%畫素的強度值必須小於或等於255*p%。這條規則也是直方圖均衡化演算法所採用的,程式語言描述如下:
lut.at<uchar>(i) = static_cast<uchar>(p[i]*255.0/image.total());
p[i]是強度值小於或等於i的畫素數量,成為累計直方圖。total返回影象畫素總數,p[i]/image.total代表某堆灰度值的百分比。
具體實現:
cv::Mat myequalizeHist(cv::Mat& image)
{
Histogram1D h;
cv::Mat hist = h.getHistogram(image);
cv::Mat lut(1, 256, CV_8U);
float cumulate = 0;//強度值小於或等於某值的畫素數量
for (int i = 0; i < 256; i++)
{
cumulate += hist.at<float>(i);
lut.at<uchar>(i) = static_cast<uchar>(cumulate*255.0/image.total());
}
cv::Mat result;
cv::LUT(image, lut, result);
return result;
}
執行效果:
源影象vs直方圖均衡化之後的影象
源影象直方圖 vs均衡化之後的直方圖