先给自己打个广告,本人的微信公众号:嵌入式Linux江湖,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题。
本文主要思想是基于雷神的博客,做了一些修改,绝大部分思想仍然是参考的雷神的文章,以此纪念雷神,并查看自己的掌握程度。
一 why
前面的博文《视音频学习基础篇(一)----YUV采样格式和存储格式》,介绍了YUV各种格式,本文主要介绍如何对YUV420P格式的像素数据做处理,以加深我们对YUV格式的理解,其他YUV格式的像素数据处理交给读者们自己实现。
YUV420P的YU12格式,它的YUV存储格式如下,首先是Y,然后U,最后是V,在对这个文件进行处理的时候,按照这样的文件组成格式来分析即可。
二 how
以下几个章节都是以YUV420P中的YU12为例。
假设YU12格式的文件只包含一帧YUV像素数据。且Y,U,V都是8bit表示,显然在这个YUV文件中,前面W x H个字节是Y数据,后面 W x H / 4个字节是U数据,最后 W x H / 4个字节是V数据,整个YUV文件大小为W x H x 3 / 2。
2.1 分离YUV420P–YU12像素数据中的Y、U、V分量
读取源文件temp_352_288.yuv,分别将Y,U,V保存为单独格式的文件。
/* 从yuv420p文件中,分离出Y,U,V分量,并单独存成文件
* 在这里需要注意输出的Y、U、V分量在YUV播放器中当做Y分量进行播放的,播放U/V分量时注意重新设置分辨率大小为 (352/2) x (288/2)
*/
#define SRC_YUV_FILE "temp_352_288.yuv"
#define DST_YUV_FILE1 "temp_352_288_y.y"
#define DST_YUV_FILE2 "temp_352_288_u.y"
#define DST_YUV_FILE3 "temp_352_288_v.y"
static int yuv420p_split(void)
{
FILE* fp1;
FILE* fp2;
FILE* fp3;
FILE* fp4;
unsigned int size = W * H * 3 / 2;
unsigned char* content;
content = (unsigned char*)malloc(size);
if (content == NULL) {
printf("malloc error!\n");
return -1;
}
fp1 = fopen(SRC_YUV_FILE, "rb+");
if (fp1 == NULL) {
printf("cannot open file\n");
return -1;
}
fp2 = fopen(DST_YUV_FILE1, "wb+");
if (fp2 == NULL) {
printf("cannot open file\n");
return -1;
}
fp3 = fopen(DST_YUV_FILE2, "wb+");
if (fp2 == NULL) {
printf("cannot open file\n");
return -1;
}
fp4 = fopen(DST_YUV_FILE3, "wb+");
if (fp2 == NULL) {
printf("cannot open file\n");
return -1;
}
fread(content, 1, size, fp1);
fwrite(content, 1, W * H, fp2);
fwrite(content + W * H, 1, W * H / 4, fp3);
fwrite(content + W * H * 5 / 4, 1, W * H / 4, fp4);
printf("Done!\n");
free(content);
fclose(fp1);
fclose(fp2);
fclose(fp3);
fclose(fp4);
return 0;
}
然后,我们在main函数调用上面的函数即可yuv420p_split,执行完成之后,会生成三个文件temp_352_288_y.y, temp_352_288_u.y, temp_352_288_v.y。
temp_352_288_y.y:纯Y数据,分辨率为352x288。
temp_352_288_u.y:纯U数据,分辨率为176x144。
temp_352_288_v.y:纯V数据,分辨率为176x144。
程序输入的原图如下所示
程序输出的三个文件的截图如下图所示。在这里需要注意输出的U、V分量在YUV播放器中也是当做Y分量进行播放的。
temp_352_288_y.y:
需要注意的是,在播放器中需要选择y格式,如下:
temp_352_288_u.y:
temp_352_288_v.y:
在打开temp_352_288_u.y和temp_352_288_v.y的时候,不仅需要设置为Y格式,还需要修改分辨率为(352 / 2)x (288 / 2)。同时为了能够看清楚小分辨率176x144,可以设置播放器缩放为2:1。
2.2 将YUV420P像素数据去掉颜色(变成灰度图)
/*
* 将yuv420p像素去掉颜色,变成灰度图
*/
static int yuv420p_grey(void)
{
FILE* fp1;
FILE* fp2;
unsigned int size = W * H * 3 / 2;
unsigned char* content;
content = (unsigned char*)malloc(size);
if (content == NULL) {
printf("malloc error!\n");
return -1;
}
fp1 = fopen(SRC_YUV_FILE, "rb+");
if (fp1 == NULL) {
printf("cannot open file\n");
return -1;
}
fp2 = fopen(DST_YUV_FILE4, "wb+");
if (fp2 == NULL) {
printf("cannot open file\n");
return -1;
}
fread(content, 1, size, fp1);
memset(content + W * H, 128, W * H / 2);
fwrite(content, 1, size, fp2);
printf("Done!\n");
free(content);
fclose(fp1);
fclose(fp2);
return 0;
}
从代码可以看出,如果想把YUV格式像素数据变成灰度图像,只需要将U、V分量设置成128即可。这是因为U、V是图像中的经过偏置处理的色度分量。色度分量在偏置处理前的取值范围是-128至127,这时候的无色对应的是“0”值。经过偏置后色度分量取值变成了0至255,因而此时的无色对应的就是128了。输入的原图如下所示:
处理后的图像如下所示。
2.3 将YUV420P像素数据的亮度减半
/*
* 将yuv420p像素亮度减半
*/
static int yuv420p_half_y(void)
{
FILE* fp1;
FILE* fp2;
unsigned int size = W * H * 3 / 2;
unsigned char* content;
unsigned char temp;
unsigned int i;
printf("half y\n");
content = (unsigned char*)malloc(size);
if (content == NULL) {
printf("malloc error!\n");
return -1;
}
fp1 = fopen(SRC_YUV_FILE, "rb+");
if (fp1 == NULL) {
printf("cannot open file\n");
return -1;
}
fp2 = fopen(DST_YUV_FILE5, "wb+");
if (fp2 == NULL) {
printf("cannot open file\n");
return -1;
}
fread(content, 1, size, fp1);
for (i = 0; i < W * H; i++) {
content[i] = content[i] / 2;
}
fwrite(content, 1, size, fp2);
printf("Done!\n");
free(content);
fclose(fp1);
fclose(fp2);
return 0;
}
从代码可以看出,如果打算将图像的亮度减半,只要将图像的每个像素的Y值取出来分别进行除以2的工作就可以了。图像的每个Y值占用1 Byte,取值范围是0至255。输入的原图如下所示。
处理后的图像如下所示。
2.4 将YUV420P像素数据的周围加上边框
/*
* 将yuv420p像素周围加上边框
*/
static int yuv420p_boarder(void)
{
#define BOARDER 20
FILE* fp1;
FILE* fp2;
unsigned int size = W * H * 3 / 2;
unsigned char* content;
unsigned int i, j;
printf("boarder\n");
content = (unsigned char*)malloc(size);
if (content == NULL) {
printf("malloc error!\n");
return -1;
}
fp1 = fopen(SRC_YUV_FILE, "rb+");
if (fp1 == NULL) {
printf("cannot open file\n");
return -1;
}
fp2 = fopen(DST_YUV_FILE6, "wb+");
if (fp2 == NULL) {
printf("cannot open file\n");
return -1;
}
fread(content, 1, size, fp1);
for (i = 0; i < H; i++) {
for (j = 0; j < W; j++) {
if (i < BOARDER || i > H - BOARDER || j < BOARDER || j > W - BOARDER) {
content[i * W + j] = 255;
}
}
}
fwrite(content, 1, size, fp2);
printf("Done!\n");
free(content);
fclose(fp1);
fclose(fp2);
return 0;
}
从代码可以看出,图像的边框的宽度为border,本程序将距离图像边缘border范围内的像素的亮度分量Y的取值设置成了亮度最大值255。
输入的原图如下所示。
处理后的图像如下所示。
2.5 生成YUV420P格式的灰阶测试图
/*
* 生成yuv420p的灰阶测试图
*/
static int yuv420p_grey_bar(unsigned int w, unsigned h, int barnum)
{
FILE* fp1;
unsigned int size = w * h * 3 / 2;
unsigned char* content;
unsigned int i, j;
unsigned char luma_step;
unsigned int bar_width;
printf("grey_bar\n");
content = (unsigned char*)malloc(size);
if (content == NULL) {
printf("malloc error!\n");
return -1;
}
/* yuv均是8bit */
fp1 = fopen(DST_YUV_FILE7, "wb+");
if (fp1 == NULL) {
printf("cannot open file\n");
return -1;
}
luma_step = (255 - 0) / barnum;
bar_width = w / barnum;
// generate y
for (i = 0; i < h; i++) {
for (j = 0; j < w; j++) {
content[i * w + j] = luma_step * (j / bar_width);
}
}
// generate u
memset(content + w * h, 128, w * h /4);
// generate v
memset(content + w * h * 5 / 4, 128, w * h / 4);
fwrite(content, 1, size, fp1);
printf("Done!\n");
free(content);
fclose(fp1);
return 0;
}
从源代码可以看出,本程序一方面通过灰阶测试图的亮度最小值0,亮度最大值255,灰阶数量barnum确定每一个灰度条中像素的亮度分量Y的取值。另一方面还要根据图像的宽度width和图像的高度height以及灰阶数量barnum确定每一个灰度条的宽度。有了这两方面信息之后,就可以生成相应的图片了。
在main函数中调用yuv420p_grey_bar(480, 320, 10),生成分辨率是480x320的灰阶图
三 to be continue
想要获取整个项目源代码的,请关注微信公众号:嵌入式Linux江湖,回复yuv1关键字即可。