对比度增强或者称为对比度拉伸就是图像增强技术的一种,它主要解决由于图像的灰度级范围较小造成的对比度较低的问题,目的就是将输出图像的灰度级放大到指定的程度,使得图像中的细节看起来更加清晰。对比 度增强有几种常用的方法,如线性变换、分段线性变换、伽马变换、直方图正规化、直方图均衡化、局部自适应直方图均衡化等。
4.1、灰度直方图
灰度直方图是图像灰度级的函数, 用来描述每个灰度级在图像矩阵中的像素个数或者占有率(概率)。
OpenCV提供了函数calcHist
来实现直方图的构建,但是在计算8位图的灰度直方图 时,它使用起来略显复杂。下面是OpenCV源码
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
int main( int argc, char** argv )
{
Mat src, hsv;
if( argc != 2 || !(src=imread(argv[1], 1)).data )
return -1;
cvtColor(src, hsv, COLOR_BGR2HSV);
// Quantize the hue to 30 levels
// and the saturation to 32 levels
int hbins = 30, sbins = 32;
int histSize[] = {hbins, sbins};
// hue varies from 0 to 179, see cvtColor
float hranges[] = { 0, 180 };
// saturation varies from 0 (black-gray-white) to
// 255 (pure spectrum color)
float sranges[] = { 0, 256 };
const float* ranges[] = { hranges, sranges };
MatND hist;
// we compute the histogram from the 0-th and 1-st channels
int channels[] = {0, 1};
calcHist( &hsv, 1, channels, Mat(), // do not use mask
hist, 2, histSize, ranges,
true, // the histogram is uniform
false );
double maxVal=0;
minMaxLoc(hist, 0, &maxVal, 0, 0);
int scale = 10;
Mat histImg = Mat::zeros(sbins*scale, hbins*10, CV_8UC3);
for( int h = 0; h < hbins; h++ )
for( int s = 0; s < sbins; s++ )
{
float binVal = hist.at<float>(h, s);
int intensity = cvRound(binVal*255/maxVal);
rectangle( histImg, Point(h*scale, s*scale),
Point( (h+1)*scale - 1, (s+1)*scale - 1),
Scalar::all(intensity),
CV_FILLED );
}
namedWindow( "Source", 1 );
imshow( "Source", src );
namedWindow( "H-S Histogram", 1 );
imshow( "H-S Histogram", histImg );
waitKey();
}
可以定义函数calcGrayHist
来计算灰度直方图,其中输入参数为8位图,将返回的灰度直方图存储为一个1行256列的Mat
类型。
Mat calGrayHist(const Mat &img)
{
//存储256个灰度级的像素个数
Mat histogram = Mat::zeros(Size(256, 1), CV_32SC1);
//图像的宽高
int rows = img.rows;
int cols = img.cols;
//计算每个灰度级的个数
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
int index = int(img.at<uchar>(i, j));
histogram.at<int>(0, index) += 1;
}
}
return histogram;
}
图像对比度是通过灰度级范围来度量的,而灰度级范围可通过观察灰度直方图得到,灰度级范围越大代表对比度越高;反之,对比度越低,低对比度的图像在视觉上给人的感觉是看起来不够清晰,所以通过算法调整图像的灰度值,从而调整图像的对比度是有必要的。最简单的一种对比度增强方法是通过灰度值的线性变换来实现的。
4.2、线性变换
当a=1,b=0时,O为I的一个副本;如果a>1,则输出图像O的对 比度比I 有所增大;如果0<a< 1,则O的对比度比I有所减小。而b值的改变,影响的是输出图像的亮度,当b> 0时,亮度增加;当b<0时,亮度减小。
在OpenCV中实现一个常数与矩阵相乘有多种方式。
1、convertTo
Mat Input=(Mat_<uchar>(2,2)<<0,200,23,4);
Mat Output;
Input.convertTo(Output,CV_8UC1,2.0,0);//convertTo(out,type,alpha,beta)
注:当输出矩阵的数据类型是CV_8U
时, 大于255的值会自动截断为255
2、矩阵乘法运算
Mat output=2.0*Input;
使用乘法运算符“*”, 无论常数是什么数据类型, 输出矩阵的数据类型总是和输入矩阵的数据类型相同,当数据类型是CV_8U
时,在返回值中将大于255的值自动截断为255。
3、convertScaleAbs
Mat input=(Mat_<uchar>(2,2)<<0,200,23,4);
Mat output;
convertScaleAbs(input,output,2.0,0);
4.3、直方图正规化
直方图正规化是一种自动选取a和b的值的线性变换方法。
/** Finds global minimum, maximum and their positions */
CVAPI(void) cvMinMaxLoc( const CvArr* arr, double* min_val, double* max_val,
CvPoint* min_loc CV_DEFAULT(NULL),
CvPoint* max_loc CV_DEFAULT(NULL),
const CvArr* mask CV_DEFAULT(NULL) );
利用minMaxLoc
函数不仅可以计算出矩阵中的最大值和最小值, 而且可以求出最大 值的位置和最小值的位置。 当然,
在使用过程中如果只想得到最大值和最小值, 则将其 他的变量值设为NULL
即可。
Mat img = imread("Koala.jpg", IMREAD_GRAYSCALE);
//找到输入图片的最值
double Imax, Imin;
minMaxLoc(img, &Imin, &Imax, NULL, NULL);
//设置输出图片的灰度范围
double Omin = 0, Omax = 255;
//计算
double a = (Omax - Omin) / (Imax - Imin);
double b = Omin - a * Imin;
//线性变换
Mat dst;
convertScaleAbs(img, dst, a, b);
imshow("img", img);
imshow("dst", dst);
OpenCV提供的函数: normalize()
使用函数normalize
对图像进行对比度增强时, 经常令参数norm_type=NORM_MINMAX
, 和直方图正规化原理详解中提到的计算方法是相同的, 参数alpha
相当于Omax
, 参数beta
相当于Omin
。 注意, 使用normalize
可以处理多通道矩阵, 分别对每一个通道进行正规化操作。
4.4、伽马变换
非线性变换。
假设输入图像为I,宽为W、 高为H,首先将其灰度值归一化到[0,1]范围,对于8位 图来说,除以255即可。 I (r, c) 代表归一化后的第r行第c列的灰度值, 输出图像记为 O, 伽马变换就是令O(r, c) =I(r, c) γ
, 0≤r<H, 0≤c< W,
当γ=1时, 图像不变。 如果图像整体或者感兴趣区域较暗, 则令0< γ< 1可以 增加图像对比度; 相反, 如果图像整体或者感兴趣区域较亮, 则令γ>1可以降低图像对比度。
Mat img = imread("Koala.jpg", IMREAD_GRAYSCALE);
//灰度归一化
Mat grayImg;
img.convertTo(grayImg, CV_64F, 1.0 / 255, 0);
//伽马变换
double gamma = 0.125;
Mat gammaImg;
pow(grayImg, gamma, gammaImg);
//如果没有数据类型转换, 则直接保存浮点型的gammaImg, 这样虽然不会报错, 但是保存后图像呈现黑色, 看不到任何信息。
gammaImg.convertTo(gammaImg, CV_8U, 255, 0);
imwrite("gammaImg.jpg", gammaImg);
伽马变换在提升对比度上有比较好的效果, 但是需要手动调节γ值。
4.5、全局直方图均值化
全局直方图均衡化操作是对图像I进行改变, 使得输出图像O的灰度直方图hist O
是“平”的, 即每一个灰度级的像素点个数是“相等”的。 注意,其实这里的“相等”不是严格意义上的等于, 而是约等于,
上述分别为I和O的累加直方图
总结,对于直方图均衡化的实现主要分四个步骤:
第一步: 计算图像的灰度直方图。
第二步: 计算灰度直方图的累加直方图。
第三步: 根据累加直方图和直方图均衡化原理得到输入灰度级和输出灰度级之间的映射关系。
第四步: 根据第三步得到的灰度级映射关系,循环得到输出图像的每一个像素的灰度级。其中第三步和第四步对应于最后两个公式。
Mat equalHist(Mat image)
{
//输入图像应该为八位的灰度图
//灰度图片的高、宽
int rows = image.rows;
int cols = image.cols;
//step1:计算图像的灰度直方图
Mat grayHist = calGrayHist(image);
//step2:计算累加灰度直方图
Mat zeroCumuMoment = Mat::zeros(Size(256, 1), CV_32SC1);
for (int p = 0; p < 256; p++)
{
if (p == 0)
{
zeroCumuMoment.at<int>(0, p) = grayHist.at<int>(0, 0);
}
else
{
zeroCumuMoment.at<int>(0, p) = zeroCumuMoment.at<int>(0, p-1) + grayHist.at<int>(0, p);
}
}
//step3:根据累加灰度直方图得到输入灰度级和输出灰度级之间的映射关系
Mat outPut_q = Mat::zeros(Size(256, 1), CV_8UC1);
float cofficient = 256.0 / (rows*cols);
for (int p = 0; p < 256; p++)
{
float q = cofficient * zeroCumuMoment.at<int>(0, p) - 1;
if (q >= 0)
{
outPut_q.at<uchar>(0, p) = uchar(floor(q));
}
else
{
outPut_q.at<uchar>(0, p) = 0;
}
}
//step4:得到直方图均衡化后的图像
Mat equalHistImage = Mat::zeros(image.size(), CV_8UC1);
for (int r = 0; r < rows; r++)
{
for (int c = 0; c < cols; c++)
{
int p = image.at<uchar>(r, c);
equalHistImage.at<uchar>(r, c) = outPut_q.at<uchar>(0, p);
}
}
return equalHistImage;
}
OpenCV实现的直方图均衡化函数 equalize-Hist
, 其使用方法很简单, 只支持对8位图的处理。
虽然全局直方图均衡化方法对提高对比度很有效,但是均衡化处理以后暗区域的噪声可能会被放大,变得清晰可 见,而亮区域可能会损失信息。为了解决该问题, 提出了自适应直方图均衡化(Aptive Histogram Equalization) 方法。
4.6、限制对比度的自适应直方图均衡化
自适应直方图均衡化首先将图像划分为不重叠的区域块(tiles) ,然后对每一个块分别进行直方图均衡化。 显然, 在没有噪声影响的情况下, 每一个小区域的灰度直方图会被限制在一个小的灰度级范围内; 但是如果有噪声, 每一个分割的区域块执行直方图均衡化后, 噪声会被放大。为了避免出现噪声这种情况, 提出了“限制对比度”(Contrast Limiting) [3],如果直方图的bin超过了提前预设好的“限制对比度”, 那么会被裁减, 然 后将裁剪的部分均匀分布到其他的bin, 这样就重构了直方图。
OpenCV提供的函数createCLAHE
构建指向CLAHE
对象的指针, 其中默认设置“限制 对比度”为40,块的大小为8×8。
Mat img = imread("Koala.jpg", IMREAD_GRAYSCALE);
Ptr<CLAHE> clahe = createCLAHE(2.0, Size(8, 8));//2.0是limit值
Mat dst;
clahe->apply(img, dst);