Skip to content
Go back

YUYV 转 YUV420P

之前接触到一个 YUYV 的摄像头,需要将采集到的 YUYV 数据转换为 YUV420P 数据,在经历了一些弯路的同时,发现了网上流传的一些资料有误,遂写此博文。

基础


YUV,分为三个分量,「Y」表示明亮度 ( Luminance 或 Luma ) ,也就是灰度值;而「U」和「V」 表示的则是色度 ( Chrominance 或 Chroma ) ,作用是描述影像色彩及饱和度,用于指定像素的颜色。

对于每一个 Y, U,V 分量,其长度都是 1 个字节。YUV420P 和 YUYV 本质上的区别就是采样方式的不同。由于作者水平限制,文中可能出现一些纰漏,欢迎指正。

YUYV 格式介绍

yuyv 的存储结构如下表,如果是 uyvy 的话,就是顺序改变一下。

Y0U0Y1V0Y2U2Y3V2
Y4U4Y5V4Y6U6Y7V6
Y8U8Y9V8Y10U10Y11V10
Y12U12Y13V12Y14U14Y15V14

看起来这个结构很奇怪,对吧?而且 UV 分量还没有奇数的份。 我的理解是 YUYV 对应着的是两个像素,其中第一个像素由 Y0, U0, V0 组成,第二个像素由 Y1 组成,那么在一张图片上,YUYV 就是下表的样子。

Y0,U0,V0Y1,U0,V0Y2,V2,U2Y3,V2,U2
Y4,U4,V4Y5,U4,V4Y6,U6,V6Y7,U6,V6
Y8,U8,V8Y9,U8,V8Y10,U10,V10Y11,U10,V10
Y12,U12,V12Y13,U12,V12Y14,U14,V14Y15,U14,V14

这样一来,UV 分量看起来就舒服多了,和 Y 分量完美对应起来。这就是第一个表中没有奇数 UV 分量的原因。 YUV420P 和 YUYV 本质上的区别就是采样方式的不同。YUYV 使用了隔列采样。 从中可以看到, 4 个字节,一组 YUYV 代表了两个像素,因此如果使用 YUYV 存储,那么文件的大小 size = width *height* 2 容易看出,上面两个表描述了一个 4*4 像素大小的图片。其大小为 32 字节。

YUY420P 格式介绍


同样,首先来看一张 4*4 像素大小的 yuv420p 存储结构,如下表。

Y0Y1Y2Y3
Y4Y5Y6Y7
Y8Y9Y10Y11
Y12Y13Y14Y15
——-——-——-——-
U0U2U8U10
——-——-——-——-
V0V2V8V10

为了方便理解,没有按顺序排列 UV 分量,怎么好理解怎么来,这些数字都不重要! 首先 YUV420P 将 YUV 三个分量分别打包,Y 存放在一起,U 放在一起,V 放在一起。你可能会觉得 UV 分量少了,但是 UV 分量就是这么多,并没有少。我们来看在图片中的 YUV420P :

Y0,U0,V0Y1,U0,V0Y2,V2,U2Y3,V2,U2
Y4,U0,V0Y5,U0,V0Y6,V2,U2Y7,V2,U2
Y8,U8,V8Y9,U8,V8Y10,U10,V10Y11,U10,V10
Y12,U8,V8Y13,U8,V8Y14,U10,V10Y15,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
Y0U0Y1V0Y2U2Y3V2
Y4U4Y5V4Y6U6Y7V6
Y8U8Y9V8Y10U10Y11V10
Y12U12Y13V12Y14U14Y15V14
  • yuv420p
Y0Y1Y2Y3
Y4Y5Y6Y7
Y8Y9Y10Y11
Y12Y13Y14Y15
——-——-——-——-
U0U2U8U10
——-——-——-——-
V0V2V8V10

那么就是读入 yuyv 后,把它按照 yuv420 的结构重新排列一下,抛弃偶数行的 UV 分量就可以了。 分析完 YUYV 和 YUV420P 后,我写了一段 yuyv 转 yuv420 的程序,首先请分配好 out 的空间: size = width *height* 3 / 2

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 ) ;
      }
    }
  }
}

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


Share this post on: