第二章 Bayer RAW域处理:黑电平校正、镜头阴影校正、坏点校正

各位同学,欢迎来到RAW域处理的第一站。说实话,很多做三年ISP的工程师,对这三兄弟的理解还停留在“调几个参数就行”。但真正上过高通平台的都知道——寄存器配不对,后面调得再好也是白搭。

今天我们就来聊聊Bayer RAW域最基础的三个模块:黑电平校正(BLC)、镜头阴影校正(LSC)、坏点校正(DPC)。它们处理的数据都是最原始的Bayer格式,一个像素只记录一种颜色分量。

核心认知:RAW域处理是ISP Pipeline的第一道关卡。这里的数据质量,直接决定了后面所有模块的上限。我见过太多项目,因为BLC没做干净,导致AWB和CCM怎么调都偏色。
Bayer RAW域预处理流程 Sensor RAW 原始Bayer数据 BLC 黑电平校正 LSC 镜头阴影校正 DPC 坏点校正 各模块作用: • BLC:消除Sensor暗电流引入的固定偏移,恢复黑电平为0 • LSC:补偿镜头光学系统导致的中心和边缘亮度差异 • DPC:检测并替换Sensor阵列中的缺陷像素 • 处理顺序:BLC → LSC → DPC(高通平台默认Pipeline顺序)

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(通常不调)
我的习惯:拿到新Sensor的第一件事,就是拍一张全黑帧(盖上镜头盖),然后dump出RAW数据看各通道均值。这个值就是最真实的黑电平。别信Datasheet,信实测。

高通平台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;
注意:高通平台的BLC寄存器是每个通道独立的。我曾经遇到一个Bug——只配了R和B通道,Gr和Gb用了默认值0,结果画面出现网格状偏色。后来才发现是G通道没配。

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的增益表是标定出来的,不是拍脑袋写的。产线会拍一张均匀白板(DNP或X-Rite),然后反算出每个网格需要的增益。我建议你们拿到标定数据后,先画一张增益热力图看看——如果某个网格增益突然跳变,大概率是标定有问题。

高通平台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;
避坑指南:我曾经在调试LSC时发现边缘过曝。排查后发现是增益表精度不够——12bit定点数,最大只能表示4.0倍增益。但某款广角镜头边缘需要4.5倍增益。解决方案:把增益表改成13bit精度,或者分两段校正。

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的阈值不能设得太低。我见过一个项目,阈值设到5(10bit数据),结果把正常的纹理细节也当成坏点替换了,画面变得很“肉”。建议阈值设在15-30之间,具体要看Sensor的噪声水平。

高通平台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域处理,慢就是快。花时间把这三兄弟调好,后面的路会顺很多。