BMP文件数据中的四层结构,分别是位图文件头、位图信息头、调色板、实际的位图数据。从位图信息头中提取图像宽高、实际的位图数据所在数据,从BMP中读取实际的位图数据,进而索引调色板数组得到每一数据的实际物理意义,即查找得到各位图数据代表的RGB再将RGB转化为YUV
Y=0.30R+0.59G+0.11B,
U=0.493(B-Y),
V=0.877(R-Y)
老师要求的为24bit进行转化
(1)位图头文件数据结构,它包含 BMP图像文件的类型、显示内容等信息;
(2)位图信息数据结构,它包含有BMP图像的宽、高压缩方法,以及定义颜色等信息;
(3)调色板,这个部分是可选的,有些位图需要调色板,有些位图需要调色板,比如真彩(24bit的BMP)就不需要调色板;
(4)位图数据,这部分的内容根据位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜索引值。
主函数功能:
1、 读取BMP文件
2、抽取BMP文件头和信息头信息,用结构体File_header盛放文件头数据,用结构体Info_header盛放信息头数据
3、调用ReadRGB 函数,从bmp文件中读取RGB信息
4、最后调用RGB2YUV函数,将rgb数据转为yuv
5、写YUV文件
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <math.h>
#include "3.h"
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
/* 说明文件的类型 */
DWORD bfSize;
/* 说明文件的大小,用字节为单位*/
/*注意此处的字节序问题*/
WORD bfReserved1;
/*保留,设置为0 */
WORD bfReserved2;
/*保留,设置为0 */
DWORD bfOffBits;
/* 说明从BITMAPFILEHEADER结构
开始到实际的图像数据之间的字节偏移量 */
} BITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
/*说明结构体所需字节数*/
LONG biWidth;
/*以像素为单位说明图像的宽度*/
LONG biHeight;
/*以像素为单位说明图像的高速*/
WORD biPlanes;
/*说明位面数,必须为1 */
WORD biBitCount;
/*说明位数/像素,1、2、4、8、24 */
DWORD biCompression;
/*说明图像是否压缩及压缩类型BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS */
DWORD biSizeImage;
/*以字节为单位说明图像大小,必须是4的整数倍*/
LONG biXPelsPerMeter;
/*目标设备的水平分辨率,像素/米 */
LONG biYPelsPerMeter;
/*目标设备的垂直分辨率,像素/米 */
DWORD biClrUsed;
/* 说明图像实际用到的颜色数,如果为0则颜色数为2的biBitCount次方 */
DWORD biClrImportant;
/*说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。*/
} BITMAPINFOHEADER;
typedef struct tagRGBQUAD {
BYTE rgbBlue; /*指定蓝色分量*/
BYTE rgbGreen; /*指定绿色分量*/
BYTE rgbRed; /*指定红色分量*/
BYTE rgbReserved; /*保留,指定为0*/
} RGBQUAD;
bool WriteYUV(unsigned char*Y,unsigned char*U,unsigned char*V,unsigned long size,FILE*outFile)
{
if(fwrite(Y, 1, size,outFile)!= size)
return false;
if(fwrite(U, 1, size/ 4, outFile) != size/ 4)
return false;
if(fwrite(V, 1, size/ 4, outFile) != size/ 4)
return false;
return true;
}
int main(int argc,char**argv)
{
BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;
FILE*bmpFile;
FILE*yuvFile;
unsigned char*rgbBuffer, *yBuffer, *uBuffer, *vBuffer;
char name__[20];
//连接成字符串:potter.yuv,方便文件写入磁盘
strcpy_s(name__, argv[1]);
strcat_s(name__, 20, ".yuv");
fopen_s(&yuvFile,name__ , "wb");
if(!yuvFile)
{
printf("yuvfile open error!\n");
exit(0);
}
for (int i = 1; i <= atoi(argv[2]); ++i){
char str[20];
char name_[20];
//将“potter”“序号”“.bmp”连接成文件名,再用fopen函数打开文件
strcpy_s(name_, argv[1]);
_itoa_s(i, str, 20, 10);
strcat_s(name_, 20, str);
strcat_s(name_, 20,".bmp");
fopen_s(&bmpFile,name_ , "rb");
if(!bmpFile)
{
printf("bmpfile open error!\n");
exit(0);
}
//读取位图文件头
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);
}
unsigned long width, height;
if(Info_header.biWidth % 4 == 0)
width = Info_header.biWidth;
else
//为了保证图像大小是4的倍数,每一行的像素数*每像素的位数=一行总位数
//一行总位数必须是32位的整数倍。
//为什么呢?这样只有才能保证一行数据量是4字节的整数倍。
//怎么做呢?(x+31)除以32,再下取整,得到结果以后,再乘以32,就得到比原位数大且是32的倍数的数字,再除以8便得到字节数,肯定是4字节的整数倍。
width =(Info_header.biWidth*Info_header.biBitCount + 31) / 32 * (32 / 8);
//保证列数是偶数
if((Info_header.biHeight % 2) == 0)
height = Info_header.biHeight;
else
height = Info_header.biHeight + 1;
//开辟缓冲区
rgbBuffer = new unsigned char[height*width* 3];
yBuffer = new unsigned char[height*width];
uBuffer = new unsigned char[height*width/ 4];
vBuffer = new unsigned char[height*width/ 4];
//从bmp文件中读取rgb信息
ReadRGB(bmpFile, File_header,Info_header, rgbBuffer);
//核心步骤,将rgb数据转为yuv
RGB2YUV(width, height, rgbBuffer,yBuffer, uBuffer, vBuffer);
//将转好的yuv写到文件中
if(WriteYUV(yBuffer, uBuffer, vBuffer, width*height, yuvFile))
printf("writeYUV file successful!\n");
else
printf("writeYUV file failed!\n");
//free时一定要小心,指针不要指向不是堆的区域
if(rgbBuffer)
free(rgbBuffer);
if(yBuffer)
free(yBuffer);
if(uBuffer)
free(uBuffer);
if(vBuffer)
free(vBuffer);
fclose(bmpFile);
}
fclose(yuvFile);//注意多帧播放时,yuvFile一定只关一次,并且在最后关,不要重复关闭,否则会出现文件覆盖
return 0;
}
void ReadRGB(FILE *pFile,const BITMAPFILEHEADER &file_h, const BITMAPINFOHEADER &info_h, unsigned char*rgbDataOut)
{
unsigned longLoop, iLoop, jLoop, width, height, w, h;
unsigned charmask, *Index_Data, *Data;
//保证是图像大小是4字节的整数倍,具体理由见下面的注释
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;
//若是24位,则bmp中有效数据大小是长*宽*3字节
//若是16位,则bmp中有效数据大小是长*宽*2字节
//若是8位,则bmp中有效数据大小是长*宽字节
//若是4位,则bmp中有效数据大小是长*宽/2字节
//若是2位,则bmp中有效数据大小是长*宽/4字节
//若是1位,则bmp中有效数据大小是长*宽/8字节(这大概是为什么bmp图像的长必须是4的倍数,宽必须是2的倍数的原因吧。。。)
width = w / 8 * info_h.biBitCount;
height = h;
//倒序前数据缓存区
Index_Data = (unsigned char*)malloc(height*width);//buffer大小应该与bmp中有效数据大小相同
//倒序后数据缓存区,用于存放bmp中的有效数
Data = (unsigned char*)malloc(height*width);//buffer大小应该与bmp中有效数据大小相同
//文件指针定位到有效数据起始处,读取有效数据
fseek(pFile,file_h.bfOffBits,0);
if(fread(Index_Data, height*width, 1, pFile) != 1)
{
printf("readfile error!");
exit(0);
}
//倒序存放
for (iLoop= 0; iLoop < height; iLoop++)
for (jLoop= 0; jLoop < width; jLoop++)
{
Data[iLoop*width + jLoop] =Index_Data[(height - iLoop - 1)*width
+ jLoop];
}
//24位:直接把倒序后的缓存区数据复制给输出缓存区
if (info_h.biBitCount== 24)
{
memcpy(rgbDataOut,Data, height*width);
free(Index_Data);
free(Data);
return;
}
//非24位:解码生成rgb,需要调色板信息
//生成调色板数组,数组的下标,对应bmp文件中有效数据,通过下标对应查找,便可得到该数据对应的颜色
//debug by LiuDong:(unsignedlong long)pow(),pow前的强制类型转换不能转换成unsignedint、unsignedchar等
//因为pow((float)2, info_h.biBitCount)最大值是2^24,unsigned char最大能表示255,unsigned int最大能表示2^32
RGBQUAD*pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow((float)2,info_h.biBitCount));
/*一个单元代表一种颜色
调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。
数组中每个元素的类型是一个RGBQUAD结构。真彩色无调色板部分。
biBitCount;位数/像素,1、2、4、8、24.假如是16位,R占5位,G占5位,B占5位,空出一位,排列组合一共有2^15种颜色,其他位数同理。*/
//读取位图调色板数据
if(!MakePalette(pFile, file_h,info_h,pRGB))
printf("Nopalette!");
//16位:移位操作,从2字节中取出RGB信息,存到3字节中
if (info_h.biBitCount== 16)
{
for(Loop = 0; Loop < height * width; Loop += 2)
{
*rgbDataOut= (Data[Loop] & 0x1F) << 3;//B:用0001 1111取出低字节的右五位,再放到目标字节的高5位(通过右移3位),得到五位的B
*(rgbDataOut+ 1) = ((Data[Loop] & 0xE0) >> 2) + ((Data[Loop + 1] & 0x03)<< 6);//G:11100000取出低字节的左三位,00000011取出高字节的右两位,合并后,放到再放到目标字节的高5位,得到五位的G
*(rgbDataOut+ 2) = (Data[Loop + 1] & 0x7C) << 1; //R:0111 1100取出高字节的中间五位,再放到目标字节的高5位,得到5位的R
rgbDataOut+= 3;
}//RGB都各自位于字节的高5位
}
//1~8位:移位操作,从有限固定位中取出RGB信息,存到3字节中
//循环次数:有效数据字节数
for (Loop =0; Loop<width*height; Loop++)
{
//根据位深设置掩膜
switch(info_h.biBitCount)
{
case1://1000 0000,1位,黑白双色图
mask = 0x80;
break;
case2://1100 0000,2位,4色图
mask = 0xC0;
break;
case4://1111 0000,4位,16色图
mask = 0xF0;
break;
case8://1000 0000,8位,256色图
mask = 0xFF;
}
int shiftCnt = 1;//控制mask的移位,决定取字节中哪些数据
while(mask)//循环一次就是一个字节的解析过程
{
unsigned char index = mask == 0xFF ? Data[Loop] : ((Data[Loop] & mask) >> (8 -shiftCnt * info_h.biBitCount));
//Loop代表第几个带转换的原始有效数据
//pRGB代表调色板
//8位:mask=1111 1111,index=data[Loop],即index为bmp中原始有效数据,直接对应调色板数组下标,得到相应颜色
//查找调色板,取出对应BGR存入目标buffer:rgbDataOut
*rgbDataOut= pRGB[index].rgbBlue;//B
*(rgbDataOut+ 1) = pRGB[index].rgbGreen;//G
*(rgbDataOut+ 2) = pRGB[index].rgbRed;//R
if(info_h.biBitCount== 8)
mask = 0;//如果是8位bmp,一次性取完一个字节的颜色数据,直接跳出循环即可
else
mask >>= info_h.biBitCount;//若是1位bmp,则一字节取8次数据;若是2位bmp,则一字节取4次数据;若是4位bmp,则一字节取2次数据
//debugby LiuDong
if(Loop == width*height - 1)
{
rgbDataOut= rgbDataOut + 3 - width*height*3;
break;
}
rgbDataOut+= 3;
shiftCnt++;
}
}
if(Index_Data)
free(Index_Data);
if (Data)
free(Data);
if (pRGB)
free(pRGB);
}
void RGB2YUV(unsigned longw,unsigned longh,unsigned char* rgbData, unsigned char*y,unsigned char*u,unsigned char*v)
{
int LookupTable();//初始化查找表
unsigned char*ytemp = NULL;
unsigned char*utemp = NULL;
unsigned char*vtemp = NULL;
utemp = (unsigned char*)malloc(w*h);
vtemp = (unsigned char*)malloc(w*h);
unsigned long i, nr, ng, nb, nSize;
//对每个像素进行 rgb -> yuv的转换
for (i = 0,nSize = 0; nSize<w*h* 3; nSize += 3)
{
nb = rgbData[nSize];
ng = rgbData[nSize+ 1];
nr = rgbData[nSize+ 2];
y[i]= (unsigned char)(RGBYUV02990[nr]+ RGBYUV05870[ng] + RGBYUV01140[nb]);
utemp[i] = (unsignedchar)(-RGBYUV01684[nr]- RGBYUV03316[ng] + nb / 2 + 128);
vtemp[i] = (unsignedchar)(nr/ 2 - RGBYUV04187[ng] - RGBYUV00813[nb] + 128);
i++;
}
//对u信号及v信号进行采样,因为是4:2:0格式,所以u的数据是y的数据的1/4,v的数据是y的数据的1/4
int k = 0;
for (i = 0;i<h; i += 2)
for (unsigned long j = 0; j<w; j += 2)
{
u[k]= (utemp[i*w + j] + utemp[(i + 1)*w+ j] + utemp[i*w + j + 1] + utemp[(i + 1)
*w+ j + 1]) / 4;
v[k]= (vtemp[i*w + j] + vtemp[(i + 1)*w+ j] + vtemp[i*w + j + 1] + vtemp[(i + 1)
*w+ j + 1]) / 4;
k++;
}
//对y、u、v 信号进行限电平处理
for (i = 0;i<w*h;i++)
{
if(y[i]<16)
y[i]= 16;
if(y[i]>235)
y[i]= 235;
}
for (i = 0;i<h*w/ 4; i++)
{
if(u[i]<16)
u[i]= 16;
if(v[i]<16)
v[i]= 16;
if(u[i]>240)
u[i]= 240;
if(v[i]>240)
v[i]= 240;
}
if (utemp)
free(utemp);
if(vtemp)
free(vtemp);
}
bool MakePalette(FILE* pFile,const BITMAPFILEHEADER &file_h, const BITMAPINFOHEADER &info_h, RGBQUAD *pRGB_out)
{
//先判断是否存在调色板:有效数据开始处离文件开头的距离-位图文件头大小-位图信息头大小=调色数据大小
if ((file_h.bfOffBits- sizeof(BITMAPFILEHEADER)-info_h.biSize)== sizeof(RGBQUAD)*pow((float)2,info_h.biBitCount))
{
fseek(pFile,sizeof(BITMAPFILEHEADER)+info_h.biSize,0);
//把调色数据放到pRGB_out中,完成make palette工作
if(fread(pRGB_out, sizeof(RGBQUAD), (unsigned int)pow((float)2,info_h.biBitCount),pFile)!= (unsigned int)pow((float)2,info_h.biBitCount))
{
printf("Failto read RGBQUAD!\n");
}
return true;
}
else
return false;
}
实验结果导出
实验总结:
bmp文件24bit有其在结构深度上的特殊性,能够将其进行更好的处理,但相应数据送到rgb时的字节顺序需要格外注意。
另外我自己的编程能力还要进一步提高哦,强制转换都忘记了