黑电平校正原理:暗电流与光响应非均匀性

黑电平校正,简称BLC。说白了,就是让传感器在没光的时候,输出值归零。

你可能会问:传感器没光不就是全黑吗?输出应该是0才对啊。嗯,理想情况确实如此。但现实世界哪有这么完美?我做过这么多项目,还没见过哪颗传感器能在全黑下输出0的。

暗电流:传感器天生的"漏电"问题

先说说暗电流。这玩意儿是半导体器件的物理特性,没法完全消除。

每个像素点其实就是一个光电二极管。光照时产生光生电子,没光时呢?热激发也会产生电子。温度越高,热激发越强,暗电流就越大。

我在项目中遇到过这样的情况:夏天户外测试,传感器温度飙到60多度,暗电流直接翻了好几倍。画面暗部全是偏红的,校正参数完全失效。后来才意识到,暗电流是随温度变化的,不能用一个固定值去校正。

暗电流的几个特点:

  • 温度敏感:每升高10度,暗电流大约翻一倍
  • 曝光时间相关:曝光越长,积累的暗电荷越多
  • 像素差异:不同像素的暗电流大小不一样

光响应非均匀性:像素之间的"性格差异"

光响应非均匀性,英文叫PRNU。这名字听着挺唬人,其实意思很简单:同样光照下,不同像素的输出值不一样。

为什么会这样?你想想看,一颗几百万像素的传感器,每个像素的尺寸、掺杂浓度、量子效率都不可能完全一致。就像同一批生产的灯泡,亮度也会有细微差别。

PRNU主要分两种:

  • 固定模式噪声(FPN):每个像素的偏移量是固定的,不随光照变化
  • 光响应非均匀性(PRNU):增益差异,光照越强越明显

黑电平校正主要处理的是FPN这部分。PRNU通常留给后面的增益校正去处理。

黑电平的数学模型

搞清楚了物理原理,咱们来看看数学怎么描述。

一个像素的输出值,可以拆解成三部分:

Pixel_out = Signal + Dark_current + FPN_offset

其中:

  • Signal:真实的光信号,我们想要的部分
  • Dark_current:暗电流贡献,与曝光时间和温度相关
  • FPN_offset:固定模式噪声,每个像素独有的偏移

黑电平校正的目标,就是把Dark_current和FPN_offset去掉,只保留Signal。

实际工程中,我们通常用一个更简单的模型:

Pixel_corrected = Pixel_raw - BLC_value

这个BLC_value怎么来的?一般有两种方式:

  • OB像素法:传感器边缘有被遮挡的像素,它们的输出就是黑电平参考值
  • 暗场标定法:盖上镜头盖拍一张全黑图,统计平均值作为BLC

重要提醒:OB像素法更实用,因为它能实时跟踪温度变化。暗场标定法只适合实验室环境,温度一变就不准了。

校正的基本公式

好了,到了动手环节。黑电平校正的公式其实很简单:

// 全局校正:所有像素减去同一个值
for (int i = 0; i < width * height; i++) {
    pixel[i] = pixel[i] - blc_value;
    // 防止下溢
    if (pixel[i] < 0) pixel[i] = 0;
}

这是最基础的版本。但我建议你别这么用,原因有二:

  1. 不同通道的BLC不一样:R、Gr、Gb、B四个通道,暗电流特性不同
  2. 不同位置的BLC也不一样:传感器边缘和中心的暗电流有差异

我个人习惯用通道独立的BLC:

// 通道独立的BLC校正
// blc_r, blc_gr, blc_gb, blc_b 分别对应四个通道
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        int idx = y * width + x;
        if ((y % 2 == 0) && (x % 2 == 0)) {
            // R通道
            pixel[idx] = pixel[idx] - blc_r;
        } else if ((y % 2 == 0) && (x % 2 == 1)) {
            // Gr通道
            pixel[idx] = pixel[idx] - blc_gr;
        } else if ((y % 2 == 1) && (x % 2 == 0)) {
            // Gb通道
            pixel[idx] = pixel[idx] - blc_gb;
        } else {
            // B通道
            pixel[idx] = pixel[idx] - blc_b;
        }
        // 钳位到有效范围
        pixel[idx] = CLAMP(pixel[idx], 0, max_value);
    }
}

经验之谈:我曾经在调试一款手机摄像头时,发现画面偏绿。查了半天,原来是Gr和Gb的BLC值搞反了。这两个通道虽然都是绿色,但暗电流特性确实有差异,千万别偷懒用同一个值。

BLC与动态范围的关系

这个知识点容易被忽略,但它真的很重要。

动态范围,简单说就是传感器能同时记录的最亮和最暗的比值。BLC校正做不好,动态范围直接缩水。

举个例子:

场景 BLC值 有效信号范围 动态范围损失
理想情况 0 0 ~ 1023 (10bit) 0%
BLC偏大 64 0 ~ 959 6.25%
BLC偏小 32 0 ~ 991 3.125%

看到没?BLC值越大,暗部被抬得越高,亮部的可用范围就越小。动态范围就这么被压缩了。

更严重的问题是什么?

  • BLC偏大:暗部噪声被放大,画面发灰,对比度下降
  • BLC偏小:暗部细节丢失,出现黑切现象

避坑指南:我曾经在一个安防项目中,为了追求暗部细节,把BLC值设得偏小。结果晚上监控画面暗部全是黑的,什么也看不清。后来才发现,BLC偏小导致暗部信号被截断了,信息全丢了。校正不是越小越好,要找到那个平衡点。

那怎么找到合适的BLC值?我的做法是:

  1. 先拍一张全黑图,统计每个通道的均值
  2. 再拍一张均匀灰卡,看校正后的线性度
  3. 微调BLC值,直到暗部噪声和动态范围达到平衡

嗯,这里要注意一点:BLC校正必须在RAW域进行,而且要在其他校正之前。顺序搞错了,后面的白平衡、伽马校正都会受影响。

最后总结一下我的经验:黑电平校正看似简单,就是个减法操作。但要做好,得理解暗电流的物理特性,得考虑温度和曝光时间的影响,还得平衡动态范围和噪声。别小看这一步,它直接决定了整个ISP pipeline的底子好不好。