视音频学习基础篇(二)----YUV格式像素数据处理

先给自己打个广告,本人的微信公众号:嵌入式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:
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关键字即可。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值