直方图反映了图像中像素值的分布情况,很多时候,图像的视觉缺陷可以根据图像的直方图来分析。比如直方图太窄,说明图像使用的灰度值范围太窄;比如直方图有一个很强烈的峰值,说明图像部分灰度值的使用频率比其他强度值要高得多。
所以,可以通过直方图信息来修改图像的灰度值。如果将一种灰度修改为另一种灰度,那么这意味着这种改变不是针对某些像素的,而是整体性的,新的颜色值只与当前像素的颜色值相关。这种关系,通常可以用一个映射函数来表示,称之为查找表。
对于灰度图,查找表是一个一维数组,包含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均衡化之后的直方图