4. 黑电平与固定模式噪声校正

黑电平校正,说白了就是告诉相机「什么是真正的黑色」。你想想看,传感器在完全无光的情况下,输出并不是零。这个非零的底数,就是我们常说的黑电平。如果不把它减掉,整张画面就会发灰发雾,暗部细节全被埋没了。

我刚开始调ISP的时候,犯过一个低级错误——直接用全局固定值减黑电平。结果呢?画面边缘偏色,暗部还有条纹。后来才明白,黑电平不是个常数,它跟温度、增益、像素位置都有关系。这一章,咱们就把这块硬骨头啃下来。

4.1 暗电流原理:黑电平的物理根源

暗电流,是传感器在没有光照时,由于热激发产生的电子-空穴对。温度每升高6-8°C,暗电流大约翻一倍。这个特性,决定了黑电平校正不能一劳永逸。

参数 典型值范围 影响因素
暗电流密度 10-100 e⁻/pixel/s (25°C) 温度、工艺、像素尺寸
温度系数 每6-8°C翻倍 硅材料本征特性
像素间差异 5-20% (RMS) 掺杂不均匀、缺陷
增益影响 随模拟增益线性放大 暗电流噪声被同步放大

关键认知:暗电流不是噪声,是偏置。它决定了黑电平的基线。但暗电流的像素间差异,会形成固定模式噪声(FPN)。这两个概念经常被混淆,但校正方法完全不同。

4.2 OB区域提取:从硬件获取黑电平参考

OB(Optical Black)区域,是传感器上被金属遮挡的像素行或列。这些像素收不到光,只输出暗电流信号。我们用它来实时估算当前帧的黑电平。

我在项目中遇到过一种情况:某款传感器的OB区域只有4行,而且分布在传感器顶部。当画面底部温度升高时,OB区域估算的黑电平偏低,导致底部暗部发紫。后来我们改用多区域OB加权平均,才解决了这个问题。

4.2.1 OB区域的典型布局

  • 顶部OB行:最常见,2-16行,位于有效像素上方
  • 底部OB行:部分传感器提供,用于补偿垂直温度梯度
  • 左右OB列:较少见,用于水平方向校正
  • 嵌入式OB像素:分散在有效像素阵列中,用于局部校正

4.2.2 OB提取的工程要点

  1. 剔除坏点:OB区域也可能有缺陷像素,需要先做坏点检测
  2. 统计方法:我习惯用中位数而非均值,对异常值更鲁棒
  3. 行/列独立统计:每行OB单独计算,保留行间差异信息
  4. 时间滤波:对OB统计值做IIR滤波,防止单帧跳变
// OB区域提取伪代码
for each row in OB_rows:
    // 剔除坏点(亮度超出3σ的像素)
    valid_pixels = reject_outliers(OB_row_data, sigma=3.0)
    // 计算该行OB中位数
    row_black_level[row] = median(valid_pixels)
    // 时间滤波(α=0.1)
    filtered_black_level[row] = α * row_black_level[row] + (1-α) * filtered_black_level[row]

// 全局黑电平 = 所有OB行的加权平均
global_black_level = weighted_average(filtered_black_level, weights=[1.0, 1.0, ...])

我的经验:OB区域的权重不要平均分配。靠近有效像素区域的OB行,权重应该更高。我曾经用抛物线权重分配,让中间OB行权重最大,边缘OB行权重递减,效果比均匀加权好很多。

4.3 FPN校正算法:消除固定模式噪声

FPN(Fixed Pattern Noise)是暗电流的像素间差异造成的。它不像随机噪声那样随时间变化,而是固定在每个像素上。你拍一张全黑图,放大到100%看,那些固定的亮暗条纹或颗粒,就是FPN。

FPN校正的核心思路很简单:预先测量每个像素的暗响应,然后在实际拍摄时减去这个偏移量。但实现起来,坑不少。

4.3.1 单帧校正 vs 多帧校正

方法 原理 优点 缺点
单帧OB校正 用当前帧OB区域估算全局黑电平,统一减去 实时性好,无存储开销 无法消除像素级FPN
多帧平均校正 拍摄多帧全黑图,平均后作为校正模板 可消除像素级FPN 需要存储模板,温度变化后失效
混合校正 多帧模板 + 实时OB偏移调整 兼顾精度和温度适应性 算法复杂度高

注意:多帧平均校正的模板,必须在完全相同的温度和增益条件下采集。我曾经在实验室25°C下做了完美的FPN校正模板,结果到户外35°C时,暗部出现了明显的网格状噪声。原因就是温度变了,暗电流分布也变了。

4.3.2 行噪声与列噪声的独立处理

行噪声和列噪声是FPN的两种特殊形式。它们不是单个像素的差异,而是整行或整列像素的公共偏移。

  • 行噪声:通常由读出电路的行缓冲器不一致引起,表现为水平条纹
  • 列噪声:通常由列放大器或ADC的偏移差异引起,表现为垂直条纹

我处理行噪声的方法很简单:对每一行,计算该行所有像素的均值,然后减去这个均值与全局均值的差值。列噪声同理。但要注意,这个操作必须在减完黑电平之后做,否则会引入新的偏置。

// 行噪声校正
for each row in image:
    row_mean = mean(pixels_in_row)
    global_mean = mean(all_pixels)
    row_offset = row_mean - global_mean
    for each pixel in row:
        pixel = pixel - row_offset

// 列噪声校正
for each col in image:
    col_mean = mean(pixels_in_col)
    global_mean = mean(all_pixels)
    col_offset = col_mean - global_mean
    for each pixel in col:
        pixel = pixel - col_offset

避坑指南:行噪声和列噪声校正的顺序有讲究。我建议先做行噪声,再做列噪声。因为行噪声校正会改变每行的均值,如果先做列噪声,行噪声校正时又会引入新的列方向误差。嗯,这个顺序我踩过坑才总结出来的。

4.4 温度补偿策略:让校正随温度自适应

温度是黑电平校正最大的敌人。暗电流随温度变化,FPN模板也会随温度漂移。如果不做温度补偿,你会发现相机在冷启动和长时间工作后的黑电平完全不同。

4.4.1 温度传感器的布局

我建议在传感器附近至少放置两个温度传感器:一个靠近传感器中心,一个靠近边缘。为什么?因为传感器中心的温度通常比边缘高2-5°C,这个温差会导致黑电平的梯度分布。

4.4.2 温度补偿的三种策略

  1. 查表法:预先在多个温度点下采集黑电平数据,建立温度-黑电平映射表。运行时根据当前温度查表插值。
  2. 线性拟合:假设暗电流与温度呈线性关系(在一定范围内),用两个温度点的数据拟合出斜率,实时计算。
  3. 模型驱动:建立暗电流的物理模型,包含温度、曝光时间、增益等参数,用模型预测黑电平。
策略 精度 实现复杂度 适用场景
查表法 高(插值后) 低 温度范围固定,存储空间充足
线性拟合 中等 低 温度变化范围小,对精度要求不高
模型驱动 高 高 宽温度范围,高精度要求

我的推荐:对于量产产品,我倾向于查表法+线性插值。查表法在标定阶段工作量较大,但运行时稳定可靠。模型驱动虽然精度高,但参数标定复杂,而且不同芯片的模型参数可能不一致,量产一致性难以保证。

4.4.3 温度补偿的工程实现

实际项目中,温度补偿不是单独做的,而是与OB提取、FPN校正联动。我的做法是:

  1. 用OB区域实时估算当前帧的黑电平基线
  2. 用温度传感器获取当前温度,查表得到温度补偿系数
  3. 用补偿系数调整FPN校正模板的偏移量
  4. 最终黑电平 = OB基线 + 温度补偿量

这样做的好处是:OB基线负责跟踪快速变化(比如场景亮度突变导致的电源波动),温度补偿负责跟踪慢速变化(比如传感器长时间工作后的温升)。两者互补,覆盖了黑电平漂移的所有因素。

注意:温度补偿系数不是全局统一的。不同像素区域的温度系数可能有差异,尤其是大尺寸传感器。我建议将传感器划分为多个区域,每个区域独立做温度补偿。区域划分的粒度,取决于传感器尺寸和温度梯度的大小。

黑电平与FPN校正流程 传感器原始数据 OB区域提取与统计 FPN校正模板 温度补偿系数 黑电平校正与FPN消除 校正后图像数据 OB提供实时基线 温度补偿修正漂移

这张图展示了黑电平与FPN校正的完整流程。你看,OB提取和温度补偿是并行的两条线,最终合并到校正环节。这个结构我用了很多年,稳定可靠。

最后说一句:黑电平校正看似简单,但它是整个图像质量的基础。基础没打好,后面的白平衡、色彩校正、伽马曲线都会跟着跑偏。我见过太多团队花大量精力调色彩、调锐度,结果暗部噪声和偏色问题一直解决不了,最后发现是黑电平校正没做好。嗯,基础工作,值得花时间。