YUV 是比较常用的原始视频数据数据格式,视频采集芯片输出的码流大部分都是 YUV 数据流形式,而视频处理(如 H264、H265编码等),也是在原始 YUV 码流进行编码和解析。所以,了解熟悉 YUV 数据流对于做视频领域的人而言,至关重要。
YUV,分为三个分量,Y:表示明亮度(Luminance 或 Luma),也就是灰度值;而 U 和 V :表示的则是色度(Chrominance 或 Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。这样设计分开的主要原因是,人眼对色度的敏感程度要低于对亮度的敏感程度。
与我们熟知的 RGB 类似,YUV 也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有 UV 信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV 不像 RGB 那样要求三个独立的视频信号同时传输,所以用 YUV 方式传送占用极少的频宽。
YUV 码流有多种不同的格式,要分析 YUV 码流,就必须搞清楚你面对的到底是哪一种格式,并且必须搞清楚这种格式的 YUV 采样和分布情况。
YUV 按像素分布规律被分为两大类,packed 打包格式和 plannar 平面格式,
目前比较主流的采样方式有:YUV444、YUV422、YUV420,下面会对这三种格式存储方式加以介绍,并说明如何根据其采样格式来从码流中还原每个点的 YUV 值。
常讲的的 YUV A:B:C 的意思一般是指基于 4 个象素来讲,其中 Y 采样了 A 次,U 采样了 B 次,V 采样了 C 次
下面的三个图可以直观的表示采集的方式,以黑点表示像素点的 Y 分量,以空心圆圈表示该像素点的 UV 分量。
先记住下面这句话,以后提取每个像素的 YUV 分量会用到。
YUV 4:4:4 采样,每一个 Y 对应一组 UV 分量,每像素 24 位
YUV 4:2:2 采样,每两个 Y 共用一组 UV 分量,每像素 16 位
YUV 4:2:0 采样,每四个 Y 共用一组 UV 分量,每像素 12 位
YUV 的优点之一是基于人眼对色度的敏感程度要低于对亮度的敏感程度,可以适当降低色度的采样率,同时不会明显降低视觉质量。因此,常见的 YUV422、YUV420 都适当降低了色度分量。
下面用图的形式给出常见的 YUV 码流的存储方式,其中,Cb、Cr 的含义等同于 U、V。
YUYV 为 YUV422 采样方式的存储格式之一,相邻的两个 Y 分量共用其相邻的两个Cb、Cr,因此,对于像素点 Y’00、Y’01 而言,其 Cb、Cr 的值均为 Cb00、Cr00,其他的像素点的 YUV 取值依次类推。
UYVY 格式也是 YUV422 采样方式的存储格式之一,只不过与 YUYV 不同的是 UV 的排列顺序不一样而已,还原其每个像素点的YUV值的方法与上面一样。
YUV422P 是一种 plannar 模式,即平面模式。与前面其他 YUV422 采访方式不同,它并不是将 YUV 数据交错存储,而是先存放所有的 Y 分量,然后存储所有的 U分量, 最后存储所有的 V 分量。
如上图所示。其每一个像素点的 YUV 值提取方法也是遵循 YUV422 格式的最基本提取方法,即两个 Y 共用一个 UV。比如, 对于像素点 Y’00、Y’01 而言,其 Cb、Cr 的值均为 Cb00、Cr00。
YU12 和 YV12 属于 YUV420 采样格 式之一,也是一种 plannar
模式,将 Y、U、V 分量分别打包,依次存储。
其每一个像素点的 YUV 数据提取遵循 YUV420 格式的提取方式,即 4 个 Y 分量共用一 组 UV,即上图中,Y’00、Y’01、Y’10、Y’11 共用 Cr00、Cb00,其他依次类推。
NV12 和 NV21 属于 YUV420 采样格式之一,也是一种 plannar 模式,但是与 YV12 不同的是,将 Y 和 UV 分为两个 plane,Y 连续存储,但是 UV 交错存储。
其提取方式与上一种类似,即 Y’00、Y’01、Y’10、Y’11 共用 Cr00、Cb00。
这种采样存储方式也被称为 YUV420SP,即 UV 交错存储,作为区分,上文 4) 介绍的存储方式被称为 YUV420P。
下面用一种表说明几种常见存储方式所占用的内存,以 w*h 大小的图像为例:
采样方式 | 占用 |
---|---|
YUV 4:4:4 | Y(w * h) + U(w * h) + V(W * h) = 3 * w * h |
YUV 4:2:2 | Y(w * h) + U(w * h * 1/2) + V(W * h * 1/2) = 2 * w * h |
YUV 4:2:0 | Y(w * h) + U(w * h * 1/4) + V(W * h * 1/4) = 1.5 * w * h |
由于 YUV420P 各分量的连续存储性质,可以很方便快捷的直接提取出三个分量。
下面的程序将 YUV420P 中的 Y、U、V 三个分量分离出来。代码如下:
/**
* Split Y, U, V planes in YUV420P file.
* @param url Location of Input YUV file.
* @param w Width of Input YUV file.
* @param h Height of Input YUV file.
* @param num Number of frames to process.
*
*/
void simplest_yuv420_split(char *url, int w, int h)
{
FILE *fp = fopen(url, "rb+");
FILE *fp1 = fopen("output_420_y.y", "wb+");
FILE *fp2 = fopen("output_420_u.y", "wb+");
FILE *fp3=fopen("output_420_v.y", "wb+");
unsigned char *pic = (unsigned char*) malloc(w * h * 3 / 2);
fread(pic, 1, w * h * 3 / 2, fp);
//Y
fwrite(pic, 1, w * h, fp1);
//U
fwrite(pic + w * h, 1, w * h / 4, fp2);
//V
fwrite(pic + w * h * 5 / 4, 1, w * h / 4, fp3);
free(pic);
fclose(fp);
fclose(fp1);
fclose(fp2);
fclose(fp3);
}
从代码可以看出, 如果视频帧的宽和高分别为 w 和 h,那么一帧 YUV420P 像素数据一共占用 (w * h * 3 / 2) Byte 的数据。其中前 (w * h) Byte存储 Y,接着的 (w * h * 1 / 4) Byte 存储 U,最后 (w * h * 1 / 4) Byte 存储V。
上述调用函数的代码运行后,将会把一张分辨率为 256x256 分辨率 的 YUV420P 格式的像素数据文件分离成为三个文件:
output_420_y.y:纯 Y 数据,分辨率为 256x256
output_420_u.y:纯 U 数据,分辨率为 128x128
output_420_v.y:纯 V 数据,分辨率为 128x128
下面是一个运行实例:
输出原图:
程序输出三个分量:
1). output_420_y.y
2). output_420_u.y
3). output_420_v.y
本程序中的函数可以通过将 YUV 数据中的亮度分量 Y 的数值减半的方法,降低图像的亮度。函数代码如下所示
/**
* Halve Y value of YUV420P file
* @param url Location of Input YUV file.
* @param w Width of Input YUV file.
* @param h Height of Input YUV file.
* @param num Number of frames to process.
*/
int simplest_yuv420_halfy(char *url, int w, int h)
{
FILE *fp = fopen(url,"rb+");
FILE *fp1 = fopen("output_half.yuv", "wb+");
unsigned char *pic = (unsigned char*)malloc(w * h * 3 / 2);
fread(pic, 1, w * h * 3 / 2, fp);
//Half
for (int j = 0; j < w * h; j++) {
unsigned char temp = pic[j] / 2;
pic[j] = temp;
}
fwrite(pic, 1, w * h * 3 / 2, fp1);
free(pic);
fclose(fp);
fclose(fp1);
return 0;
}
从代码可以看出,如果打算将图像的亮度减半,只要将图像的每个像素的 Y 值取出来分别进行除以 2 的工作就可以了。
输入原图:
处理后图片:
void YUV420spRotate90(uchar *des, uchar *src, int width, int height)
{
int wh = width * height;
//旋转Y
int k = 0;
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
des[k] = src[width * j + i];
k++;
}
}
for (int i = 0; i < width; i += 2) {
for (int j = 0; j < height / 2; j++) {
des[k] = src[wh+ width * j + i];
des[k+1] = src[wh + width * j + i + 1];
k += 2;
}
}
}
其他待添加…