RAW域预处理:黑电平校正、镜头阴影校正、坏点校正、绿色不平衡校正
各位好,欢迎来到RAW域预处理这一章。说实话,这是整个ISP流水线里最「脏」也最「累」的活。为什么这么说?因为Sensor送出来的RAW数据,压根儿就不是我们人眼想看到的样子。它带着各种系统性的偏差和随机噪声,你得先把它「洗干净」,后面的处理才有意义。
我个人习惯把RAW域预处理比作「地基工程」。地基没打好,后面盖的楼再漂亮也得塌。这章我们要聊的四个模块——黑电平校正、镜头阴影校正、坏点校正、绿色不平衡校正——就是最基础的四根桩。
黑电平校正(BLC)
黑电平校正,说白了就是告诉系统:「当Sensor完全没接收到光的时候,它输出的数值应该是多少。」
理想情况下,没光就是0。但现实不是这样。Sensor的模拟电路、ADC转换、甚至温度变化都会引入一个固定的偏移量。这个偏移量就是黑电平。我见过有些刚入行的工程师,拿到RAW图发现暗部偏紫,第一反应是去调白平衡——其实根源就是黑电平没校好。
校正公式很简单:
// 黑电平校正核心逻辑
// 假设Sensor输出12-bit RAW,黑电平为64
uint16_t blc_value = 64;
for (int i = 0; i < frame_height * frame_width; i++) {
raw_data[i] = (raw_data[i] > blc_value) ? (raw_data[i] - blc_value) : 0;
}
但实际项目里没这么简单。我踩过几个坑,分享给大家:
- 黑电平不是全局统一的——不同通道(R/Gr/Gb/B)的黑电平可能不同,甚至同一通道不同列也有差异。我遇到过一款Sensor,左边和右边的黑电平差了8个LSB。
- 黑电平会随温度漂移——相机开机后Sensor温度上升,黑电平会缓慢变化。所以很多方案会做动态黑电平校正,实时更新OB(Optical Black)区域的均值。
- 减完黑电平后要防溢出——如果原始值小于黑电平,直接减会变成负数。必须做clamp到0。
镜头阴影校正(LSC)
镜头阴影,也叫亮度衰减或暗角。你想想看,镜头中心的光线是垂直入射的,边缘的光线是斜着进来的——斜着走的路程更长,能量衰减自然更大。再加上镜头本身的cos⁴定律,边缘亮度可能只有中心的60%甚至更低。
校正思路就是给边缘像素「补光」。具体做法是生成一个增益网格(Gain Grid),每个像素乘以对应的增益系数。
// LSC校正简化示例
// 假设已生成一个32x24的增益网格,需要插值到全分辨率
for (int y = 0; y < frame_height; y++) {
for (int x = 0; x < frame_width; x++) {
float gain = interpolate_gain(x, y, gain_grid);
raw_data[y][x] = (uint16_t)(raw_data[y][x] * gain + 0.5f);
}
}
这里有几个关键点:
- 增益网格的精度——网格越密,校正越精细,但存储开销也越大。我做过一个项目,用64x48的网格,效果已经很好。但有些高端方案会用到256x192甚至更密。
- 插值方式——双线性插值是最常用的,但要注意边缘的插值方向。我建议用双三次插值,虽然计算量大一点,但能避免网格边界处的「块效应」。
- 增益上限——边缘增益不能无限大。一般限制在2x~4x,否则噪声会被放大到不可接受的程度。
坏点校正(DPC)
坏点,就是Sensor上那些「不听话」的像素。有的永远亮着(亮点),有的永远黑着(暗点),有的忽亮忽暗(闪烁点)。
坏点校正分两步:检测和替换。
检测方法:
- 静态坏点检测——出厂时标定,存储在OTP或EEPROM中。优点是准确,缺点是只能检测出厂时的坏点,使用过程中新产生的坏点就无能为力了。
- 动态坏点检测——运行时实时判断。常用方法是比较当前像素与周围同色通道像素的差异,如果差异超过阈值,就判定为坏点。
// 动态坏点检测简化逻辑
// 以当前像素为中心,取3x3同色通道窗口
bool is_dead_pixel(uint16_t center, uint16_t neighbors[], int threshold) {
uint16_t median = get_median(neighbors, 8);
int diff = abs((int)center - (int)median);
return (diff > threshold);
}
替换方法:
- 中值替换——用周围同色像素的中值替换坏点。效果不错,但计算量大。
- 均值替换——用周围像素的均值。简单快速,但可能模糊细节。
- 梯度导向插值——根据边缘方向选择替换值。效果最好,但实现复杂。
绿色不平衡校正(GIC)
这个问题很有意思。Bayer阵列里有两个绿色通道——Gr(绿色在红色行)和Gb(绿色在蓝色行)。理论上它们应该一样,但实际上因为工艺差异、微透镜偏移等原因,Gr和Gb的响应会有差异。表现出来就是图像上出现「棋盘格」状的纹理,尤其在均匀区域特别明显。
校正方法也很直接:让Gr和Gb的增益对齐。
// GIC校正简化示例
// 计算Gr和Gb的均值,然后调整增益
float mean_gr = calculate_mean(gr_channel);
float mean_gb = calculate_mean(gb_channel);
float gain_gr = mean_gb / mean_gr; // 以Gb为基准
float gain_gb = 1.0f;
for (int y = 0; y < frame_height; y++) {
for (int x = 0; x < frame_width; x++) {
if (is_gr_pixel(y, x)) {
raw_data[y][x] = (uint16_t)(raw_data[y][x] * gain_gr + 0.5f);
}
// Gb像素保持不变
}
}
但这里有个陷阱:Gr和Gb的差异不是全局一致的。在图像中心区域差异小,边缘区域差异大。所以更好的做法是分区域计算增益,甚至逐像素计算。