YUV格式八股文

2 阅读11分钟

YUV格式八股文

一、基础概念

1. 什么是YUV?

YUV是一种颜色编码方法,主要用于视频系统。它将亮度信息(Y)和色度信息(U、V)分离,相比RGB格式更适合视频压缩和传输。

2. YUV的三个分量分别代表什么?

  • Y(Luma):亮度分量,表示图像的明暗程度
  • U(Cb):蓝色色度分量,表示蓝色与亮度的差值
  • V(Cr):红色色度分量,表示红色与亮度的差值

3. YUV与RGB的转换公式是什么?

RGB转YUV:

Y = 0.299R + 0.587G + 0.114B
U = 0.492(B - Y) = -0.147R - 0.289G + 0.436B
V = 0.877(R - Y) = 0.615R - 0.515G - 0.100B

YUV转RGB:

R = Y + 1.140V
G = Y - 0.395U - 0.581V
B = Y + 2.032U

4. 为什么YUV比RGB更适合视频压缩?

  1. 人眼特性:人眼对亮度敏感,对色度不敏感,可以降低色度采样率
  2. 节省带宽:通过降低色度采样率,可以大幅减少数据量
  3. 兼容性:Y分量可以直接用于黑白显示,兼容黑白电视
  4. 压缩效率:适合视频压缩算法(如H.264、H.265)

二、YUV采样格式

5. YUV444、YUV422、YUV420有什么区别?

格式采样比例存储大小特点
YUV4444:4:43字节/像素每个像素都有完整的YUV分量,质量最高
YUV4224:2:22字节/像素水平方向色度采样减半,每2个像素共享UV
YUV4204:2:01.5字节/像素水平和垂直方向色度采样都减半,每4个像素共享UV

6. YUV420的采样方式是怎样的?

YUV420采用2x2的采样方式:

  • 4个Y分量(每个像素一个Y)
  • 1个U分量(4个像素共享)
  • 1个V分量(4个像素共享)

采样示意图:

Y0 Y1 Y2 Y3
Y4 Y5 Y6 Y7
Y8 Y9 Y10 Y11
Y12 Y13 Y14 Y15

U0   U1
U2   U3

V0   V1
V2   V3

7. 为什么YUV420最常用?

  1. 数据量小:比YUV422节省25%带宽,比YUV444节省50%带宽
  2. 质量损失小:人眼对色度不敏感,质量损失不明显
  3. 性价比高:在质量和带宽之间取得良好平衡
  4. 广泛支持:几乎所有视频编码格式都支持YUV420

三、YUV存储格式

8. 什么是Planar格式和Semi-Planar格式?

  • Planar格式:Y、U、V分量分别连续存储
    • 优点:内存访问连续,缓存友好
    • 缺点:需要三次内存访问才能获取一个像素的完整信息
  • Semi-Planar格式:Y分量连续存储,UV分量交错存储
    • 优点:UV分量连续,适合硬件加速
    • 缺点:UV交错,需要解包

9. I420和YV12有什么区别?

两者都是YUV420P格式,区别在于UV分量的存储顺序:

  • I420:Y → U → V(U在前,V在后)
  • YV12:Y → V → U(V在前,U在后)

10. NV12和NV21有什么区别?

两者都是YUV420SP格式,区别在于UV分量的交错顺序:

  • NV12:Y → UVUVUV...(U在前,V在后)
  • NV21:Y → VUVUVU...(V在前,U在后)

11. NV12和I420有什么区别?

特性NV12I420
格式类型Semi-PlanarPlanar
UV存储交错存储分开存储
内存布局Y + UVY + U + V
硬件加速更适合一般
软件处理需要解包直接访问

四、格式转换

12. RGB转YUV时为什么要加128?

U和V分量可能为负值(-128到+127),而存储时使用无符号8位整数(0-255),因此需要加128将其映射到无符号范围。

13. YUV转RGB时为什么要减128?

与RGB转YUV相反,YUV转RGB时需要将U和V分量从无符号范围(0-255)映射回有符号范围(-128到+127),因此需要减128。

14. 如何实现YUV420P到NV12的转换?

步骤:

  1. 复制Y分量(直接拷贝)
  2. 将U和V分量交错存储到UV平面

代码示例:

void YUV420PToNV12(const uint8_t* i420, uint8_t* nv12, int width, int height) {
    int y_size = width * height;
    int uv_size = y_size / 4;
    
    // 复制Y分量
    memcpy(nv12, i420, y_size);
    
    // 交错UV分量
    const uint8_t* u_plane = i420 + y_size;
    const uint8_t* v_plane = i420 + y_size + uv_size;
    uint8_t* uv_plane = nv12 + y_size;
    
    for (int i = 0; i < uv_size; i++) {
        uv_plane[i * 2] = u_plane[i];
        uv_plane[i * 2 + 1] = v_plane[i];
    }
}

15. 如何实现NV12到YUV420P的转换?

步骤:

  1. 复制Y分量(直接拷贝)
  2. 将UV平面分离为U和V分量

代码示例:

void NV12ToYUV420P(const uint8_t* nv12, uint8_t* i420, int width, int height) {
    int y_size = width * height;
    int uv_size = y_size / 4;
    
    // 复制Y分量
    memcpy(i420, nv12, y_size);
    
    // 分离UV分量
    const uint8_t* uv_plane = nv12 + y_size;
    uint8_t* u_plane = i420 + y_size;
    uint8_t* v_plane = i420 + y_size + uv_size;
    
    for (int i = 0; i < uv_size; i++) {
        u_plane[i] = uv_plane[i * 2];
        v_plane[i] = uv_plane[i * 2 + 1];
    }
}

五、性能优化

16. YUV转换有哪些优化方法?

  1. 定点运算:使用整数运算代替浮点运算
  2. 查表法:预计算转换系数,避免重复计算
  3. SIMD指令:使用NEON/SSE指令进行并行计算
  4. 多线程:并行处理不同行或块
  5. 内存对齐:确保内存访问对齐,提高缓存命中率
  6. 循环展开:减少循环开销

17. 如何使用NEON指令优化YUV转换?

NEON是ARM架构的SIMD指令集,可以同时处理多个数据:

#include <arm_neon.h>

void RGB888ToYUV420P_NEON(const uint8_t* rgb, uint8_t* yuv, int width, int height) {
    int y_size = width * height;
    uint8_t* y_plane = yuv;
    uint8_t* u_plane = yuv + y_size;
    uint8_t* v_plane = yuv + y_size + y_size / 4;
    
    // 加载转换系数
    float32x4_t coef_r = vdupq_n_f32(0.299f);
    float32x4_t coef_g = vdupq_n_f32(0.587f);
    float32x4_t coef_b = vdupq_n_f32(0.114f);
    
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j += 8) {
            // 加载8个像素的RGB数据
            uint8x8x3_t rgb_data = vld3_u8(rgb + (i * width + j) * 3);
            
            // 转换为浮点数
            float32x4_t r_low = vcvtq_f32_u32(vmovl_u16(vget_low_u8(rgb_data.val[0])));
            float32x4_t g_low = vcvtq_f32_u32(vmovl_u16(vget_low_u8(rgb_data.val[1])));
            float32x4_t b_low = vcvtq_f32_u32(vmovl_u16(vget_low_u8(rgb_data.val[2])));
            
            // 计算Y分量
            float32x4_t y_low = vmlaq_f32(vmulq_f32(coef_r, r_low), coef_g, g_low);
            y_low = vmlaq_f32(y_low, coef_b, b_low);
            
            // 存储Y分量
            uint16x4_t y_u16 = vqmovn_u32(vcvtq_u32_f32(y_low));
            vst1_u8(y_plane + i * width + j, vmovn_u16(y_u16));
            
            // UV分量采样(每2x2像素采样一次)
            if (i % 2 == 0 && j % 2 == 0) {
                // 计算UV分量
                // ... 类似Y分量的计算
            }
        }
    }
}

18. 如何使用多线程优化YUV转换?

将图像分成多个块,每个线程处理一个块:

#include <thread>
#include <vector>

void convertBlock(const uint8_t* src, uint8_t* dst, int start_row, int end_row, int width) {
    for (int i = start_row; i < end_row; i++) {
        for (int j = 0; j < width; j++) {
            // 转换逻辑
        }
    }
}

void parallelConvert(const uint8_t* src, uint8_t* dst, int width, int height, int num_threads) {
    vector<thread> threads;
    int rows_per_thread = height / num_threads;
    
    for (int i = 0; i < num_threads; i++) {
        int start_row = i * rows_per_thread;
        int end_row = (i == num_threads - 1) ? height : (i + 1) * rows_per_thread;
        threads.emplace_back(convertBlock, src, dst, start_row, end_row, width);
    }
    
    for (auto& t : threads) {
        t.join();
    }
}

六、常见问题

19. YUV转换时为什么会出现颜色偏差?

可能原因:

  1. 转换系数不准确:不同标准(BT.601、BT.709)使用不同的转换系数
  2. 精度损失:浮点运算转整数运算时的精度损失
  3. 钳位处理不当:超出0-255范围的值处理不当
  4. 采样方式错误:UV采样位置不正确

20. 如何选择合适的YUV格式?

根据应用场景选择:

  • 高质量编辑:YUV444
  • 专业视频:YUV422
  • 视频压缩:YUV420
  • 硬件加速:NV12/NV21
  • 软件处理:I420/YV12

21. YUV420的存储大小如何计算?

YUV420的存储大小 = Y分量大小 + U分量大小 + V分量大小

  • Y分量大小 = width × height
  • U分量大小 = width × height / 4
  • V分量大小 = width × height / 4
  • 总大小 = width × height × 3 / 2

例如:1920×1080的YUV420图像大小 = 1920 × 1080 × 1.5 = 3,110,400字节 ≈ 3MB

22. RGB565和RGB888有什么区别?

特性RGB565RGB888
位深16位24位
存储大小2字节/像素3字节/像素
R分量5位8位
G分量6位8位
B分量5位8位
颜色精度较低较高
应用场景嵌入式系统通用显示

23. 如何判断YUV数据的格式?

  1. 查看文件头:某些格式有特定的文件头标识
  2. 查看FourCC:视频文件通常有FourCC标识
  3. 查看数据大小:根据图像尺寸和数据大小推断格式
  4. 查看文档:查看API文档或设备规格说明

24. YUV转换的性能瓶颈在哪里?

  1. 内存访问:频繁的内存访问是主要瓶颈
  2. 浮点运算:浮点运算比整数运算慢
  3. 分支预测:条件判断影响流水线效率
  4. 缓存未命中:随机访问导致缓存未命中

七、面试高频题

25. 解释YUV420的采样原理

YUV420采用2x2的采样方式,每4个像素共享一组UV分量:

  • 4个Y分量:每个像素一个Y值
  • 1个U分量:4个像素共享
  • 1个V分量:4个像素共享

这种采样方式基于人眼对色度不敏感的特性,在保证视觉质量的同时大幅减少数据量。

26. NV12和I420哪个更适合硬件加速?

NV12更适合硬件加速,原因:

  1. UV分量连续存储,适合DMA传输
  2. 交错存储便于硬件并行处理
  3. 减少内存访问次数
  4. 大多数硬件编解码器都支持NV12

27. 如何优化YUV转换的性能?

优化方法:

  1. 使用SIMD指令(NEON/SSE)进行并行计算
  2. 使用多线程并行处理
  3. 使用定点运算代替浮点运算
  4. 使用查表法预计算转换系数
  5. 优化内存访问模式,提高缓存命中率
  6. 循环展开减少循环开销

28. YUV转换时如何处理边界情况?

边界情况处理:

  1. 图像边界:使用边界扩展或镜像
  2. UV采样边界:确保UV采样不越界
  3. 奇数尺寸:处理奇数宽高的图像
  4. 钳位处理:确保RGB值在0-255范围内

29. YUV和RGB在视频处理中的应用场景

场景推荐格式原因
视频采集YUV422质量高,适合专业设备
视频压缩YUV420数据量小,压缩效率高
视频显示RGB显示设备直接支持
图像处理RGB计算简单,直观
硬件加速NV12适合DMA传输

30. 如何验证YUV转换的正确性?

验证方法:

  1. 单元测试:使用已知输入和输出进行测试
  2. 视觉检查:转换后图像与原图对比
  3. 数值对比:逐像素对比转换结果
  4. 性能测试:测试转换速度和内存使用
  5. 边界测试:测试各种边界情况

八、总结

YUV格式核心要点

  1. YUV分离亮度和色度,适合视频压缩
  2. YUV420最常用,性价比最高
  3. Planar和Semi-Planar各有优缺点
  4. NV12适合硬件加速,I420适合软件处理
  5. 性能优化是关键,SIMD和多线程是主要手段

面试准备建议

  1. 理解YUV的基本概念和原理
  2. 掌握常见YUV格式的区别和应用场景
  3. 熟悉YUV与RGB的转换公式
  4. 了解性能优化的方法
  5. 能够编写基本的YUV转换代码
  6. 了解NEON/SSE等SIMD指令集的基本使用