3、RAW域基础:Bayer格式、黑电平校正(BLC)、线性化处理

好,我们进入RAW域。这是ISP流水线的起点,也是很多问题的根源。说白了,Sensor送出来的原始数据长什么样,我们怎么把它“洗干净”,就是这一章要聊的事。

3.1 Bayer格式:Sensor的“马赛克”世界

大部分单颗CMOS sensor,每个像素只能感知光的强度,分不清颜色。那彩色照片怎么来的?靠滤光片阵列——Bayer pattern。每个像素头顶盖一个红、绿或蓝的滤光片,只让对应颜色的光通过。

最常见的排列是RGGB,也就是一个2x2单元里,1个红、2个绿、1个蓝。为什么绿点多?因为人眼对绿色最敏感,sensor也这么设计,能模拟人眼的亮度感知。

关键点:Bayer raw数据不是“图像”,是“马赛克”。每个像素只有一种颜色信息,另外两种颜色要靠后续的Demosaic插值算出来。

我在项目中遇到过一种情况:调试时发现画面边缘有彩色条纹,查了半天,结果是sensor输出的Bayer排列顺序跟ISP配置的不一致。sensor是BGGR,我配成了RGGB,整个颜色通道全乱了。嗯,这个坑踩过一次就记住了。

常见Bayer排列

排列名称 2x2单元顺序 常见场景
RGGB R G / G B 最主流,Sony、OmniVision常用
BGGR B G / G R 部分Samsung、安森美sensor
GRBG G R / B G 少数老款sensor
GBRG G B / R G 极少数特殊sensor

你想想看,如果sensor输出的是10bit数据,那每个像素的值范围就是0~1023。但别急着用,这数据里藏着两个问题:暗电流和响应非线性。

3.2 黑电平校正(BLC):把“假黑”拉回真黑

sensor在完全无光的情况下,像素依然会输出一个非零值。这叫黑电平,也叫暗电流。原因嘛,主要是sensor本身的电子噪声和热噪声。如果不校正,画面看起来会发灰,暗部细节被抬升,对比度也受影响。

BLC的做法很简单:从每个像素值里减去一个固定的黑电平值。但这个值怎么定?

  • OB(Optical Black)行/列:sensor边缘有被遮挡的像素,它们不感光,输出值就是黑电平参考。我习惯取这些像素的平均值作为减去的offset。
  • 寄存器配置:有些sensor允许直接读黑电平寄存器,或者通过I2C写死一个值。
  • 动态BLC:环境温度变化时,暗电流会漂。我见过一些方案会实时监测OB区域,动态调整offset。

小技巧:调试时,先拍一张全黑图(盖上镜头盖),看raw数据的直方图。如果峰值不在0附近,说明BLC没做对。我曾经遇到sensor的OB区域有坏像素,导致BLC算出来的offset偏大,画面暗部被削没了。后来加了中值滤波才解决。

BLC的数学表达很简单:

// 伪代码示例
uint16_t raw_pixel = sensor_read(x, y);
uint16_t black_level = get_ob_average(); // 从OB区域取平均
uint16_t corrected = (raw_pixel > black_level) ? (raw_pixel - black_level) : 0;

注意,减完后的值不能为负,所以要做clamp到0。另外,有些sensor的BLC是在硬件层面做的,raw数据出来已经是校正过的。但我不建议完全信任硬件,最好自己验证一下。

3.3 线性化处理:让响应曲线变直

理想情况下,sensor的响应应该是线性的:光强翻倍,输出值也翻倍。但现实不是这样。尤其是高光区域,sensor会逐渐饱和,响应曲线开始弯曲。如果不做线性化,后续的AWB、CCM、Gamma都会算错。

线性化处理的目的,就是把sensor的非线性响应映射回线性空间。怎么做?

  • 查表法(LUT):sensor厂商会提供一条响应曲线,我们建一个映射表,把非线性值映射到线性值。这是最常用的方法。
  • 分段线性拟合:把曲线分成几段,每段用一条直线近似。计算量小,但精度取决于分段数。
  • 多项式拟合:用高次多项式拟合整条曲线。精度高,但计算量大,嵌入式平台慎用。

注意:线性化必须在BLC之后做。因为黑电平偏移会改变响应曲线的形状,先减掉黑电平,再做线性化,顺序不能乱。

我个人习惯的做法是:先拍一组不同曝光时间的灰阶卡图像,提取每个灰阶的raw值,跟标准光强做对比,拟合出一条校正曲线。然后生成一个12bit到12bit的LUT,直接查表。

举个例子,假设sensor的响应在高端有压缩:

// 线性化LUT示例(12bit输入,12bit输出)
uint16_t linearization_lut[4096];

// 假设sensor响应:输入2000时,实际光强对应线性值应为1800
// 那么 LUT[2000] = 1800
// 其他点类似,通过插值或拟合得到

uint16_t linearized = linearization_lut[blc_corrected_pixel];

3.4 本章知识结构

我把这三步的关系画了张图,方便你理解整个流程:

RAW域预处理流程 Sensor Raw Bayer马赛克数据 黑电平校正 减去暗电流offset 线性化处理 LUT/拟合校正 Bayer排列示例 R G G B → 每个像素单色 → 绿像素占一半 → 需Demosaic恢复全彩 BLC关键点 • 参考OB行/列平均值 • 减后clamp到0 • 注意温度漂移 线性化关键点 • 必须在BLC之后 • 常用LUT查表法 • 高光区域尤其重要 输出:干净的线性RAW数据 后续AWB、CCM、Demosaic等模块都依赖这个线性输入

3.5 常见问题与避坑

做RAW域调试时,有几个问题我反复遇到,列出来给你参考:

  • BLC后出现负值:如果OB区域的像素有坏点,平均值会偏高,导致正常像素减完变负。我建议先对OB区域做坏点剔除,再算平均值。
  • 线性化后出现条纹:LUT的精度不够,或者分段线性拟合的分段点选得不好。可以试试增加LUT的bit深度,或者用平滑样条拟合。
  • Bayer排列搞错:画面整体偏色,或者出现彩色摩尔纹。用一张纯色图(比如白墙)拍一下,看raw数据的2x2单元顺序,跟sensor datasheet对一下。
  • 温度变化导致BLC漂移:sensor刚上电和运行半小时后的黑电平可能差好几个LSB。我习惯在sensor稳定后再做一次BLC校准,或者用动态BLC方案。

一句话总结:Bayer格式是sensor的原始语言,BLC去掉暗电流的“假黑”,线性化把弯曲的响应拉直。这三步做扎实了,后面的ISP模块才能正常工作。