总算放假了,也有闲下来的功夫去写一些博文了。接下来我会出一些关于音视频的技术博文。
如果有小伙伴想学习音视频的可以关注一下这位大佬的博客: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);
}
效果如下