1.BMP格式文件格式转换基本原理
BMP文件的内部储存格式如下图所示,包括文件头,信息头,调色板和位图数据。在VS下以二进制方式打开BMP文件时,我们可以清晰地看到这四个模块的内容。
1)文件头的内部存储数据如下图所示,其中,WORD--2字节,DWORD--4字节,在二进制文件中,可以根据字节数推算,从而找到表示相应含义的字节。
2)信息头的内部存储数据如下图所示,WORD--2字节,DWORD--4字节,LONG--2字节。
3)调色板——本质是一个数组,它包含的元素和位图具有的颜色数相同,由biClrUsed和biBitCount所决定。元素的类型都是RGBQUAD结构。但是本实验中,只有8bit, 4bit, 1bit位图有调色板,24bit和16bit的真彩色无调色板部分。这也就为转换时的处理带来了方便,尤其是24bit的真彩图可以在读入文件头和信息头之后直接将IMAGE DATA部分直接写入rgbBuf,24bit位图的图像数据部分存储和rgb格式相同。调色板包含的数据如下图所示。
2.实验流程分析
首先,打开并读入BMP文件、定义变量,开辟存储空间。BMP文件中首先被读入的应该是文件头和信息头,读入之后才能根据其中给出的关于位图的宽高、bit数等信息进行开辟空间等操作。在本实验中,定义两个指针:BITMAPFILEHEADER File_header; BITMAPINFOHEADER Info_header; 分别用来获取文件头和信息头的数据。注意,这两种类型需要加<windows.h>头文件。
其次,提取出RGB数据,写入缓冲区。不同bit的文件不同处理。24bit图可以直接取图片数据;16bit比较复杂,需要取像素数据转换成8bit彩色分量后再写入rgbBuf;8bit,4bit,1bit均需要构造调色板。需要注意的是图像数据需要倒置,具体代码见代码分析部分。
再次,调用上次实验写好的RGB转YUV的函数实现转换,并写入新的YUV文件。生成视频时可以循环写入,每张图片素材40帧。打开方式“wb+”可以下次继续写入。
最后,释放缓冲区,关闭文件。
3.关键代码分析
1,获取文件的宽高,开辟储存空间。
u_int width, 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字节整数倍的图像大小,以便开辟储存空间时除4时不出现问题
if ((Info_header.biHeight % 2) == 0)
height = Info_header.biHeight;
else
height = Info_header.biHeight + 1;
/* get the output buffers for a frame */
rgbBuf = (u_int8_t*)malloc(height*width * 3);
yBuf = (u_int8_t*)malloc(height*width * 3);
uBuf = (u_int8_t*)malloc(height*width / 4);
vBuf = (u_int8_t*)malloc(height*width / 4);
2,读入BMP图像数据,在本实验中,将读取BMP中rgb数据的过程写在bmp2rgb.cpp文件中,下面给出bmp2rgb.cpp的代码。这个过程中,要注意的一点是,BMP的图像数据部分虽然和rgb形式相同,但是是倒序存储在文件中的,需要我们在提取时将数据倒序存储在rgbBuf中,详见代码注释部分。在完成后需要注意释放开辟的空间。
#include<windows.h>
#include "rgb2yuv.h"
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<math.h>
int BMP2RGB(FILE * pFile, BITMAPFILEHEADER & file_h, BITMAPINFOHEADER & info_h, unsigned char * rgbDataOut)
{
unsigned long Loop,i,j,width,height,w,h;
unsigned char mask,*Index_Data,* Data;
if (((info_h.biWidth/8*info_h.biBitCount)%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!\n\n");
exit(0);
}
//倒序存储,考虑到BMP的图像存储顺序是倒序,尾行在第一行,在这里做倒转。
//正序图像数据存入Data
for ( i = 0;i < height;i++)
{
for (j = 0;j < width;j++)
{
Data[i*width+j] = Index_Data[(height-i-1)*width+j];
}
}
if(info_h.biBitCount==24)
{
memcpy(rgbDataOut,Data,height*width);
}
if(info_h.biBitCount==16)
{
for (Loop = 0;Loop < height * width;Loop +=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(float(2),info_h.biBitCount));
int temp = sizeof(pRGB);
if(!MakePalette(pFile,file_h,info_h,pRGB))
printf("No palette!\n\n");
if(info_h.biBitCount!=24&&info_h.biBitCount!=16)
{
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 ++;
}
}
}
if(Index_Data) free(Index_Data);
if(Data) free(Data);
if(pRGB) free(pRGB);
return(0);
}
4,实验结果及其分析
24bit 16bit 8bit
原图 4bit 1bit
转换结果和原图如上面的六幅图所示,实验中选取了四幅微信头像作为素材,其中这一幅天空颜色渐变,在不同的量化比特数下效果十分明显,故此处以此图为例。第一幅24bit的图可以实现2^24种颜色,颜色过渡自然,用原图和24bit转换后的图进行比较,可以发现几乎没有差别。从16bit和8bit可以看到天空的颜色出现明显分层。4bit时,图中只有16种颜色。1bit时,只有黑白两色。
生成视频时要尤其注意,图片的宽高应该一致,这在处理单张图片时不会出现问题,但是多幅图拼接成视频就会出现问题。由于选取的第五张图宽高不同于前四张头像,所以出现图像混乱,如下图所示。最终我们以四幅图,每幅图50帧的方式写满200帧的视频并正常播放。
5,调试过程问题总结
1)rgb2yuv.h中提示FILE未定义符号,提示BITMAPFILEHEADER和BITMAPINFOHEADER未定义。包括我在内,不少同学都遇到了头文件中报错的问题。后者会出现红色下划线,包含了<windows.h>头文件后报错下划线就会消失。但是FILE未定义时,并没有红色的下划线提示,报错时反复看也看不出问题。一般对类型报错都是由没有包含定义该类型的头文件导致。其实这样的问题很容易解决:右键FILE->转到定义。此时会弹出定义FILE的文件窗口,即“stdio.h”,将它include即可。
#ifndef RGB2YUV_H_
#define RGB2YUV_H_
#include<windows.h>
#include<stdio.h>
int BMP2RGB(FILE * pFile, BITMAPFILEHEADER & file_h, BITMAPINFOHEADER & info_h, unsigned char * rgbDataOut);
int RGB2YUV (int x_dim, int y_dim, void *bmp, void *y_out, void *u_out, void *v_out, int flip);
bool MakePalette(FILE * pFile, BITMAPFILEHEADER &file_h, BITMAPINFOHEADER & info_h, RGBQUAD *pRGB_out);
void InitLookupTable();
#endif
2)复制代码出现问题。报错:“ error C4335: 检测到 Mac 文件格式: 请将源文件转换为 DOS 格式或 UNIX 格式 ”此处再次强调编程习惯的重要性,实验时粘贴代码,尤其是从ppt或者网页上直接粘贴到VS上会导致出现如下错误。这个错误非常麻烦,一般是空格或者换行的格式有误,比如用了全角,但是往往我们是看不出空格和换行符的错误的。所以一旦出现问题,就把复制过的地方重新敲一遍,或者把换行重新敲一下。另外,复制代码还可能 随之出现不识别已经定义的数据类型符报错的问题,比如1)中提到的不识别BITMAPINFOHEADER,这时尝试保存好工程然后关闭所有文件重新打开一下。可能会弹出下面的对话框,选择是,标准化后错误可能就消失了。
参考:http://blog.youkuaiyun.com/leixiaohua1020/article/details/13506099 致敬师哥。