终于下定决心开始研究多媒体技术、开始进实验室学习!庆祝撒花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个帧大小)。
三、存储格式
(一)平面格式与打包格式
YUV有打包格式(packed)和平面格式(planar)两种。
打包格式:将YUV三个分量放在同一个数组中,通常是几个相邻像素组成一个宏像素。例如YUV422中的YUVY(存储顺序为Y1->Cb->Y2->Cr)。
平面格式:使用三个数组分开存放YUV三个分量(每一帧)。其中YUV444p、YUV422p、YUV420p和YUV420sp使用的都是这种存储方式,格式末尾的p代表平面模式。下图是YUV422p。
(二)各种格式
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++
头文件:
#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对比:
(二)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对比:
(三)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对比:
五、问题
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,处理视频肯定远远不够呀。
解决方法是项目名右击找到属性-链接器-系统中改变堆栈预留空间的大小就好~