Lab2 bmp文件与yuv文件的转换——C语言代码实现
一、实验内容
- 自行生成多个BMP文件,至少含5个不同的场景画面,要求带含有班级、学号后四位和本人姓名(缩写或昵称均可)的logo。
- 编写代码实现将第一步所生成的多个BMP文件转化为YUV文件,要求可在命令行中设置每个画面出现的帧数,且最后形成的YUV文件应至少包含200帧。
- 对整个程序进行调试,并将生成的YUV文件用播放软件观看,验证是否正确。
已知:
- 自行生成了6个bmp文件
- 每个bmp文件均为256*256
- 每个bmp文件均为32bit深度
- 每个bmp文件在输出的yuv文件中均出现40帧
- 输出的yuv文件为4:2:0的采样格式
具体的素材图片如下:
素材源于网络
二、实验基础
bmp图像文件的数据组成
图片源自数据压缩第二章课件
除此之外,有关于bmp文件还有几点需要特别注意:
- bmp图像规定每一扫描行的字节数必须是4的整数倍,即以DWORD对齐。(如不满4的整数倍,则采取补0处理)
- 对于倒向DIB,扫描行是由底向上存储的。
三、实现思路
1、定义相关变量,对于输出的lab2_out.yuv文件设置相应的指针。
2、设置for循环,用以循环读取输入的lab2_x的bmp文件数据。
3、对于每一个输入的bmp文件,先读取其File_header和Info_header,在读取实际的位图文件图像数据。
4、调用Lab1中RGB2YUV转换函数,进行转换。
5、将转换好的yuv数据写入输出的lab2_out.yuv文件,并循环40次。
6、释放缓冲、关闭文件。
!!需要注意的小问题!!
- 由于输出的lab2_out.yuv文件只有一个,且需要追加写入数据,所以使用lab2_out.yuv文件的yuvFile指针打开文件、关闭lab2_out.yuv文件的操作都需要在读取输入的lab2_x的bmp文件数据的循环外执行,但对于每个bmp文件的打开、数据的读取和相关内存的释放则需要在该循环中实现。
- 对于程序中需要输入的数据,可以使用命令参数写入程序中,既简单又高效。
- 在读取File_header和Info_header的数据时,要使用结构体变量,既便捷又能保证高准确率,便于后续调用结构体变量中的图像宽biWidth、高biHeight。(注意添加相关的头文件)
- 由于本次实验所使用的bmp文件均为32bit深度,是真彩图像,但其数据部分为各像素的B、G、R、A数据的循环,所以在RGB2YUV的转换函数中,需要调整b指针的"b+=3"为"b+=4"。
四、实现代码
bmp2yuv.h
int RGB2YUV (int x_dim, int y_dim, void *bmp, void *y_out, void *u_out, void *v_out, int flip);
void InitLookupTable();
main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include<windows.h>
#include"bmp2yuv.h"
BITMAPFILEHEADER File_header; /* 结构体变量 */
BITMAPINFOHEADER Info_header;
int main(int argc, char** argv)
{
char* bmpFileName = NULL;
char* yuvFileName = NULL;
FILE* bmpFile = NULL;
FILE* yuvFile = NULL;
unsigned char* bmpBuf = NULL;
unsigned char* yBuf = NULL;
unsigned char* uBuf = NULL;
unsigned char* vBuf = NULL;
long frameWidth;
long frameHeight;
bool flip = true;
/* open the YUV file */
yuvFileName = argv[1];
yuvFile = fopen(yuvFileName, "wb");
if (yuvFile == NULL)
{
printf("cannot find yuv file\n");
exit(1);
}
else
{
printf("The output yuv file is %s\n", yuvFileName);
}
int bmpNum;
bmpNum = atoi(argv[2]);
for(int j=3;j<=bmpNum+2;j++)
{
bmpFileName = argv[j];
/* open the bmp file */
bmpFile = fopen(bmpFileName, "rb");
if (bmpFile == NULL)
{
printf("cannot find bmp file\n");
exit(1);
}
else
{
printf("The input bmp file is %s\n", bmpFileName);
}
/*read file&info header*/
if(fread(&File_header,sizeof(BITMAPFILEHEADER),1,bmpFile)!=1)
{
printf("read file header error!\n");
exit(0);
}
if(File_header.bfType!=0x4D42)
{
printf("Not bmp file!\n");
exit(0);
}
else
{
printf("This is a bmp file!\n");
}
if(fread(&Info_header,sizeof(BITMAPINFOHEADER),1,bmpFile)!=1)
{
printf("read info header error!\n");
exit(0);
}
frameWidth = Info_header.biWidth;
frameHeight = Info_header.biHeight;
/* get an input buffer for a frame */
bmpBuf = (unsigned char*)malloc(frameWidth * frameHeight * 4);
/* get the output buffers for a frame */
yBuf = (unsigned char*)malloc(frameWidth * frameHeight);
uBuf = (unsigned char*)malloc((frameWidth * frameHeight) / 4);
vBuf = (unsigned char*)malloc((frameWidth * frameHeight) / 4);
if (bmpBuf == NULL || yBuf == NULL || uBuf == NULL || vBuf == NULL)
{
printf("no enought memory\n");
exit(1);
}
if(fread(bmpBuf, 1, frameWidth * frameHeight * 4, bmpFile)==NULL)
{
printf("read data error!\n");
exit(1);
}
/*change bmp to yuv*/
if(RGB2YUV (frameWidth, frameHeight, bmpBuf, yBuf, uBuf, vBuf, flip))
{
printf("error");
exit(0);
}
for (int i = 0; i < frameWidth*frameHeight; i++)
{
if (yBuf[i] < 16) yBuf[i] = 16;
if (yBuf[i] > 235) yBuf[i] = 235;
}
for (int i = 0; i < frameWidth*frameHeight/4; i++)
{
if (uBuf[i] < 16) uBuf[i] = 16;
if (uBuf[i] > 240) uBuf[i] = 240;
if (vBuf[i] < 16) vBuf[i] = 16;
if (vBuf[i] > 240) vBuf[i] = 240;
}
/*write data into yuv*/
for (int i = 0; i < 40; i++)
{
fwrite(yBuf, 1, frameWidth * frameHeight, yuvFile);
fwrite(uBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
fwrite(vBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
}
free(bmpBuf);
free(yBuf);
free(uBuf);
free(vBuf);
fclose(bmpFile);
}
fclose(yuvFile);
return(0);
}
rgb2yuv.cpp的具体实现代码已于前文链接给出,此处不再赘述。
五、结果验证
使用YUVviewerPlus播放输出的lab2_out.yuv文件(部分结果截图如下):
从输出的命令行窗口和lab2_out.yuv的播放结果来看,已经成功将6幅BMP文件转为了YUV文件,且每个BMP文件重复了40帧,结果正确。
六、总结
- 在本次实验中,使用了命令参数和结构体变量,极大减轻了代码的书写量,简单高效。
- 对于个别不确定用法的函数,应及时查看有关定义,而不是在报错后再去排查问题。
- 对于自己开辟的内存,在使用完毕后要及时释放,以免出现内存不可用的问题。
总的来说,这次的实验完成地比较顺利,希望下次继续努力吖!