RAW图与Bayer格式:从传感器到数字图像的第一次握手

做ISP这些年,我经常被问到同一个问题:“RAW图到底是个什么东西?” 很多人以为相机拍出来就是JPEG,其实那已经是ISP处理完的结果了。真正的原始数据,就是RAW图。今天咱们就聊聊这个ISP的起点——Bayer阵列、RAW的位深格式,还有黑电平和白平衡这两个最基础的操作。

Bayer阵列:为什么是RGGB?

先说说传感器。CMOS传感器本身是“色盲”的——它只能感知光的强度,分不清颜色。那怎么得到彩色图像呢?答案就是在每个像素上覆盖一层滤色片。最经典的方案就是Bayer阵列。

Bayer阵列的排列方式很简单:每2x2的像素块里,有1个红色、2个绿色、1个蓝色。为什么绿色多一个?因为人眼对绿色最敏感。说白了,这是模仿人眼视锥细胞的分布。

核心要点: Bayer阵列不是“丢失”了颜色信息,而是用空间换色彩。每个像素只记录一种颜色,剩下的颜色靠插值算出来。

我刚开始做ISP时,总觉得RGGB排列有点浪费——明明可以每个像素都记录全色嘛。后来才明白,如果每个像素都做三层滤色片,感光效率会暴跌,暗光下根本没法用。Bayer阵列是在分辨率和感光度之间找平衡。

常见的Bayer模式有这几种:

模式排列常见场景
RGGB第一行RGRG,第二行GBGB最主流,90%的传感器用这个
BGGR第一行BGBG,第二行GRGR部分索尼传感器
GRBG第一行GRGR,第二行BGBG少数工业相机

你可能会问:“为什么有这么多变种?” 其实只是起始像素偏移不同,算法上没本质区别。但做硬件设计时,这个偏移会影响后续的demosaic模块,所以得提前确认。

RAW10 / RAW12 / RAW14:位深到底差多少?

说完了颜色排列,再聊聊数据精度。RAW图每个像素的亮度值用多少bit来表示,这就是位深。常见的位深有10bit、12bit、14bit。

我举个例子你就明白了:

  • RAW10:每个像素0-1023,共1024个灰度级
  • RAW12:每个像素0-4095,共4096个灰度级
  • RAW14:每个像素0-16383,共16384个灰度级

数字越大,能记录的亮度细节越丰富。但代价也很直接——数据量翻倍。一张1200万像素的RAW10照片约15MB,换成RAW14就变成约21MB。

我的经验: 手机传感器现在普遍用RAW10,因为带宽和功耗受限。专业相机和工业相机用RAW12或RAW14。如果你做算法调试,建议先用RAW12——精度够用,数据量也不至于太大。

这里有个坑要注意:RAW位深和ADC位深不是一回事。传感器ADC可能是14bit,但输出时只取高10bit,这就是RAW10。我曾经在项目里被这个坑过——明明传感器标称14bit,读出来的RAW图却是10bit,查了半天才发现是寄存器配置错了。

// 伪代码:RAW10打包成16bit存储的常见方式
// 每个像素占10bit,两个像素拼成20bit,对齐到32bit
uint16_t pack_raw10(uint16_t pixel1, uint16_t pixel2) {
    // pixel1和pixel2都是10bit有效数据
    return (pixel1 << 6) | (pixel2 >> 4);
}
// 解包时反向操作
uint16_t unpack_raw10_high(uint16_t packed) {
    return (packed >> 6) & 0x3FF;
}
uint16_t unpack_raw10_low(uint16_t packed) {
    return (packed << 4) & 0x3FF;
}

黑电平:为什么像素值不是从0开始?

按理说,没有光照时像素值应该是0。但实际传感器有暗电流,即使完全遮光,也会读出非零值。这个非零值就是黑电平。

黑电平的典型值:

  • RAW10:通常64左右(对应0-1023范围)
  • RAW12:通常256左右(对应0-4095范围)
  • RAW14:通常1024左右(对应0-16383范围)

处理黑电平很简单:每个像素减去黑电平值。但要注意,减完后可能得到负数,需要做截断或钳位。

避坑指南: 我曾经在某个项目中,黑电平校准没做好,导致暗部出现偏色。后来发现是不同通道的黑电平不一样——R、G、B通道的暗电流有差异。正确的做法是每个通道单独校准黑电平。

// 黑电平校正:每个通道独立处理
void black_level_correction(uint16_t* raw, int width, int height, 
                            int bl_r, int bl_gr, int bl_gb, int bl_b) {
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int idx = y * width + x;
            // 根据Bayer模式判断当前像素属于哪个通道
            if (is_red(x, y)) {
                raw[idx] = (raw[idx] > bl_r) ? (raw[idx] - bl_r) : 0;
            } else if (is_green(x, y)) {
                // 注意:G通道分Gr和Gb,位置不同
                raw[idx] = (raw[idx] > bl_gr) ? (raw[idx] - bl_gr) : 0;
            } else {
                raw[idx] = (raw[idx] > bl_b) ? (raw[idx] - bl_b) : 0;
            }
        }
    }
}

白平衡:让白色回归白色

白平衡的本质是消除光源色温的影响。不同光源下,白色物体拍出来可能偏蓝或偏黄。白平衡就是通过调整R、G、B通道的增益,让中性色恢复中性。

白平衡的数学表达很简单:

R_out = R_in * gain_R
G_out = G_in * gain_G  // 通常G通道增益固定为1.0
B_out = B_in * gain_B

关键是怎么确定gain_R和gain_B。常见方法有:

  • 灰度世界法:假设场景平均色是灰色,计算R和B的增益
  • 白点法:找到场景中最亮的点,假设它是白色
  • 色温曲线法:查表得到不同色温下的增益值

注意: 白平衡是在RAW域做的,也就是在demosaic之前。因为Bayer格式下每个像素只有一种颜色,所以白平衡增益是按通道整体乘,而不是按像素乘。

我个人习惯用灰度世界法做快速白平衡,虽然精度一般,但胜在稳定。工业场景下,我更推荐用色温曲线法——提前标定好不同光源的增益,查表插值就行。

本章知识结构图

下面这张图把本章的核心逻辑串起来了:

RAW图与Bayer格式知识结构 RAW图 Bayer阵列 RAW位深格式 预处理操作 RGGB BGGR RAW10 RAW12 RAW14 黑电平校正 白平衡 核心流程 传感器输出Bayer RAW → 黑电平校正 → 白平衡增益 → Demosaic → 后续ISP处理 注意:黑电平和白平衡都在RAW域完成,是ISP流水线的第一步

小结

这一章我们聊了三个核心概念:

  1. Bayer阵列:用RGGB模式让传感器感知颜色,每个像素只记录一种颜色
  2. RAW位深:10bit、12bit、14bit决定了亮度精度,数据量和画质要权衡
  3. 黑电平与白平衡:黑电平消除暗电流偏移,白平衡校正光源色温

这些是ISP流水线的第一步。做算法调试时,我建议你先确保黑电平和白平衡正确,再往下走。否则后面的demosaic、降噪、锐化都会带着系统性偏差,调来调去都调不好。

嗯,RAW图这块内容其实挺多的,但核心就是这些。理解了Bayer格式和预处理操作,你就掌握了ISP的“原材料”特性。后面章节我们会一步步把这些RAW数据变成漂亮的彩色图像。