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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void yuyv_to_yuv420P(uint8_t *in, uint8_t *out, int width, int height) {
uint8_t *y, *u, *v;
int i, j, offset = 0, yoffset = 0;

y = out; // yuv420的y放在前面
u = out + (width * height); // yuv420的u放在y后
v = out + (width * height * 5 / 4); // yuv420的v放在u后
//总共size = width * height * 3 / 2

for (j = 0; j < height; j++) {
yoffset = 2 * width * j;
for (i = 0; i < width * 2; i = i + 4) {
offset = yoffset + i;
*(y++) = *(in + offset);
*(y++) = *(in + offset + 2);
if (j % 2 == 1) { //抛弃奇数行的UV分量
*(u++) = *(in + offset + 1);
*(v++) = *(in + offset + 3);
}
}
}
}

程序非常正常,完美运行!

作者

Yida

发布于

2018-03-07

更新于

2022-02-11

许可协议

评论