ISP-镜头阴影校正(LSC)

概述

介绍

镜头阴影校正(Lens Shading Correction)是为了解决由于lens的光学特性,由于镜头对于光学折射不均匀导致的镜头周围出现阴影的情况。

shading可以细分为luma shading和color shading:

  • luma shading:
    由于Lens的光学特性,Sensor影像区的边缘区域接收的光强比中心小,所造成的中心和四角亮度不一致的现象。镜头本身就是一个凸透镜,由于凸透镜原理,中心的感光必然比周边多。如图所示:


    这里写图片描述
  • chrom/color shading:
    由于各种颜色的波长不同,经过了透镜的折射,折射的角度也不一样,因此会造成color shading的现象,这也是为什么太阳光经过三棱镜可以呈现彩虹的效果。如图所示:


    这里写图片描述

此外,还有CRA的原因会导致shading现象的出现,这里不再赘述,这里推荐《What's CRA》这篇文章,详细讲述了由于镜头的CRA带来的shading。

镜头CRA大于Sensor的CRA时容易出现Color shading;
镜头CRA小于Sensor的CRA时容易出现Len Shading;
建议优先保证Color Shading,因为Lens Shading比Color Shading容易调试;

影响

luma shading:会造成图像边角偏暗,就是所谓的暗角。


这里写图片描述

color shading:中心和四周颜色不一致,体现出来一般为中心或者四周偏色。如图所示:


这里写图片描述

校正

lens shading的校正是分别对于bayer的四个通道进行校正,每个通道的校正过程是相对独立的过程。

考虑到芯片设计的成本,因此一般情况下不会存储整幅图像的lut,目前主流的都是存储128*128个点的增益,利用双线性插值的方法计算每个pixel的增益。

算法

由于条件限制,图像仅用于算法验证,不做图像质量评判标准
这里写了一个shading的算法,将图像分为16x16的方块,求取每个交点的增益值,对平面进行四次方拟合,分别计算了luma shading 和 chrom shading,先计算出来一个lut用于存储,校正的世行通过对这个lut进行双线性插值得到每个pixel的值乘以原本像素点。

16x16的分块并非固定,可以对块的大小进行调整,比如中心块偏大,靠近边缘的方块变小,这些都是可以自定义的,本算法由于做演示使用,故不做其他功能。如图所示:


这里写图片描述

code

由于代码量较大,这里分别附上一部分算法

shading lut caculate:

function [image_r_gain, image_gr_gain, image_gb_gain, image_b_gain] = ...
isp_lsc_lut(image_r, image_gr, image_gb, image_b, side_num)
[height, width] = size(image_r);
side_y = floor(height/side_num);
side_x = floor(width/side_num);

% figure,imshow(image_r);
% hold on;
% for k=0:side_num
%     line_x = side_x * k;
%     line_y = side_y * k;
%     if(k==side_num && line_y ~= width) line_y = height;end
%     if(k==side_num && line_x ~= width) line_x = width;end
%     line([line_x,line_x],[0,height],'Color','red');
%     line([0,width], [line_y, line_y],'Color','red');
% %     line(Xd,Yd,'Color','red');
% end
% hold off

%% compress resolution
image_point = zeros(side_num,side_num);
for i = 0:side_num
    for j = 0:side_num
        x_clip = floor([j*side_x - side_x/2, j*side_x + side_x/2]);
        y_clip = floor([i*side_y - side_y/2, i*side_y + side_y/2]);
        if(i==side_num && y_clip(2) ~= height) y_clip(2) = height;end
        if(j==side_num && x_clip(2) ~= width) x_clip(2) = width;end
        x_clip(x_clip<1) = 1;x_clip(x_clip>width) = width;
        y_clip(y_clip<1) = 1;y_clip(y_clip>height) = height;
        data_r_in = image_r(y_clip(1):y_clip(2), x_clip(1):x_clip(2));
        image_r_point(i+1,j+1) = mean(mean(data_r_in));
        data_gr_in = image_gr(y_clip(1):y_clip(2), x_clip(1):x_clip(2));
        image_gr_point(i+1,j+1) = mean(mean(data_gr_in));
        data_gb_in = image_gb(y_clip(1):y_clip(2), x_clip(1):x_clip(2));
        image_gb_point(i+1,j+1) = mean(mean(data_gb_in));
        data_b_in = image_b(y_clip(1):y_clip(2), x_clip(1):x_clip(2));
        image_b_point(i+1,j+1) = mean(mean(data_b_in));
    end
end

% figure,imshow(uint8(image_r_point));
%% caculate lsc luma gain
for i = 1:side_num+1
    for j = 1:side_num+1
        image_r_luma_gain_point(i,j) = mean2(image_r_point(uint8(side_num/2)-1:uint8(side_num/2)+1, uint8(side_num/2)-1:uint8(side_num/2)+1)) / image_r_point(i,j);
        image_gr_luma_gain_point(i,j) = mean2(image_gr_point(uint8(side_num/2)-1:uint8(side_num/2)+1, uint8(side_num/2)-1:uint8(side_num/2)+1)) / image_gr_point(i,j);
        image_gb_luma_gain_point(i,j) = mean2(image_gb_point(uint8(side_num/2)-1:uint8(side_num/2)+1, uint8(side_num/2)-1:uint8(side_num/2)+1)) / image_gb_point(i,j);
        image_b_luma_gain_point(i,j) = mean2(image_b_point(uint8(side_num/2)-1:uint8(side_num/2)+1, uint8(side_num/2)-1:uint8(side_num/2)+1)) / image_b_point(i,j);
    end
end

bilinear interpolation:

image_r_luma_gain_reshape = reshape(image_r_luma_gain_point, [], 1);
image_gr_luma_gain_reshape = reshape(image_gr_luma_gain_point, [], 1);
image_gb_luma_gain_reshape = reshape(image_gb_luma_gain_point, [], 1);
image_b_luma_gain_reshape = reshape(image_b_luma_gain_point, [], 1);
for i = 1:17
    for j = 1:17
        x((i-1)*17+j) = i;
        y((i-1)*17+j) = j;
    end
end
x=x';
y=y';
% scatter3(x,y,image_r_luma_gain_reshape)
% hold on
Z=[ones(length(x),1),x,y,x.^2,x.*y,y.^2,x.^3,x.^2.*y,x.*y.^2,y.^3];
[x y]=meshgrid(1:17,1:17);
A=Z\image_r_luma_gain_reshape;
image_r_luma_gain=A(1)+A(2)*x+A(3)*y+A(4)*x.^2+A(5)*x.*y+A(6)*y.^2+A(7)*x.^3+A(8)*x.^2.*y+A(9)*x.*y.^2+A(10)*y.^3;
A=Z\image_gr_luma_gain_reshape;
image_gr_luma_gain=A(1)+A(2)*x+A(3)*y+A(4)*x.^2+A(5)*x.*y+A(6)*y.^2+A(7)*x.^3+A(8)*x.^2.*y+A(9)*x.*y.^2+A(10)*y.^3;
A=Z\image_gb_luma_gain_reshape;
image_gb_luma_gain=A(1)+A(2)*x+A(3)*y+A(4)*x.^2+A(5)*x.*y+A(6)*y.^2+A(7)*x.^3+A(8)*x.^2.*y+A(9)*x.*y.^2+A(10)*y.^3;
A=Z\image_b_luma_gain_reshape;
image_b_luma_gain=A(1)+A(2)*x+A(3)*y+A(4)*x.^2+A(5)*x.*y+A(6)*y.^2+A(7)*x.^3+A(8)*x.^2.*y+A(9)*x.*y.^2+A(10)*y.^3;
% surf(x,y,image_r_luma_gain)
% hold on 
% surf(x,y,image_r_luma_gain_point)


%% calulate lsc chroma gain
for i = 1:side_num+1
    for j = 1:side_num+1
        image_r_chroma_gain(i,j) = image_r_luma_gain(i,j) - image_r_luma_gain_point(i,j);
        image_gr_chroma_gain(i,j) = image_gr_luma_gain(i,j) - image_gr_luma_gain_point(i,j);
        image_gb_chroma_gain(i,j) = image_gb_luma_gain(i,j) - image_gb_luma_gain_point(i,j);
        image_b_chroma_gain(i,j) = image_b_luma_gain(i,j) - image_b_luma_gain_point(i,j);
    end
end
%% caculate lsc result gain
image_r_gain = image_r_luma_gain - image_r_chroma_gain;
image_gr_gain = image_gr_luma_gain - image_gr_chroma_gain;
image_gb_gain = image_gb_luma_gain - image_gb_chroma_gain;
image_b_gain = image_b_luma_gain - image_b_chroma_gain;



function image_gain_lut = lsc_data_gain_interpolation(image_gain, height, width, side_num)
side_y_ori = floor(height/side_num);
side_x_ori = floor(width/side_num);
k = 0;
l = 0;
[gain_height, gain_width] = size(image_gain);
for i = 1:gain_height-1
    for j = 1:gain_width-1
        data_gain_11 = image_gain(i, j);
        data_gain_12 = image_gain(i, j+1);
        data_gain_21 = image_gain(i+1, j);
        data_gain_22 = image_gain(i+1, j+1);
        if(j == gain_width-1 && ((j-1)*side_x + l) ~= width) 
            side_x = width - (j-1)*side_x_ori;
        else
            side_x = side_x_ori;
        end
        
        if(i == gain_width-1 && ((i-1)*side_y + k) ~= width)
            side_y = height - (i-1)*side_y_ori;
        else
            side_y = side_y_ori;
        end
        
        for k = 1:side_y
            for l = 1:side_x
                label_y1 = 1;
                label_x1 = 1;
                label_y2 = side_y;
                label_x2 = side_x;
                image_gain_lut((i-1)*side_y_ori + k, (j-1)*side_x_ori + l) = ...
                    data_gain_22/(label_x2-label_x1)/(label_y2-label_y1)* ...
                    (l - label_x1) * (k - label_y1) + ...
                    data_gain_21/(label_x2-label_x1)/(label_y2-label_y1)* ...
                    (label_x2 - l) * (k - label_y1) + ...
                    data_gain_12/(label_x2-label_x1)/(label_y2-label_y1)* ...
                    (l - label_x1) * (label_y2 - k) + ...
                    data_gain_11/(label_x2-label_x1)/(label_y2-label_y1)* ...
                    (label_x2 - l) * (label_y2 - k);
            end
        end
        
    end
end
end

效果展示:

实验条件有限,图片有水波纹,仅用于理解算法

original image:


这里写图片描述

luma shading

这里写图片描述

这里写图片描述

这里写图片描述

chroma shading


这里写图片描述

这里写图片描述

这里写图片描述

luma shading + chroma shading:


这里写图片描述

这里写图片描述

这里写图片描述

tuning

LSC的tuning一定要把校正图采集好,一般情况下raw图的G通道中心亮度在8bit的70%~80%之间,由于在不同色温情况下是经过插值的,因此需要校正多个光源,一般情况下TL84、D65、A光源下进行校正。将得到的LUT写入RAM中即可
注意:采集的raw图不要有filcker。

LSC强度一般是可调的,由于图像边角的增益会很大,因此在高倍gain下,可以把强度给降低,防止图像边角噪声压不住的情况。

由于各个平台不同,这里不做详细介绍,想到再补充。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容