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排列有四种:
| 模式 | 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。我个人习惯用高位对齐,因为做黑电平校正时直接减一个值就行,不用考虑移位。
下面是一段读取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)的思路就两种:
- 静态校正:出厂时标定坏点坐标,存到OTP里,ISP直接查表替换。
- 动态校正:实时检测坏点,用周围像素插值替换。
海思平台两种都支持。我个人更推荐静态校正为主、动态校正为辅。为什么?因为动态校正算法再聪明,也难免误判——比如把星空中的亮点当成热点给抹掉了,那就尴尬了。
动态校正的常见算法是「中值滤波变种」:
// 以当前像素为中心,取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倍。太小了会把正常像素误判为坏点,太大了又漏检。这个值需要针对具体传感器微调,没有万能公式。
4.5 本章知识结构图
下面这张图把本章的核心逻辑串起来了。从RAW图的Bayer格式出发,到存储读取,再到黑电平校正和坏点校正,每一步都是ISP管线的基石。
这张图里我特意把「BLC → DPC」的顺序标红了。你记住,黑电平校正永远在坏点校正之前。这个顺序错了,后面所有调试都是白费功夫。