【图像处理】一种低光照图像的亮度提升方法(Adaptive Local Tone Mapping Based on Retinex for High Dynamic Range Images)

前言
  在实际的拍照过程中,常常会遇到,光线不足的情况。这时候单反用户一般会调大感光度,调大光圈,以让照片整体更清晰,更亮。那么如果照片已经被拍的很暗了,怎么办呢?这时候我们可以利用算法来提升图像整体的光照情况,让图像更清晰。

  2013年这篇《Adaptive Local Tone Mapping Based on Retinex for High Dynamic Range Images》发表在了IEEE上,如题目所说,文章提到将高动态图像在低动态范围显示设备上进行显式时,会面临信息丢失的问题。因此结合传统的CENTER/SURROUND RETINEX 技术提出了全局自适应和局部自适应的HDR实现过程,对HDR image 进行色调映射。而文中的全局自适应方法对于低照度图像具有很好的照度提升效果。作者将他的Matlab脚本上传到了Github,有兴趣的可以点击这里去查看。

全局自适应原理
  全局自适应方法的原理很简单,就是两个公式;
                           【图像处理】一种低光照图像的亮度提升方法(Adaptive Local Tone Mapping Based on Retinex for High Dynamic Range Images)

  上述式子中,Lg(x,y) L_{g}(x,y)L g(x,y) 代表全局自适应处理的输出结果;Lw(x,y) L_{w}(x,y)L w (x,y) 表示输入图像的亮度值;Lwmax L_{wmax}L wmax 表示输入图像亮度的最大值;Lw¯¯¯¯ \bar{L_{w}} Lwˉ表示输入图像的亮度值的对数平均值;由以下公式求得; 

                                【图像处理】一种低光照图像的亮度提升方法(Adaptive Local Tone Mapping Based on Retinex for High Dynamic Range Images)

  其中,m∗n m*nm∗n 代表图像的尺寸。σ \sigmaσ 是一个很小的值,为了防止遇到图像中亮度为0的黑点的情况;

  文中提到“随着对数均值趋近于高值,函数的形状从对数趋势转换为线性趋势,因此对低对数均值的图像具有更好的提升效果。”这个方法利用到了图像的对数均值,这就是自适应的体现。

  我在Matlab中分别简单的画了一下曲线,发现在对数均值比较大的时候曲线确实非常接近直线。

【图像处理】一种低光照图像的亮度提升方法(Adaptive Local Tone Mapping Based on Retinex for High Dynamic Range Images)
代码&结果
   我根据自己的理解以及作者的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空间。这样做有一个问题,只提升了图像的亮度,而没有提高色度和浓度,图像会显得很亮,但是色彩不够鲜艳。而从作者提供的代码的思路来看,他用新得到的图像亮度除以原始图像亮度,得到一个亮度增益,然后将这个增益附加在每个颜色通道上。这样做三通道可以得到同样程度的亮度增强,但是这种方法可能会让图像色彩过饱和。

  来看看结果;

【图像处理】一种低光照图像的亮度提升方法(Adaptive Local Tone Mapping Based on Retinex for High Dynamic Range Images)
总结
   到底谁的思路好,我觉得我还是尊重作者的思路。但是也许有些情况下,我的方法会取得更好的效果,也可能存在更好的思路。但是最主要的方法还是在上面的两个公式中。而作者在文中主要介绍的是局部的映射方法,全局自适应是为后面做局部自适应进行铺垫的,当然,这篇博客中,不会讲到局部的方法了。

   对于低照度图像的计处理,我并不在行,这也是第一次接触,有些原理上面的东西还没摸清楚,只是觉得有意思就去实现了一下,有些不足的地方,还请大家指针(指正)。

参考
Ahn H, Keum B, Kim D, et al. Adaptive local tone mapping based on retinex for high dynamic range images[C]//Consumer Electronics
(ICCE), 2013 IEEE International Conference on. IEEE, 2013:
153-156.

https://github.com/IsaacChanghau/OptimizedImageEnhance/tree/master/matlab/ALTMRetinex
转载:https://blog.csdn.net/u013921430/article/details/84111247