4. RAW图基础:Bayer格式详解、RAW图存储与读取、黑电平与坏点校正原理

各位好,欢迎来到第四章。从这一章开始,我们正式进入ISP管线的「原材料」环节——RAW图。

很多刚入行的朋友会问我:为什么非要折腾RAW?直接拿RGB或者YUV图像做处理不行吗?我的回答是:不行。RAW图是传感器最原始的「供血」,所有后续的图像质量天花板,在RAW阶段就已经被决定了。你想想看,如果地基没打好,上面装修得再漂亮,也经不起推敲。

4.1 Bayer格式详解

先说Bayer格式。这是目前绝大多数CMOS传感器采用的色彩滤波阵列(CFA)方案。说白了,就是每个像素只能感知一种颜色——红、绿或蓝。那全彩图像怎么来的?靠猜,靠插值。

Bayer阵列最常见的模式是RGGB,也就是一个2x2的单元里,有1个R、2个G、1个B。为什么绿色多一个?因为人眼对绿色最敏感,传感器厂商就顺着这个特性来设计。我个人习惯把Bayer模式简称为「拜耳模式」,大家知道是一回事就行。

核心要点: Bayer图不是灰度图,也不是彩色图。它是「马赛克图」——每个像素只有一种颜色分量,另外两个颜色分量需要靠周围像素猜出来。

常见的Bayer排列有四种:

模式 2x2单元排列 常见传感器
RGGB R G / G B 索尼IMX系列、海思常用
BGGR B G / G R 部分安森美传感器
GRBG G R / B G 三星部分型号
GBRG G B / R G 较少见,但遇到过

我在项目中遇到过最坑的一次:海思平台默认配置的是RGGB,结果客户拿了一颗BGGR的传感器过来,没改配置直接出图。出来的画面偏色到离谱,红色和蓝色完全互换。排查了半天才发现是Bayer相位没对上。所以,拿到新传感器第一件事——确认Bayer排列,别想当然。

4.2 RAW图的存储与读取

RAW图怎么存?最常见的是两种格式:

  • Pack格式(打包):10bit数据用16bit存,高位对齐或低位对齐。优点是处理方便,缺点是浪费空间。
  • Unpack格式(解包):10bit数据紧挨着存,5个字节存4个像素。省空间,但读取时要做位运算拆包。

海思平台默认输出一般是16bit的Pack格式,也就是每个像素占2字节,但有效数据只有10bit或12bit。高位对齐时,低6位补0;低位对齐时,高6位补0。我个人习惯用高位对齐,因为做黑电平校正时直接减一个值就行,不用考虑移位。

小技巧: 读取RAW图时,先确认一下字节序(Endian)。海思平台默认是小端(Little Endian),但有些PC工具是大端。读出来数据全是乱的,别急着怀疑算法,先检查字节序。

下面是一段读取10bit Pack格式RAW图的示例代码:

// 假设图像宽高为 width x height,每个像素2字节,高位对齐
uint16_t* raw_data = (uint16_t*)malloc(width * height * 2);
fread(raw_data, 2, width * height, fp);

// 提取有效10bit数据
for (int i = 0; i < width * height; i++) {
    uint16_t pixel = raw_data[i] >> 6;  // 高位对齐,右移6位得到10bit
    // 此时 pixel 范围 0~1023
}

嗯,这里要注意:如果传感器输出的是12bit,那右移4位。别死记硬背,看规格书。

4.3 黑电平校正原理

黑电平(Black Level)是什么?说白了,就是传感器在完全无光环境下,理论上应该输出0,但实际上会输出一个非零值。这个值可能是64(10bit下),也可能是256(12bit下)。为什么会有这个偏移?

  • 传感器电路本身的暗电流
  • ADC的参考电压偏移
  • 温度变化引起的漂移

黑电平校正(BLC)就是把这个偏移减掉。公式很简单:

pixel_out = pixel_in - black_level;
if (pixel_out < 0) pixel_out = 0;  // 防止下溢

但实际项目中没这么简单。我曾经遇到过一个案例:某款传感器在高温下黑电平漂了20个DN(Digital Number),结果画面暗部发紫。排查后发现是黑电平没做温度补偿。后来我们在ISP的OTP里烧录了不同温度下的黑电平表,实时查表校正,问题才解决。

避坑指南: 黑电平校正一定要在坏点校正之前做。因为坏点检测依赖的是像素值,如果黑电平没减干净,坏点阈值会整体偏移,导致误检或漏检。我曾经因为顺序搞反,浪费了整整两天调试时间。

海思平台的黑电平校正通常放在ISP管线的第一步,也就是RAW域处理的最前端。参数可以从传感器的OTP或寄存器读取,也可以手动标定。标定方法很简单:盖上镜头盖,拍一张全黑图,取所有像素的平均值,就是黑电平。

4.4 坏点校正原理

坏点(Dead Pixel / Hot Pixel)是传感器的「胎记」。每个传感器或多或少都有坏点,只是数量不同。坏点分两种:

  • 死点(Dead Pixel):永远输出0或接近0,不响应光照。
  • 热点(Hot Pixel):永远输出最大值或接近最大值,像一颗「星星」。

坏点校正(DPC)的思路就两种:

  1. 静态校正:出厂时标定坏点坐标,存到OTP里,ISP直接查表替换。
  2. 动态校正:实时检测坏点,用周围像素插值替换。

海思平台两种都支持。我个人更推荐静态校正为主、动态校正为辅。为什么?因为动态校正算法再聪明,也难免误判——比如把星空中的亮点当成热点给抹掉了,那就尴尬了。

动态校正的常见算法是「中值滤波变种」:

// 以当前像素为中心,取3x3邻域
// 如果当前像素值与邻域中值的差超过阈值,则判定为坏点
uint16_t neighbors[8];
int idx = 0;
for (int dy = -1; dy <= 1; dy++) {
    for (int dx = -1; dx <= 1; dx++) {
        if (dy == 0 && dx == 0) continue;
        neighbors[idx++] = raw_data[(y+dy)*width + (x+dx)];
    }
}
// 排序取中值
uint16_t median = get_median(neighbors, 8);
if (abs(raw_data[y*width + x] - median) > threshold) {
    raw_data[y*width + x] = median;  // 替换为中值
}

阈值怎么设?我一般取传感器噪声标准差的3~5倍。太小了会把正常像素误判为坏点,太大了又漏检。这个值需要针对具体传感器微调,没有万能公式。

经验之谈: 坏点校正的阈值不要设得太死。我习惯在调试阶段先设一个宽松的阈值(比如10bit下阈值=100),跑一遍全黑图和全白图,把明显的坏点抓出来,写入静态表。然后再把阈值收紧到30~50,做动态校正兜底。这样既保证效果,又降低误判风险。

4.5 本章知识结构图

下面这张图把本章的核心逻辑串起来了。从RAW图的Bayer格式出发,到存储读取,再到黑电平校正和坏点校正,每一步都是ISP管线的基石。

RAW图基础与预处理流程 RAW图基础与预处理流程 Bayer格式 RAW图存储与读取 黑电平校正 (BLC) 坏点校正 (DPC) 进入ISP后续处理 (去噪/去马赛克/白平衡...) 关键顺序: Bayer格式确认 → RAW读取 → BLC → DPC 常见坑点: • Bayer相位搞错 • BLC与DPC顺序颠倒

这张图里我特意把「BLC → DPC」的顺序标红了。你记住,黑电平校正永远在坏点校正之前。这个顺序错了,后面所有调试都是白费功夫。