YUV的一些基础知识

<Excerpt in index | 首页摘要>

YUV的一些基础知识

<The rest of contents | 余下全文>

前提

最近在看FFMPEG的东西,发现很多内容由于没有相关基础知识支撑 导致看得人云里雾里的。

所以决定先好好学习学习基础理论知识。

什么是YUV

之前做FFmepeg视频播放的时候,里面用到了一个工具类(livyuv)。

就是用于将ffmpeg读出来的每一个帧的视频(YUV)->rgb 然后显示出来.

那么这个YUV到底是什么呢?

RGB和YUV:

1
2
3
4
5
RGB和YUV都是色彩空间,用于表示颜色,两者可以相互转化。
YUV(亦称YCrCb)是被欧洲电视系统所采用的一种颜色编码方法(属于PAL)。
YUV主要用于优化彩色视频信号的传输,使其向后兼容老式黑白电视。
与R GB视频信号传输相比,
它最大的优点在于只需占用极少的带宽(RGB要求三个独立的视频信号同时传输)

简单地说,YUV和RGB的作用是一样的,用来描述一个点的颜色。

RGB通过色彩来让人眼感知颜色,YUV通过亮度(这个很有趣,有兴趣的可以自己去了解一下)。

而且YUV比RGB更省空间!

YUV的处理

现在大概搞懂了YUV是个什么东西。

YUV详尽的就自己去看一下吧 [YUV格式详解][1]
[1]: https://msdn.microsoft.com/en-us/library/aa904813(VS.80).aspx

本文中像素的采样位数一律为8bit。由于1Byte=8bit,所以一个像素的一个分量的采样值占用1Byte。

YUV420视频的分离YUV

首先,你需要下载一个YUV视频播放器(文章结尾的打包下载中提供)

首先说一下,一个yuv文件的每一帧的大小(yuv420p)为

宽度 * 高度 * 3 / 2

其中,y占2/3的大小,u/v 各占1/6的大小.

那么首先看看我们的分离代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>

int simplest_yuv420_split(int w, int h,int num){
FILE *fp = fopen("../video/yuv_420p.yuv", "rb+");
FILE *fp1 = fopen("../video/output_420_y.y", "wb+");
FILE *fp2 = fopen("../video/output_420_u.y", "wb+");
FILE *fp3 = fopen("../video/output_420_v.y", "wb+");

unsigned char * pic = (unsigned char *)malloc(w * h * 3 / 2);

for(int i = 0; i < num; i++){
fread(pic, 1, w * h * 3 / 2, fp);

fwrite(pic, 1, w * h , fp1);

fwrite(pic + w * h, 1, w * h / 4, fp2);

fwrite(pic + w * h * 5 / 4, 1, w * h / 4, fp3);
}

fclose(fp3);
fclose(fp2);
fclose(fp1);
fclose(fp);

return 0;
}

int main(){
simplest_yuv420_split(352, 288, 300); //这个300是说这个文件有300帧
return 0;
}

跑起来之后让我们看一下对比图(顺序依次为:原图、Y、U、V):

在这里需要注意输出的U、V分量在YUV播放器中也是当做Y分量进行播放的


注意播放后三个视频的时候,像素格式选择为Y,然后分辨率大小需要调整(U/V)

这里的UV、文件大小是正常的大小/2,即原来的为352x288 u/v为176x144

YUV444视频的分离YUV

首先,你需要下载一个YUV视频播放器(文章结尾的打包下载中提供)

首先说一下,一个yuv文件的每一帧的大小(yuv444p)为

宽度 * 高度 * 3

其中,y/u/v各占1/3

那么首先看看我们的分离代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <stdlib.h>

int simplest_yuv444_split(int w, int h,int num){
FILE *fp = fopen("../video/yuv_444p.yuv", "rb+");
FILE *fp1 = fopen("../video/output_444_y.y", "wb+");
FILE *fp2 = fopen("../video/output_444_u.y", "wb+");
FILE *fp3 = fopen("../video/output_444_v.y", "wb+");

unsigned char * pic = (unsigned char *)malloc(w * h * 3);

for(int i = 0; i < num; i++){
fread(pic, 1, w * h * 3, fp);

fwrite(pic, 1, w * h , fp1);

fwrite(pic + w * h, 1, w * h, fp2);

fwrite(pic + w * h * 2, 1, w * h, fp3);
}

fclose(fp3);
fclose(fp2);
fclose(fp1);
fclose(fp);

return 0;
}

int main(){
simplest_yuv444_split(256, 256, 1);

return 0;
}

跑起来之后让我们看一下对比图(顺序依次为:原图、Y、U、V):

在这里需要注意输出的U、V分量在YUV播放器中也是当做Y分量进行播放的

这里的视频文件大小都是256x256

播放的时候注意选择一下分辨率和像素格式

YUV420去掉像素颜色(灰度)

先看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>

int yuv420_gray(int w, int h,int num){
FILE *fp = fopen("../video/yuv_420p.yuv", "rb+");
FILE *fp1 = fopen("../video/output_420_gray.yuv", "wb+");

unsigned char * pic = (unsigned char *)malloc(w * h * 3 / 2);

for(int i = 0; i < num; i++){
fread(pic, 1, w * h * 3 / 2, fp);
for(int j = 0; j < w * h / 2; j++){
(pic + w * h)[j] = 128; //u/v修改为128
//printf("%d ", (pic + w * h)[j]);
}
fwrite(pic, 1, w * h * 3 / 2, fp1);
}


fclose(fp1);
fclose(fp);

return 0;
}

int main(){
yuv420_gray(352, 288, 300);
return 0;
}

从代码可以看出,如果想把YUV格式像素数据变成灰度图像,只需要将U、V分量设置成128即可。这是因为U、V是图像中的经过偏置处理的色度分量。色度分量在偏置处理前的取值范围是-128至127,这时候的无色对应的是“0”值。经过偏置后色度分量取值变成了0至255,因而此时的无色对应的就是128了

看一下效果:

YUV420亮度减半

还是先看一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdlib.h>

int yuv420_halfy(int w, int h,int num){
FILE *fp = fopen("../video/yuv_420p.yuv", "rb+");
FILE *fp1 = fopen("../video/output_420_half.yuv", "wb+");

unsigned char * pic = (unsigned char *)malloc(w * h * 3 / 2);

for(int i = 0; i < num; i++){
fread(pic, 1, w * h * 3 / 2, fp);
for(int j = 0; j < w * h; j++){
pic[j] /= 2; //y减半
}
fwrite(pic, 1, w * h * 3 / 2, fp1);
}


fclose(fp1);
fclose(fp);

return 0;
}

int main(){
yuv420_halfy(352, 288, 300);
return 0;
}

很简单,就是将Y的值减半即可.

看一下效果图:

YUV420像素数据的周围加上边框

就是将视频的周围N个像素点的亮度调整为255即可.

看一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdlib.h>

#define BORDER 10

int yuv420_border(int w, int h,int num){
FILE *fp = fopen("../video/yuv_420p.yuv", "rb+");
FILE *fp1 = fopen("../video/output_420_border.yuv", "wb+");

unsigned char * pic = (unsigned char *)malloc(w * h * 3 / 2);

for(int i = 0; i < num; i++){
fread(pic, 1, w * h * 3 / 2, fp);

for(int j = 0;j < h; j++){
for(int k = 0; k < w; k++){
//这个判断就是为了计算出边框10个像素点的数据
if(k < BORDER || k > (w-BORDER) || j < BORDER || j > ( h - BORDER))
pic[j*w+k]=255;
}
}

fwrite(pic, 1, w * h * 3 / 2, fp1);
}


fclose(fp1);
fclose(fp);

return 0;
}

int main(){
yuv420_border(352, 288, 300);
return 0;
}

看一下效果图:

生成YUV420的灰阶图

直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
#include <stdlib.h>

void create_gray_yuv420(int width, int height, int ymin, int ymax, int barnum){
int barwidth;
float lum_inc;
unsigned char lum_temp;
int uv_width,uv_height;
FILE *fp=NULL;
unsigned char *data_y=NULL;
unsigned char *data_u=NULL;
unsigned char *data_v=NULL;
int t=0,i=0,j=0;

barwidth=width/barnum;
lum_inc=((float)(ymax-ymin))/((float)(barnum-1));
uv_width=width/2;
uv_height=height/2;

data_y=(unsigned char *)malloc(width*height);
data_u=(unsigned char *)malloc(uv_width*uv_height);
data_v=(unsigned char *)malloc(uv_width*uv_height);

if((fp=fopen("../video/out_put_420p_graybar.yuv","wb+"))==NULL){
printf("Error: Cannot create file!");
return;
}

printf("Y, U, V value from picture's left to right:\n");
for(t=0;t<(width/barwidth);t++){
lum_temp=ymin+(char)(t*lum_inc);
printf("%3d, 128, 128\n",lum_temp);
}

for(j=0;j<height;j++){
for(i=0;i<width;i++){
t=i/barwidth;
lum_temp=ymin+(char)(t*lum_inc);
data_y[j*width+i]=lum_temp;
}
}

for(j=0;j<uv_height;j++){
for(i=0;i<uv_width;i++){
data_u[j*uv_width+i]=128;
}
}

for(j=0;j<uv_height;j++){
for(i=0;i<uv_width;i++){
data_v[j*uv_width+i]=128;
}
}

fwrite(data_y,width*height,1,fp);
fwrite(data_u,uv_width*uv_height,1,fp);
fwrite(data_v,uv_width*uv_height,1,fp);
fclose(fp);
free(data_y);
free(data_u);
free(data_v);
}

int main(){
create_gray_yuv420(352, 288,0,255,10);
return 1;
}

这个就是根据宽度计算每个条目的宽度,然后U、V都是255, Y从0-255递增

看看效果图:

源码下载

ok,YUV的学习就到这里为止。

后面会继续讲讲RGB.

Github下载