开篇介绍
由于先前在课程当中听说了低照度视频增强算法及其应用,在课程结束后,我做了一个学习标记未完成,打算往后再深入。于是最近又开始重新搜刮此类文章,无意中找到了发表于2003年的一篇欧洲图形学协会的古董级别的期刊——《Adaptive Logarithmic Mapping For Displaying High Contrast Scenes》
先从摘要上了解该文章的框架脉络:
I.研究问题:该文章提出了一种快速、高质量的色调映射算法,以用于在亮度值动态范围受限的设备上显示高对比度图像。
(HDR image——High Dynamic Range image)
II.算法处理:
(1)基于亮度值进行对数压缩,模仿了人类对视觉的感受特性。
(2)通过引入偏置幂函数,自适应地改变对数基,使得图像细节和对比度得到很好的保留。
(3)通过修改伽马校正流程,提升偏暗区域的对比度。
III.算法应用:通过高动态范围的视频播放器演示了的色调映射技术的成功应用,该播放器能够为任何类型的显示设备调整最佳的观看条件,同时考虑到用户对亮度、对比度压缩和细节再现的偏好。
一、研究背景
首先,文章开篇基于人眼对亮度的响应具有对数性质展开研究。人眼正是通过这一对数性质来接收宽达
1
0
8
10^8
108倍的亮度范围。但是人所能察觉到的亮度增量的度量是以B为底的对数增量形式,不是线性增量。(因为毕竟你还是不能直视太阳的对吧?-_-)
Δ
L
=
log
B
(
y
+
Δ
y
)
−
log
B
y
=
log
B
(
y
+
Δ
y
y
)
\Delta L=\log _{B}\left ( y+\Delta y \right )-\log_{B}y=\log_{B}(\frac{y+\Delta y}{y})
ΔL=logB(y+Δy)−logBy=logB(yy+Δy)
因此,作者依据Stockham论文上推荐的亮度关系公式进行自适应对数映射算法的设计:
L
d
=
log
(
L
w
+
1
)
log
(
L
m
a
x
+
1
)
①
L_{d}=\frac{\log(L_{w}+1)}{\log(L_{max}+1)}①
Ld=log(Lmax+1)log(Lw+1)①。
首先,这个亮度关系式的好处是能把图像最大像素亮度映射为数值1(理解为归一化),然后其他像素值会由于对数关系呈现缓慢上升的趋势。然而,通过这个亮度关系式运算后,我们发现图像整体的亮度过度压缩了,并且会感觉到图像的高对比度内容失真。于是,作者进一步展开了自适应对数映射的算法研究。
二、自适应对数映射
作者的自适应对数映射算法必须满足的前提(我主要列出3个):
①尽管外界自然景观种类繁多或者HDR照片可能会产生的亮度不准确,算法应该保持鲁棒性;
②具有适应性和可扩展性,基于当前显示设备的能力以及各种显示手段未来的演化。
③在捕获物理外观的同时,避免引入人为的对比度反转和黑光泛晕。
核心思想和算法
I.对比度调节
由算法名字中“自适应”这一词不难想到,算法是可以动态调节的。作者采用的是我们数学中耳熟能详的对数换底公式进行展开的。本文提出的核心的最具特色的色调映射函数就是根据每个像素的信息来自适应的调整函数中的对数基。(从2到10)
log
b
a
s
e
(
x
)
=
log
(
x
)
log
(
b
a
s
e
)
②
\log_{base}(x)=\frac{\log(x)}{\log(base)}②
logbase(x)=log(base)log(x)②
当对数基小于2时,其值迅速增加,导致曝光调整很困难。另一方面,当对数基大于10时,亮度压缩的量很小,导致这个图片丢失了太多的对比度。同时,我们也观察到了高对数基时的一些颜色偏移现象。
很明显,上面这两个图都没有给出非常令人满意的结果。
为了实现不同像素不同的对数基以及像素的连续性的要求,作者参考了 Perlin和Hoffert的偏置对数函数,该函数是纹理分析的的标准工具并且在计算机视觉上广为应用。偏置函数定义在单位区间[0,1]之间的power函数,有一个参数b,直接决定了输出值的大小,具体形式如下:
b
i
a
s
b
(
t
)
=
t
log
(
b
)
log
(
0.5
)
③
bias_{b}(t)=t^{\frac{\log(b)}{\log(0.5)}}③
biasb(t)=tlog(0.5)log(b)③
可以发现,通过引入幂函数和偏置值b后,图像的亮度情况可以进行动态调节了。
II.核心算法(本文核心!)
算法思路与流程:
①将读到的RGB图像中各个通道像素进行归一化,再通过线性变换变换到CIE-XYZ色度空间上。由于在表色空间CIE上,Y分量是表征亮度信息,我们根据公式①,算出图像中最大亮度
L
w
m
a
x
L_{wmax}
Lwmax,即①中的
L
m
a
x
L_{max}
Lmax。(常用的表色空间有:RGB、YCrCb、CIE,详情见表色空间及其变换)
②根据上述公式①②③的关系,以及自适应动态映射的思路,得到了新的亮度公式:
L
d
=
L
d
m
a
x
⋅
0.01
log
10
(
L
w
m
a
x
+
1
)
⋅
log
(
L
w
+
1
)
log
(
2
+
(
(
L
w
L
w
m
a
x
)
log
(
b
)
log
(
0.5
)
)
⋅
8
)
④
L_d=\frac{L_{dmax}\cdot0.01}{\log_{10}(L_{wmax}+1)}\cdot\frac{\log(L_w+1)}{\log(2+((\frac{L_w}{L_wmax})^{\frac{\log(b)}{\log(0.5)}})\cdot8)}④
Ld=log10(Lwmax+1)Ldmax⋅0.01⋅log(2+((LwmaxLw)log(0.5)log(b))⋅8)log(Lw+1)④
其中,
L
d
m
a
x
L_{dmax}
Ldmax是指显示设备的显示能力,一般取
L
d
m
a
x
=
100
c
d
/
m
2
L_{dmax}=100cd/m^2
Ldmax=100cd/m2,再乘以比例系数0.01,代表常用的CRT显示器的显示能力。然后,第二个分式中,根据对数的换底公式不难发现,第二个分式的对数基取值范围是在2到10之间(也即分式的分母括号中部分)。于是,整个亮度公式
L
d
L_d
Ld便是公式①的一个动态调节版本,也就是印证了本论文的题目——自适应对数映射!
III.伽马校正
现实世界中几乎所有的CRT显示设备、摄影胶片和许多电子照相机的光电转换特性都是非线性的。这些非线性部件的输出与输入之间的关系(例如,电子摄像机的输出电压与场景中光强度的关系,CRT发射的光的强度与输入电压的关系)可以用一个幂函数来表示,它的一般形式是:
o
u
t
p
u
t
=
i
n
p
u
t
γ
output=input^{\gamma}
output=inputγ其中
γ
\gamma
γ是一个幂函数的一个指数。本文作者采用的伽马校正函数修正为:
这样一来,算法使用者可以按需进行伽马函数的修正。(在本博客中,不采用伽马校正效果更好)
三、代码复现
至于代码的复现就比较简单了,在pycharm编辑器下采用numpy和cv2的库进行复现,其中要注意在读到RGB图像后先进行归一化操作,在进行表色空间的映射,而后才可对CIE表色空间中的亮度Y分量按照公式④进行自适应对数映射,最后通过等比例缩放,逆变换到RGB颜色空间,再把图像显示出来。
import numpy as np
import cv2
def transform(t):
if t <= 0.05:
return t * 2.64
else:
return 1.099 * np.power(t, 0.9 / 2.2) - 0.099
if __name__ == '__main__':
fn = "1.png"
img = cv2.imread(fn)
# img = transform.resize(img,(600,680)) #将灰度图片大小转换为1024*860
shape = img.shape
s = np.zeros((shape[0], shape[1], 3), dtype=np.float)
out_img = np.zeros((shape[0], shape[1], 3), dtype=np.uint8)
rows = shape[0]
cols = shape[1]
r = 0
g = 0
b = 0
lwmax = -1.0
base = 0.85
for i in range(rows):
for j in range(cols):
b = img[i][j][0] / 255.0
g = img[i][j][1] / 255.0
r = img[i][j][2] / 255.0
# RGB表示映射为CIE XYZ
s[i][j][0] = (0.433953 * r + 0.376219 * g + 0.189828 * b)
s[i][j][1] = (0.212671 * r + 0.715160 * g + 0.072169 * b)
s[i][j][2] = (0.017758 * r + 0.109477 * g + 0.872765 * b)
lwmax = max(lwmax, s[i][j][1])
for i in range(rows):
for j in range(cols):
xx = s[i][j][0] / (s[i][j][0] + s[i][j][1] + s[i][j][2])
yy = s[i][j][1] / (s[i][j][0] + s[i][j][1] + s[i][j][2])
tp = s[i][j][1]
# 修改CIE:X,Y,Z
s[i][j][1] = 1.0 * np.log(s[i][j][1] + 1) / np.log(
2 + 8.0 * np.power((s[i][j][1] / lwmax), np.log(base) / np.log(0.5))) / np.log10(lwmax + 1)
x = s[i][j][1] / yy * xx
y = s[i][j][1]
z = s[i][j][1] / yy * (1 - xx - yy)
# 转化为用RGB表示
r = 3.079931 * x - 1.537147 * y - 0.542784 * z
g = -0.921233 * x + 1.875989 * y + 0.045244 * z
b = 0.052890 * x - 0.204042 * y + 1.151152 * z
r = max(0, min(r, 1))
g = max(0, min(g, 1))
b = max(0, min(b, 1))
# 修正补偿
r = transform(r)
g = transform(g)
b = transform(b)
out_img[i][j][0] = int(b * 255)
out_img[i][j][1] = int(g * 255)
out_img[i][j][2] = int(r * 255)
print(out_img)
cv2.imshow("logmap", out_img)
cv2.imwrite("logmap.png", out_img)
cv2.waitKey()
四、效果演示
I.实验图①
先给大家看个球!不难看出这个足球左半部分的轮廓和细节十分明朗,亮度足以看清楚球的大致状况,而足球的右边因为亮度和对比度很低,丢失了足球右边的细节。
通过本文自适应对数映射算法后的效果:
可以发现,足球的右半部分轮廓变得明亮了,起码以直观观察到球的左边起皮了,而且比较严重,而足球右边并没有严重的起皮。
由于上述效果是我特意把伽马校正去掉的效果,当加入伽马校正后,足球的效果就变为:
这时发现,整个足球变得更加明亮了,足球右边的细节纹理也越来越突出,但产生了一个缺点是有些因为非线性校正产生的光斑块。-_-!!
II.实验图②
先上一个黑糊糊的黑人吧!
不加入伽马校正的自适应亮度映射结果:
加入伽马校正后的结果:
III.实验图③
最后来个真实风景照吧!
不加入伽马校正的结果:
加入伽马校正后的结果:
五、实验心得与总结
通过本篇论文的多次阅读和流程梳理,终于复现出代码了。个人直观上感觉图像在不加入gamma校正的情况下会好一些,加入了gamma校正后,会随之而来产生光斑问题。那根据使用者的显示设备,人们可以动态去选择是否需要gamma校正。
本博客目前只完成了代码复现的工作,详细的优化与改进还没深入,先留个小博客,以后有缘再更新吧!