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格式下每个像素只有一种颜色,所以白平衡增益是按通道整体乘,而不是按像素乘。
我个人习惯用灰度世界法做快速白平衡,虽然精度一般,但胜在稳定。工业场景下,我更推荐用色温曲线法——提前标定好不同光源的增益,查表插值就行。
本章知识结构图
下面这张图把本章的核心逻辑串起来了:
小结
这一章我们聊了三个核心概念:
- Bayer阵列:用RGGB模式让传感器感知颜色,每个像素只记录一种颜色
- RAW位深:10bit、12bit、14bit决定了亮度精度,数据量和画质要权衡
- 黑电平与白平衡:黑电平消除暗电流偏移,白平衡校正光源色温
这些是ISP流水线的第一步。做算法调试时,我建议你先确保黑电平和白平衡正确,再往下走。否则后面的demosaic、降噪、锐化都会带着系统性偏差,调来调去都调不好。
嗯,RAW图这块内容其实挺多的,但核心就是这些。理解了Bayer格式和预处理操作,你就掌握了ISP的“原材料”特性。后面章节我们会一步步把这些RAW数据变成漂亮的彩色图像。