引言

始于Edwin Herbert Land(埃德温·赫伯特·兰德)于1971年提出的一种被称为色彩恒常的理论,并基于此理论的图像增强方法。Retinex这个词由视网膜(Retina)和大脑皮层(Cortex)合成而来.之所以这样设计,表明Land他也不清楚视觉系统的特性究竟取决于此两个生理结构中的哪一个,抑或两者都有关系。不同于传统的图像增强算法,如线性、非线性变换、图像锐化等只能增强图像的某一类特征,如压缩图像的动态范围,或增强图像的边缘等,Retinex可以在动态范围压缩、边缘增强和颜色恒常三方面达到平衡,可以对各种不同类型的图像进行自适应性地增强,在很多方面得到了广泛的应用。

算法概述

Retinex 理论的基本内容是物体的颜色是由物体对长波(红)、中波(绿)和短波(蓝)光线的反射能力决定的,而不是由反射光强度的绝对值决定的;物体的色彩不受光照非均性的影响,具有一致性,即Retinex理论是以色感一致性(颜色恒常性)为基础的。如下图所示,观察者所看到的物体的图像S是由物体表面对入射光L反射得到的,反射率R由物体本身决定,不受入射光L变化。 

对于观察图像S,有公式表示为:

S(x,y)=R(x,y)L(x,y)(1)

其中, L(x,y) 表示亮度分量, R(x,y) 表示物体反射分量, S(x,y)表示观测到的图像 。 
Retinex理论增强算法的思想就是利用式(1),去除亮度分量 L 求得反射分量 R ,从而达到图像增强效果。 
对(1)式两边取对数

log(S(x,y))=log(L(x,y))+log(R(x,y))(2)

由(2)得

log(R(x,y))=log(S(x,y))−log(L(x,y))(3)

由式(3)可知,只需要估计亮度分量 L 就能求得反射分量,因此L的估计直接决定图像去雾恢复效果.Jobson等论证了高斯卷积函数可以从已知图像 S 中更好地估计出亮度分量,即

L(x,y)=S(x,y)∗G(x,y)(4)

其中,’*’代表卷积操作,高斯函数 G(x,y)=k⋅exp(−x2+y2σ2) , σ 是高斯函数尺度参数, k 为归一化因子,使 ∬G(x,y)dxdy=1 。对于RGB彩色图像的某一个通道,

log(Rc(x,y))=log(Sc(x,y))−log(Sc(x,y))∗G(x,y)(5)

其中, c 表示RGB3个通道的某一个通道; σ  为尺度函数,取值大时,颜色失真小但细节恢复差,取值小时,细节恢复好,但颜色失真大。 
为弥补SSR算法不足,MSR算法对每一个通道进行3次不同尺度滤波,加权求和,处理时间也会变长,且使用不同的尺度,导致恢复的RGB比值与原图比值不太一样,颜色失真。

log(R(x,y))=Weight1⋅log(Rσ1(x,y))+Weight2⋅log(Rσ2(x,y))+Weight3⋅log(Rσ3(x,y))(6)

Rehman等提出MSRCR算法,引入分量比值调整因子从而降低色彩失真的影响。

RMSRCR(x,y)=C(x,y)RMSR(x,y)(7)

其中 C(x,y)=G{log[αIc(x,y)]−log[∑c=13Ic(x,y)]} 。

流程

  1. 按照(4)式,计算入射分量L;
  2. 按照(3)式,计算单一尺度下的log(Rσ(x,y));
  3. 按照(6)式,对不同尺度进行加权求和;
  4. 按照(7)式,进行色彩恢复;
  5. 将RMSRCR量化为0到255范围,输出。

入射分量获取

Retinex算法核心在于入射分量的获取,简单来说,这个可以对原图像进行高斯卷积获取,卷积核的大小与原图像大小相等。当图像比较大的时候,其计算过程是非常复杂的,O(M2N2),M,N是原图像的高度和宽度。实现的时候可以使用递归高斯滤波,复杂度O(MN)(参见paper:Recursive implementation of the Gaussian filter,源码在GIMP中有)。下面我给出在3种不同尺度下获取的亮度图,等分权重。 

色彩恢复

GIMP中色彩恢复代码如下:

<code class="hljs cpp has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*  Final calculation with original value and cumulated filter values.  The parameters gain, alpha and offset are constants.  */</span>   <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* Ci(x,y)=log[a Ii(x,y)]-log[ Ei=1-s Ii(x,y)] */</span>   alpha  = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">128.0f</span>;   gain   = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1.0f</span>;   offset = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.0f</span>;   <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> ( i = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>; i < size; i += bytes )   {   <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> logl;   psrc = src+i;   pdst = dst+i;   logl = (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>)<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">log</span>( (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>)psrc[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>] + (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>)psrc[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>] + (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>)psrc[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>] + <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3.0f</span> );   pdst[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>] = gain * ((<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>)(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">log</span>(alpha * (psrc[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>]+<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1.0f</span>)) - logl) * pdst[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>]) + offset;   pdst[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>] = gain * ((<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>)(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">log</span>(alpha * (psrc[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>]+<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1.0f</span>)) - logl) * pdst[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>]) + offset;   pdst[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>] = gain * ((<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>)(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">log</span>(alpha * (psrc[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>]+<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1.0f</span>)) - logl) * pdst[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>]) + offset;   }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li></ul>

另外,最后一步代码又增加了调节项,用于控制饱和度和颜色,最后将其量化到[0~255],详细代码如下。

<code class="hljs applescript has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">    /*  Adapt <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> dynamics <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">of</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> colors according <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> statistics <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">of</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">first</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">and</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">second</span> order.  The use <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">of</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> variance makes <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">it</span> possible <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> control <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> degree <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">of</span> saturation <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">of</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> colors.  */   pdst = dst;compute_mean_var( pdst, &mean, &var, size, bytes );   mini = mean - rvals.cvar*var;   maxi = mean + rvals.cvar*var;   range = maxi - mini;   <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ( !range ) range = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1.0</span>;   <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> ( i = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>; i < size; i+= bytes )   {   psrc = src + i;   pdst = dst + i;   <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (j = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> ; j < <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span> ; j++)   {   float c = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">255</span> * ( pdst[j] - mini ) / range;   psrc[j] = (unsigned char)clip( c, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">255</span> );   }   }   free (dst);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li></ul>

效果

我依据GIMP中插件contrast-retinex.c用Matlab实现了一遍,效果如下

宽动态图像


水下图像


低照度图像

雾天图像


评论

Retinex算法在图像增强诸多领域应用广泛,如上述宽动态图像增强,水下图像增强,雾天图像增强,低照度图像增强等等,更多可以参考NASA关于Retinex的介绍。Retinex的实现,C语言用了递归高斯滤波器,我不知道Matlab是否有对应的函数,我在这里是将图像转换到频率域高斯低通滤波处理的,因此算法时间耗费也并不大。OpenCV版本的Retinex算法已传到CSDN上共享,下载请点击这里,记得给好评哦。

更多阅读

http://dragon.larc.nasa.gov/ 
http://www.cnblogs.com/Imageshop/archive/2013/04/17/3026881.html 
A multiscale retinex for bridging the gap between color images and the human observation of scenes

MSRCR(Multi-Scale Retinex with Color Restore)多尺度Retinex图像增强相关推荐

  1. 《MA‑CRNN: a multi‑scale attention CRNN for Chinese text line recognition in natural scenes》论文阅读

    参考博文: CRNN的一个变种,可以读一读,看看相对于CRNN来说有什么变化?以及为什么? 文章目录 make decision step1:读摘要 step2:读Introduction step3 ...

  2. 单尺度Retinex(SSR) + 代码实现

    我看了n多关于基于单尺度Retinex图像增强的文献,用的高斯滤波函数都不一样,有的直接用高斯滤波器,就是自带的gaussian函数实现,有的是用自定义高斯函数,然而公式系数又都不一样,我快被搞疯了, ...

  3. msrcr(Multi-Scale Retinex with Color Restoration) 带色彩恢复的多尺度视网膜增强算法 整理

    本文主要总结整理msrcr算法相关,包括基本原理,效果和实现代码 为了提高低亮度条件和背光条件下拍摄图像的效果,尝试了各种常规的数字图像处理方法,包括: 线性变换 对数变换 直方图均衡等 对yuv分量 ...

  4. Retinex图像增强算法——SSR,MSR,MSRCR,MSRCP,autoMSRCR

    系列文章目录 关于OpenCV的一些图像处理函数 图象基本操作(读取.显示.通道提取.边界填充.融合.保存) Retinex图像增强算法--SSR,MSR,MSRCR,MSRCP,autoMSRCR ...

  5. Retinex算法解读

    Retinex是一种常用的建立在科学实验和科学分析基础上的图像增强方法,它是Edwin.H.Land于1963年提出的.就跟Matlab是由Matrix和Laboratory合成的一样,Retinex ...

  6. 单幅图像去雾算法研究综述

    来源 <计算机工程与应用>北大核心期刊,CSCD数据库. 影响因子:2.348 简介 图像去雾算法是以满足特定场景需求,突出图片细节并增强图片质量为目的的图像分析与处理方法.在雾霾天气下, ...

  7. 数字图像处理之第三章

    第三章 图像增强 1.图像增强的方法有很多种,按增强的目的和效果来划分,图像增强的方法大致可以分为灰度级修正.图像平滑.图像锐化.图像增晰和色彩增晰等. 2.灰度级修正(主要用来提高图像对比度,提高人 ...

  8. 《声呐图像处理》---霍冠英

    1 最近邻插值 可视化算法 1 最近邻插值 2 改进的最近邻插值算法 3 R-theta算法 4 双立方差值 传统空间域滤波 1传统均值滤波   1) 加权均值滤波   2 )K邻点平均法   3 ) ...

  9. 基于MATLAB的图像去雾GUI系统(集成4种去雾算法)

    00 目录 应用背景 去雾算法理论 MATLAB程序实现 源码获取 01 应用背景 由于大气中存在灰尘.烟雾.薄雾和其他漂浮颗粒物,因此大气中拍摄的图像通常会出现颜色失真.模糊.对比度低等问题,而模糊 ...

最新文章

  1. Mysql高级调优篇——第五章:Sql调优在面试中深度剖析
  2. iOS开发debug集锦
  3. Codeforces 1246D/1225F Tree Factory (构造)
  4. 打破双亲委派么,怎么打破_打破了vs你错了
  5. Ios开发之定位CLLocationManager
  6. Vue.js经典开源项目汇总
  7. 【第三方对接】使用 永中Office 实现在线 Office 整合到 Spring 项目
  8. 华为hcie认证是什么?华为hcie认证前景怎么样?
  9. Redis6.0.6_06_Redis 集群
  10. 关于vs编译的程序无法正常启动(0xc0150002)的问题
  11. 某大型连锁超市库存管理系统分析报告
  12. 【转】Photoshop中快速复制的技巧有哪些
  13. 超简单集成华为HMS Scankit 扫码SDK实现扫一扫二维码
  14. ResNet 论文阅读笔记
  15. 2022-2028全球与中国纳米保湿美容仪市场现状及未来发展趋势
  16. 使用人性化的Linux防火墙CFW阻止DDOS攻击
  17. 每天一个linux命令(4):mkdir命令
  18. Web网页设计之jQuery_1. 认识jQuery
  19. Python彩色图片转手绘风格
  20. 总结关于webpack

热门文章

  1. python3根据地址批量获取百度地图经纬度
  2. 酷狗音乐的大数据实践
  3. 20年IT老兵分享:账务系统设计及应用--转
  4. 深入redis内部--实现双向链表
  5. 阿里2020 净利润约: 2000亿元 腾讯2020 净利润约: 1000亿元 百度2020 净利润约: 300亿元 美团2020 净利润约: 150亿元 --互联网一响,黄金万两
  6. JS中map()与forEach()的用法
  7. Vue.js(一) Vue.js + element-ui 扫盲
  8. 一台加密货币ATM机月营收额高达3万美金
  9. 使用Facebook方法处理复杂问题
  10. 大数据时代医疗行业爆发 政策壁垒仍是最大障碍