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。

影响

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下,可以把强度给降低,防止图像边角噪声压不住的情况。

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

  • 25
    点赞
  • 218
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
### 回答1: 下面是用C语言编写的单片机代码,实现了每隔一秒钟将数字0-9在数码管上循环显示一次: ```c #include<reg52.h> #include<intrins.h> sbit LSA=P2^2; sbit LSB=P2^3; sbit LSC=P2^4; sbit key=P3^4; unsigned char code table[]={ 0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90 }; //共阴数码管的编码表 void delay(unsigned int i) //延时函数,i表示延时的时间 { while(i--); } void main() { unsigned char i,j; while(1) { for(i=0;i<10;i++) { for(j=0;j<50;j++) //每秒钟循环50次,即大约20ms循环一次,50次约等于1秒 { P0=table[i]; //在数码管上显示数字i LSA=1;LSB=LSB=0;P0=0xff;delay(1); LSA=0;LSB=1;LSC=0;P0=0xff;delay(1); LSB=0;LSC=1;P0=0xff;delay(1); } } } } ``` 代码的原理是,先定义一个共阴数码管的编码表,里面存储了0-9的编码,然后在无限循环中,依次显示0-9的数字。具体实现是,先用LSA、LSB、LSC三个引脚控制数码管的位选,然后将要显示的数字通过P0口输出到数码管上。每隔20毫秒切换一次位选,这样可以在一个秒钟内将0-9的数字在数码管上循环显示一次。 ### 回答2: 要使用单片机数码管进行倒计时功能,可以使用定时器来控制数码管的显示间隔。以下是用C语言实现该功能的代码: ```c #include <reg51.h> // 数码管段选对应的引脚 sbit seg_a = P1^0; sbit seg_b = P1^1; sbit seg_c = P1^2; sbit seg_d = P1^3; sbit seg_e = P1^4; sbit seg_f = P1^5; sbit seg_g = P1^6; sbit seg_dp = P1^7; // 数码管共阳极的引脚 sbit digit_1 = P2^0; sbit digit_2 = P2^1; sbit digit_3 = P2^2; sbit digit_4 = P2^3; // 数码管的显示数据 unsigned char seg_table[] = { /* 0-9 LED段码表 */ 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F, // 9 }; // 延时函数 void delay(unsigned int ms) { unsigned int i, j; for (i = 0; i < ms; i++) { for (j = 0; j < 123; j++); } } void main() { unsigned int count; // 倒计时初始值 unsigned int i; // 计数值 unsigned char digits[4]; // 数码管显示数据 TMOD = 0x01; // 设置定时器0为模式1 TH0 = 0xFC; // 定时器0高8位赋初值 TL0 = 0x18; // 定时器0低8位赋初值 ET0 = 1; // 开启定时器0中断 TR0 = 1; // 启动定时器0 count = 10; // 设置倒计时初始值为10秒 while (1) { // 拆分倒计时初始值为4位数码管显示数据 for (i = 0; i < 4; i++) { digits[i] = count % 10; count /= 10; } // 数码管显示倒计时数字 for (i = 0; i < 4; i++) { // 按位选通数码管 switch (i) { case 0: digit_1 = 1; digit_2 = 0; digit_3 = 0; digit_4 = 0; break; case 1: digit_1 = 0; digit_2 = 1; digit_3 = 0; digit_4 = 0; break; case 2: digit_1 = 0; digit_2 = 0; digit_3 = 1; digit_4 = 0; break; case 3: digit_1 = 0; digit_2 = 0; digit_3 = 0; digit_4 = 1; break; } // 设置数码管段选 P0 = seg_table[digits[i]]; // 间隔一秒显示下一个数字 delay(1000); } } } ``` 以上代码通过定时器0控制数码管的显示间隔,使用一个计数变量将倒计时初始值分解为4个数码管显示数据,并通过按位选通数码管和设置段选来实现数码管的显示。 ### 回答3: 在C语言中,可以通过设置定时器的计数器和中断来实现单片机数码管的间隔显示0-9。 首先,需要初始化定时器的参数,包括计数器的初始值、定时器的工作模式等。然后,需要编写一个中断函数,用于定时器溢出时的操作。在中断函数中,每次溢出时,将数码管显示的数字加1,并将计数器重新设置为初始值。 以下是一个简单的示例代码: ```c #include <reg51.h> // 使用51单片机的头文件 // 定义数码管编码0-9 unsigned char code digitCodes[] = { 0xC0, // 0 0xF9, // 1 0xA4, // 2 0xB0, // 3 0x99, // 4 0x92, // 5 0x82, // 6 0xF8, // 7 0x80, // 8 0x90 // 9 }; // 定时器中断函数 void timer_isr(void) interrupt 1 { static unsigned char count = 0; count++; // 每次中断计数器加1 if (count > 9) { count = 0; } // 将 count 对应的数码管编码写入数据寄存器,供数码管显示 P2 = digitCodes[count]; } // 主函数 void main() { TMOD = 0x01; // 设置定时器0为工作方式1,即16位定时器模式 TH0 = 0xFC; // 设置计数器初始值为 65536 - (1s / 12.5us) = 0xFC TL0 = 0x66; ET0 = 1; // 允许定时器0中断 EA = 1; // 允许总中断 TR0 = 1; // 启动定时器0 while (1) { // 程序的其他操作 // ... } } ``` 在上述示例代码中,我们使用定时器0作为计时器,并设置它在每次溢出时触发中断。在中断函数中,我们通过修改count的值来控制数码管的显示。在主函数中,我们启动定时器并保持一个无限循环,以保持程序的持续运行。 需要注意的是,上述示例代码是基于51单片机的,如果你使用的是其他型号的单片机,可能需要修改一些寄存器和引脚的定义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值