1、实验原理
(1).BMP文件格式回顾
位图文件头BITMAPFILEHEADER |
位图信息头BITMAPINFOHEADER |
调色板Palette |
实际的位图数据ImageData |
(2)分别是BITMAPFILEHEADER和BITMAPINFOHEADER结构体内的情况
(3)调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。数组中每个元素的类型是一个RGBQUAD结构。真彩色无调色板部分。
在#include<windows.h>文件头中都有包括,可以不用再写,不然就会报错为重复定义。
(4)bmp位图的图像深度
一般分为6种,分别是1、2、4、8、16、24,有时还有32位的。2色图像为8字节;16色图像位64字节;256色图像为1024字节。其中,每4字节表示一种颜色,并以B(蓝色)、G(绿色)、R(红色)、alpha(32位位图的透明度值,一般不需要)。对于真彩色图,图象数据就是实际的R、G、B值。对于2色位图,用1位就可以表示该象素的颜色(一般0表示黑,1表示白),所以一个字节可以表示8个象素。对于16色位图,用4位可以表示一个象素的颜色,所以一个字节可以表示2个象素。对于256色位图,一个字节刚好可以表示1个象素。
(5)bmp写入rgb再写入yuv
需要注意的是BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序,所以当我们写入rgb的时候,其实不用先倒叙写,可以在rgb写成yuv时加入flip=false,就可以倒叙写入,yuv就是正确数据了。
(6)yuv文件从一帧变为一个视频
这里主要是写入yuvFile的时候的写入方式改变,写成ab+可以将…(注释里有),然后利用循环,一帧yuv写成有至少200帧的yuv视频。
2、实验过程
(1)BMP2YUV文件转换流程分析
1. 程序初始化(打开两个文件、定义变量和缓冲区等)
2. 读取BMP文件,抽取或生成RGB数据写入缓冲区
3. 调用RGB2YUV的函数实现RGB到YUV数据的转换
4. 写YUV文件
5. 程序收尾工作(关闭文件,释放缓冲区)
(2)读取bmp文件
(3)像素位数在前有提到
(4)储存方式
16位2个字节中RGB的保存顺序如左,第一个字节前5位保存红色R,后3位保存绿色G的高3位G1;第二个字节前3位保存绿色G的低3位G2,后5位保存蓝色B。存储时使用小端存储,就变成了这样如右。
24位RGB各占1字节,因此每次只要顺序读取即可。小端方式保存,保存顺序是B,G,R。读完之后前进3字节读取下一颜色值。
3、实验主要程序分析
1、bmp2rgb2yuv.h
与之前的没太大差别,就是申明用了的函数,注意可以加入#include<windows.h>,之前我没加报了错
2、main.cpp
for (int k = 1; k < 21; k++)
{
if ((yuvFile = fopen(argv[21], "ab+")) == NULL)
{
printf("yuv file failed!");
exit(0);
}
(3)读bmp文件
if (fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1)
{
printf("read file header error!");
exit(0);
}
if (File_header.bfType != 0x4D42)
{
printf("Not bmp file!");
exit(0);
}
else
{
printf("this is a %c\n", File_header.bfType);//debug
}
if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)
{
printf("read info header error!");
exit(0);
}
else
{
}
// end read header
(4)这里就是规定我们的yuv文件的大小
int width ;
int height ;
if ((Info_header.biWidth % 4) == 0)
width = Info_header.biWidth;
else
width = (Info_header.biWidth*Info_header.biBitCount + 31) / 32 * 4;
//加31再除以32后下取整,就保证了计算结果是离这个数最近的而且是比它大的32的倍数,
//也就保证了是4字节的整数倍。乘以4和行数,得到4字节整数倍的图像大小。
if ((Info_header.biHeight % 2) == 0)
height = Info_header.biHeight;
else
height = Info_header.biHeight + 1;
(5)给我们写的rgbBuf、yBuf、uBuf、vBuf开完空间后我们就直接进入函数进行转换
BMP2RGB2YUV( File_header, Info_header, bmpFile,rgbBuf);//调用bmp2rgbyuv.cpp中的bmp2rgb()函数
//将文件名、头文件、信息头文件、储存rgb数据的空间传入函数
RGB2YUV(width, height, rgbBuf, yBuf, uBuf, vBuf, flip);
for (int m = 0; m < 10; m++)
{
fwrite(yBuf, 1, width * height, yuvFile);
fwrite(uBuf, 1, (width * height) / 4, yuvFile);
fwrite(vBuf, 1, (width * height) / 4, yuvFile);
}
(7)之后就是close文件们和free指针空间啦。
3、bmp2rgb2yuv.cpp
void BMP2RGB2YUV(BITMAPFILEHEADER & file_h, BITMAPINFOHEADER & info_h, FILE * pFile, unsigned char * rgbDataOut)
unsigned long Loop, i, j, width, height, w, h;
unsigned char mask, *Index_Data, *Data;
if ((info_h.biWidth % 4) == 0)
w = info_h.biWidth;
else
w = (info_h.biWidth*info_h.biBitCount + 31) / 32 * 4;
if ((info_h.biHeight % 2) == 0)
h = info_h.biHeight;
else
h = info_h.biHeight + 1;
width = w / 8 * info_h.biBitCount;
height = h;
Index_Data = (unsigned char *)malloc(height*width);
Data = (unsigned char *)malloc(height*width);
fseek(pFile, file_h.bfOffBits, 0);
if (fread(Index_Data, height*width, 1, pFile) != 1)
{
printf("read file error!");
exit(0);
}
switch(info_h.biBitCount)//根据bmp位数来选择操作
case24:
memcpy(rgbDataOut, Data, height*width);
free(Data);
return;
case 16:
for (Loop = 0; Loop < height * width; Loop += 2)
//纠错:"loop++"改为"Loop+=2";loop每次循环中应该要逐次增加2,因为loop表示一个字节,而它应该每次16位,即2个字节。
{
*rgbDataOut = (Data[Loop] & 0x1F) << 3;
*(rgbDataOut + 1) = ((Data[Loop] & 0xE0) >> 2) + ((Data[Loop + 1] & 0x03) << 6);
*(rgbDataOut + 2) = (Data[Loop + 1] & 0x7C) << 1;
rgbDataOut += 3;
}
RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow(2, (float)info_h.biBitCount));
//把unsigned char 改为unsigned int
if (!MakePalette(pFile, file_h, info_h, pRGB))
printf("No palette!");
mask = 0x80;
for (Loop = 0; Loop<height*width; Loop++)
{
switch (info_h.biBitCount)
{
case 1:
mask = 0x80;
break;
case 2:
mask = 0xC0;
break;
case 4:
mask = 0xF0;
break;
case 8:
mask = 0xFF;
}
int shiftCnt = 1;
while (mask)
{
unsigned char index = mask == 0xFF ? Data[Loop] : ((Data[Loop] & mask) >> (8 - shiftCnt * info_h.biBitCount));
*rgbDataOut = pRGB[index].rgbBlue;
*(rgbDataOut + 1) = pRGB[index].rgbGreen;
*(rgbDataOut + 2) = pRGB[index].rgbRed;
if (info_h.biBitCount == 8)
mask = 0;
else
mask >>= info_h.biBitCount;
rgbDataOut += 3;
shiftCnt++;
}
}
bool MakePalette(FILE * pFile, BITMAPFILEHEADER &file_h, BITMAPINFOHEADER & info_h, RGBQUAD *pRGB_out)
{
if ((file_h.bfOffBits - sizeof(BITMAPFILEHEADER) - info_h.biSize) == sizeof(RGBQUAD)*pow(2, (float)info_h.biBitCount))
{
fseek(pFile, sizeof(BITMAPFILEHEADER) + info_h.biSize, 0);
fread(pRGB_out, sizeof(RGBQUAD), (unsigned int)pow(2, (float)info_h.biBitCount), pFile);
return true;
}
else
return false;
}
4、rgb2yuv.cpp
老师给的文件,这里就略了。
4、实验结果
(1)先用画图软件另存找到的512*512.jpg文件,分别称为1、4、8、24位图,然后用ps生成16位图。我找了四张不一样的jpg, 大小都是512*512的,这样方便看yuv。分别给每张图的位图。没有数字的jpg为原图,1~5分别是1、4、8、16、24位图。
(2)转换成yuv视频截图
我们随机截图看一下
这是一张一位图
这是两张4位图
这是两张8位图
这是两张16位图
这是一张24位图
5、实验体会
这次实验主要还是转换文件格式的操作,但是介绍了一种我们不熟悉的文件格式,一开始这种陌生感带来的紧张致使项目里的.cpp文件报错无数,主要还是文件头的意义没有理解。其实到现在还有一点疑惑,当我输入的是720*576的bmp文件时,转换出来的1位图转为yuv时会出错,而其他文件没有问题。而且这次实验需要我们把yuv写成视频,要有至少200帧,所以还要在原来的程序基础上加一个循环,写入一个yuv文件里,这也是一个新点。