## Retinex 图像增强算法及 App 端移植 文/周景锦 >Retinex 是一种图像增强算法,常用于去雾和夜景增强等场景。本文介绍其实现和应用。 ### 背景介绍 Retinex 是一种常用的图像增强算法,由 Land 和 McCann 在1971年提出。Retinex 是一个合成词,分别是“retina”(视网膜)和“cortex”(皮层),该算法的主要思想是:人类视觉系统并不是获取到了光线的绝对强度,而是获取到了光照的“相对强度”。 ![enter image description here](http://images.gitbook.cn/b5539890-0d8b-11e8-b4b7-854caae7e14f) 图1 人类感知物体原理 如图1所示,进入观察者眼中或摄像头镜头中的光,不是入射光,而是入射光经过物体反射后的光。Retinex 旨在对人类视觉系统如何感知物体进行建模,从而还原出物体的“真实”颜色。 Retinex 本质上属于一种图像增强算法,常用于去雾/夜景增强等场景,如图2、3显示了几张效果图,左边为原图,右边为 Retinex 算法处理之后的结果图。 ![enter image description here](http://images.gitbook.cn/ed3663a0-0d8b-11e8-8af0-0dd3b4a6fc04) 图2 去雾 ![enter image description here](http://images.gitbook.cn/27e885e0-0d8d-11e8-b4b7-854caae7e14f) 图3 夜景增强 ### 原理简介 Retinex 自提出至今近十年来,发展出了很多种改良和变种算法,但是万变不离其宗,其基本原理如下式所示: S(x,y) = R(x,y) * L(x,y) 对照第一张图,上式中,S(x,y)为人眼/相机最终“看到”的颜色/光亮,R(x,y)为物体本身的颜色/光亮属性,L(x,y)为入射光。其中,S(x,y)是可以直接得到的,R(x,y)是我们想到得到的(物体的真实颜色),L(x,y)是未知的,需要通过某种估计方式得到。则: R(x,y) = S(x,y)/L(x,y) R(x,y) = S(x,y)/F(S(x,y)) 其中 F 即为某映射函数,我们需要通过已知的 S(x,y)结合合理的F来估计出 L(x,y),进而求得 R(x,y)。两边取 log 得到: ``` log(R(x,y)) = log(S(x,y)) - log(F(S(x,y))) R(x,y) = exp(log(S(x,y)) - log(F(S(x,y)))) ``` 其中估计光照 L(x,y)(即定义F函数的方式)有多种,常用的有2种: - 最原始的路径估计法 - 提出的高斯核方法 这边主要介绍高斯核方法,即定义 F 为: F(S(x,y)) = G_sigma * S(x,y) 上式中,G_sigma 为半径 sigma 的高斯核,星号*表示卷积操作。它的物理含义是:用当前像素点的亮度值和周围像素点的加权平均值来估计当前像素点的亮度值。 此处 sigma 的值可以取单一值,对应 Singlescale Retinex 算法;也可以取多个值,对应 Multiscale Retinex 算法。顾名思义,Multiscale Retinex 即多尺度 Retinex,它通过不同尺度(不同大小的领域加权平均)来更精准的估计光照: ``` L(x,y) = F(S(x,y)) = 1/N * Sum(G_sigma_i * S(x,y)) ``` ### 算法实现及改进 上一节中提到了 Retinex 算法的基本原理和公式,据此我们及可写出对应的算法流程。值得注意的是,上面提到的算法是单通道的黑白图,而我们的应用场景是三通道的彩色图;此外,需要对结果值做相应的归一化(例如将 RGB 各通道映射到0-255)。 论文中给出了一种 Multiscale Retinex 算法 MSRCP,在我们的数据集上效果较好,其伪代码如图4所示。 ![enter image description here](http://images.gitbook.cn/5f9edb00-0d93-11e8-8af0-0dd3b4a6fc04) 图4 MSRCP 该算法以 RGB 的均值为强度值(Intensity),并以此为基准,依次调节计算出真实的 RGB 各通道值(R_i(x,y))。 在我们的数据集上,发现将 RGB 颜色空间转到 HSV 中间,并以V为基准,只调节 V 值有更好的效果(原因是在我们的应用场景,我们希望它的色度信息,而只调整亮度信息);此外,将结果图与原图做一定的比例融合效果更自然。图5显示了原图-原始算法-改进算法的效果图,可以看到改进算法对原图的增强效果更自然。 ![enter image description here](http://images.gitbook.cn/73ec74f0-0d93-11e8-bfd7-a56b79478884) 图5 原图-原始算法-改进算法的效果图对比 ### App 端移植 我们的最终目标是将该算法移植到手机端,并能够对摄像头/视频进行实时处理,故需要对上述算法的性能做优化,以达到实时。 关于图像的逐像素变换操作(如 RGB 转 HSV)可以利用 OPENGL 编写 Shader 实现,不会带来明显的时间损耗。参考上一节提到的算法流程,主要比较耗时的操作有两步: - 三个不同尺度的高斯核(论文中提出核的大小的经验值为15,80,200) - SimplestColorBalance 中需要计算图像的上下分位数信息 #### 图像高斯处理 由于高斯处理需要在图像的每个点处采样周围的点,高斯核的 sigma 如果取到200势必会非常慢,达不到实时性要求。在 App 端移植时,为了达到实时性,我们将多个高斯核改为一个高斯核(相当于 Multiscale Retinex 退化为 Singlescale Retinex)。同时,我们调小了高斯核半径。这两部操作理论上会影响算法的效果,但是在我们的使用场景下,效果并没有变差多少,甚至反而对于光照环境比较正常的图不会过度增强。 #### 分位数计算 求 N 个数中第 k 大的数的算法平均时间复杂度为O(N)(算法实现方式类似快排的Partion)。Retinex 算法中需要求上下分位数(例如第99.9%大的数和第0.1%大的数),需要用到同样的算法。由于在视频实习应用中,需要对每一帧做相应的算法处理,并且大部分操作是在 shader 上进行的,而分位数计算需要在 GPUS 上实现,所以每一帧都涉及将数据从 GPU 拷贝到 CPU 的操作,假设视频的分辨率为 M*N,则分位数计算的时间复杂度为O(MN),无法保证实时性。为此,在计算分位数时,我们将每一帧图像进行下采样,长宽 resize 到原来的1/5。合理的下采样可以加速数据拷贝以及分位数计算这两个过程,同时保证计算的准确性与原来大体一致。 我们在 iPhone6 上进行测试,如果利用原图进行分位数计算,帧率为15fps;利用1/5下采样之后的图进行分位数计算,帧率为30fps,达到了实时性要求。 ### App 端效果图 如图6、图7显示了几张 App 端算法优化后跑出来的效果图。可以看到,对于光照环境比较正常的图,经过 Retinex 算法之后没有较大变化,这正是我们需要的,即不希望对正常的进行进一步增强;而对于光照环境比较暗的夜场景,经过我们的算法之后图像被增强了。 ![enter image description here](http://images.gitbook.cn/73ba3a00-0d96-11e8-bfd7-a56b79478884) 图6 效果图1 ![enter image description here](http://images.gitbook.cn/a51994a0-0d97-11e8-8af0-0dd3b4a6fc04) 图7 效果图2 ### 总结 最后分享一下经验教训,在碰到一个实际问题时,如果找不到直接可用的库,可以先找搜寻一下相应的论文。梳理论文中提到的算法,建议先在 PC 端快速复现算法,证明该算法在相应的实际应用场景确实可以 work。在复现的同时,可以进一步理解算法,思考每一步、每个参数的意义和作用,尝试面向应用场景调整算法。多读几篇论文或技术文章,将几种算法结合,取长补短也是不错的思路。最后在移动端移植时主要要考虑性能问题,毕竟移动端的计算资源有限,有时可能要牺牲效果换取性能。另外要充分利用 GPU,一些适合 GPU 并行计算的部分可以考虑迁移到 GPU 上。