3. RAW图采集与解析:从传感器裸数据到可用的图像起点

各位做图像的朋友,大家好。今天我们来聊聊RAW图。很多刚入行的工程师觉得RAW图就是“原始数据”,直接拿来用就行。其实不然。RAW图是传感器最诚实的输出,但也是最“脏”的数据——里面藏着坏点、黑电平偏移、各种噪声。我做了这么多年调试,可以说一句:RAW图处理的好坏,直接决定了你后面ISP流水线的天花板。

这一章,我们聚焦三个核心问题:RAW图长什么样?怎么校验它是不是对的?以及,怎么把黑电平这个“基准”校准好。

3.1 RAW图格式:Bayer、RCCB与Quad Bayer

先说格式。传感器输出的RAW图,本质上就是一个二维数组,每个像素点只记录一个颜色通道的强度。但人眼需要RGB三个通道才能还原彩色,所以传感器前面得盖一层彩色滤光片阵列(CFA)。

最常见的,就是Bayer格式。

Bayer阵列以2x2为最小单元,包含1个R、2个G、1个B。为什么G有两个?因为人眼对绿色最敏感。我早期做安防摄像头时,用的就是标准Bayer。那时候觉得它简单可靠,直到后来接触了手机传感器,才发现世界远不止如此。

格式 最小单元 特点 典型应用
标准Bayer 2x2 (R,G,G,B) 最成熟,算法支持好 安防、工业相机
RCCB 2x2 (R,C,C,B) Clear像素进光量更大,低光性能好 车载、低光监控
Quad Bayer 4x4 (4R,8G,4B) 高像素合成,支持HDR和Binning 手机主摄

关键点:RCCB中的C(Clear)像素没有滤光片,进光量是普通像素的2-3倍。但代价是色彩容易偏,需要额外的色彩校正矩阵。我在一个车载项目里用过RCCB,低光效果确实好,但调试色彩一致性花了我整整两周。

Quad Bayer又是怎么回事?

说白了,就是把4个同色像素放在一起。比如一个2x2的红色块,实际上都是R。这样做的目的有两个:一是高像素模式下,通过Remosaic算法还原出真正的Bayer排列;二是在低光下,把4个同色像素合并成一个(Binning),信噪比直接提升6dB。嗯,手机厂商的“4800万像素”和“1200万像素”模式切换,就是靠这个实现的。

3.2 RAW数据校验与坏点标定

拿到RAW图的第一件事是什么?不是调参数,而是校验数据完整性。我见过太多人,花了半天调ISP参数,最后发现RAW图本身就有问题——数据位宽不对、通道顺序反了、甚至整帧都是黑的。

校验步骤,我建议按这个顺序来:

  1. 检查位宽:10bit还是12bit?用直方图看,如果最大值只有1023,那就是10bit;如果是4095,就是12bit。如果最大值是255,那可能是已经被压缩过了。
  2. 检查通道顺序:Bayer有RGGB、BGGR、GRBG、GBRG四种排列。随便取一个4x4区域,看R和B的位置对不对。我习惯用“R在左上角”作为参考。
  3. 检查坏点:坏点就是那些“不听话”的像素——要么一直亮(白点),要么一直暗(黑点),要么响应异常。

我的小技巧:拍一张均匀光照的灰卡,然后看每个像素的响应。如果某个像素的亮度偏离周围像素超过3个标准差,基本可以判定为坏点。别用肉眼去数,写个脚本自动跑。

坏点标定怎么做?

坏点标定分为静态标定和动态标定。静态标定是在产线上做的:拍全黑图和全白图,记录下所有异常像素的位置,生成一张坏点地图(Defect Map)。动态标定则是运行时实时检测,比如用中值滤波来判断当前像素是否异常。

// 坏点检测伪代码(C风格)
for each pixel (x, y) {
    // 取周围3x3邻域的中值
    median = getMedian(surrounding_3x3);
    // 如果当前像素与中值的差异超过阈值
    if (abs(pixel(x,y) - median) > threshold) {
        mark_as_defect(x, y);
        // 用中值替换
        pixel(x,y) = median;
    }
}

注意:动态坏点检测的阈值不能设得太低,否则会把正常纹理误判为坏点。我踩过这个坑——在拍树叶时,整张图被修成了“马赛克”。后来我把阈值设为当前区域标准差的4倍,才解决问题。

3.3 黑电平校准(OB Calibration)

黑电平,就是传感器在完全没有光照时输出的数字值。理论上应该是0,但实际上不是。因为传感器有暗电流、读出噪声、ADC偏移,所以黑电平通常是一个非零值,比如64(12bit下)。

为什么要校准黑电平?

你想想看,如果黑电平是64,而你直接拿原始数据去算颜色,那暗部的颜色就会偏。比如一个本该是黑色的物体,因为黑电平没减掉,结果变成了深灰色。更严重的是,黑电平还会随温度、增益变化——温度每升高10度,暗电流可能翻倍。

OB校准的两种方式:

方式 原理 优点 缺点
光学黑电平(OB) 传感器边缘有被遮挡的像素,取它们的平均值 实时、准确 需要硬件支持,OB区域可能受温度梯度影响
软件黑电平 拍全黑图,统计全图平均值 简单、无需硬件 不能实时更新,受环境温度影响大

我个人更倾向于使用OB方式。在项目中,我通常的做法是:取OB区域(一般是传感器边缘的4-8行像素)的平均值,然后对每个通道分别减去这个值。注意,R、G、B通道的黑电平可能不一样,要分开算。

一个常见的坑:OB区域也可能有坏点!如果OB区域里有一个亮坏点,那算出来的黑电平就会偏高,导致整张图偏暗。我曾经在一个项目中,发现暗部细节丢失,查了两天才发现是OB区域的一个坏点搞的鬼。解决方案:在计算OB平均值之前,先对OB区域做一次坏点剔除。

黑电平校准的完整流程:

  1. 从RAW图中提取OB区域像素(通常是前几行或后几行)。
  2. 对OB区域做坏点剔除(去掉最高和最低的5%)。
  3. 分别计算R、G、B通道的OB平均值。
  4. 从有效像素中减去对应的OB值。
  5. 对减完后的数据做Clamp,确保不出现负值。
// 黑电平校准示例(Python风格)
def ob_calibration(raw_frame, ob_rows=8):
    # 提取OB区域(假设OB在图像顶部)
    ob_region = raw_frame[:ob_rows, :]
    
    # 按通道分离(假设Bayer排列为RGGB)
    ob_r = ob_region[0::2, 0::2].mean()
    ob_g1 = ob_region[0::2, 1::2].mean()
    ob_g2 = ob_region[1::2, 0::2].mean()
    ob_b = ob_region[1::2, 1::2].mean()
    
    # 从有效像素中减去OB值
    raw_frame[ob_rows:, :] -= [ob_r, ob_g1, ob_g2, ob_b]
    
    # Clamp到0
    raw_frame = np.clip(raw_frame, 0, None)
    
    return raw_frame

进阶技巧:如果传感器支持,可以动态调整OB值。比如在增益变化时,OB值也会变化。我习惯做一个查找表(LUT),把增益和OB值对应起来,运行时查表校准。

本章知识体系

下面这张图,把RAW图采集与解析的核心流程串起来了。从传感器输出到可用的干净数据,每一步都不能少。

RAW图采集与解析流程 RAW图采集与解析核心流程 传感器原始输出 格式识别(Bayer/RCCB/Quad) 数据校验(位宽/通道/坏点) 黑电平校准(OB Calibration) 干净的RAW数据 → ISP 常见问题 • 位宽不匹配导致数据截断 • Bayer顺序搞反,颜色全乱 • OB区域坏点污染黑电平 • 动态坏点误判正常纹理 • 黑电平随温度漂移未补偿 前置准备 • 传感器驱动配置 • MIPI/LVDS接口调试 • 时钟与同步信号