一、像素格式
1. 一句话理解
每个像素是一个小点,用 3 个数(红/绿/蓝)就能描述颜色。但"这 3 个数在内存里怎么摆"能摆出几十种姿势,每种姿势就是一种像素格式。
2. 区别就 3 件事
① 字节顺序:RGB 还是 BGR?
RGB: 内存里依次是 红 绿 蓝
BGR: 内存里依次是 蓝 绿 红
颜色完全一样,只是顺序相反。BGR 来自 Windows BMP 文件格式的早年约定,显示硬件和 OpenCV 跟着抄。纯历史惯例,没有谁更好。
② 占几个字节:3 个还是 4 个?
RGB (3字节): R G B —— 省内存
RGBX (4字节): R G B X —— 多 1 个没用的 X(占位)
RGBA (4字节): R G B A —— 多 1 个 A(透明度, 255=不透明)
CPU/GPU 读内存喜欢 4 字节对齐,3 字节别扭还不能按整数一口气算;4 字节一个像素正好一个整数,SIMD/GPU 直接吃。多花 33% 内存换速度。
③ 空间换不换:RGB 还是 YUV?
人眼: 对明暗(亮度)敏感, 对颜色(色度)不敏感
YUV = 把颜色拆成 亮度 Y + 颜色 U/V
相机做法: 亮度全保留, 颜色偷工减料
YUV444: 颜色一点不少 = 3 字节/像素
YUYV : 颜色横着减半 = 2 字节/像素 (省 33%)
NV12 : 颜色横竖都减半 = 1.5 字节/像素 (省 50%)
1280x720 一张图,NV12 比 RGBA 省一半内存,人眼几乎看不出区别。JPEG 压缩也利用这一点(默认 4:2:0)。
3. 20 种格式总表
| 格式 | 字节/像素 | 内存排布 | 是谁在用 |
|---|---|---|---|
| RGB | 3 | R G B | libjpeg 解码默认输出,通用 |
| BGR | 3 | B G R | Windows BMP / 老显示控制器 DMA |
| RGBX | 4 | R G B X | D3D/GPU 纹理,X 未定义 |
| BGRX | 4 | B G R X | BMP 风格 4 字节对齐 |
| XBGR | 4 | X B G R | 小端下当 uint32 = 0x00RRGGBB,位运算友好 |
| XRGB | 4 | X R G B | OpenGL XRGB8888 约定 |
| RGBA | 4 | R G B A | GPU/合成管线,需要透明度 |
| BGRA | 4 | B G R A | Windows 32bpp 屏幕 / COCOA |
| ABGR | 4 | A B G R | OpenGL GL_ABGR_EXT |
| ARGB | 4 | A R G B | Qt ARGB32(数值 0xAARRGGBB 存小端内存) |
| GRAY | 1 | Y | 灰度,最省内存 |
| CMYK | 4 | C M Y K | 印刷分色(转 RGB 仅近似) |
| YCBCR444 | 3 | Y Cb Cr | JPEG 内部色彩空间 |
| YUYV | 2 | Y0 U Y1 V | UVC/USB 摄像头经典输出 |
| UYVY | 2 | U Y0 V Y1 | 视频采集卡常见 |
| NV12 | 1.5 | Y 平面 + UV 半平面 | 硬件编码器 / RV1106 MPP |
| NV21 | 1.5 | Y 平面 + VU 半平面 | Android 相机默认 |
| I420 | 1.5 | Y | U | V 三平面 | FFmpeg / 软件编码通用 |
| I444 | 3 | Y | U | V 三平面 | 专业视频中间格式 |
实测(1920x1080):GRAY 2.0MB < NV12/I420 3.0MB < YUYV 4.0MB < RGB 5.9MB < 4 字节格式 7.9MB
4. YUV 家族详解
采样档位(人眼对色彩不敏感,逐级偷色度):
4:4:4 每像素 3 字节, 色度全保留 (I444 / YCBCR444)
4:2:2 每像素 2 字节, 色度水平减半 (YUYV / UYVY)
4:2:0 每像素 1.5 字节, 色度横竖都减半 (NV12 / NV21 / I420)
色度摆放:同样是偷色度,摆法不同 → 不同格式。
交织(混装): YUYV [Y0 U Y1 V] — USB 摄像头芯片直接吐出, 拿来就能用
半平面: NV12 [Y整块][UV交替] — 硬件编码器最小 DMA 单元
全平面: I420 [Y整块][U整块][V整块] — 软件编码器缓存友好
原则上:传感器给你什么就用什么,少转一次就快一次。
5. 为什么搞这么多格式?(3 个推动力)
- 历史惯性:Windows/BMP/OpenGL/Qt 各定各的顺序,互不相让(BGR/ARGB 家族)。
- 硬件胃口:有的硬件要 4 字节对齐才好 DMA(X/RGBA 家族);有的硬件按 YUV 半平面搬运(NV12)。格式跟着芯片走。
- 人眼物理:颜色可以偷懒少存,偷多少、怎么偷 → 4:2:2 / 4:2:0 各档位。
6. 选型一句话
省内存 → YUV (1.5~2B),要速度 → 4 字节 RGBX/RGBA,
要通用 → RGB,显示硬件要 BGR → 给它 BGR
二、视频本质
1. 视频 = 按固定频率连续播放图片
1 秒视频 = 30 张图(帧率 30fps)。1080p RGB 一张 6MB,30 张 = 180MB/秒,根本存不下。所以要"视频编码"。
2. 视频编码 = 两种偷懒的组合
懒法一·帧内压缩: 单张图自己压 (JPEG 那套) → 一张图砍到 1/20
懒法二·帧间压缩: 相邻帧几乎一样, 只存"变化" → 再省 10~30 倍
| 门派 | 做法 | 代表 | 压缩率 |
|---|---|---|---|
| 只用懒法一 | 每帧压成独立图片 | MJPEG | 约 1/20 |
| 两个都用 | 关键帧完整 + 后续帧只存变化 | H.264 / H.265 | 约 1/200 |
3. 为什么 MJPEG 既是图片又是视频?
因为它的每一帧就真的是一个标准 JPEG 图片:
MJPEG 视频流 = [JPEG图片][JPEG图片][JPEG图片]... 按帧率播放
- 当图片看:随便抽一帧都是完整 JPEG,能单独打开、精确跳转、单独裁剪。哪帧坏了解哪帧,互不影响。
- 当视频看:装在时间轴容器里(如 AVI),按帧率播放就是视频。
所以 MJPEG 站在中间:内容是图片编码,载体是视频容器。
代价:文件是 H.264 的 5~10 倍。
用在哪:安防录像(容错第一)、医疗/工业图像(逐帧精确)、行车记录仪、RV1106 这种硬件 JPEG 编码器现成的板卡。
4. 和我们学的 libjpeg 的关系
MJPEG 编码 = 循环调用 jpeg_en (一帧一个 JPEG)
MJPEG 解码 = 循环调用 jpeg_de (逐帧解压播放)
libjpeg 就是 MJPEG 的全部内核 —— 这也是"JPEG 是图片/视频双栖格式"的根本原因:编解码器是同一个。
三、配套代码(/root/c-std/libjpeg/)
| 文件 | 内容 |
|---|---|
pixfmt.h / pixfmt.c | 20 种像素格式转换工具(信息表、逐像素函数、整图转换) |
demo/pixfmt_demo.c | 格式信息/字节形态/内存占用/往返误差演示 |
demo/jpeg_en.c / jpeg_de.c | libjpeg 编码/解码(MJPEG 的帧处理核心) |
demo/turbo_demo.c | TurboJPEG 一行 API + 缩放解码 |
下一步建议:mjpeg_demo.c —— 连续生成 30 帧动态画面,逐帧用 jpeg_en 的流程编码,按 AVI/MJPG 容器头存成视频文件,直观看到"视频 = 一串 JPEG"。