第二章 Bayer RAW域处理:黑电平校正、镜头阴影校正、坏点校正
各位同学,欢迎来到RAW域处理的第一站。说实话,很多做三年ISP的工程师,对这三兄弟的理解还停留在“调几个参数就行”。但真正上过高通平台的都知道——寄存器配不对,后面调得再好也是白搭。
今天我们就来聊聊Bayer RAW域最基础的三个模块:黑电平校正(BLC)、镜头阴影校正(LSC)、坏点校正(DPC)。它们处理的数据都是最原始的Bayer格式,一个像素只记录一种颜色分量。
2.1 黑电平校正(BLC)
黑电平校正,说白了就是告诉ISP:“Sensor在完全无光时输出的值是多少,把它减掉”。你想想看,理想情况下,全黑场景每个像素都应该输出0。但实际Sensor有暗电流,会有一个固定的偏移量——这个偏移就是黑电平。
我在项目中遇到过最典型的坑:某款OV Sensor的黑电平是64(10bit数据),但产线标定文件里写的是60。结果暗光场景下画面偏紫,AWB怎么都拉不回来。查了两天才发现是BLC参数错了。
BLC的核心参数
| 参数 | 说明 | 典型值(10bit) |
|---|---|---|
| BL_Offset | 黑电平偏移量,每个通道独立 | R:64, Gr:64, Gb:64, B:64 |
| BL_Clip | 校正后是否截断到0 | Enable(默认开启) |
| BL_Channel_Gain | 各通道增益补偿 | 1.0(通常不调) |
高通平台BLC寄存器配置
以高通SM8450平台为例,BLC模块在ISP IFE(Image Front End)内部。配置流程如下:
// 1. 使能BLC模块
IFE_BLC_CFG = 0x1;
// 2. 设置各通道黑电平偏移(10bit模式)
IFE_BLC_OFFSET_R = 64; // R通道偏移
IFE_BLC_OFFSET_GR = 64; // Gr通道偏移
IFE_BLC_OFFSET_GB = 64; // Gb通道偏移
IFE_BLC_OFFSET_B = 64; // B通道偏移
// 3. 使能负值截断(防止校正后出现负值)
IFE_BLC_CLIP_EN = 0x1;
// 4. 设置截断下限
IFE_BLC_CLIP_MIN = 0;
// 5. 应用配置
IFE_UPDATE_TRIGGER = 0x1;
2.2 镜头阴影校正(LSC)
镜头阴影,也叫暗角。你想想看,镜头中心到边缘的光路长度不一样,边缘的光强自然比中心弱。LSC就是给边缘像素“补光”,让整幅画面亮度均匀。
高通平台的LSC实现方式很有意思——它用网格增益表来做校正。说白了,就是把画面分成M×N个网格,每个网格存一个增益值。边缘的增益大,中心的增益小。
LSC网格表结构
| 参数 | 说明 | 典型值 |
|---|---|---|
| Grid Size | 网格数量(水平×垂直) | 17×13(高通推荐) |
| Gain Range | 增益范围 | 1.0 ~ 4.0 |
| Interpolation | 网格间插值方式 | Bilinear |
| Channel | 各通道独立增益表 | R, Gr, Gb, B |
高通平台LSC寄存器配置
// 1. 使能LSC模块
IFE_LSC_CFG = 0x1;
// 2. 设置网格尺寸(17×13)
IFE_LSC_GRID_H = 17;
IFE_LSC_GRID_V = 13;
// 3. 加载增益表(以R通道为例)
// 增益表存储在LUT中,每个增益为12bit定点数
for (int i = 0; i < 17*13; i++) {
IFE_LSC_LUT_R[i] = gain_table_r[i];
}
// 4. 设置插值方式(0=Bilinear, 1=Bicubic)
IFE_LSC_INTERP = 0;
// 5. 应用配置
IFE_UPDATE_TRIGGER = 0x1;
2.3 坏点校正(DPC)
坏点,就是Sensor阵列中那些“不听话”的像素。有的永远亮着(亮点),有的永远暗着(暗点),有的随机闪烁(闪点)。DPC的任务就是找到它们,然后用邻居像素的值替换掉。
高通平台的DPC分两种模式:静态DPC和动态DPC。
| 模式 | 原理 | 适用场景 |
|---|---|---|
| 静态DPC | 产线标定坏点坐标,直接替换 | 固定坏点(出厂就有) |
| 动态DPC | 实时检测异常像素,动态替换 | 热像素、随机闪点 |
我个人习惯是静态+动态同时开启。静态DPC处理已知坏点,动态DPC兜底处理新出现的坏点。你想想看,Sensor用久了热像素会增多,只靠静态表肯定不够。
动态DPC检测原理
动态DPC的核心算法很简单:当前像素和周围8个邻居比。如果差值超过阈值,就判定为坏点。
// 动态DPC检测伪代码
for each pixel (x, y) {
// 计算周围8邻域均值
mean = average(neighbors[x][y]);
// 计算差值
diff = abs(pixel[x][y] - mean);
// 判断是否坏点
if (diff > DPC_THRESHOLD) {
pixel[x][y] = mean; // 替换为邻域均值
}
}
高通平台DPC寄存器配置
// 1. 使能DPC模块
IFE_DPC_CFG = 0x1;
// 2. 使能静态DPC
IFE_DPC_STATIC_EN = 0x1;
// 3. 加载静态坏点表(坐标列表)
// 坏点表存储在LUT中
for (int i = 0; i < bad_pixel_count; i++) {
IFE_DPC_LUT_X[i] = bad_pixels[i].x;
IFE_DPC_LUT_Y[i] = bad_pixels[i].y;
}
// 4. 使能动态DPC
IFE_DPC_DYNAMIC_EN = 0x1;
// 5. 设置动态检测阈值
IFE_DPC_THRESHOLD = 20;
// 6. 设置替换模式(0=均值, 1=中值)
IFE_DPC_REPLACE_MODE = 0;
// 7. 应用配置
IFE_UPDATE_TRIGGER = 0x1;
小结
BLC、LSC、DPC这三个模块,是RAW域处理的基础。它们处理的是最原始的Sensor数据,任何一个出问题,都会传导到后面的所有模块。
嗯,这里要注意:高通平台的寄存器配置虽然看起来简单,但时序和依赖关系很容易搞错。比如LSC必须在BLC之后使能,因为LSC的增益计算是基于校正后的数据。我建议你们在调试时,先单独验证每个模块,再串联起来测。
最后送大家一句话:RAW域处理,慢就是快。花时间把这三兄弟调好,后面的路会顺很多。