3、RAW格式详解:RAW8、RAW10、RAW12的位宽与打包方式

好,咱们接着聊RAW格式。前面讲了RAW数据是什么,现在要深入到底层——位宽和打包方式。说白了,就是每个像素点用多少比特来表示,以及这些比特在内存里是怎么排列的。

我个人习惯把RAW格式比作「数字底片」。你想想看,JPEG是冲印好的照片,而RAW就是相机传感器直接读出来的原始数据。位宽越大,这张「底片」能记录的亮度层次就越丰富。

3.1 RAW8:最基础的8位格式

RAW8是最简单的。每个像素用8个比特,也就是1个字节。取值范围0-255。我在项目中遇到过不少低端摄像头模组,比如VGA分辨率的安防摄像头,用的就是RAW8。为什么?省带宽、省存储。

打包方式?没什么好打包的。一个像素一个字节,连续排列就行。比如一行有1920个像素,那这一行的数据就是1920个字节。

RAW8数据排列示例(一行4个像素):

像素值:  120  45  200  88
内存:    [0x78] [0x2D] [0xC8] [0x58]

嗯,这里要注意:RAW8虽然简单,但动态范围有限。暗部细节容易丢失,亮部容易过曝。如果你做的是工业检测或者医疗影像,我建议至少用RAW10起步。

3.2 RAW10:10位数据的两种打包方式

RAW10每个像素用10个比特。取值范围0-1023。这比RAW8多了4倍的数据量,但换来的是更平滑的亮度过渡。

问题来了:10比特不是2的整数倍。怎么存?总不能一个像素占1.25个字节吧?这就引出了两种主流打包方式。

3.2.1 方式一:高位对齐打包(MSB对齐)

这种方式,每个像素的10比特数据占用16比特(2个字节)。高10位有效,低6位填充0。说白了就是「浪费」了6个比特,但换来的是CPU处理方便。

我的经验:如果你用的是ARM Cortex-A系列处理器,内存带宽不是瓶颈,我建议用高位对齐。因为你可以直接用16位数据类型(uint16_t)来操作,省去位运算的麻烦。

// 高位对齐打包示例
// 像素值: 0x03FF (1023)
// 存储为: [0xFF] [0x0F]  // 注意:低6位为0
uint16_t raw10_pixel = pixel_value << 6;

3.2.2 方式二:紧凑打包(4像素占5字节)

这才是真正的「打包」。4个10比特的像素,总共40比特,正好塞进5个字节。一点都不浪费。

我曾经在调试一个USB 3.0摄像头时遇到过坑:驱动层用紧凑打包传输数据,但应用层直接按字节偏移去读,结果图像全是乱的。后来才发现是打包方式没对齐。

避坑指南:紧凑打包虽然省带宽,但处理起来很麻烦。你需要用位掩码和移位操作来提取每个像素。我曾经因为一个移位方向搞反了,排查了整整一个下午。

// 紧凑打包:4个10位像素 -> 5个字节
// 像素0: 10位, 像素1: 10位, 像素2: 10位, 像素3: 10位
// 字节0: [像素0 9:2]  (高8位)
// 字节1: [像素0 1:0][像素1 9:4] (低2位 + 高6位)
// 字节2: [像素1 3:0][像素2 9:6] (低4位 + 高4位)
// 字节3: [像素2 5:0][像素3 9:8] (低6位 + 高2位)
// 字节4: [像素3 7:0]  (低8位)

3.3 RAW12:12位数据的打包方式

RAW12每个像素12比特。取值范围0-4095。这已经是很多专业相机和工业相机的标配了。动态范围更大,后期处理空间也更足。

打包方式跟RAW10类似,但更「挤」一些。

3.3.1 高位对齐(2字节存1像素)

每个12位像素占用16位,高12位有效,低4位填充0。简单粗暴,处理方便。

// 高位对齐
uint16_t raw12_pixel = pixel_value << 4;

3.3.2 紧凑打包(2像素占3字节)

2个12位像素,总共24比特,正好3个字节。这是最常见的打包方式。

RAW12紧凑打包示例(2像素 -> 3字节):

// 像素A: 12位, 像素B: 12位
// 字节0: [像素A 11:4]  (高8位)
// 字节1: [像素A 3:0][像素B 11:8] (低4位 + 高4位)
// 字节2: [像素B 7:0]  (低8位)

// 提取像素A:
uint16_t pixel_a = (buf[0] << 4) | (buf[1] >> 4);
// 提取像素B:
uint16_t pixel_b = ((buf[1] & 0x0F) << 8) | buf[2];

3.4 三种格式对比

格式 位宽 取值范围 紧凑打包比例 典型应用
RAW8 8位 0-255 1:1(无需打包) 低端摄像头、监控
RAW10 10位 0-1023 4像素:5字节 中端工业相机
RAW12 12位 0-4095 2像素:3字节 专业相机、医疗影像

3.5 实战中的选择建议

我个人在项目中总结了几条经验,分享给你:

  • 带宽优先:如果传输带宽是瓶颈(比如USB 2.0、MIPI低速模式),用紧凑打包。虽然处理麻烦,但能省下30%左右的带宽。
  • 性能优先:如果CPU算力够,内存带宽也够,用高位对齐。代码写起来清爽,调试也方便。
  • 兼容性优先:有些ISP(图像信号处理器)只支持特定打包方式。比如高通平台的某些ISP,强制要求RAW10用紧凑打包。这时候你没得选。

一个小技巧:在HAL层驱动里,我通常会加一个配置项,让上层选择打包方式。这样既兼容了不同硬件,又方便调试。代码里加个枚举就行:

typedef enum {
    RAW_PACKING_ALIGNED,   // 高位对齐
    RAW_PACKING_TIGHT      // 紧凑打包
} raw_packing_mode_t;

好了,RAW的位宽和打包方式就讲到这里。下一章咱们聊聊RAW数据在内存中的Bayer排列模式——RGGB、BGGR这些,以及怎么在驱动层做正确的像素重排。到时候我会分享一个我踩过的坑,跟Bayer排列搞反了,图像颜色全乱套...嗯,到时候细说。