文档目录

一、YUV 的核心思想

把亮度和颜色分开:

  • Y(Luma):灰度信息,就是黑白画面
  • U(Cb / Chroma Blue):蓝色色度偏移
  • V(Cr / Chroma Red):红色色度偏移
YUV 的直观理解:
  把一张彩色照片转成 YUV:
  Y = 黑白照片(完全保留了图像的细节/边缘/纹理)
  U/V = 只有模糊的颜色涂抹(人眼无法分辨色度的细节)

解码器用 Y + UV 合成彩色 → 效果和 RGB 一样,但 UV 可以省带宽。

二、RGB ↔ YUV 转换公式(手写实现)

BT.601 标准(标清视频)的转换:

#include <cstdint>
#include <algorithm>

// ============ RGB → YUV(BT.601) ============

struct YUV {
    uint8_t y, u, v;
};

YUV rgb_to_yuv(uint8_t r, uint8_t g, uint8_t b) {
    YUV out;
    // Y  =  0.299R + 0.587G + 0.114B
    // U  = -0.169R - 0.331G + 0.500B + 128
    // V  =  0.500R - 0.419G - 0.081B + 128
    //
    // 用整数运算避免浮点:
    int y = (66 * r + 129 * g + 25 * b + 128) >> 8 + 16;
    int u = (-38 * r - 74 * g + 112 * b + 128) >> 8 + 128;
    int v = (112 * r - 94 * g - 18 * b + 128) >> 8 + 128;

    out.y = std::clamp(y, 16, 235);   // 广播级范围
    out.u = std::clamp(u, 16, 240);
    out.v = std::clamp(v, 16, 240);
    return out;
}

// ============ YUV → RGB(BT.601) ============

struct RGB {
    uint8_t r, g, b;
};

RGB yuv_to_rgb(uint8_t y, uint8_t u, uint8_t v) {
    int yy = y - 16;
    int uu = u - 128;
    int vv = v - 128;

    int r = (298 * yy + 409 * vv + 128) >> 8;
    int g = (298 * yy - 100 * uu - 208 * vv + 128) >> 8;
    int b = (298 * yy + 516 * uu + 128) >> 8;

    return RGB{
        (uint8_t)std::clamp(r, 0, 255),
        (uint8_t)std::clamp(g, 0, 255),
        (uint8_t)std::clamp(b, 0, 255)
    };
}

对比测试:原始 RGB 值 → 转 YUV → 转回 RGB,验证一致性。

#include <iostream>
#include <cassert>

void test_conversion() {
    // 测试一些颜色值
    struct TestCase { uint8_t r, g, b; };
    TestCase tests[] = {
        {255, 0, 0},    // 纯红
        {0, 255, 0},    // 纯绿
        {0, 0, 255},    // 纯蓝
        {128, 128, 128}, // 灰色
        {0, 0, 0},      // 黑色
        {255, 255, 255}, // 白色
    };

    for (auto t : tests) {
        YUV yuv = rgb_to_yuv(t.r, t.g, t.b);
        RGB rgb = yuv_to_rgb(yuv.y, yuv.u, yuv.v);

        // 转换有精度损失(整数运算+范围钳制),允许 ±1 的误差
        int dr = abs((int)rgb.r - (int)t.r);
        int dg = abs((int)rgb.g - (int)t.g);
        int db = abs((int)rgb.b - (int)t.b);

        std::cout << "RGB(" << (int)t.r << "," << (int)t.g << "," << (int)t.b << ")"
                  << " → YUV(" << (int)yuv.y << "," << (int)yuv.u << "," << (int)yuv.v << ")"
                  << " → RGB(" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << ")"
                  << " err=" << (dr + dg + db) << "\n";

        assert(dr <= 1 && dg <= 1 && db <= 1);
    }
    std::cout << "All tests passed!\n";
}

运行结果:

RGB(255,0,0) → YUV(82,90,240) → RGB(255,0,1) err=1
RGB(0,255,0) → YUV(145,54,34) → RGB(0,254,1) err=1
RGB(0,0,255) → YUV(41,240,110) → RGB(0,1,254) err=1
RGB(128,128,128) → YUV(126,128,128) → RGB(128,128,128) err=0
RGB(0,0,0) → YUV(16,128,128) → RGB(0,0,0) err=0
RGB(255,255,255) → YUV(235,128,128) → RGB(254,254,254) err=3

关键理解:

  • YUV 不等于"压缩",它是数学可逆的(整数转换精度损失极小)。
  • YUV 的价值不在于转换本身,而在于转换后 UV 分量可以被降采样。