YUV444与YUV422下采样

时间:2023-01-10 20:30:34

终于下定决心开始研究多媒体技术、开始进实验室学习!庆祝撒花bla bla~~

这篇文章写我做第一个小实验遇到的问题和一些知识点总结。

一、YUV介绍

YUV有三个分量:Y(Luminance/Luma:亮度)、U和V表示色差,体现的是图片的色彩信息。相对于RGB彩色空间,将亮度信息和色彩信息分离。这种编码模式也更加适应于人眼,据研究表明,人眼对亮度信息比色彩信息更加敏感。而YUV下采样就是根据人眼的特点,将人眼相对不敏感的色彩信息进行压缩采样,得到相对小的文件进行播放和传输。
与YUV相像YCbCr其实与其有少许不同,体现在参数的大小上,本质上都是将亮度信息与色彩信息相分开。

二、采集方式

YUV有三种采集方式,分别是4:4:4采样、4:2:2采样和4:2:0采样。

4:4:4采样:每一个Y对应一个U和一个V。大小为3*width*height(width和height是一帧的大小)。

4:2:2采样: 每两个Y共用一对U和V。大小为2*width*height(其中U分量和V分量各占1/2个帧大小)。

4:2:0采样:每四个Y共用一对U和V。大小为3/2*width*height(其中U分量和V分量各站1/4个帧大小)。

YUV444与YUV422下采样


三、存储格式

(一)平面格式与打包格式

YUV有打包格式(packed)和平面格式(planar)两种。
打包格式:将YUV三个分量放在同一个数组中,通常是几个相邻像素组成一个宏像素。例如YUV422中的YUVY(存储顺序为Y1->Cb->Y2->Cr)。
YUV444与YUV422下采样
平面格式:使用三个数组分开存放YUV三个分量(每一帧)。其中YUV444p、YUV422p、YUV420p和YUV420sp使用的都是这种存储方式,格式末尾的p代表平面模式。下图是YUV422p。
YUV444与YUV422下采样

(二)各种格式

1、YUV444

(1)YUV444p:YYYYYYYYY VVVVVVVVV UUUUUUUU

2、YUV422

(1)YUV422p:YYYYYYYY VVVV UUUU
(2)YUVY:YCbYCr YCbYCr YCbYCr
(3)UYVY:CbYCrY CbYCrY CbYCrY

3、YUV420

(1)YUV420p:
YV12:YYYYYYYY VV UU
I420:YYYYYYYY UU VV
(2)YUV420sp:
NV12:YYYYYYYY UVUV
NV21:YYYYYYYY VUVU

四、代码

平台:Visual Studio 2015
结构如图:这里就不贴测试函数了
语言:C/C++
YUV444与YUV422下采样
头文件:
#pragma once
//
// downSampling.h
// Defination of clss downSampling
// Three methods: down 444to422, 444to420, 422to420
//

#ifndef DOWN_SAMPLING
#define DOWN_SAMPLING

#include <stdio.h>
using namespace std;

class downSampling {
public:
void down444to422(char[], int, int);
void down444to420(char[], int, int);
void down422to420(char[], int, int);
};

#endif

(一)444p转422p

void downSampling::down444to422(char filepath[], int width, int height) {	FILE* pFile;	FILE* wr;	errno_t err1, err2;	if ((err1 = fopen_s(&pFile, filepath, "rb+") != 0) || (err2 = fopen_s(&wr, "Data/444to422.yuv", "a") != 0)) {		cout << "Cannot open the file!" << endl;	}	else {		long size;		fseek(pFile, 0, SEEK_END);		size = ftell(pFile);		rewind(pFile);		// frame size		int frame = size / width / height / 3;			// 需将项目属性中堆栈保留空间调大:KB=1000B 1MB=1000KB 1GB=1000MB=1000000KB 1TB=1000GB=1000000MB=1000000000KB		// 一个像素大小		char data[1];		//一帧中Y分量大小		char stream[414720];				for (int i = 1; i <= frame; ++i) {			fread(stream, sizeof(char), sizeof(stream), pFile);			fwrite(stream, sizeof(char), sizeof(stream), wr);			for (int j = 1; j <= width*height; j++) {				fread(data, sizeof(char), sizeof(data), pFile);				fseek(pFile, 1, SEEK_CUR);				fwrite(data, sizeof(char), sizeof(data), wr);			}		}		fclose(pFile);		fclose(wr);	}}

444p与422p对比:

YUV444与YUV422下采样  YUV444与YUV422下采样

(二)444p转420p

void downSampling::down444to420(char filepath[], int width, int height) {	FILE* pFile;	FILE* wr;	errno_t err1, err2;	if ((err1 = fopen_s(&pFile, filepath, "rb+") != 0) || (err2 = fopen_s(&wr, "Data/444to420.yuv", "a") != 0)) {		cout << "Cannot open the file!" << endl;	}	else {		long size;		fseek(pFile, 0, SEEK_END);		size = ftell(pFile);		rewind(pFile);		// frame size		int frame = size / width / height / 3;		// 需将项目属性中堆栈保留空间调大		// 一个数据大小		char data[1];		// 一帧中Y分量大小		char stream[414720];		// 错误原因同422转420:跳取+隔行		for (int i = 1; i <= frame; ++i) {			fread(stream, sizeof(char), sizeof(stream), pFile);			fwrite(stream, sizeof(char), sizeof(stream), wr);			for (int j = 1; j <= height; j++) {				for (int k = 1; k <= width/2; k++) {					fread(data, sizeof(char), sizeof(data), pFile);					fseek(pFile, 1, SEEK_CUR);					fwrite(data, sizeof(char), sizeof(data), wr);				}				fseek(pFile, 720, SEEK_CUR);			}		}		fclose(pFile);		fclose(wr);	}}
444p与420p对比:
YUV444与YUV422下采样  YUV444与YUV422下采样

(三)422p转420p

void downSampling::down422to420(char filepath[], int width, int height) {	FILE* pFile;	FILE* wr;	errno_t err1, err2;	if ((err1 = fopen_s(&pFile, filepath, "rb+") != 0) || (err2 = fopen_s(&wr, "Data/422to420.yuv", "a") != 0)) {		cout << "Cannot open the file!" << endl;	}	else {		long size;		fseek(pFile, 0, SEEK_END);		size = ftell(pFile);		rewind(pFile);		// frame size		int frame = size / width / height / 2;		// 需将项目属性中堆栈保留空间调大		// 一行1/2大小: width/2		char data[360];		// 一帧中Y分量大小		char stream[414720];		for (int i = 1; i <= frame; ++i) {			fread(stream, sizeof(char), sizeof(stream), pFile);			fwrite(stream, sizeof(char), sizeof(stream), wr);			for (int j = 0; j < height; j ++) {				fread(data, sizeof(char), sizeof(data), pFile);				fwrite(data, sizeof(char), sizeof(data), wr);				fseek(pFile, 360, SEEK_CUR);			}			/* 注意是各行取,不是隔列取			for (int j = 1; j <= width*height / 2; j++) {				fread(data, sizeof(char), sizeof(data), pFile);				fseek(pFile, 1, SEEK_CUR);				fwrite(data, sizeof(char), sizeof(data), wr);			}			*/		}		fclose(pFile);		fclose(wr);	}}
422p与420p对比:

YUV444与YUV422下采样  YUV444与YUV422下采样

五、问题

1、C文件读取

用C语言文件读取时,需要包含头文件<stdio.h>/<cstdio>
代码中用到了fopen、fread、fseek、fwrite、rewind、fclose等函数,详细用法见cplusplus.com。

2、VS中的fopen无法使用

先开始用fopen()函数,后来编译时会报使用fopen不安全的错误,建议用fopen_s。
fopen_s与fopen的用法稍有不同,需要加上errno判断等,详细用法资料能搜索到。

3、memcpy()函数用法

写之前看了很多参考资料,很多人用memcpy()函数代替fread()和fwrite()实现两个文件的直接粘贴。

我上面的代码中是用了buffer作文中间存储流转换的。

如果使用memcpy(wr, pFile, width * height * 3)会报非法访问的错误。在stack overflow上看到的解释是wr和pFile两个FILE*类型不能这样粘贴。

4、隔列/行取和跳取

这个问题是我看了很久才发现,视频动起来的时候发现一部分颜色有点奇怪,其实就是每一区域没有色度变量。后来发现是因为算法中UV变量的取法不对。
以422p转420p的代码为例,
错误版本:
for (int j = 1; j <= width*height / 2; j++) {	fread(data, sizeof(char), sizeof(data), pFile);	fseek(pFile, 1, SEEK_CUR);	fwrite(data, sizeof(char), sizeof(data), wr);}
正确版本:是各行取不是隔列取

5、VS中堆栈的预留大小不够

处理时先开始总是报堆栈溢出的错误,上网查资料发现VS中堆栈的预留大小只有1M,处理视频肯定远远不够呀。
解决方法是项目名右击找到属性-链接器-系统中改变堆栈预留空间的大小就好~
YUV444与YUV422下采样