【fishing-pan:https://blog.csdn.net/u013921430 转载请注明出处】
前言
在实际的拍照过程中,常常会遇到,光线不足的情况。这时候单反用户一般会调大感光度,调大光圈,以让照片整体更清晰,更亮。那么如果照片已经被拍的很暗了,怎么办呢?这时候我们可以利用算法来提升图像整体的光照情况,让图像更清晰。
2013年这篇《Adaptive Local Tone Mapping Based on Retinex for High Dynamic Range Images》发表在了IEEE上,如题目所说,文章提到将高动态图像在低动态范围显示设备上进行显式时,会面临信息丢失的问题。因此结合传统的CENTER/SURROUND RETINEX 技术提出了全局自适应和局部自适应的HDR实现过程,对HDR image 进行色调映射。而文中的全局自适应方法对于低照度图像具有很好的照度提升效果。作者将他的Matlab脚本上传到了Github,有兴趣的可以点击这里去查看。
全局自适应原理
全局自适应方法的原理很简单,就是两个公式;
上述式子中,
代表全局自适应处理的输出结果;
表示输入图像的亮度值;
表示输入图像亮度的最大值;
其中,
代表图像的尺寸。
是一个很小的值,为了防止遇到图像中亮度为0的黑点的情况;
文中提到“随着对数均值趋近于高值,函数的形状从对数趋势转换为线性趋势,因此对低对数均值的图像具有更好的提升效果。”这个方法利用到了图像的对数均值,这就是自适应的体现。
我在Matlab中分别简单的画了一下曲线,发现在对数均值比较大的时候曲线确实非常接近直线。
代码&结果
我根据自己的理解以及作者的Matlab脚本,分别利用OpenCV实现了全局自适应方法。两者有很多细节不一样,结果也有一些差异;
首先是作者的matlab 代码;
function outval = ALTM_Retinex(I)
II = im2double(I);
Ir=double(II(:,:,1)); Ig=double(II(:,:,2)); Ib=double(II(:,:,3));
% Global Adaptation
Lw = 0.299 * Ir + 0.587 * Ig + 0.114 * Ib;% input world luminance values
Lwmax = max(max(Lw));% the maximum luminance value
[m, n] = size(Lw);
Lwaver = exp(sum(sum(log(0.001 + Lw))) / (m * n));% log-average luminance
Lg = log(Lw / Lwaver + 1) / log(Lwmax / Lwaver + 1);
gain = Lg ./ Lw;
gain(find(Lw == 0)) = 0;
outval = cat(3, gain .* Ir, gain .* Ig, gain .* Ib);
figure;
imshow(outval)
接着是我参考作者提供的脚本实现的C++ 代码
//-------------------------------
//函数名:adaptHDR;参照作者脚本实现
//函数功能:全局自适应光照度提升
//参数:Mat &scr,输入图像
//参数:Mat &dst,输出图像
//------------------------------
bool adaptHDR(Mat &scr, Mat &dst)
{
if (!scr.data) //判断图像是否被正确读取;
{
cerr << "输入图像有误"<<endl;
return false;
}
int row = scr.rows;
int col = scr.cols;
Mat ycc; //转换空间到YUV;
cvtColor(scr, ycc, COLOR_RGB2YUV);
vector<Mat> channels(3); //分离通道,取channels[0];
split(ycc, channels);
Mat Luminance(row, col, CV_32FC1);
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
Luminance.at<float>(i, j) =(float)channels[0].at<uchar>(i, j)/ 255;
}
}
double log_Ave = 0;
double sum = 0;
for (int i = 0; i < row; i++) //求对数均值
{
for (int j = 0; j < col; j++)
{
sum += log(0.001 + Luminance.at<float>(i, j));
}
}
log_Ave = exp(sum / (row*col));
double MaxValue, MinValue; //获取亮度最大值为MaxValue;
minMaxLoc(Luminance, &MinValue, &MaxValue);
Mat hdr_L (row,col,CV_32FC1);
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
hdr_L.at<float>(i, j) = log(1 + Luminance.at<float>(i, j) / log_Ave) / log(1 + MaxValue / log_Ave);
if (channels[0].at<uchar>(i, j) == 0) //对应作者代码中的gain = Lg ./ Lw;gain(find(Lw == 0)) = 0;
{
hdr_L.at<float>(i, j) = 0;
}
else
{
hdr_L.at<float>(i, j) /= Luminance.at<float>(i, j);
}
}
}
vector<Mat> rgb_channels; //分别对RGB三个通道进行提升
split(scr, rgb_channels);
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
int r = rgb_channels[0].at<uchar>(i, j) *hdr_L.at<float>(i, j); if ( r> 255){r = 255; }
rgb_channels[0].at<uchar>(i, j) = r;
int g = rgb_channels[1].at<uchar>(i, j) *hdr_L.at<float>(i, j); if (g> 255){ g = 255; }
rgb_channels[1].at<uchar>(i, j) = g;
int b = rgb_channels[2].at<uchar>(i, j) *hdr_L.at<float>(i, j); if (b> 255){ b = 255; }
rgb_channels[2].at<uchar>(i, j) = b;
}
}
merge(rgb_channels, dst);
return true;
}
最后是我自己根据自己的理解实现代码
//-------------------------------
//函数名:my_AdaptHDR;自己理解实现
//函数功能:全局自适应光照度提升
//参数:Mat &scr,输入图像
//参数:Mat &dst,输出图像
//------------------------------
bool my_AdaptHDR(Mat &scr, Mat &dst)
{
if (!scr.data) //判断图像是否被正确读取;
{
cerr << "输入图像有误" << endl;
return false;
}
int row = scr.rows;
int col = scr.cols;
Mat ycc; //转换空间到YUV;
cvtColor(scr, ycc, COLOR_RGB2YUV);
vector<Mat> channels(3); //分离通道,取channels[0];
split(ycc, channels);
double log_Ave = 0;
double sum = 0;
for (int i = 0; i < row; i++) //求对数均值
{
for (int j = 0; j < col; j++)
{
sum += log(0.001 + channels[0].at<uchar>(i, j));
}
}
log_Ave = exp(sum / (row*col));
double MaxValue, MinValue; //获取亮度最大值为MaxValue;
minMaxLoc(channels[0], &MinValue, &MaxValue);
Mat hdr_L(row, col, CV_32FC1);
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
hdr_L.at<float>(i, j) = log(1 + channels[0].at<uchar>(i, j) / log_Ave) / log(1 + MaxValue / log_Ave);
}
}
double L_MaxValue, L_MinValue; //获取亮度最大值为MaxValue;
minMaxLoc(hdr_L, &L_MinValue, &L_MaxValue);
for (int i = 0; i < row; i++) //对亮度通道进行提升;
{
for (int j = 0; j < col; j++)
{
channels[0].at<uchar>(i, j) = floor(0.5 + 255 * (hdr_L.at<float>(i, j) - L_MinValue) / (L_MaxValue - L_MinValue));
}
}
merge(channels, ycc);
cvtColor(ycc, dst, COLOR_YUV2RGB);
return true;
}
两种思路的区别主要在于,我的方法是先把图像重RGB空间转换到YUV空间,然后对亮度进行提升,然后转换回RGB空间。这样做有一个问题,只提升了图像的亮度,而没有提高色度和浓度,图像会显得很亮,但是色彩不够鲜艳。而从作者提供的代码的思路来看,他用新得到的图像亮度除以原始图像亮度,得到一个亮度增益,然后将这个增益附加在每个颜色通道上。这样做三通道可以得到同样程度的亮度增强,但是这种方法可能会让图像色彩过饱和。
来看看结果;
总结
到底谁的思路好,我觉得我还是尊重作者的思路。但是也许有些情况下,我的方法会取得更好的效果,也可能存在更好的思路。但是最主要的方法还是在上面的两个公式中。而作者在文中主要介绍的是局部的映射方法,全局自适应是为后面做局部自适应进行铺垫的,当然,这篇博客中,不会讲到局部的方法了。
对于低照度图像的计处理,我并不在行,这也是第一次接触,有些原理上面的东西还没摸清楚,只是觉得有意思就去实现了一下,有些不足的地方,还请大家指针(指正)。
参考
https://github.com/IsaacChanghau/OptimizedImageEnhance/tree/master/matlab/ALTMRetinex