在图像处理中,由于获取的图像质量不好,需要通过对比度增强来提升图片质量,主要解决的是由于图像灰度级范围较小造成的对比度较低的问题,作用是使图像的灰度级范围放大,从而让图像更加清晰。
本文所总结的内容出自张平的《opencv算法精讲》
对比度提升的几种常用方法:
线性变换、直方图正规化、伽马变换、全局直方图均衡化、限制对比度的自适应直方图均衡化等。
我们知道简单的线性方程公式是 y = a*x+b,对于图像亮度提升来说,此时的x,y都是二维矩阵,通过系数a来调整原始图像中的图像灰度级的范围(指的是图像转换为灰度图后的范围 [最小灰度值,最大灰度值] )
当00时,图像灰度级范围扩大。
当b>0时,亮度增加,当b<0时,亮度减小。
代码如下:
/*
线性变换
变换后的矩阵B = a*原矩阵A+b
image:原图像
rtype:输出矩阵的数据类型,CV_8U等
a:上述公式中的a
b:上述公式中的b
*/
Mat LinearTransform(const Mat& image, int rtype, double a, double b) {
Mat Out;
image.convertTo(Out, rtype, a, b);
return Out;
}
直方图正规化其实是一种自动选取a和b的线性变换的方法。
假设输入矩阵 I,高为H、宽为W,I(r,c)代表第r行第c列的灰度值,将 I 中的最小灰度值记为 I min,最大值记为 I max,即 I(r,c)∈[ I min, I max],为使输出矩阵 ***O***的灰度级范围为[ O min, O max],则 I(r,c)和 O(r,c)的映射关系如下:
O ( r , c ) = O m a x − O m i n I m a x − I m i n ( I ( r , c ) − I m i n ) + O m i n O(r,c) = \frac{O_{max}-O_{min}}{I_{max}-I_{min}}(I{(r,c)}-I_{min})+O_{min} O(r,c)=Imax−IminOmax−Omin(I(r,c)−Imin)+Omin
其中 0 ≤ r < H 0\le r
a = O m a x − O m i n I m a x − I m i n , b = O m i n − O m a x − O m i n I m a x − I m i n ∗ I m i n a= \frac{O_{max}-O_{min}}{I_{max}-I_{min}},b=O_{min}-\frac{O_{max}-O_{min}}{I_{max}-I_{min}} * I_{min} a=Imax−IminOmax−Omin,b=Omin−Imax−IminOmax−Omin∗Imin
代码如下:
Mat HistgramNormalization(Mat gray_image)
{
//获取输入矩阵的最大值和最小值
double Imax, Imin;
minMaxLoc(gray_image, &Imin, &Imax, NULL, NULL);
// 定义输出矩阵的最大值和最小值
double Omin = 0, Omax = 255;
// 确定映射关系函数中的系数,其实就是确定线性变换中的系数a和b
double a = (Omax - Omin) / (Imax - Imin);
double b = Omin - a * Imin;
Mat Out;//输出矩阵
convertScaleAbs(gray_image, Out, a, b);//进行线性变换
return Out;
}
伽马变换的第一步是将灰度值归一化带 [ 0 , 1 ] [0,1] [0,1]的范围。归一化后的图像矩阵记为 I I I,高为H,宽为W,第r行第c列的值记为 I ( r , c ) I{(r,c)} I(r,c),输出矩阵击为 O O O,伽马变换就是:
O ( r , c ) = I ( r , c ) γ , 0 ≤ r < H , 0 ≤ c < W O_{(r,c)} = I{(r,c)}^\gamma,0\le r
当 γ = 1 \gamma = 1 γ=1时,图像不变,当 0 < γ < 1 0<\gamma<1 0<γ<1时,可以增加对比度,当 γ > 1 \gamma>1 γ>1 时,可以降低对比度,接下来我们在看一下Gamma变换的的图示,这样理解起来容易点。下图中从左上角到右下角的曲线为 y = x a y=x^a y=xa,a∈[0.125,0.25,0.5,1,2,4,8],其实就是高中的幂函数,只是定义域在[0,1]之间,
代码如下:
Mat GammaTransform(Mat gray_image, double gamma)
{
//1.归一化
Mat fI;
gray_image.convertTo(fI, CV_64F, 1 / 255, 0);
//2.伽马变换,其实就是幂运算,gamma的区间为(0,1)
Mat Out;
pow(fI, gamma, Out);
//3.再次进行线性变换,转换为0,255之间的像素值
Out.convertTo(Out, CV_8U, 255, 0);
return Out;
}
假设输入的图像为 I I I,高为 H H H,宽为 W W W, h i t s I hits_I hitsI代表 I I I的灰度直方图, h i s t I ( k ) hist_I(k) histI(k)代表灰度值等于k的像素点的个数,其中 k ∈ [ 0 , 255 ] k\in[0,255] k∈[0,255]。全局直方图均衡化的操作就是对图像 I I I进行改变,使得输出图像 O O O的灰度直方图 h i s t O hist_O histO是‘平的’,即每一个像素值的个数是‘相等’的。这里的相等不是严格意义上的相等,而是‘约等于’,即 h i s t O ( k ) ≈ H ∗ W 256 , k ∈ [ 0 , 255 ] hist_O(k)\approx\frac{H*W}{256},k\in[0,255] histO(k)≈256H∗W,k∈[0,255],那么对于任意灰度级p, 0 ≤ p ≤ 255 0\le p \le 255 0≤p≤255,总能找到q, 0 ≤ q ≤ 255 0\le q \le 255 0≤q≤255,使得
∑ k = 0 p h i s t I ( k ) = ∑ k = 0 q h i s t O ( k ) \sum_{k=0}^phist_I(k)=\sum_{k=0}^qhist_O(k) k=0∑phistI(k)=k=0∑qhistO(k)
其中 ∑ k = 0 p h i s t I ( k ) \sum_{k=0}^{p}hist_I(k) ∑k=0phistI(k)和 ∑ k = 0 q h i s t I ( k ) \sum_{k=0}^{q}hist_I(k) ∑k=0qhistI(k)称为 I I I和 O O O的累加直方图,又因为 h i s t O ( k ) ≈ H ∗ W 256 hist_O(k)\approx\frac{H*W}{256} histO(k)≈256H∗W,所以
∑ k = 0 p h i s t I ( k ) ≈ ( q + 1 ) H ∗ W 256 \sum_{k=0}^phist_I(k)\approx(q+1)\frac{H*W}{256} k=0∑phistI(k)≈(q+1)256H∗W
化简上式得
q ≈ ∑ k = 0 p h i s t I ( k ) H ∗ W ∗ 256 − 1 q\approx\frac{\sum_{k=0}^{p}hist_I(k)}{H*W}*256-1 q≈H∗W∑k=0phistI(k)∗256−1
上式给出了一个从亮度级为p的输入像素到亮度级为q的输出像素的映射关系,p代表的是I(r,c),是原矩阵第r行第c列的像素值,此时令p=I(r,c);q代表的是O(r,c),是输出矩阵的第r行第c列的像素值,将两个进行替换得到最终的映射关系
O ( r , c ) ≈ ∑ k = 0 I ( r , c ) h i s t I ( k ) H ∗ W ∗ 256 − 1 O(r,c)\approx\frac{\sum_{k=0}^{I(r,c)}hist_I(k)}{H*W}*256-1 O(r,c)≈H∗W∑k=0I(r,c)histI(k)∗256−1
代码如下:
Mat equalHist(Mat image)
{
//检查图像数据类型是否符合要求,如不符合,则抛出异常
// CV_Assert的官方简要说明如下
// Checks a condition at runtime and throws exception if it fails
CV_Assert(image.type() == CV_8UC1);
// 灰度图像的宽和高
int rows = image.rows;
int cols = image.cols;
//第一步:计算图像的灰度直方图
Mat gray_hist = CalcGrayHist(image);
//第二步:计算累加灰度直方图
Mat zero_comu_moment = Mat::zeros(Size(256, 1), CV_32SC1); //生成一行256列的零矩阵
for (int p = 0; p < 256; p++)
{
if (p==0)
{
zero_comu_moment.at<int>(0, p) = gray_hist.at<int>(0, 0);
}
else
{
zero_comu_moment.at<int>(0, p) = zero_comu_moment.at<int>(0, p - 1) + gray_hist.at<int>(0, p);
}
}
//第三步:根据累加直方图得到输入灰度级和输出灰度级直方图的映射关系
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 * zero_comu_moment.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;
}
}
//第四步:得到直方图均衡化后的图像
Mat equal_hist_image = 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);
//根据原像素值p获得新的像素值q重新赋值给对应位置
equal_hist_image.at<uchar>(r, c) = output_q.at<uchar>(0, p);
}
}
return equal_hist_image;
}
自适应直方图均衡化首先将图像划分为不重叠的区域块,然后对每一个快进行直方图均衡化,同时为了避免噪声被放大,提出了“限制对比度”,即如果某个像素点的统计数据超过某个界限,便将多余的数量平均分配到每一个像素点上。
由此我们可以大致总结其流程:
1.将图像分割成不重叠的区域块;
2.对每个区域块进行直方图均衡化;
3.为避免噪声的出现,设置一个像素点出现的最高数量,如果超过这个数量,将多出来的平均分配到每个像素点上。
代码如下:
Mat AdaptEqualHist(Mat gray_image)
{
//构建CLAHE对象,其默认设置“限制对比度”为40,块的大小为8*8
Ptr<CLAHE> clahe = createCLAHE(2.0, Size(8, 8));
Mat dst;
//限制对比度的自适应直方图均衡化
clahe->apply(gray_image, dst);
return dst;
}
以上为亮度提升的一些基础算法,个人认为亮度提升的关键是要提升图像像素级的范围和亮度值,最基础的就是一个的线性变换。以上内容为自己学习总结,有理解不到位之处,欢迎在评论区指出。