一、实验目的
1.理解图像文件的基本组成。
2.掌握结构体作为复杂数据对象的用法,进一步熟练由问题到程序 的解决方法,并掌握编程细节:如内存分配,倒序读写,字节序,文件读写过程等等。
3.学会将BMP图像转换为YUV图像进一步转换为YUV视频,自学转场代码的编写。
二、实验过程
1.获取图片
获取bmp格式图片三张:
2.bmp格式基础
BMP文件采用位映射存储格式,大多只通过选择位的大小进行压缩,因此会占用比较大的空间,BMP文件的图像深度(每个像素用几bit表示)可选1bit、4bit、8bit以及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。
典型的BMP图像文件由四部分组成:
- 位图文件头FileHeader
- 位图信息头InfoHeader
- 调色板Palette
- 实际的位图数据ImageData
使用#include<windows.h>可以方便地将文件头信息读入结构体
3.编程实现
主函数
主函数实现以下过程:
读参数中的bmp文件,获取宽高,色彩信息
rgb转yuv
从第二张图片开始写入转场
写静态图片
将当前图片存入temp中,留给下一张转场用
代码:
int main(int argc, char** argv)
{
u_int8_t* y_temp;
u_int8_t* u_temp;
u_int8_t* v_temp;
int transFrames = 30;//转场帧数
int photoFrames = 30;//静止图片帧数
for (int pic = 2; pic < argc; pic++) //从第二个参数到最后一个是图片
{
//1.读bmp文件头,获取宽高
BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;
FILE* bmpFile;
bmpFile = fopen(argv[pic], "rb");
fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile);
fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile);
u_int frameWidth;
u_int frameHeight;
frameWidth = Info_header.biWidth;
frameHeight = Info_header.biHeight;
//2.色彩信息读入rgb_buffer
u_int8_t* rgbBuf = (u_int8_t*)malloc(frameWidth * frameHeight * 3);
fread(rgbBuf, 1, frameWidth * frameHeight * 3, bmpFile);
fclose(bmpFile);
//3.追加模式打开yuv文件
FILE* yuvFile = fopen(argv[1], "ab+");
//4.rgb转yuv
u_int8_t* yBuf = (u_int8_t*)malloc(frameWidth * frameHeight);
u_int8_t* uBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4);
u_int8_t* vBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4);
if (RGB2YUV(frameWidth, frameHeight, rgbBuf, yBuf, uBuf, vBuf, FALSE))
{
printf("error");
return 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;
}
//5.从第二张图片开始加入转场
if (pic > 2) {
for (int frame = 0; frame < transFrames; frame++) {
u_int8_t* y_mix = getInsertFrames(y_temp, yBuf, transFrames, frame, frameWidth, frameHeight);
u_int8_t* u_mix = getInsertFrames(u_temp, uBuf, transFrames, frame, frameWidth / 2, frameHeight / 2);
u_int8_t* v_mix = getInsertFrames(v_temp, vBuf, transFrames, frame, frameWidth / 2, frameHeight / 2);
fwrite(y_mix, 1, frameWidth * frameHeight, yuvFile);
fwrite(u_mix, 1, (frameWidth * frameHeight) / 4, yuvFile);
fwrite(v_mix, 1, (frameWidth * frameHeight) / 4, yuvFile);
}
}
//6.写静态图片
for (int frame = 0; frame < photoFrames; frame++) {
fwrite(yBuf, 1, frameWidth * frameHeight, yuvFile);
fwrite(uBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
fwrite(vBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
}
printf("\n%u %ux%u video frames written\n",
pic, frameWidth, frameHeight);
fclose(yuvFile);
//7.将当前图片存入temp中,留给下一张转场用
y_temp = (u_int8_t*)malloc(frameWidth * frameHeight);
u_temp = (u_int8_t*)malloc(frameWidth * frameHeight*0.5);
v_temp = (u_int8_t*)malloc(frameWidth * frameHeight*0.5);
for (int i = 0; i < frameWidth * frameHeight; i++)
{
*(y_temp + i) = *(yBuf + i);
}
for (int i = 0; i < frameWidth * frameHeight/4; i++)
{
*(u_temp + i) = *(uBuf + i);
*(v_temp + i) = *(vBuf + i);
}
}
return(0);
}
其中,RGB2YUV()函数就是【实验一】中老师给的代码,所以不贴出
转场函数 getInsertFrames()
getInsertFrames()函数用来获得两张图片的混合帧,写了叠加的转场方式
叠加:
u_int8_t* getInsertFrames_mix(u_int8_t* buf1, u_int8_t* buf2, int frame,int currentFrame, u_int frameWidth,u_int frameHeight) {
//获取两帧混合(插值)
//frame:总转场帧数
//currentFrame:当前帧
u_int8_t * mix = (u_int8_t*)malloc(frameWidth * frameHeight);
for (int j = 0; j < frameHeight * frameWidth; j++) {
*(mix + j) = int((*(buf2 + j)* currentFrame + *(buf1 + j)*(frame- currentFrame)) / frame);
}
return mix;
}
三、实验结果
四、实验总结
结构体的使用使得确定图片长、宽、偏移量等信息的获取更加简单,便于实验后续的操作。但是对于YUV视频文件,每一帧之间都相互独立,没有压缩任何空间,冗余度非常高。