黑电平校正:从物理机理到FPGA实现

黑电平校正,听起来好像是个小问题?

我刚开始做ISP的时候,也这么想。直到有一次,客户拿着一张纯黑图过来,说你们这相机拍出来怎么是灰的?我一看,暗部像素值居然有50多。嗯,从那以后,我再也不敢小看黑电平校正了。

黑电平是怎么来的?

说白了,黑电平就是传感器在完全无光的情况下,依然输出的信号值。

你想想看,一个理想的传感器,没光就应该输出0。但现实世界哪有那么完美?

黑电平的产生主要有三个来源:

  • 暗电流:硅基半导体在热激发下,即使没有光子入射,也会产生电子-空穴对。温度每升高6-8度,暗电流就翻一倍。我在项目中遇到过,夏天户外35度时,黑电平比实验室25度时高了整整一倍。
  • 读出噪声:像素电荷在转移、放大、模数转换过程中,电路本身会引入偏移。这个偏移量是固定的,跟温度关系不大。
  • ADC偏移:模数转换器为了避开近地噪声,通常会人为加一个偏置。比如12位ADC,可能把0点设在64附近。

核心结论:黑电平 = 暗电流(温度相关) + 读出噪声(固定) + ADC偏移(固定)

暗场采集:你得知道怎么拍

要校正黑电平,首先得知道黑电平是多少。这就需要采集暗场数据。

暗场采集的方法,我总结了几条经验:

  1. 完全遮光:镜头盖盖好,最好用黑色遮光布包住镜头。我见过有人用手捂着,结果手指缝漏光,采集出来的暗场全是条纹。
  2. 相同曝光参数:暗场采集时,曝光时间、增益、帧率必须和实际拍摄一致。因为暗电流跟曝光时间成正比。
  3. 多帧平均:单帧暗场噪声太大,建议采集16-64帧做平均。我习惯用32帧,效果和64帧差不多,但省时间。
  4. 温度控制:如果产品工作温度范围宽,建议在多个温度点采集暗场。比如25℃、45℃、65℃各采一组。

小技巧:暗场采集时,把传感器温度记录下来。这样后期做温度补偿时,就有数据支撑了。

全局黑电平校正:简单粗暴

全局校正,就是整幅图像减去同一个值。公式很简单:

// 全局黑电平校正
uint16_t black_level = 64;  // 从暗场数据统计得到
for (int i = 0; i < width * height; i++) {
    pixel[i] = (pixel[i] > black_level) ? (pixel[i] - black_level) : 0;
}

这种方法的好处是快,一个减法就搞定。但问题也很明显——它假设所有像素的黑电平都一样。

实际上呢?传感器边缘和中心的暗电流往往不同。我测过一款CMOS,左上角黑电平是62,右下角是68。用全局校正,一边偏暗一边偏亮。

注意:全局校正只适用于黑电平一致性好的传感器。如果你发现校正后图像有区域性偏色,那就得考虑局部校正了。

局部黑电平校正:精细活

局部校正,说白了就是给每个像素或每个区域一个独立的黑电平值。

常用的方法有两种:

1. 基于OB像素的实时校正

传感器通常会在像素阵列边缘预留一些光学黑像素(OB,Optical Black)。这些像素被金属层遮挡,不感光,只反映暗电流。

算法流程:

// 基于OB像素的局部校正
// 假设每行有16个OB像素在左侧
for (int row = 0; row < height; row++) {
    // 计算当前行OB像素均值
    uint32_t ob_sum = 0;
    for (int col = 0; col < 16; col++) {
        ob_sum += ob_pixels[row][col];
    }
    uint16_t row_black = ob_sum / 16;
    
    // 用该行的黑电平校正整行
    for (int col = 16; col < width + 16; col++) {
        pixel[row][col] = (pixel[row][col] > row_black) ? 
                          (pixel[row][col] - row_black) : 0;
    }
}

2. 基于暗场标定的二维校正

这种方法更精确,但需要预先存储一张暗场图像。

我习惯的做法是:

  • 采集暗场图像D(x,y),做32帧平均
  • 对D(x,y)做高斯滤波,去除随机噪声
  • 实际校正时:P_out(x,y) = P_in(x,y) - D(x,y)

经验之谈:二维校正的效果最好,但需要额外存储一帧图像。对于资源紧张的嵌入式系统,可以用分块校正——把图像分成16x16的块,每个块存一个黑电平值,块内做双线性插值。

FPGA实现要点

在FPGA上实现黑电平校正,有几个坑我得提醒你:

1. 流水线设计

校正是一个像素级操作,非常适合流水线。我通常这样设计:

// 流水线阶段
Stage 1: 读取像素值 + 读取对应黑电平值
Stage 2: 减法运算
Stage 3: 钳位到0(防止下溢)
Stage 4: 输出

2. 存储资源

如果是基于OB像素的行校正,只需要一个行缓存存OB均值。如果是二维校正,需要存整帧暗场数据。

我建议:

  • 行校正:用BRAM存256个16位值,够用
  • 二维校正:用DDR3/DDR4存暗场数据,FPGA内部只做缓存

3. 时序约束

减法器在FPGA上延迟很小,但要注意数据对齐。特别是当黑电平值和像素值来自不同时钟域时,一定要做同步处理。

曾经踩过的坑:有一次我忘了对OB像素做同步,结果每行校正用的黑电平是上一行的值。图像出来是一行亮一行暗,像斑马线一样。查了两天才找到原因。

4. 资源估算

校正方式 LUT BRAM DSP
全局校正 ~50 0 0
行校正(OB) ~200 1 0
二维校正 ~500 2-4 0

本章知识体系

下面这张图,是我画的黑电平校正整体流程。你可以看到从物理机理到FPGA落地的完整链路:

黑电平校正知识体系 黑电平产生机理 暗电流(温度相关) 读出噪声(固定偏移) ADC偏置(人为加入) 暗场采集方法 完全遮光 · 相同曝光参数 多帧平均 · 温度控制 校正算法 全局校正(单值减法) 局部校正(OB像素) 二维校正(暗场标定) FPGA实现要点 流水线设计 存储资源规划 时序约束 资源估算

这张图把黑电平校正的四个环节串起来了。从产生机理出发,到采集方法,再到算法选择,最后落地到FPGA。每个环节都有对应的技术要点。

我的建议:刚接触黑电平校正时,先从全局校正入手。跑通流程后,再根据实际效果决定要不要升级到局部校正。别一上来就搞二维校正,容易把自己绕进去。

好了,黑电平校正就讲到这里。记住一句话:暗部细节好不好,黑电平校正见分晓。这个环节做扎实了,后面的白平衡、去马赛克才能发挥出效果。


公众号:蓝海资料掘金营,微信deep3321