YUV

YUV 简介

YUV 是一种彩色编码系统,相对于 RGB 颜色空间,设计 YUV 的目的就是为了编码、传输的方便,减少带宽占用和信息出错。

YUV 设计的初衷是为了使彩色电视能够兼容黑白电视。对于黑白电视信号,没有色度信息也就是(UV),那么在彩色电视显示的时候指显示亮度信息。

Y 为亮度信息,UV 为色度(Chroma)信息。U/V分别等于 blue–luminance/red–luminance。Y 信号分量为黑白灰度图。U、V信号分量为单色彩色图。

YUV 编码

人眼的视觉特点是对亮度更敏感,对位置、色彩相对来说不敏感。在视频编码系统中为了降低带宽,可以保存更多的亮度信息(luma),保存较少的色差信息(chroma)。这叫做色度二次采样。原则,数字图像中:

  • 每一个图形像素都要包含 luma(亮度)值。
  • 几个图形像素共用一个 Cb + Cr 值,一般是 2、4、8 个像素。

常见的 YUV 格式以及其对应的采样方式:

如上图中所示,左侧一列,每一个小矩形是图形像素表示,黑框矩形是色度像素表示,小黑点是表示色度像素值(Cb+Cr),表示图形像素和色度像素在水平和垂直方向的比例关系。比如:

  • 4:4:0 水平方向是1/1,垂直方向是1/2,表示一个色度像素对应了两个图形像素。
  • 4:2:2 水平方向是1/2,垂直方向是1/1,表示一个色度像素对应了两个图形像素。
  • 4:2:0 水平方向是1/2,垂直方向是1/2,表示一个色度像素对应了四个图形像素。

右侧一列是二次采样模式记号表示, 是 J:a:b 模式,实心黑色圆圈表示包含色度像素(Cb+Cr),空心圆圈表示不包含色度像素。对于 J:a:b 模式,主要是围绕参考块的概念定义的,这个参考块是一个 J x 2 的矩形,J 通常是 4。这样,此参考块就是宽度有 4 个像素、高度有 2 个像素的矩形。a 表示参考块的第一行包含的色度像素样本数,b 表示在参考块的第二行包含的色度像素样本数。

  • 4:4:0 参考块第一行包含四个色度样本,第二行没有包含色度样本。

  • 4:2:2 参考块第一行包含两个色度样本,第二行也包含两个色度样本,他们是交替出现。

  • 4:2:0 参考块第一行包含两个色度样本,第二行没有包含色度样本。

yuv444,yuv422,yuv420 等像素格式的本质是:每个图形像素都会包含亮度值,但是某几个图形像素会共用一个色度值,这个比例关系就是通过 4 x 2 的矩形参考块来定的。这样很容易理解类似 yuv440,yuv420 这样的格式了。

存储格式

  • 平面格式(planar formats) :先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的 V,UV的顺序可能会调换。

  • 紧缩格式(packed formats):对于 packed 的YUV格式,每个像素点的Y,U,V是连续交替存储的,如,yuv444 可能是YUV YUV YUV YUV, yuv420 可能是YYUV,YYUV,UV的顺序可能会调换,有些代码里面packed 也称为 Interleaved。

YUV420SP, YUV420P中的 P 表示的都是planar, SP 是 semi-Planar 他们的区别是:

  • YUV420P: YUV都是planer格式, 即 YYYY UU VV的顺序(UV的顺序可能会调换)。
  • YUV420SP: Y是 planer 格式, UV 是 packet 格式,即 YYYY YYYY UV UV的顺序(UV的顺序可能会调换)。

yuv422

yuyv(yuy2)

[ y u ] [ y v ] [ y u ] [ y v ]
[ y u ] [ y v ] [ y u ] [ y v ]
[ y u ] [ y v ] [ y u ] [ y v ]
[ y u ] [ y v ] [ y u ] [ y v ]

uyvy

[ u y ] [ v y ] [ u y ] [ v y ]
[ u y ] [ v y ] [ u y ] [ v y ]
[ u y ] [ v y ] [ u y ] [ v y ]
[ u y ] [ v y ] [ u y ] [ v y ]

yuv422p (yu16) 或 (yv16)

[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ u u u u ]
[ u u u u ]
[ v v v v ]
[ v v v v ]
--------------
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ v v v v ]
[ v v v v ]
[ u u u u ]
[ u u u u ]

yuv422sp (nv16) 或 (nv61)

[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ u v u v ]
[ u v u v ]
[ u v u v ]
[ u v u v ]
------------------
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ v u v u ]
[ v u v u ]
[ v u v u ]
[ v u v u ]

yuv420

yuv420p(yu12)或(yv12)

[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ u u ]
[ u u ]
[ v v ]
[ v v ]
--------------
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ v v ]
[ v v ]
[ u u ]
[ u u ]

yuv420sp(nv12)或(nv21)

[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ u v u v ]
[ u v u v ]
---------------
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ y y y y ]
[ v u v u ]
[ v u v u ]

小结

  • nv 都是 semi-plane 系列
  • nv12 表示正常的顺序,即uv plane,先是u,然后是v,nv 21 表示相反的顺序,即uv plane,先是v,然后是u,nv16 和 nv61 也类似此含义。
  • 12 表示一个像素占用12bit,其中y是定死的占8bit,也就是u占2bit,v占2bit。
  • 16 表示一个像素占用16bit,其中y是定死的8bit,也即是u占4bit,v占4bit。

分解 YUV 数据

分解 YUV420P

/*
* @brief:splict y u v from yuv420p
* param[in] src: srource file name
* param[in] w: srource file width
* param[in] h: srource file height
* param[in] frames: srource frame numbers
*/
int split_yuv420P(const char* src, int w, int h, int frames)
{
	assert(src != nullptr);
	FILE* fsrc = fopen(src, "rb");
	if (fsrc == nullptr)
		return -1;

	const char* out_y = "out_y.y";
	const char* out_u = "out_u.u";
	const char* out_v = "out_v.v";

	FILE* fy = fopen(out_y, "wb");
	FILE* fu = fopen(out_u, "wb");
	FILE* fv = fopen(out_v, "wb");

	assert(fy && fu && fv);
	const int frame_len = w * h * 3 / 2;
	uint8_t* buf = new uint8_t[frame_len];
	for (int i = 0; i < frames; ++i)
	{
		int n = fread(buf, 1, frame_len, fsrc);
		if (n != frame_len)
			break;

		fwrite(buf, 1, w * h, fy);
		fwrite(buf + w * h, 1, w * h / 4, fu);
		fwrite(buf + w * h * 5 / 4, 1, w * h / 4, fv);
	}

	free(buf);
	fclose(fsrc);
	fclose(fy);
	fclose(fu);
	fclose(fv);

	return 0;
}

分解 YUV444P

/*
* @brief:splict y u v from yuv444p
* param[in] src: srource file name
* param[in] w: srource file width
* param[in] h: srource file height
* param[in] frames: srource frame numbers
*/
int split_yuv444P(const char* src, int w, int h, int frames)
{
	assert(src != nullptr);
	FILE* fsrc = fopen(src, "rb");
	if (fsrc == nullptr)
		return -1;

	const char* out_y = "out_y.y";
	const char* out_u = "out_u.u";
	const char* out_v = "out_v.v";

	FILE* fy = fopen(out_y, "wb");
	FILE* fu = fopen(out_u, "wb");
	FILE* fv = fopen(out_v, "wb");

	assert(fy && fu && fv);
	const int frame_len = w * h * 3;
	uint8_t* buf = new uint8_t[frame_len];
	for (int i = 0; i < frames; ++i)
	{
		int n = fread(buf, 1, frame_len, fsrc);
		if (n != frame_len)
			break;

		fwrite(buf, 1, w * h, fy);
		fwrite(buf + w * h, 1, w * h, fu);
		fwrite(buf + w * h * 2, 1, w * h, fv);
	}

	delete(buf);
	fclose(fsrc);
	fclose(fy);
	fclose(fu);
	fclose(fv);

	return 0;
}

测试:
下载 Lena 图转换成 YUV 格式:

.ffmpeg.exe -i Lena.jpg -s 480*420 -pix_fmt yuv42p Lena480_420_420p.yuv
.ffmpeg.exe -i Lena.jpg -s 480*420 -pix_fmt yuv444p Lena480_420_444p.yuv

原文地址:https://www.cnblogs.com/xiaojianliu/p/14686080.html