目录
一、介绍
二、增益表格生成
三、双线性插值算法
四、LSC verilog代码
一、介绍
由于镜头对不同区域光线的折射率并不均匀,中心区域的透光率通常高于边缘区域,因此画面四角容易出现亮度下降(luma shading),有时还会伴随色彩偏移(color shading)。镜头阴影矫正(Lens Shading Correction)正是为了消除这类成像缺陷而设计的。
校正方式:
1、预先拍一张均匀光照的灰卡,分别生成R、G(Gr,Gb共用)、B的张增益表格。
2、将增益表格存储在ISP内部,运行时通过像素坐标查找增益值,再将增益值乘以像素值作品为输出。
luma shading 会导致图像中间亮边角暗,如下图所示:
二、增益表格生成
由于硬件存储资源有限,生成增益表格时会将图像划分为网格,仅存储格点上的增益数据;使用时,再通过插值法计算得到每个像素的增益。
增益表格生成步骤:
1、图像网格划分
以将图像划分为16*16的网格为例(R、Gr、Gb、B四通道数据单独处理),每个网格包含sideX列sideY行像素。
sideX=floor(图像宽度/16),sideY=floor(图像高度/16)。
2、提取格点亮度
- 图像被划分为16*16的网格,则有17*17的格点。
- 以每个格点为中心,向四周取sideX*sideY个像素,当触碰到图像上下左右边界时,仅取到边界上的像素。
- 以ideX*sideY区域内像素的均值,作为该格点的亮度。
3、计算 LSC 亮度增益
以最中心3*3格个点的平局亮度作为参考值,每个格点的增益为:参考值/格点亮度。
具体的MATLAB代码参考:
ISP-镜头阴影校正(LSC)-CSDN博客文章浏览阅读3.2w次,点赞30次,收藏239次。本文介绍了镜头阴影校正(Lens Shading Correction)的基本概念及其在图像处理中的应用。包括lumashading和colorshading的形成原因及影响,并提供了一种通过计算增益并采用双线性插值的方法来校正镜头阴影的算法。https://blog.csdn.net/xiaoyouck/article/details/77206505?sharetype=blog&shareId=77206505&sharerefer=APP&sharesource=qq_42744011https://blog.csdn.net/xiaoyouck/article/details/77206505?sharetype=blog&shareId=77206505&sharerefer=APP&sharesource=qq_42744011https://blog.csdn.net/xiaoyouck/article/details/77206505?sharetype=blog&shareId=77206505&sharerefer=APP&sharesource=qq_42744011https://blog.csdn.net/xiaoyouck/article/details/77206505?sharetype=blog&shareId=77206505&sharerefer=APP&sharesource=qq_42744011
三、双线性插值算法
双线性插值的目的是,在已知以下四个格点的坐标及其增益值时:
Q11,坐标为(x1,y1),增益值为 f(Q11);
Q21,坐标为(x2,y1),增益值为 f(Q21);
Q12,坐标为(x1,y2),增益值为 f(Q12);
Q22,坐标为(x2,y2),增益值为 f(Q22);
计算P的增益值。
具体计算过程如下:
1、用x方向的单线性插值计算R1和R2的增益值:
2、用y方向的单线性插值计算P的增益值:
对每个网格都建立如上坐标系,令 x1=0,x2=sideX,y1=0,y2=sideY,其中 x、y 为像素在网格内的相对坐标,则:
最终可以推导出:
四、LSC verilog代码
module lsc #(parameter DATA_WIDTH=12,GAIN_WIDTH=16,GAIN_ADDR_WIDTH=9,sideX=64,sideY=64,IMG_WIDTH=960,IMG_HEIGHT=540,mesh_num_X=31,mesh_num_Y=18) ( input wire clk, input wire rst_n, input wire [DATA_WIDTH-1:0] din, input wire din_vld, input wire [GAIN_WIDTH-1:0] gain1,gain2,gain3,gain4, output reg [DATA_WIDTH-1:0] dout, output reg dout_vld, output wire [GAIN_ADDR_WIDTH-1:0] gain1_addr,gain2_addr,gain3_addr,gain4_addr ); /* 处理图像尺寸:1080*1920 划分网格数:17*30 增益存储格式:16bit定点数据,2bit整数位,14bit小数位 每个通道使用两个FPGA的Dual port ROM IP 存储LSC增益表格,ROM的读取延时为1个clk gain1,gain2,gain3,gain4分别表示左上,右上,左下,右下格点对应的增益值,以左上格点为原点。 增益值按x轴正方形存储 */ localparam ONE = 512'd1; localparam ZERO = 512'd0; localparam FF = 64'hFFFF_FFFF_FFFF_FFFF; localparam IMG_WIDTH_CNT_WIDTH = $clog2(IMG_WIDTH); localparam IMG_HEIGHT_CNT_WIDTH = $clog2(IMG_HEIGHT); localparam sideX_CNT_WIDTH = $clog2(sideX); localparam sideY_CNT_WIDTH = $clog2(sideY); localparam meshX_WIDTH = $clog2(mesh_num_X); localparam meshY_WIDTH = $clog2(mesh_num_Y); /* cntW,cntH 记录像素在图像上的绝对坐标 cntX,cntY 记录像素在网格内的相对坐标 meshX,meshY 记录像素所在网格的左上格点坐标 */ reg [IMG_WIDTH_CNT_WIDTH-1:0] cntW; wire cntW_start,cntW_end; reg [IMG_HEIGHT_CNT_WIDTH-1:0] cntH; wire cntH_start,cntH_end; reg [sideX_CNT_WIDTH-1:0] cntX; wire cntX_start,cntX_end; reg [sideY_CNT_WIDTH-1:0] cntY; wire cntY_start,cntY_end; reg [meshX_WIDTH-1:0] meshX; wire meshX_start,meshX_end; reg [meshY_WIDTH-1:0] meshY; wire meshY_start,meshY_end; assign cntW_start = din_vld; assign cntW_end = cntW == IMG_WIDTH-1; always @(posedge clk) begin if(!rst_n) cntW<=ZERO[IMG_WIDTH_CNT_WIDTH-1:0]; else if (cntW_start) if(cntW_end) cntW<=ZERO[IMG_WIDTH_CNT_WIDTH-1:0]; else cntW<=cntW+1'b1; end assign cntH_start = cntW_end & cntW_start; assign cntH_end = cntH == IMG_HEIGHT-1; always @(posedge clk) begin if(!rst_n) cntH<=ZERO[IMG_HEIGHT_CNT_WIDTH-1:0]; else if (cntH_start) if(cntH_end) cntH<=ZERO[IMG_HEIGHT_CNT_WIDTH-1:0]; else cntH<=cntH+1'b1; end assign cntX_start = din_vld; assign cntX_end = cntX == sideX-1; always @(posedge clk) begin if(!rst_n) cntX<=ZERO[sideX_CNT_WIDTH-1:0]; else if (cntH_start) //最右侧网格像素个数不足sideX cntX<=ZERO[sideX_CNT_WIDTH-1:0]; else if (cntX_start) if(cntX_end) cntX<=ZERO[sideX_CNT_WIDTH-1:0]; else cntX<=cntX+1'b1; end assign cntY_start = cntH_start; assign cntY_end = cntY == sideY-1; always @(posedge clk) begin if(!rst_n) cntY<=ZERO[sideY_CNT_WIDTH-1:0]; else if (cntH_start & cntH_end) //最下方网格像素个数不足sideY cntY<=ZERO[sideY_CNT_WIDTH-1:0]; else if (cntY_start) if(cntY_end) cntY<=ZERO[sideY_CNT_WIDTH-1:0]; else cntY<=cntY+1'b1; end assign meshX_start = cntX_start & cntX_end; always @(posedge clk) begin if(!rst_n) meshX<=ZERO[meshX_WIDTH-1:0]; else if (cntH_start) meshX<=ZERO[meshX_WIDTH-1:0]; else if (meshX_start) meshX<=meshX+1'b1; end assign meshY_start = cntY_start & cntY_end; always @(posedge clk) begin if(!rst_n) meshY<=ZERO[meshY_WIDTH-1:0]; else if (cntH_start & cntH_end) meshY<=ZERO[meshY_WIDTH-1:0]; else if (meshY_start) meshY<=meshY+1'b1; end //四格点增益地址计算 assign gain1_addr = meshX + meshY*mesh_num_X; assign gain2_addr = meshX + meshY*mesh_num_X+1; assign gain3_addr = meshX + (meshY+1)*mesh_num_X; assign gain4_addr = meshX + (meshY+1)*mesh_num_X+1; //像素插值增益计算 wire [GAIN_WIDTH:0] add_temp1,add_temp2; wire [GAIN_WIDTH+1:0] add_temp3; wire [GAIN_WIDTH+sideX_CNT_WIDTH:0] mul_temp1; wire [GAIN_WIDTH+sideY_CNT_WIDTH:0] mul_temp2; wire [GAIN_WIDTH+sideX_CNT_WIDTH+sideY_CNT_WIDTH+1:0] mul_temp3; wire [GAIN_WIDTH+2:0] gain_temp; reg [GAIN_WIDTH-1:0] gain; assign add_temp1 = {1'b0,gain2}-{1'b0,gain1}; assign add_temp2 = {1'b0,gain4}-{1'b0,gain3}; assign add_temp3 = {add_temp2[GAIN_WIDTH],add_temp2}-{add_temp1[GAIN_WIDTH],add_temp1}; assign mul_temp1 = {{sideX_CNT_WIDTH{add_temp1[GAIN_WIDTH]}},add_temp1}*cntX; assign mul_temp2 = {{sideY_CNT_WIDTH{add_temp2[GAIN_WIDTH]}},add_temp2}*cntY; assign mul_temp3 = {{(sideX_CNT_WIDTH+sideY_CNT_WIDTH){add_temp3[GAIN_WIDTH+1]}},add_temp3}*cntX*cntY; assign gain_temp = gain1+ {{2{mul_temp1[GAIN_WIDTH]}},mul_temp1[GAIN_WIDTH+sideX_CNT_WIDTH:sideX_CNT_WIDTH]} + {{2{mul_temp2[GAIN_WIDTH]}},mul_temp2[GAIN_WIDTH+sideY_CNT_WIDTH:sideY_CNT_WIDTH]} + {{mul_temp3[GAIN_WIDTH+1]},mul_temp3[GAIN_WIDTH+sideX_CNT_WIDTH+sideY_CNT_WIDTH+1:sideX_CNT_WIDTH+sideY_CNT_WIDTH]}; always @(posedge clk) begin if(!rst_n) gain<=ZERO[GAIN_WIDTH-1:0]; else if (din_vld) gain<=gain_temp[GAIN_WIDTH+2] ? 16'h4000 : |gain_temp[GAIN_WIDTH+1:GAIN_WIDTH] ? 16'hFFFF : gain_temp[0+:GAIN_WIDTH]; /* gain_temp[GAIN_WIDTH+2] == 1 gain_temp为负值,下溢出,让增益为1输出原值, gain_temp[GAIN_WIDTH+2] == 0 gain_temp[GAIN_WIDTH+1:GAIN_WIDTH] 不全为0 :上溢出 取最大值 */ end //lsc 输出 // 输入数据与插值增益同步 reg [DATA_WIDTH-1:0] din_reg ; reg din_vld_reg ; always @(posedge clk) begin if(!rst_n) din_reg<=ZERO[DATA_WIDTH-1:0]; else if (din_vld) din_reg<= din; end always @(posedge clk) begin if(!rst_n) din_vld_reg<=1'b0; else din_vld_reg<= din_vld; end //输出计算 wire [DATA_WIDTH+GAIN_WIDTH-1:0] dout_temp; assign dout_temp = din_reg * gain;//插值增益出现负值时输出原像素值 always @(posedge clk) begin if(!rst_n) dout<=ZERO[DATA_WIDTH-1:0]; else if (din_vld_reg) dout<= ~(|dout_temp[DATA_WIDTH+GAIN_WIDTH-1:DATA_WIDTH+GAIN_WIDTH-2]) ? FF[DATA_WIDTH-1:0] : dout_temp[14+:DATA_WIDTH]; //结果上溢出时取最大值 end always @(posedge clk) begin if(!rst_n) dout_vld<=1'b0; else dout_vld<= din_vld_reg; end endmodule