对比度增强
在图像处理中,由于获取的图像质量不好,需要通过对比度增强来提升图片质量,主要解决的是由于图像灰度级范围较小造成的对比度较低的问题,作用是使图像的灰度级范围放大,从而让图像更加清晰。
本文所总结的内容出自张平的《opencv算法精讲》
一、对比度增强的方法?
对比度提升的几种常用方法:
线性变换、直方图正规化、伽马变换、全局直方图均衡化、限制对比度的自适应直方图均衡化等。
二、各方法的原理
1.线性变换
我们知道简单的线性方程公式是 y = a*x+b,对于图像亮度提升来说,此时的x,y都是二维矩阵,通过系数a来调整原始图像中的图像灰度级的范围(指的是图像转换为灰度图后的范围 [最小灰度值,最大灰度值] )
当0<a<1时,图像灰度级方位缩小,当a>0时,图像灰度级范围扩大。
当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;
}
2.直方图正规化
直方图正规化其实是一种自动选取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 <H
0≤r<H,
0
≤
c
<
W
0\le c <W
0≤c<W,
O
(
r
,
c
)
O(r,c)
O(r,c)代表第r行第c列的灰度值。这个过程就是直方图正规化。因为
0
≤
I
(
r
,
c
)
−
I
m
i
n
I
m
a
x
−
I
m
i
n
≤
1
0 \le \frac{I_{(r,c)}-I_{min}}{I_{max}-I_{min}} \le 1
0≤Imax−IminI(r,c)−Imin≤1,所以
O
(
r
,
c
)
∈
[
O
m
i
n
,
O
m
a
x
]
O{(r,c)} \in [O_{min},O_{max}]
O(r,c)∈[Omin,Omax],一般令
O
m
i
n
=
0
O_{min}=0
Omin=0,
O
m
a
x
=
255
O_{max}=255
Omax=255。我们将上面的公式展开后花间,可达到线性变换的a和b,
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;
}
3.伽马变换
伽马变换的第一步是将灰度值归一化带
[
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<H,0\le c<W
O(r,c)=I(r,c)γ,0≤r<H,0≤c<W
当
γ
=
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;
}
4.全局直方图均衡化
假设输入的图像为
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;
}
5.限制对比度的自适应直方图均衡化
自适应直方图均衡化首先将图像划分为不重叠的区域块,然后对每一个快进行直方图均衡化,同时为了避免噪声被放大,提出了“限制对比度”,即如果某个像素点的统计数据超过某个界限,便将多余的数量平均分配到每一个像素点上。
由此我们可以大致总结其流程:
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;
}
总结
以上为亮度提升的一些基础算法,个人认为亮度提升的关键是要提升图像像素级的范围和亮度值,最基础的就是一个的线性变换。以上内容为自己学习总结,有理解不到位之处,欢迎在评论区指出。