文档目录

一、为什么 UV 可以压缩?

一张 1920×1080 的图片,Y 分量保留了所有边缘和纹理细节。把 UV 分辨率降低到 960×540,人眼几乎看不出区别——因为人眼对颜色的空间分辨率远低于亮度。

原始 RGB:1920×1080 × 3 bytes = 6.2 MB
YUV 4:4:4:1920×1080 + 1920×1080 + 1920×1080 = 也 6.2 MB(没省空间)
YUV 4:2:0:1920×1080(Y) + 960×540(U) + 960×540(V) = 3.1 MB → 省了 50%

二、4:4:4 / 4:2:2 / 4:2:0 图解

符号含义:"J:a:b"
  J = 水平参考宽度(通常 4 个像素)
  a = 第一行每个 J 像素中有几个色度样本
  b = 第二行中多出的色度样本数

4:4:4(无压缩)
  像素位置: 0    1    2    3    4    5    6    7
  Y:        Y0   Y1   Y2   Y3   Y4   Y5   Y6   Y7
  U:        U0   U1   U2   U3   U4   U5   U6   U7
  V:        V0   V1   V2   V3   V4   V5   V6   V7
  每个像素都有自己的 Y/U/V → 数据量 = 1920×1080×3

4:2:2(水平减半)
  像素位置: 0    1    2    3    4    5    6    7
  Y:        Y0   Y1   Y2   Y3   Y4   Y5   Y6   Y7
  U:        U0        U2        U4        U6
  V:        V0        V2        V4        V6
  每 2 个像素共享一组 UV → 数据量 = 1920×1080 × 2
  常见于专业视频采集、广播设备

4:2:0(水平和垂直都减半)
  像素位置: 0    1    2    3
  第 0 行 Y: Y00  Y10  Y20  Y30
            U:  U00       U20
            V:  V00       V20
  第 1 行 Y: Y01  Y11  Y21  Y31
            U:  (使用第 0 行的 U/V,不重复存储)
            V:
  每 4 个像素共享一组 UV → 数据量 = 1920×1080 × 1.5
  所有视频编码标准(H.264/HEVC/VP9/AV1)的默认格式

三、4:4:4 → 4:2:0 下采样的手写实现

#include <vector>
#include <cstdint>

// 把 YUV 4:4:4 的 UV 平面下采样为 4:2:0
// 输入:UV 平面和 Y 一样大(w × h)
// 输出:UV 平面各缩小为 (w/2) × (h/2)
void subsample_420(const uint8_t* src_u, const uint8_t* src_v,
                   uint8_t* dst_u, uint8_t* dst_v,
                   int width, int height) {
    int uv_w = width / 2;
    int uv_h = height / 2;

    for (int y = 0; y < uv_h; y++) {
        for (int x = 0; x < uv_w; x++) {
            // 4:2:0 的 U/V 取 2×2 块的均值
            int sum_u = 0, sum_v = 0;
            for (int dy = 0; dy < 2; dy++) {
                for (int dx = 0; dx < 2; dx++) {
                    int src_idx = (y * 2 + dy) * width + (x * 2 + dx);
                    sum_u += src_u[src_idx];
                    sum_v += src_v[src_idx];
                }
            }
            int dst_idx = y * uv_w + x;
            dst_u[dst_idx] = sum_u / 4;
            dst_v[dst_idx] = sum_v / 4;
        }
    }
}

四、三种采样格式的数据量对比

void print_sizes(int width, int height) {
    int y_size = width * height;
    int uv_444 = width * height;
    int uv_422 = (width / 2) * height;
    int uv_420 = (width / 2) * (height / 2);

    auto fmt = [](int bytes) {
        if (bytes > 1024 * 1024)
            return std::to_string(bytes / 1024 / 1024) + "MB";
        return std::to_string(bytes / 1024) + "KB";
    };

    std::cout << width << "x" << height << " 一帧:\n"
              << "  RGB24:     " << fmt(y_size * 3) << "\n"
              << "  YUV 4:4:4: " << fmt(y_size + uv_444 * 2) << "\n"
              << "  YUV 4:2:2: " << fmt(y_size + uv_422 * 2) << "\n"
              << "  YUV 4:2:0: " << fmt(y_size + uv_420 * 2) << "\n";
}

// print_sizes(1920, 1080);
// 输出:
//   RGB24:     6.2 MB
//   YUV 4:4:4: 6.2 MB
//   YUV 4:2:2: 4.1 MB
//   YUV 4:2:0: 3.1 MB