YUV
、RGB
是我们常见的两大类像素格式。
YUV
是一种颜色模型。相比较于RGB,它在考虑人类感知的情况下对彩色图像进行编码,从而减少色度分量的带宽。
在YUV模型的定义中:Y
表示亮度分量,可以看作是灰度图。U
、V
是两个色度分量,分别代表蓝色投影、红色投影。
YUV与YCrCb
YCrCb
是YUV
的别称,Cr
、Cb
分别指U
、V
。
下采样:4:4:4、4:2:2、4:1:1、4:2:0
下采样:人眼对亮度的变化敏感度是高于对色度变化的。所以,YUV的下采样,在不改变亮度分量Y的情况下,对U、V两个颜色分量,采取不同的下采样策略,以达到适当降低图像质量,优化内存占用的目的。
YUV4:4:4
:无下采样。Y、U、V每个像素都进行采样。如果每个分量占8bit,那么每个像素占24bit。
YUV4:2:2
:使用 2:1 的水平下采样。Y每个像素都进行采样,而U、V则在水平方向上每两个像素采样一次。如果每个分量占8bit,则两个像素占32bit,即平均一个像素占16bit。
YUV4:1:1
:使用 4:1 的水平下采样。Y每个像素都进行采样,而U、V则在水平方向上每四个像素采样一次。如果每个分量占8bit,则四个像素占48bit,即平均一个像素占12bit。
YUV4:2:0
:使用 2:1 的水平下采样和 2:1 的垂直下采样。Y每个像素都进行采样,而U、V则在每个 2x2 像素块处采样一次。如果每个分量占8bit,则四个像素占48bit,即平均一个像素占12bit。
Packed、Planar和Semi-planar
关于Packed、Planar,可参考:图像存储策略:Packed与Planar。
YUV的存储格式有三种:Packed
、Planar
和Semi-planar
。
Packed
:Y、U、V三个分量,在内存中交错保存。如YUYV422
:
Planar
:Y、U、V三个分量,保存在不同的平面中。不同的平面代表不同的连续内存块。通常内存排列先是Y平面,然后紧跟U平面,最后是V平面。当然,拆开保存成不同的文件亦可。该格式通常以P
为结尾,如YUV420P
:
Semi-Planar
:Y分量保存在一个平面。U、V分量保存在另一个平面,两者交错保存。该格式通常以SP
为结尾,如YUV420SP
:
I420与YV12
两者都是YUV420P
,区别在于U
、V
的内存排列:
I420
:先是Y平面,然后是U
平面,最后是V
平面。(I420又称IYUV)
YV12
:先是Y平面,然后是V
平面,最后是U
平面。
NV12与NV21
两者都是YUV420SP
,Y分量保存在一个平面。U、V分量保存在另一个平面,两者交错保存。区别在于U
、V
的排列先后。
NV12
:在U&V
平面,第一个字节是U
,第二个字节是V
。周而复始。
NV21
:在U&V
平面,第一个字节是V
,第二个字节是U
。周而复始。
YUV444P10BE与YUV444P10LE
BE
即大端字节序。LE
即小端字节序。
10
即10bit表示一个分量:
YUV444P
采用8bit表示一个分量。每个分量的可取值是有8 = 256种。每个像素的可取值有 28 * 28 * 28 = 16,777,216种。
YUV444P10
采用10bit表示一个分量。每个分量的可取值有210 = 1,024种。每个像素的可取值有 210 * 210 * 210 = 1,073,741,824种,远多于YUV444P
。能在光暗、颜色上表现得更加细腻、逼真,但内存占用也更大。
计算YUV大小
一张分辨率为w * h
的YUV图:
如果是YUV444P
,则:
size(YUV) = size(Y) + size(U) + size(V)
= w * h + w * h + w * h
= w * h * 3
如果是YUV422P
,则:
size(YUV) = size(Y) + size(U) + size(V)
= w * h + (w / 2) * h + (w / 2) * h
= w * h * 2
如果是YUV420P
,则:
size(YUV) = size(Y) + size(U) + size(V)
= w * h + (w / 2) * (h / 2) + (w / 2) * (h / 2)
= w * h * 1.5
YUVResource提供了几张不同YUV格式,分辨率均为720P,即1280x720的图片。
park_joy_444_1280x720.yuv
为YUV444P
,则:
size = 1280 * 720 * 3 = 2,764,800 byte = 2700 kb
park_joy_422_1280x720.yuv
为YUV422P
,则:
size = 1280 * 720 * 2 = 1,843,200 byte = 1800 kb
park_joy_420_1280x720.yuv
为YUV420P
,则:
size = 1280 * 720 * 1.5 = 1,382,400 byte = 1350 kb
YUV像素处理
请参考雷霄骅的视音频数据处理入门:RGB、YUV像素数据处理:
YUV查看工具
推荐雷霄骅的修改了一个YUV/RGB播放器。下载地址:yuvplayer.exe。
该软件支持YUV/RGB
的查看。
YUV转RGB
转换公式
RGB888转为YUV444
Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16
U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128
V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128
YUV444转为RGB888
R = clip(( 298 * (Y - 16 ) + 409 * (V - 128) + 128) >> 8)
G = clip(( 298 * (Y - 16 ) - 100 * (U - 128) - 208 * (V - 128) + 128) >> 8)
B = clip(( 298 * (Y - 16 ) + 516 * (U - 128) + 128) >> 8)
clip()
表示将结果裁剪到[0..255]
的范围。
实现代码可以参考YUVSample:
注意:示例代码是采用逐像素替换的方式,时间复杂度是O(wh),也是最低效的转换方式
。
高效转换的几种方法
- libyuv:Google开源的一个YUV缩放和转换功能的开源项目。libyuv使用CPU的特殊指令集,对CPU的运算做加速,如:row_neon.cc。
- FFmpeg:开源的音视频处理库。它的libswscale也会使用CPU指令集进行加速,如:yuv2rgb_neon.S。
- OpenCV:开源计算机视觉库。与前两者类似,支持指令集加速,如:color_yuv.simd.hpp。
- GPU转换:比如调用OpenGL来转换,速度远大于CPU。但如果转换的结果,要作为其他运算的输入,比如人脸识别,就需要拷贝结果到CPU上,那整体耗时又不如单纯靠CPU处理了。关于如何高效拷贝GPU数据,可参考:Unreal:如何高效的将数据从GPU拷贝到CPU。
- NPU转换:嵌入式同事的一个思路:直接以NV21作为模型输入,利用NPU来做YUV到RGB的转换。模型输出(或后处理)同时可级联至其他模型(detector)的输入(或复用它的前处理)。这样做的目的是,CPU拷贝的数据量少,传输要求带宽少。测试结果来看,速度和CPU相差无几。
- 2D图形加速驱动:看看厂商有没有提供2D图形加速驱动。嵌入式同事:2D图形加速驱动,驱动是内核本来就会自动调用的,会比CPU特殊指令集还快。
FFmpeg YUV转换命令
YUV444转YUV420
ffmpeg -s 1280x720 -pix_fmt yuv444p -i park_joy_444_1280x720.yuv -pix_fmt yuv420p park_joy_420_1280x720.yuv
YUV444转RGB24
ffmpeg -s 1280x720 -pix_fmt yuv444p -i park_joy_444_1280x720.yuv -pix_fmt rgb24 park_joy_888_1280x720.rgb
截取YUV视频流某一帧
ffmpeg -i park_joy_444_720p50.y4m -video_size hd70 -c:v rawvideo -filter:v "select='between(n\, 100\, 100)'" park_joy_444_1280x720.yuv
参考资料
Pixel and Planar Image Formats