ISP-LSC 鏡頭陰影校正
參考:
https://zhuanlan.zhihu.com/p/389334269https://blog.csdn.net/xiaoyouck/article/details/77206505https://www.cnblogs.com/wnwin/p/11805901.htmlhttp://kb.colorspace.com.cn/kb/2022/09/05/isp-%E9%95%9C%E5%A4%B4%E9%98%B4%E5%BD%B1%E6%A0%A1%E6%AD%A3%EF%BC%88lsc%EF%BC%89/
LSC(Lens Shading Correction)鏡頭陰影校正。
Shading細分為Lens Shading(Luma Shaing)和Color Shading(chrom Shading)。
1. Shading產生原因及影響
由于Lens的光學特性,sensor影像區的邊緣區域接收的光強比中心小,造成中心和四角亮度不一致的現象,并且鏡頭本身是一個凸透鏡,由于凸透鏡的原理,中心的感光必然比周邊多。
如下圖,綠色和藍色的光強在進入鏡頭前是一致的,由于Lens的特性,在邊緣的綠色光線會有一部分被遮擋,sensor邊緣部分能捕捉到的光信號就比較少;
另外由于綠色部分光線經過的距離比較長,光的衰減也要比藍色部分的衰減大,也導致了到達邊緣部分的光信號的強度弱;
以上來自:https://zhuanlan.zhihu.com/p/389334269
Lens Shading會造成圖像邊角偏暗,即暗角。
color Shading
各種顏色的波長不同,經過透鏡折射后,折射的角度也不一樣,就會造成color Shading的現象,另外由于CRA的原因也會導致shading現象,如下:
Color Shading 中心和四周顏色不一致,表現出來一般是中心或者四周偏色。
2. Shading校正-LSC
在ISP Pipeline中,Shading一般在OB和DPC后面。另外需要注意,如果3A的統計數據是在shading校正之后獲取的,那么shading校正結果會影響3A的統計數據。
2.1 校正方法
LSC校正目前主流的方法有兩種:同心圓法和網格法。
同心圓法:
找到RGB三通道的圓心;
以同心圓的形狀將畫面的中心和畫面的邊緣的三通道乘以不同的增益。
如下圖,一般考慮shading漸變的曲率從中心到邊緣逐漸增大,所以等增益曲線中心稀疏,邊緣密集。一般lens shading增益最好不要超過2倍,因為這會引入噪聲。
網格法:
也叫做mesh shading correction,把整幅圖像分成m*n個網格,然后針對網格頂點求出校正的增益,然后把這些頂點的增益儲存到內存中,其它點的增益通過插值的方式求出。
比如圖像分成如下的網格:
如下圖是每個網格的亮度分布,這里有一個cos4θcos^{4}\thetacos4θ的關系。
針對上面的亮度求出的增益圖如下:
cos4θcos^{4}\thetacos4θ的函數如下:
同心圓校正方法的優點是計算量小,缺點是鏡頭若裝配時稍有不對稱則校正失敗–這個方法可以通過先找到圖像的實際中心,然后以實際中心為圓點去校正。
網格法的優點是能夠應對各種shading情況,缺點是運算量大。
網格法校正代碼流程:
整張raw圖像分為四通道;對四通道圖像劃分為m*n個網格,并求出每個網格的均值;根據每個網格均值求出該網格對應的增益;根據得到的增益再利用插值算法得到每個像素校正后的值;
template <typename T>
bool lsc(const T
*src
, T
*dst
, int width
, int height
)
{int sub_width
= width
/ 2;int sub_height
= height
/ 2;T
*pr
, *pb
, *pgr
, *pgb
;pr
= new T
[sub_width
* sub_height
];pb
= new T
[sub_width
* sub_height
];pgr
= new T
[sub_width
* sub_height
];pgb
= new T
[sub_width
* sub_height
];if (!pr
|| !pb
|| !pgr
|| !pgb
){printf("allocate channel buf failed!\n");return false;}memset(pr
, 0, sizeof(pr
[0]) * sub_width
* sub_height
);memset(pb
, 0, sizeof(pb
[0]) * sub_width
* sub_height
);memset(pgr
, 0, sizeof(pgr
[0]) * sub_width
* sub_height
);memset(pgb
, 0, sizeof(pgb
[0]) * sub_width
* sub_height
);general
::raw_split_to_4channel(src
, pr
, pgr
, pgb
, pb
, width
, height
);int grid_width
, grid_height
; const int Nx
= 17; const int Ny
= 13;const int nx
= Nx
+ 1;const int ny
= Ny
+ 1;grid_width
= floor(sub_width
* 1.0 / (Nx
));grid_height
= floor(sub_height
* 1.0 / (Ny
));float rave
[Ny
+ 1][Nx
+ 1],grave
[Ny
+ 1][Nx
+ 1],gbave
[Ny
+ 1][Nx
+ 1],bave
[Ny
+ 1][Nx
+ 1];memset(rave
, 0, sizeof(rave
));memset(grave
, 0, sizeof(grave
));memset(gbave
, 0, sizeof(gbave
));memset(bave
, 0, sizeof(bave
));int sx
, sy
, ex
, ey
;sx
= sy
= ex
= ey
= 0;for (int i
= 0; i
<= Ny
; i
++){for (int j
= 0; j
<= Nx
; j
++){sx
= j
* grid_width
- grid_width
/ 2;ex
= j
* grid_width
+ grid_width
/ 2;sy
= i
* grid_height
- grid_height
/ 2;ey
= i
* grid_height
+ grid_height
/ 2;if (i
== Ny
&& ey
!= sub_height
)ey
= sub_height
;if (j
== Nx
&& ex
!= sub_width
)ex
= sub_width
;sx
= sx
< 0 ? 0 : sx
;sy
= sy
< 0 ? 0 : sy
;rave
[i
][j
] = general
::get_average_roi(pr
, sub_width
, sx
, sy
, ex
, ey
);grave
[i
][j
] = general
::get_average_roi(pgr
, sub_width
, sx
, sy
, ex
, ey
);gbave
[i
][j
] = general
::get_average_roi(pgb
, sub_width
, sx
, sy
, ex
, ey
);bave
[i
][j
] = general
::get_average_roi(pb
, sub_width
, sx
, sy
, ex
, ey
);}}float max
[4] = {0, 0, 0, 0};for (int i
= 0; i
<= Ny
; i
++){for (int j
= 0; j
<= Nx
; j
++){max
[0] = (max
[0] < rave
[i
][j
] ? rave
[i
][j
] : max
[0]);max
[1] = (max
[1] < grave
[i
][j
] ? grave
[i
][j
] : max
[1]);max
[2] = (max
[2] < gbave
[i
][j
] ? gbave
[i
][j
] : max
[2]);max
[3] = (max
[3] < bave
[i
][j
] ? bave
[i
][j
] : max
[3]);}}float rgain
[ny
][nx
], grgain
[ny
][nx
],gbgain
[ny
][nx
], bgain
[ny
][nx
];memset(rgain
, 0, sizeof(rgain
));memset(grgain
, 0, sizeof(grgain
));memset(gbgain
, 0, sizeof(gbgain
));memset(bgain
, 0, sizeof(bgain
));for (int i
= 0; i
<= Ny
; i
++){for (int j
= 0; j
<= Nx
; j
++){rgain
[i
][j
] = max
[0] / float(rave
[i
][j
]);grgain
[i
][j
] = max
[1] / float(grave
[i
][j
]);gbgain
[i
][j
] = max
[2] / float(gbave
[i
][j
]);bgain
[i
][j
] = max
[3] / float(bave
[i
][j
]);}}float gaintmp
= 0;int gainx
, gainy
;gainx
= gainy
= 0;int tmp_grid_width
, tmp_grid_height
;int tmp_x
, tmp_y
;float tmp
= 0;int curgain
= 0;T
*prdst
, *pbdst
, *pgrdst
, *pgbdst
;prdst
= new T
[sub_width
* sub_height
];pbdst
= new T
[sub_width
* sub_height
];pgrdst
= new T
[sub_width
* sub_height
];pgbdst
= new T
[sub_width
* sub_height
];if (!prdst
|| !pbdst
|| !pgrdst
|| !pgbdst
){printf("allocate channel buf failed!\n");return false;}memset(prdst
, 0, sizeof(prdst
[0]) * sub_width
* sub_height
);memset(pbdst
, 0, sizeof(pbdst
[0]) * sub_width
* sub_height
);memset(pgrdst
, 0, sizeof(pgrdst
[0]) * sub_width
* sub_height
);memset(pgbdst
, 0, sizeof(pgbdst
[0]) * sub_width
* sub_height
);tmp
= 0;for (int y
= 0; y
< sub_height
; y
++){for (int x
= 0; x
< sub_width
; x
++){gainy
= floor(float(y
) / grid_height
);gainy
= (gainy
> Ny
- 1) ? (Ny
- 1) : gainy
;gainx
= floor(float(x
) / grid_width
);gainx
= (gainx
> Nx
- 1) ? (Nx
- 1) : gainx
;gaintmp
= (rgain
[gainy
][gainx
+ 1] - rgain
[gainy
][gainx
]) * (x
- gainx
* grid_width
) / grid_width
+(rgain
[gainy
+ 1][gainx
] - rgain
[gainy
][gainx
]) * (y
- gainy
* grid_height
) / grid_height
+(rgain
[gainy
+ 1][gainx
+ 1] + rgain
[gainy
][gainx
] - rgain
[gainy
+ 1][gainx
] - rgain
[gainy
][gainx
+ 1]) * (x
- gainx
* grid_width
) / grid_width
* (y
- gainy
* grid_height
) / grid_height
+rgain
[gainy
][gainx
];prdst
[x
+ y
* sub_width
] = T(float(pr
[x
+ sub_width
* y
]) * gaintmp
);gaintmp
= (grgain
[gainy
][gainx
+ 1] - grgain
[gainy
][gainx
]) * (x
- gainx
* grid_width
) / grid_width
+(grgain
[gainy
+ 1][gainx
] - grgain
[gainy
][gainx
]) * (y
- gainy
* grid_height
) / grid_height
+(grgain
[gainy
+ 1][gainx
+ 1] + grgain
[gainy
][gainx
] - grgain
[gainy
+ 1][gainx
] - grgain
[gainy
][gainx
+ 1]) * ((x
- gainx
* grid_width
) / grid_width
) * ((y
- gainy
* grid_height
) / grid_height
) +grgain
[gainy
][gainx
];pgrdst
[x
+ y
* sub_width
] = T(float(pgr
[x
+ sub_width
* y
]) * gaintmp
);gaintmp
= (gbgain
[gainy
][gainx
+ 1] - gbgain
[gainy
][gainx
]) * (x
- gainx
* grid_width
) / grid_width
+(gbgain
[gainy
+ 1][gainx
] - gbgain
[gainy
][gainx
]) * (y
- gainy
* grid_height
) / grid_height
+(gbgain
[gainy
+ 1][gainx
+ 1] + gbgain
[gainy
][gainx
] - gbgain
[gainy
+ 1][gainx
] - gbgain
[gainy
][gainx
+ 1]) * ((x
- gainx
* grid_width
) / grid_width
) * ((y
- gainy
* grid_height
) / grid_height
) +gbgain
[gainy
][gainx
];pgbdst
[x
+ y
* sub_width
] = T(float(pgb
[x
+ sub_width
* y
]) * gaintmp
);gaintmp
= (bgain
[gainy
][gainx
+ 1] - bgain
[gainy
][gainx
]) * (x
- gainx
* grid_width
) / grid_width
+(bgain
[gainy
+ 1][gainx
] - bgain
[gainy
][gainx
]) * (y
- gainy
* grid_height
) / grid_height
+(bgain
[gainy
+ 1][gainx
+ 1] + bgain
[gainy
][gainx
] - bgain
[gainy
+ 1][gainx
] - bgain
[gainy
][gainx
+ 1]) * ((x
- gainx
* grid_width
) / grid_width
) * ((y
- gainy
* grid_height
) / grid_height
) +bgain
[gainy
][gainx
];pbdst
[x
+ y
* sub_width
] = T(float(pb
[x
+ sub_width
* y
]) * gaintmp
);}}general
::channels4_to_raw(dst
, prdst
, pgrdst
, pgbdst
, pbdst
, width
, height
);return true;
}
校正后效果:
總結
以上是生活随笔為你收集整理的【camera】【ISP】Lens Shading Correction镜头阴影校正的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。