入射光线 $L(x,y)$ 经过物体 $R(x,y)$ 反射后,被相机捕获,形成图像 $S(x,y)$ 。即 $$ S(x,y) = L(x,y) \cdot R(x,y) $$
单尺度 Retinex 即 Single Scale Retinex,它是所有基于 Retinex 方法的最简版本,原理如下:
上式两边取对数变换: $$ \begin{array}{l} \log S = \log \left( {L\cdot R} \right) \\ \quad \quad ; = \log L + \log R \\ \end{array} $$ 于是得到物体真实图像 $R$ $$ R = \exp{(\log S - \log L)} $$
但是入射光线 $L$ 真实情况下是无法获取的,于是通过高斯函数进行卷积,获取高斯模糊图像,来近似地估算光照分量: $$ L\left( {x,y} \right) = S\left( {x,y} \right) * G\left( {x,y} \right) $$ 于是,对于照度反射模型,可整理出一个通用的表达式: $$ R =\exp{(\log S - \log S * G)} $$ 即给定一个输入图像 $S$ 和一个高斯函数 $G$ 就可以根据上述公式计算出物体真实图像 $R$ ,从而一定程度上对将拍摄得到的图像中光照带来的影响减弱。这里的高斯函数表达式如下:
$$
G(x,y)=\frac{1}{{2\pi{\sigma ^2}}}{e^{ - \frac{{{x^2} + {y^2}}}{{2{\sigma ^2}}}}}
$$
且要满足归一化条件 $$ \iint {G\left( {x,y} \right)}{\text{d}}x{\text{d}}y = 1 $$
在实际操作中,卷积的实现有多种方法,其中比较快速的做法是转换到频域做乘积,因为
时域中(图像中通常成为空域)的卷积等于频域中的乘积
$$ \begin{array}{l} L = S * G \\ ;;; = IFT\left( {FT\left( S \right)FT\left( G \right)} \right) \\ \end{array} $$
而且通常得到的最后的 $\log{R}$ 在对数域中,尽管做指数变换可以还原得到 $R$,但图像已经减去了反应光照情况的分量,所以得到的图像视觉效果并不好,为此需要做增益补偿,通常可直接对 $log{R}$ 做一个线性拉伸处理。 $$ {R_{new}} = 255\frac{{R - {R_{\min }}}}{{{R_{\max }} - {R_{\min }}}} $$
function [R,R_temp,L] = ssr4gray(S,sigma)
% S = 0 ~ 255 , 单通道图像
% R = 0 ~ 1 , 单通道图像
% R_temp = any , 用于进一步计算,避免做归一化影响结果
% S = R×L
% log(R) = log(S) - log(L)
% = log(S) - log(S*L)
G = fspecial('gaussian',size(S),sigma); % 高斯函数
% 时域卷积等于频域乘积,估计光照图像 L
%
L = real(ifft2(fft2(S).*fftshift(fft2(G,size(S,1),size(S,2)))));
L = L - min(L(:));
%}
% L = imfilter(S,G,'replicate','conv'); % 使用滤波卷积效果不好
R_temp = log(S+1) - log(L+1); % 去除光照影响
R = mat2gray(exp(R_temp)); % 线性拉伸
end
上述过程只能处理单通道的图像,对于三通道 RGB 图像,每一个通道做一遍即可获得 SSR 增强图像。
可以看出单尺度 Retinex 最大的缺陷在于图像的增强效果非常依赖于高斯函数的的 $\sigma $ ,即所谓的 Retienx尺度,$\sigma $ 取值越小,高斯模糊效果越弱,光照估计的越不准,图像细节保留的越多,于是处理后的图像细节较好,但色彩严重失真;取值越大,则图像细节丢失,光照估计的越准确,处理后的图像颜色越自然,但细节丢失较多,且容易出现光晕现象。
所以为了弥补单一尺度下的这种缺陷,采取多个尺度的 SSR 进行处理,然后再对处理后的多个图像做加权求和,一定程度上实现细节和颜色方面的效果兼容。
$$ R = \sum\limits_{i = 1}^N {{\omega _i}\left( {\log S - \log S * {G_i}} \right)} $$
这里的 $R$ 同样也是仅针对的是一个通道的处理,多通道的重复使用即可。
function [R,R_temp] = ssr(S,sigma)
% S = 0 ~ 255 , 三通道彩色图像
% Si = Ri×Li
% log(Ri) = log(Si) - log(Li)
% = log(Si) - log(S*Li)
% 结果初始化
R = zeros(size(S));
R_temp = zeros(size(S));
for i = 1:size(S,3)
Si = S(:,:,i); % 提取单通道图像
[~,Ri,~]= ssr4gray(Si,sigma); % 单通道 ssr
R_temp(:,:,i) = Ri; % 单通道结果存储
R(:,:,i) = mat2gray(Ri); % 单通道结果存储
end
end
其中,使用到了前面已经写好的单通道单尺度 Retinex 函数 ssr4gray
当然,我们也可以将图像转换到 HSV 颜色空间,使用多尺度 Retinex 进行增强
function R = msr4hsv(S,sigma)
% S = 0 ~ 255 , 三通道彩色图像
% R = 0 ~ 1 , 三通道彩色图像
% RGB --> HSV
[h,s,v] = rgb2hsv(S);
V = zeros(size(v)); % 增强后亮度分量初始化
for i = 1:length(sigma)
[~, Vi]= ssr(v,sigma(i)); % 多尺度 ssr
V = V + Vi/length(sigma); % 加权求和
end
V = mat2gray(V); % 归一化
% HSV --> RGGB
R = hsv2rgb(cat(3,h,s,V));
end
根据前面介绍,Retienx 算法一直致力于单通道图像的处理,对于其他通道的处理都是同样的操作,这使得通道间的分量比例明显不协调,从而失去了真彩色,图像颜色不能令人满意,于是再在多尺度 Retinex 的基础上加入了各通道之间的色彩比例调节系数,以此来恢复色彩比例。
对于第 $i$ 个通道图像 $S_i(x,y)$ 其通道色彩恢复调节系数 $C_i(x,y)$ 被表示为 $$ {C_i}\left( {x,y} \right) = \beta \log \left( {\alpha \frac{{{S_i}\left( {x,y} \right)}} {{\sum\limits_{i = 1}^3 {{S_i}\left( {x,y} \right)} }}} \right) $$ 于是,将其与之前的多尺度 Retinex 增强图像进行相乘,调整像素取值,实现通道间色彩比例调节。
$$ {R_j} = \sum\limits_{i = 1}^N {{C_j}{\omega _i}\left( {\log S - \log S*{G_i}} \right)} $$ 这里的 $R_j$ 表示第 $j$ 个色彩通道。
function [R,R_temp] = msrcr(S,sigma,beta,alpha,G,b)
% S = 0 ~ 255 , 三通道彩色图像
% R = 0 ~ 1 , 三通道彩色图像
[~,R_msr] = msr(S,sigma); % 多尺度 msr
% 结果初始化
R = zeros(size(S));
R_temp = zeros(size(S));
for i = 1:size(S,3)
Si = S(:,:,i); % 提取单通道图像
Sa = sum(S,3); % 按通道求和
Ci = beta*(log(alpha*Si+1) - log(Sa+1)); % 色彩恢复系数
R_temp(:,:,i) = G*(R_msr(:,:,i).*Ci+b); % 单通道恢复结果存储
R(:,:,i) = mat2gray(R_temp(:,:,i)); % 单通道结果存储
end
end
其中,使用到了前面已经写好的单通道多尺度 Retinex 函数 msr
我们知道,传统的伽马矫正,就是在原图像的基础上,$\gamma$ 次幂运算,$\gamma$ 若大于 1 则图像整体会被变暗,等于 1 则保持不变,小于 1 则图像整体变亮。
$$ {R_{new}}\left( {x,y} \right) = R{\left( {x,y} \right)^\gamma } $$
这就导致,图像是整体变化的,缺乏局部像素的考虑,因此可以针对每一个像素设置一个合适的 $\gamma$ 值,自适应地调整图像亮度。在该文章中,提出了一种新的二维伽马函数(文中公式写反了)。
$$
{\left{ {\begin{array}{*{20}{c}} {{S_{new}}\left( {x,y} \right) = 255\left( {\frac{{S\left( {x,y} \right)}} {{255}}} \right)} \hfill \ {\gamma = {{\left( {\frac{1} {2}} \right)}^{\frac{{m - L\left( {x,y} \right)}} {m}}}} \hfill \\end{array} } \right.^\gamma }
$$
即,根据根据多尺度 Retinex 方法,计算出光照分量 $L$
$$ L = \sum\limits_{i = 1}^N {{\omega _i}S * {G_i}} $$
然后依据光照分量 $L$ 计算每一个像素值的 $\gamma$ ,再将其应用在要处理的图像 $S$ 上,进行逐像素伽马矫正。
这里,论文选择将图像转换到 HSV 颜色空间,然后针对亮度分量 $V$ 使用上述方法增强后,还原图像,得到RGB增强图像。
function img_out = gamma2d(img_in)
% rgb --> hsv
img_in = double(img_in);
[h,s,v] = rgb2hsv(img_in);
% 多尺度光照估计
[~,~,l1] = ssr4gray(v,15);
[~,~,l2] = ssr4gray(v,80);
[~,~,l3] = ssr4gray(v,250);
l = (l1+l2+l3)/3;
% 自适应二维伽马矫正
% m = mean2(l);
m=128;
r = (1/2).^((m-l)./m);
v1 = (v/255).^r;
% v1 = mat2gray(v1);
% hsv --> rgb
img_out = hsv2rgb(cat(3,h,s,v1));
end
论文首先绘制了亮度图像,然后给出了二维伽马函数的曲线对比图,以及三个不同场景下的图像经直方图均衡化(HE),带颜色恢复的多尺度 Retinex ,以及二维伽马函数矫正的方法。下面是论文插图的整体实现。
clear;close all;clc
addpath('./model');
addpath('./src');
%% 图2
figure(2)
img_gray = imread('Fig0310(a)(Moon Phobos).tif');
subplot(1,2,1);imshow(img_gray);xlabel('(a) 灰度图像')
img_gray = double(img_gray);
[~,~,l1] = ssr4gray(img_gray,15);
[~,~,l2] = ssr4gray(img_gray,80);
[~,~,l3] = ssr4gray(img_gray,250);
img_gray_l = (l1+l2+l3)/3;
img_gray_l = wcodemat(img_gray_l,255);
subplot(1,2,2);imshow(uint8(img_gray_l));xlabel('(b) 光照分量')
%% 图2
figure(3)
img_rgb = imread('彩色图像.png');
subplot(1,2,1);imshow(img_rgb);xlabel('(a) 彩色图像')
[h,s,v] = rgb2hsv(img_rgb); % 提取亮度分量
[~,~,l1] = ssr4gray(v,15);
[~,~,l2] = ssr4gray(v,80);
[~,~,l3] = ssr4gray(v,250);
img_rgb_l = (l1+l2+l3)/3;
img_rgb_l = wcodemat(img_rgb_l,255);
subplot(1,2,2);imshow(uint8(img_rgb_l));xlabel('(b) 光照分量')
%% 图4
figure(4)
F = 0:10:255;
I = [0, 64, 128, 192, 255];
m = 128;
func = @(F,I) 255*(F/255).^((1/2).^((m-I)/m));
lineset = {
'r--s','$L(x,y)$=0';
'g-o','$L(x,y)$=64';
'b-*','$L(x,y)$=128';
'c-x','$L(x,y)$=192';
'k-p','$L(x,y)$=255';
};
for n = 1:length(I)
plot(F,func(F,I(n)),lineset{n,1},'linewidth',1,'MarkerSize',5,'MarkerEdgeColor','k','MarkerFaceColor',lineset{n,1}(1));
hold on
end
legend(lineset(:,2),'location','NorthWest','box','off','interpreter','latex')
xlabel('输入图像亮度');ylabel('校正后输出图像亮度');
% axis square
axis tight
%% 模型对比
models = {
'(a)原图像',@(x) x;
'(b)HE算法处理结果', @(x) he(x);
'(c)MSRCR算法处理结果',@(x) msrcr(double(x),[15,80,250],46,125,192,-30);
'(d)本文算法处理结果',@(x) gamma2d(x);
};
%% 图7
figure(7)
img = imread('场景1.png');
for i = 1:length(models)
model = models{i,2};
img_en = model(img);
subplot(3,size(models,1),i);
imshow(img_en);
xlabel(models{i,1});
end
%% 图8
% figure(8)
img = imread('场景2.png');
for i = 1:length(models)
model = models{i,2};
img_en = model(img);
subplot(3,size(models,1),i+4);
imshow(img_en);
xlabel(models{i,1});
end
%% 图9
% figure(9)
img = imread('场景3.png');
for i = 1:length(models)
model = models{i,2};
img_en = model(img);
subplot(3,size(models,1),i+8);
imshow(img_en);
xlabel(models{i,1});
end
这里,文中并没有取均值作为 $m$ ,而是直接使用了 128 作为固定的 $m$ 。并且在求光照分量时,也达不到图像那样的高斯模糊效果(只有当高斯模糊的卷积用 imfilter 实现时,才会和论文中那样,但复现的结果表明作者使用了频域乘积去计算时域卷积,所以到底怎么回事?)
这里的所有图像,均来自论文截图,并未找到对应的标准测试图像,可能是作者自己拍的吧。
在做图像处理时,要十分注意数据类型的转换,图像一般取值 0 - 255 或者 0 - 1 ,但很多情况下,论文或者代码对输入的图像数据取值范围是有条件的,必须弄清楚,否则效果或出现很大的异常,这也是为什么我要在函数输出时,写一个 R_temp,接住这个中间计算结果。因为在其他函数调用该函数时,我们希望它不是经过转型或者归一化处理的,这样才能保证数据计算一致性。
从原理上来讲,Retinex 系列算法之间是存在递进关系的,因此在编写函数时,尤其要关注算法的输入输出,既保证函数编写完成后,能够实现自身功能的同时,也能为其他调用它的函数提供模块化的处理,这样一层套一层,代码变得很结构化、看起来会比较有层次。这些都需要前期对函数功能进行仔细地分析。
本文在复现效果对比时,使用了句柄和元胞来存放一些同类型的函数或变量,这样既方便了修改,也减少了代码的冗余度,个人觉得这个做法值得推广。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。