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更适合视频压缩?
- 人眼特性:人眼对亮度敏感,对色度不敏感,可以降低色度采样率
- 节省带宽:通过降低色度采样率,可以大幅减少数据量
- 兼容性:Y分量可以直接用于黑白显示,兼容黑白电视
- 压缩效率:适合视频压缩算法(如H.264、H.265)
二、YUV采样格式
5. YUV444、YUV422、YUV420有什么区别?
| 格式 | 采样比例 | 存储大小 | 特点 |
|---|---|---|---|
| YUV444 | 4:4:4 | 3字节/像素 | 每个像素都有完整的YUV分量,质量最高 |
| YUV422 | 4:2:2 | 2字节/像素 | 水平方向色度采样减半,每2个像素共享UV |
| YUV420 | 4:2:0 | 1.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最常用?
- 数据量小:比YUV422节省25%带宽,比YUV444节省50%带宽
- 质量损失小:人眼对色度不敏感,质量损失不明显
- 性价比高:在质量和带宽之间取得良好平衡
- 广泛支持:几乎所有视频编码格式都支持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有什么区别?
| 特性 | NV12 | I420 |
|---|---|---|
| 格式类型 | Semi-Planar | Planar |
| UV存储 | 交错存储 | 分开存储 |
| 内存布局 | Y + UV | Y + 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的转换?
步骤:
- 复制Y分量(直接拷贝)
- 将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的转换?
步骤:
- 复制Y分量(直接拷贝)
- 将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转换有哪些优化方法?
- 定点运算:使用整数运算代替浮点运算
- 查表法:预计算转换系数,避免重复计算
- SIMD指令:使用NEON/SSE指令进行并行计算
- 多线程:并行处理不同行或块
- 内存对齐:确保内存访问对齐,提高缓存命中率
- 循环展开:减少循环开销
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转换时为什么会出现颜色偏差?
可能原因:
- 转换系数不准确:不同标准(BT.601、BT.709)使用不同的转换系数
- 精度损失:浮点运算转整数运算时的精度损失
- 钳位处理不当:超出0-255范围的值处理不当
- 采样方式错误: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有什么区别?
| 特性 | RGB565 | RGB888 |
|---|---|---|
| 位深 | 16位 | 24位 |
| 存储大小 | 2字节/像素 | 3字节/像素 |
| R分量 | 5位 | 8位 |
| G分量 | 6位 | 8位 |
| B分量 | 5位 | 8位 |
| 颜色精度 | 较低 | 较高 |
| 应用场景 | 嵌入式系统 | 通用显示 |
23. 如何判断YUV数据的格式?
- 查看文件头:某些格式有特定的文件头标识
- 查看FourCC:视频文件通常有FourCC标识
- 查看数据大小:根据图像尺寸和数据大小推断格式
- 查看文档:查看API文档或设备规格说明
24. YUV转换的性能瓶颈在哪里?
- 内存访问:频繁的内存访问是主要瓶颈
- 浮点运算:浮点运算比整数运算慢
- 分支预测:条件判断影响流水线效率
- 缓存未命中:随机访问导致缓存未命中
七、面试高频题
25. 解释YUV420的采样原理
YUV420采用2x2的采样方式,每4个像素共享一组UV分量:
- 4个Y分量:每个像素一个Y值
- 1个U分量:4个像素共享
- 1个V分量:4个像素共享
这种采样方式基于人眼对色度不敏感的特性,在保证视觉质量的同时大幅减少数据量。
26. NV12和I420哪个更适合硬件加速?
NV12更适合硬件加速,原因:
- UV分量连续存储,适合DMA传输
- 交错存储便于硬件并行处理
- 减少内存访问次数
- 大多数硬件编解码器都支持NV12
27. 如何优化YUV转换的性能?
优化方法:
- 使用SIMD指令(NEON/SSE)进行并行计算
- 使用多线程并行处理
- 使用定点运算代替浮点运算
- 使用查表法预计算转换系数
- 优化内存访问模式,提高缓存命中率
- 循环展开减少循环开销
28. YUV转换时如何处理边界情况?
边界情况处理:
- 图像边界:使用边界扩展或镜像
- UV采样边界:确保UV采样不越界
- 奇数尺寸:处理奇数宽高的图像
- 钳位处理:确保RGB值在0-255范围内
29. YUV和RGB在视频处理中的应用场景
| 场景 | 推荐格式 | 原因 |
|---|---|---|
| 视频采集 | YUV422 | 质量高,适合专业设备 |
| 视频压缩 | YUV420 | 数据量小,压缩效率高 |
| 视频显示 | RGB | 显示设备直接支持 |
| 图像处理 | RGB | 计算简单,直观 |
| 硬件加速 | NV12 | 适合DMA传输 |
30. 如何验证YUV转换的正确性?
验证方法:
- 单元测试:使用已知输入和输出进行测试
- 视觉检查:转换后图像与原图对比
- 数值对比:逐像素对比转换结果
- 性能测试:测试转换速度和内存使用
- 边界测试:测试各种边界情况
八、总结
YUV格式核心要点
- YUV分离亮度和色度,适合视频压缩
- YUV420最常用,性价比最高
- Planar和Semi-Planar各有优缺点
- NV12适合硬件加速,I420适合软件处理
- 性能优化是关键,SIMD和多线程是主要手段
面试准备建议
- 理解YUV的基本概念和原理
- 掌握常见YUV格式的区别和应用场景
- 熟悉YUV与RGB的转换公式
- 了解性能优化的方法
- 能够编写基本的YUV转换代码
- 了解NEON/SSE等SIMD指令集的基本使用