YUV420转RGB实战:用Python+OpenCV自制简易图片查看器
最近在调试一个视频处理项目时,遇到了一个挺有意思的问题:从摄像头采集到的原始数据是YUV420格式的,但手头常用的图片查看工具要么不支持,要么需要复杂的配置。这让我意识到,很多开发者虽然天天和视频流、图像数据打交道,但对YUV这种“幕后英雄”格式的底层结构,其实并不那么熟悉。于是,我决定自己动手,用Python和OpenCV从零开始写一个简易的YUV420图片查看器。这个过程不仅能解决实际问题,更能让你彻底搞懂YUV420的文件结构、色度上采样的门道,以及色彩空间转换的核心原理。这篇文章就是这次实践的完整记录,无论你是刚接触图像处理的开发者,还是想深入理解视频编码底层的老手,相信都能从中获得一些启发。
1. 从像素到比特流:深入解析YUV420文件结构
在开始写代码之前,我们必须先搞清楚YUV420格式的“长相”。很多人知道YUV比RGB省空间,但具体怎么个省法,数据在文件里是怎么排列的,往往是一头雾水。YUV420是一种色度子采样格式,它的核心思想是利用人眼对亮度(Y)敏感、对色度(U、V)相对不敏感的特性,对色度信息进行“抽稀”存储。
想象一张640x480的彩色图片。如果使用RGB24格式(每个像素用R、G、B三个8位字节表示),总数据量是 640 * 480 * 3 = 921,600 字节。而在YUV420中,亮度Y通道保持全分辨率,即640x480个样本。但色度U和V通道,则在水平和垂直方向上都进行2:1的下采样。也就是说,U和V通道的尺寸都变成了 320x240。我们来算一下总数据量:Y通道 640480 = 307,200字节;U通道 320240 = 76,800字节;V通道同样76,800字节。加起来是 460,800 字节,正好是RGB24的一半。这就是它高效压缩的秘诀。
注意:YUV420的“4:2:0”命名法容易引起误解。它并非指U、V通道的采样率是Y的2/4和0/4。一种更准确的解释是:在水平方向上,每两个Y样本共享一个U和一个V样本;在垂直方向上,每两行Y样本共享同一行的U和V样本。因此,U和V的采样率在水平和垂直方向都是Y的1/2。
那么,这些Y、U、V数据在文件里是怎么存放的呢?主要有两种平面存储格式:
- I420 (或 YV12):这是最普遍的顺序。文件内容依次是:所有Y分量 -> 所有U分量 -> 所有V分量。三个分量连续存放,没有任何交错。
- NV12:这是一种半平面格式。文件内容依次是:所有Y分量 -> 交错存储的U、V分量(即UVUV...)。这种格式在移动设备和一些视频编码中很常见。
我们的查看器主要针对最标准的I420格式。理解了这个结构,读取文件就变成了一个简单的数学问题:根据已知的图像宽度和高度,计算出每个分量应该读取多少字节。
def parse_yuv420_i420_file_size(file_path, width, height):
"""
根据给定的宽高,计算一个I420格式YUV420文件的理论大小。
用于快速验证文件是否完整或格式是否正确。
"""
y_size = width * height
uv_size = (width // 2) * (height // 2)
total_size = y_size + uv_size * 2 # Y + U + V
return total_size
# 示例:计算一个640x480的I420文件大小
expected_size = parse_yuv420_i420_file_size('dummy.yuv', 640, 480)
print(f"一个640x480的I420文件理论大小为:{expected_size} 字节")
如果实际文件大小与计算结果不符,那很可能意味着文件损坏、格式不是纯I420,或者宽高参数给错了。这是排查问题的第一步。
2. 色度上采样:从“马赛克”到全分辨率的关键一步
读取到U和V分量后,你会发现它们的尺寸只有Y分量的一半。直接把它们和Y合并是无法进行色彩空间转换的,因为三个通道的尺寸必须一致。所以,我们需要一个关键的步骤:色度上采样。这个过程,形象地说,就是把低分辨率的“色度马赛克”放大到和亮度图一样的大小。
上采样不是简单的像素复制(最近邻插值),那会产生明显的块状瑕疵。在图像处理中,我们通常使用更平滑的插值算法。OpenCV的cv2.resize()函数提供了多种选择:
| 插值方法 (Interpolation) |
|---|


1万+

被折叠的 条评论
为什么被折叠?



