这学期有幸参加学习学校韩宇星教授的 数字图像工程(全英) 课程,课程最后给了1篇paper①,是基于去雾算法的低光照图像增强算法,本人觉着非常厉害,非常感谢老师提供这篇论文,有兴趣的大伙可以一起学习一下。
① Dong X, Wang G, Pang Y, et al. Fast efficient algorithm for enhancement of low lighting video[C]//2011 IEEE International Conference on Multimedia and Expo. IEEE, 2011: 1-6.
文章作者观察到低光照图像的反转图看起来与雾图十分相似,远景部分的像素值通常都非常高,而对于近景部分,则至少存在一个颜色通道的像素值小于180。经过实验,文章作者发现低光照图像的反转图像的像素分布的确与雾图的像素分布很类似,两者都是80%以上的像素的RGB值都很高,而这些像素刚好对应远景部分。
既然低光照图像的反转图视觉上类似于雾图,那么将去雾算法应用在低光照图像的反转图上,再对处理图像进行反转,就有可能得到低光照图像的增强图像。例如,在雾图中,绿色的增强相当于变暗一点,其对应的反色相红色则变亮一点,这相当于在低光照图像中增强红色。基于该假设,作者设计了一套算法并展开相关实验。
上面这幅图是我在网站https://en.m.fontke.com/tool/rgbschemes/上做的相关实验,有兴趣的人可以自己搜索RGB在线生成,然后动手做做实验(最好找那种有“亮一点”、“暗一点”设置的网站,方便进行实验观察效果)。
我用python实现了算法,块大小值设置为1, k k k值取100, ω \omega ω值取0.76,实验效果如下:
上图为院楼夜景手机拍摄。
实验过程中发现,若 ω \omega ω值设得太大容易过增强,太小增强效果不明显。
同时,若块大小设置太大容易产生不自然边缘和斑点。
上述实验用图来源:https://www.jianshu.com/p/6e8ea77adf8b。
Python代码实现如下:
from PIL import Image
import numpy as np
def MinRgb(c):
return min(c[0], c[1], c[2])
def SumRgb(c):
return c[0] + c[1] + c[2]
def Invert(img):
img = 255 - img
return img
def GetA(R, G, B, k = 100):
# k默认是原文获取排序后前100个像素点
rlist = []
height, width = R.shape[0], R.shape[1]
for hi in range(height):
for wi in range(width):
rlist.append([R[hi][wi], G[hi][wi], B[hi][wi]])
rlist.sort(key=MinRgb)
rlist.reverse()
rlist = rlist[:k]
rlist.sort(key=SumRgb)
rlist.reverse()
return rlist[0][0], rlist[0][1], rlist[0][2]
def CalT(R, G, B, r_A, g_A, b_A, size=1, w=0.76):
# 计算A值时使用size×size窗口,以图像边缘点为窗口中心时需要进行填充
# 图像填充时上下左右各填充1行/列255
ts = (size - 1) // 2
height, width = R.shape[0], R.shape[1]
R_f = np.pad(R, ((ts, ts), (ts, ts)), 'constant', constant_values=(255, 255)) / r_A
G_f = np.pad(G, ((ts, ts), (ts, ts)), 'constant', constant_values=(255, 255)) / g_A
B_f = np.pad(B, ((ts, ts), (ts, ts)), 'constant', constant_values=(255, 255)) / b_A
shape = (height, width, size, size)
strides = R_f.itemsize * np.array([width + ts * 2, 1, width + ts * 2, 1])
blocks_R = np.lib.stride_tricks.as_strided(R_f, shape=shape, strides=strides)
blocks_G = np.lib.stride_tricks.as_strided(G_f, shape=shape, strides=strides)
blocks_B = np.lib.stride_tricks.as_strided(B_f, shape=shape, strides=strides)
t = np.zeros((height, width))
for hi in range(height):
for wi in range(width):
t[hi, wi] = 1- w * min(np.min(blocks_R[hi, wi]), np.min(blocks_G[hi, wi]), np.min(blocks_B[hi, wi]))
if t[hi, wi] < 0.5:
t[hi, wi] = 2 * t[hi, wi] * t[hi, wi]
return t
def DeHaze(filepath):
# 根据路径读取照片
img = Image.open(filepath)
# 获取图像宽度、高度
# width, height = img.size
# 获取图像的RGB数组
img = np.asarray(img, dtype=np.int32)
R, G, B = img[:, :, 0], img[:, :, 1], img[:, :, 2]
R, G, B = Invert(R), Invert(G), Invert(B)
# 计算A值
r_A, g_A, b_A = GetA(R, G, B)
t = CalT(R, G, B, r_A, g_A, b_A)
J_R = (R - r_A) / t + r_A
J_G = (G - g_A) / t + g_A
J_B = (B - b_A) / t + b_A
J_R, J_G, J_B = Invert(J_R), Invert(J_G), Invert(J_B)
r = Image.fromarray(J_R).convert('L')
g = Image.fromarray(J_G).convert('L')
b = Image.fromarray(J_B).convert('L')
image = Image.merge("RGB", (r, g, b))
image.save("./dark_result.jpg")
image.show()
if __name__ == '__main__':
DeHaze("./dark.png")
# DeHaze("./test.jpg")
代码存在可以加速的地方,例如:①sum排序可以直接遍历 O ( n ) \mathcal{O}(n) O(n)取最大值;②若块大小设置为1,无需切割滑动窗口。
文章作者将该算法应用于视频处理,为了减少计算量,作者指出了2个可以加速的地方。
第一个地方,作者指出视频每一帧之间总会存在一定相似性,相似地方的 t ( x ) t(x) t(x)可以直接采用前一帧的 t ( x ) t(x) t(x),从而减少当前帧的冗余计算量。文章作者将视频拆分为GOPs,对于每一组GOP, I I I帧的 t ( x ) t(x) t(x)都要重新计算,然后将 P P P帧拆分为不重叠的16×16块,对每个块计算动态估值,若小于预设阈值 T T T,则该块的 t ( x ) t(x) t(x)沿用前一帧相应位置的 t ( x ) t(x) t(x),否则,重新计算该块的 t ( x ) t(x) t(x)。
第二个地方在于SAD计算的加速,文章作者指出,在某些情形下,若存在多个刚体或运动是非常规的,当SAD取最小值时,它有可能不是真正的运动矢量,在该情形下,采样的像素越多,SAD算法导致的误差可能越大。文章作者通过固定模板子采样的方法来缓解这一问题,同时也加速了SAD的计算。
最后再次感谢论文作者以及老师提供了本次开阔眼界的机会,也感谢许多博主的无私分享。