音视频入门(一) - YUV像素处理

总算放假了,也有闲下来的功夫去写一些博文了。接下来我会出一些关于音视频的技术博文。
如果有小伙伴想学习音视频的可以关注一下这位大佬的博客:https://blog.csdn.net/leixiaohua1020
闲话不多说,下面开始今天讨论的主题。

一、YUV格式图片的原理

我们看到一张照片,有亮度,有色度。YUV格式中的Y就代表亮度,U和V代表色度。不严谨地说,如果把一帧YUV图像中的U和V拿掉,那么这张图片只剩下亮度,它就会变成一张黑白照,为什么这么说不严谨,这里暂时先保留这个问题。

二、YUV格式图片数据的内部排列

YUV格式的图片又有很多种排列方式,详细的可以自行百度,这里不展开讨论。
这里简单列举几种
YUV420p有分I420和YV12

I420的内部排列
Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y
U U U U U U U U U U
V V V V V V V V V V
YV12的内部排列
Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y
V V V V V V V V V V
U U U U U U U U U U

YUV420sp有分NV12和NV21

NV12的内部排列
Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y
U V U V U V U V U V
U V U V U V U V U V
NV21的内部排列
Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y
V U V U V U V U V U
V U V U V U V U V U

上述内容是一帧分辨率为10*4的YUV图像的内部组成,通过观察,很容易发现,YUV420格式的图片中的 Y U V 比例是4:1:1。
所以他为什么不叫YUV411???我这里也不太清楚。

三、将一帧YUV图像变成黑白照(灰度图)

了解了一帧YUV图像的内部组成,下面我们来编码修改一帧YUV420p(这里默认I420)图像,将他变成黑白照片。
在编码前,我们先考虑一下。先前我们说过,我们要把YUV图像的U和V数据拿掉,就能让这张图片变成黑白照(灰度图)。如果我们把U和V数据去掉,那么只剩下Y数据,打开图片的时候选择一定要选择以“Y”格式打开,而不是“YUV420”格式打开。
下面上代码

#include <stdio.h>
#include <stdlib.h>
#define outPutFileName "testGrey.y"
/*
   para: fileName: 输入图片名称
         width: 宽 
         height: 高
*/
int testYuv420Grey(char *fileName, int width, int height)
{
    
    
    FILE *fp;
    FILE *fp1;
    unsigned char *readBuf;
    readBuf = (unsigned char *)malloc(width*height*3/2);
    fp = fopen(fileName, "rb+");
    fp1 = fopen(outPutFileName, "wb+");

    /* read yuv file */
    fread(readBuf, 1, width*height, fp);
    
    /* create new file */
    fwrite(readBuf, 1, width*height, fp1);

    free(readBuf);
    fclose(fp);
    fclose(fp1);
    return 0;
}

int main()
{
    
    
    testYuv420Grey("lena_256x256_yuv420p.yuv", 256, 256);
    return 0;
}

输入图片
在这里插入图片描述
输出图片
在这里插入图片描述
这里我只保存了Y数据,那么理论上来说,它不能叫做一帧YUV图像。所以我们不一定要去将U和V的数据拿掉,也可以修改U和V的值。按照我们的常识来看,将U和V的数据修改为0就代表无色。然而U、V是图像中的经过偏置处理的色度分量。在偏置处理前,它的取值范围是-128-127,这时,把U和V数据修改为0代表无色。在偏置处理后,它的取值范围变成了0-255,所以这时候需要取中间值,即128。所以我们下面要将U和V数据修改为128

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define outPutFileName "testGrey.yuv"

int testYuv420Grey(char *fileName, int width, int height)
{
    
    
    FILE *fp;
    FILE *fp1;
    unsigned char *readBuf;
    readBuf = (unsigned char *)malloc(width*height*3/2);
    fp = fopen(fileName, "rb+");
    fp1 = fopen(outPutFileName, "wb+");

    /* read yuv file */
    fread(readBuf, 1, width*height, fp);

    /* memset U and V */
    memset(readBuf+width*height, 128, width*height/2);

    /* create new file */
    fwrite(readBuf, 1, width*height*3/2, fp1);

    free(readBuf);
    fclose(fp);
    fclose(fp1);
    return 0;
}

int main()
{
    
    
    testYuv420Grey("lena_256x256_yuv420p.yuv", 256, 256);
    return 0;
}

原图
在这里插入图片描述
修改后的图片
在这里插入图片描述

四、将一帧YUV图像亮度减半

这个很简单,只要把Y的值减半就行了

#include <stdio.h>
#include <stdlib.h>

#define outputFileName "halfy.yuv"

int simplest_yuv420_halfy(char *fileName, int width, int height)
{
    
    
    FILE *fp1 = NULL;
    FILE *fp2 = NULL;
    unsigned char *readBuf = NULL;
    unsigned char *point = NULL;
    readBuf = (unsigned char *)malloc(width*height*3/2);
    point = readBuf;

    fp1 = fopen(fileName, "rb+");
    fp2 = fopen(outputFileName, "wb+");
    fread(readBuf, 1, width*height*3/2, fp1);
    
    /* 亮度减半 */
    for(int i = 0; i < width*height; i++)
    {
    
    
        *point = *point/2;
        point++;
    }

    fwrite(readBuf, 1, width*height*3/2, fp2);

    free(readBuf);
    fclose(fp1);
    fclose(fp2);
    readBuf = NULL;
    point = NULL;
    fp1 = NULL;
    fp2 = NULL;
    return 0;
}

int main()
{
    
    
    simplest_yuv420_halfy("lena_256x256_yuv420p.yuv", 256, 256);
}

原图
在这里插入图片描述
修改后的图片
在这里插入图片描述

五、自己制作一帧YUV420灰阶测试图

U和V数据置为128,将Y数据递增即可。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define output_file "grey.yuv"

/*
    width :  输出图片的宽
    height: 输出图片的高
    num   :  输出灰阶测试图条数
*/
#define yMax 255
int output_grey_pic(int width, int height, int num)
{
    
    
    FILE *fp1 = NULL;
    int newest_y_data = 0;
    int y_distance = yMax / num;
    int uv_data = 128;
    long file_size = 0;
    int output_width = width / num; // 间隔的宽
    int y_data[64];
    unsigned char *buffer;
    file_size = height * width * 3 / 2;
    buffer = (unsigned char *)malloc(file_size);
    fp1 = fopen(output_file, "wb+");

    for (int i = 0; i < num; i++)
    {
    
    
        y_data[i] = newest_y_data;
        newest_y_data += y_distance;
        printf("Y = %d U = %d V = %d\n", y_data[i], uv_data, uv_data);
    }

    /* 写入y数据 */
    memset(buffer, 0, file_size);
    for (int h = 0; h < height; h++)
    {
    
    
        for (int cnt = 0; cnt < num; cnt++)
        {
    
    
            memset(buffer + cnt * output_width + h * width, y_data[cnt], output_width);
        }
    }

    /* 写入 U V 数据 */
    memset(buffer + height * width, uv_data, height * width / 2);

    /* 将缓存写入文件 */
    fwrite(buffer, 1, file_size, fp1);

    free(buffer);
    fclose(fp1);

    return 0;
}

int main()
{
    
    
    output_grey_pic(1920, 1080, 4);
}

效果如下
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Stone831143/article/details/113772328