3、黑电平校正:从概念到实战
黑电平校正,听起来挺专业,说白了就是告诉相机:「喂,真正的黑色长什么样?」
我刚开始接触RAW处理时,觉得这步可有可无。直到有一次,我处理一张夜景长曝光照片,暗部区域怎么调都偏紫,后来才发现是黑电平没校正好。嗯,从那以后,我再也不敢跳过这个环节了。
3.1 黑电平到底是什么?
先问一个问题:传感器在完全无光的情况下,输出的数字值应该是多少?
理想情况是0。但现实是,传感器本身有暗电流——即使盖上镜头盖,像素也会因为热噪声、读出噪声产生一个非零的底数。这个底数,就是黑电平。
核心定义:黑电平(Black Level)是传感器在零光照条件下输出的数字信号值,通常以DN(Digital Number)或ADC码值表示。
不同传感器、不同增益、不同温度下,黑电平都不一样。我见过最夸张的案例——某款工业传感器在60°C环境下,黑电平漂了将近40个DN。你想想看,这要是没校正,暗部细节全被淹没了。
3.2 为什么要做黑电平校正?
不做黑电平校正,会出什么问题?我列几个实际踩过的坑:
- 暗部偏色:黑电平没减干净,暗部残留非零值,导致偏紫或偏绿。我在手机ISP调试时遇到过,暗部总是偏品红,查了三天才发现是黑电平没对齐。
- 动态范围损失:黑电平抬高了,有效信号范围被压缩。本来12bit传感器能到4095,结果黑电平占了64,实际可用范围就少了1.5%。
- 白平衡不准:黑电平对RGB通道的影响不一致,会导致白平衡计算偏差。尤其是低光照下,这个误差会被放大。
- 降噪算法失效:很多降噪算法假设黑电平已经归零。如果没校正,噪声模型就错了,降噪效果大打折扣。
避坑指南:我曾经接手过一个项目,算法团队直接拿原始RAW数据做处理,没做黑电平校正。结果在暗光场景下,信噪比比预期低了3dB。后来加上校正,指标立刻达标。记住:黑电平校正是最基础、最容易被忽略的一步。
3.3 不同传感器的黑电平值获取方法
获取黑电平值,没有万能公式。不同传感器厂商、不同架构,方法差异很大。我按实际项目经验,把常见方法分成三类:
3.3.1 方法一:片上黑像素(OB Pixels)
这是最主流的方法。传感器会在感光区域周围布置一些被金属遮挡的像素,这些像素永远接收不到光,专门用来测量黑电平。
| 传感器类型 | OB像素位置 | 典型数量 | 注意事项 |
|---|---|---|---|
| Sony IMX系列 | 左侧和顶部 | 每行/列 4~16个 | 注意区分有效OB和无效边界 |
| Onsemi PYTHON系列 | 四周环绕 | 每行/列 8~32个 | 温度补偿很重要 |
| CMV系列 | 右侧和底部 | 每行/列 4~8个 | 高速模式下OB值会波动 |
具体做法:
- 从RAW数据中提取OB像素区域
- 计算OB像素的平均值(或中位数,防止坏点干扰)
- 用这个值作为当前帧的黑电平
// 伪代码:从RAW中提取OB像素并计算黑电平
uint16_t calculateBlackLevel(uint16_t* rawFrame, int width, int height, int obLeft, int obTop, int obWidth, int obHeight) {
uint64_t sum = 0;
int count = 0;
for (int y = obTop; y < obTop + obHeight; y++) {
for (int x = obLeft; x < obLeft + obWidth; x++) {
// 跳过坏点(阈值判断)
if (rawFrame[y * width + x] < OB_BAD_PIXEL_THRESHOLD) {
sum += rawFrame[y * width + x];
count++;
}
}
}
return (count > 0) ? (uint16_t)(sum / count) : DEFAULT_BLACK_LEVEL;
}
个人经验:我习惯用中位数代替平均值。因为OB像素里偶尔会有坏点,平均值会被拉偏。中位数更鲁棒。另外,建议每帧都重新计算,不要用固定值——温度变化会漂移。
3.3.2 方法二:暗帧采集(Dark Frame)
有些传感器没有OB像素,或者OB像素数量太少。这时候就需要拍暗帧。
做法很简单:盖上镜头盖,在相同曝光参数下拍一张全黑图像,然后取全图平均值作为黑电平。
但要注意几个坑:
- 温度要一致:暗电流随温度指数增长。我试过在25°C和45°C下采集暗帧,黑电平差了20个DN。
- 增益要匹配:不同ISO下的黑电平不同。建议每个ISO档位都单独采集。
- 曝光时间要相同:长曝光下暗电流积累更多,黑电平会更高。
3.3.3 方法三:寄存器读取(Register Read)
部分高端传感器会把黑电平值直接写在寄存器里,或者通过SPI/I2C接口读取。
我遇到过一款工业线阵传感器,它的黑电平值存储在0x3A和0x3B两个寄存器中,读取后拼接即可:
// 从传感器寄存器读取黑电平值
uint16_t readBlackLevelFromRegister(int i2c_fd, uint8_t sensor_addr) {
uint8_t reg_high = 0x3A;
uint8_t reg_low = 0x3B;
uint8_t high_byte, low_byte;
// 读取高8位
i2c_write(i2c_fd, sensor_addr, ®_high, 1);
i2c_read(i2c_fd, sensor_addr, &high_byte, 1);
// 读取低8位
i2c_write(i2c_fd, sensor_addr, ®_low, 1);
i2c_read(i2c_fd, sensor_addr, &low_byte, 1);
return (high_byte << 8) | low_byte;
}
注意:寄存器值通常是出厂标定值,不一定反映当前实际状态。我建议用寄存器值做初始参考,再用OB像素或暗帧做实时修正。
3.4 黑电平校正的完整流程
把上面这些串起来,一个完整的黑电平校正流程应该是这样的:
3.5 校正后的验证方法
校正做没做对,怎么验证?我一般用三个指标:
- 暗场均值:盖上镜头盖拍一张,校正后全图均值应接近0。允许±1 DN的波动。
- 暗场标准差:校正后暗场的标准差应该只反映读出噪声,如果标准差异常大,说明校正不彻底。
- 暗部线性度:用均匀光源拍不同亮度的灰阶图,检查暗部区域的响应是否线性。如果低光区域出现弯曲,黑电平可能没减干净。
我的习惯:每次换传感器型号或改硬件设计,我都会重新做一遍黑电平标定。别偷懒,不同批次、不同PCB布局,黑电平都可能不一样。
好了,黑电平校正就讲到这里。记住一句话:黑电平是RAW处理的基石,基石不稳,上层算法全白搭。