YUYV转YUV420P
之前接触到一个YUYV的摄像头,需要将采集到的YUYV数据转换为YUV420P数据,在经历了一些弯路的同时,发现了网上流传的一些资料有误,遂写此博文。
基础
YUV,分为三个分量,“Y”表示明亮度(Luminance或Luma),也就是灰度值;而“U”和“V”
表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。
对于每一个 Y,U,V 分量,其长度都是1个字节。YUV420P 和 YUYV 本质上的区别就是采样方式的不同。由于作者水平限制,文中可能出现一些纰漏,欢迎指正。
YUYV格式介绍
yuyv 的存储结构如下表,如果是 uyvy 的话,就是顺序改变一下。
Y0 | U0 | Y1 | V0 | Y2 | U2 | Y3 | V2 |
---|---|---|---|---|---|---|---|
Y4 | U4 | Y5 | V4 | Y6 | U6 | Y7 | V6 |
Y8 | U8 | Y9 | V8 | Y10 | U10 | Y11 | V10 |
Y12 | U12 | Y13 | V12 | Y14 | U14 | Y15 | V14 |
看起来这个结构很奇怪,对吧?而且 UV 分量还没有奇数的份。
我的理解是 YUYV 对应着的是两个像素,其中第一个像素由 Y0,U0,V0 组成,第二个像素由 Y1 组成,那么在一张图片上,YUYV 就是下表的样子。
Y0,U0,V0 | Y1,U0,V0 | Y2,V2,U2 | Y3,V2,U2 |
---|---|---|---|
Y4,U4,V4 | Y5,U4,V4 | Y6,U6,V6 | Y7,U6,V6 |
Y8,U8,V8 | Y9,U8,V8 | Y10,U10,V10 | Y11,U10,V10 |
Y12,U12,V12 | Y13,U12,V12 | Y14,U14,V14 | Y15,U14,V14 |
这样一来,UV 分量看起来就舒服多了,和 Y 分量完美对应起来。这就是第一个表中没有奇数 UV 分量的原因。
YUV420P 和 YUYV 本质上的区别就是采样方式的不同. YUYV 使用了隔列采样.
从中可以看到, 4个字节,一组 YUYV 代表了两个像素,因此如果使用 YUYV 存储,那么文件的大小 size = width *height* 2
容易看出,上面两个表描述了一个4*4像素大小的图片。其大小为32字节。
YUY420P格式介绍
同样,首先来看一张 4*4 像素大小的 yuv420p 存储结构,如下表。
Y0 | Y1 | Y2 | Y3 |
---|---|---|---|
Y4 | Y5 | Y6 | Y7 |
Y8 | Y9 | Y10 | Y11 |
Y12 | Y13 | Y14 | Y15 |
——- | ——- | ——- | ——- |
U0 | U2 | U8 | U10 |
——- | ——- | ——- | ——- |
V0 | V2 | V8 | V10 |
为了方便理解,没有按顺序排列UV分量,怎么好理解怎么来,这些数字都不重要!
首先 YUV420P 将 YUV 三个分量分别打包,Y 存放在一起,U 放在一起,V 放在一起。你可能会觉得 UV 分量少了,但是 UV 分量就是这么多,并没有少。我们来看在图片中的 YUV420P :
Y0,U0,V0 | Y1,U0,V0 | Y2,V2,U2 | Y3,V2,U2 |
---|---|---|---|
Y4,U0,V0 | Y5,U0,V0 | Y6,V2,U2 | Y7,V2,U2 |
Y8,U8,V8 | Y9,U8,V8 | Y10,U10,V10 | Y11,U10,V10 |
Y12,U8,V8 | Y13,U8,V8 | Y14,U10,V10 | Y15,U10,V10 |
YUV420P 和 YUYV 本质上的区别就是采样方式的不同. YUV420 使用了隔行隔列采样.
可以理解成YUV420,在YUV422的基础上抛弃了偶数行的UV分量,(或者抛弃奇数行,或者U分量抛弃奇数行,V分量抛弃偶数行)
也就是说,四个相邻的像素共用一个 UV 分量,U0 和 V0 提供给了Y0,Y1,Y4,Y5四个像素。
那么采用了 YUV420 的4*4
像素图片,Y 分量大小是 width* height
;
U 分量大小是 width *height / 4
;
V 分量大小是 width* height / 4
;
整个图片大小就是:size = width *height* 3 / 2
。
转换
对比yuyv和yuv420p的存储方式
- yuyv
Y0 | U0 | Y1 | V0 | Y2 | U2 | Y3 | V2 |
---|---|---|---|---|---|---|---|
Y4 | U4 | Y5 | V4 | Y6 | U6 | Y7 | V6 |
Y8 | U8 | Y9 | V8 | Y10 | U10 | Y11 | V10 |
Y12 | U12 | Y13 | V12 | Y14 | U14 | Y15 | V14 |
- yuv420p
Y0 | Y1 | Y2 | Y3 |
---|---|---|---|
Y4 | Y5 | Y6 | Y7 |
Y8 | Y9 | Y10 | Y11 |
Y12 | Y13 | Y14 | Y15 |
——- | ——- | ——- | ——- |
U0 | U2 | U8 | U10 |
——- | ——- | ——- | ——- |
V0 | V2 | V8 | V10 |
那么就是读入yuyv后,把它按照yuv420的结构重新排列一下,抛弃偶数行的UV分量就可以了。
分析完YUYV和YUV420P后,我写了一段yuyv转yuv420的程序,首先请分配好out的空间:size = width *height* 3 / 2
。
1 | void yuyv_to_yuv420P(uint8_t *in, uint8_t *out, int width, int height) { |
程序非常正常,完美运行!