【影象處理】一種低光照影象的亮度提升方法(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,有興趣的可以點選
全域性自適應原理
全域性自適應方法的原理很簡單,就是兩個公式;
上述式子中, 代表全域性自適應處理的輸出結果; 表示輸入影象的亮度值; 表示輸入影象亮度的最大值; 表示輸入影象的亮度值的對數平均值;由以下公式求得;
其中, 代表影象的尺寸。 是一個很小的值,為了防止遇到影象中亮度為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空間。這樣做有一個問題,只提升了影象的亮度,而沒有提高色度和濃度,影象會顯得很亮,但是色彩不夠鮮豔。而從作者提供的程式碼的思路來看,他用新得到的影象亮度除以原始影象亮度,得到一個亮度增益,然後將這個增益附加在每個顏色通道上。這樣做三通道可以得到同樣程度的亮度增強,但是這種方法可能會讓影象色彩過飽和。
來看看結果;
總結
到底誰的思路好,我覺得我還是尊重作者的思路。但是也許有些情況下,我的方法會取得更好的效果,也可能存在更好的思路。但是最主要的方法還是在上面的兩個公式中。而作者在文中主要介紹的是區域性的對映方法,全域性自適應是為後面做區域性自適應進行鋪墊的,當然,這篇部落格中,不會講到區域性的方法了。
對於低照度影象的計處理,我並不在行,這也是第一次接觸,有些原理上面的東西還沒摸清楚,只是覺得有意思就去實現了一下,有些不足的地方,還請大家指標(指正)。