一、实验原理
(1)文件头 BITMAP_FILE_HEADER,包含如下内容
typedef struct tagBITMAPFILEHEADER {
//0x00~0x01,说明文件的类型
WORD bfType;
//0x02~0x05,说明文件的大小,用字节B为单位
DWORD bfSize;
//0x06~0x07,保留,设置为0
WORD bfReserved1;
//0x08~0x09,保留,设置为0
WORD bfReserved2;
//0x0a~0x0d,说明从BITMAP_FILE_HEADER结构开始到实际的图像数据之间的字节偏移量
DWORD bfOffBits;
} BITMAPFILEHEADER;
(2)信息头BITMAP_INFO_HEADER,包含如下内容
typedef struct tagBITMAPINFOHEADER {
//0x0e~0x11,说明当前结构体所需字节数
DWORD biSize;
//0x12~0x15,以像素为单位说明图像的宽度
LONG biWidth;
//0x16~0x19,以像素为单位说明图像的高度
LONG biHeight;
//0x1a~0x1b,说明位面数,必须为1
WORD biPlanes;
//0x1c~0x1d,说明图像的位深度
WORD biBitCount;
//0x1e~0x21,说明图像是否压缩及压缩类型
DWORD biCompression;
//0x22~0x25,以字节为单位说明图像大小,必须是4的整数倍
DWORD biSizeImage;
//0x26~0x29,目标设备的水平分辨率,像素/米
LONG biXPelsPerMeter;
//0x2a~0x2d,目标设备的垂直分辨率,像素/米
LONG biYPelsPerMeter;
//0x2e~0x31,说明图像实际用到的颜色数,如果为0,则颜色数为2的biBitCount次方
DWORD biClrUsed;
//0x32~0x35,说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。
DWORD biClrImportant;
} BITMAPINFOHEADER;
(3)bmp图片基本信息
iu.bmp | |
---|---|
vs中二进制方式打开 | |
图像基本信息 | 分辨率:1024*680 图片大小:1.99MB |
0x00~0x01 | 标记了此文件为BMP文件,内容是B和M两个字母的ASCII码 |
0x02~0x05 | 以字节为单位的文件大小 |
0x0a~0x0d | 实际数据的字节偏移量。 |
0x22~0x25 | BMP图像大小biSizeImage |
-
0x22~0x25,BMP图像大小biSizeImage可由下式计算
biSizeImage=⌊cx×biBitCount+3132⌋×4×cy+2
其中,cx,cy表示水平和垂直方向的像素数。
cx×biBitCount
表示一行图像占了多少位。BMP规定这个图像大小必须是4字节的整数倍,也就是32位的整数倍,因此需要把
cx×biBitCount
加31再除以32后下取整,就保证了计算结果是离这个数最近的而且是比它大的32的倍数,也就保证了是4字节的整数倍。乘以4和行数,得到4字节整数倍的图像大小。
另外,BMP文件的末尾两个字节是保留位,无论图像是什么这两个字节都为0,因此最后计算结果还要加上2字节。图像大小biSizeImage+字节偏移量bfOffBits=文件大小bfSize。
二、程序过程
(1)——–main_bmp2yuv.cpp——–
int main(int argc, char** argv)
{
//设置命令行参数
char* bmpFileName = argv[1];
char* yuvFileName = argv[2];
//打开文件
FILE* bmpFile = fopen(bmpFileName, "rb");
if (bmpFile == NULL)
{
printf("Cannot open the BMP file.\n");
exit(1);
}
else
{
printf("The BMP file is %s\n", bmpFileName);
}
FILE* yuvFile = fopen(yuvFileName, "wb");
if (yuvFile == NULL)
{
printf("Cannot open the YUV file.\n");
exit(1);
}
else
{
printf("The YUV file is %s\n", yuvFileName);
}
//读取BMP文件头,信息头,读取错误时的处理代码
BITMAPFILEHEADER file_header;
BITMAPINFOHEADER info_header;
if (fread(&file_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1)
if (file_header.bfType != 0x4D42)
{
printf("Not BMP file.\n");
exit(1);
}
if (fread(&info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)
{
printf("read info header error!");
exit(0);
}//结束读取BMP文件头
//读取图像尺寸
int width = info_header.biWidth;
int height = info_header.biHeight;
//开辟缓冲区 buf
u_int8_t* yBuf = (u_int8_t*)malloc(height*width);
u_int8_t* uBuf = (u_int8_t*)malloc(height*width / 4);
u_int8_t* vBuf = (u_int8_t*)malloc(height*width / 4);
u_int8_t* rgbBuf = (u_int8_t*)malloc(height*width * 3);
if (yBuf == NULL || uBuf == NULL || vBuf == NULL || rgbBuf == NULL)
{
printf("Not enough memory\n");
exit(1);
}
//BMP与RGB的转换,得到RGB数据
if (BMP2RGB(file_header, info_header, bmpFile, rgbBuf))
{
printf("BMP2RGB error\n");
exit(1);
}
//RGB与YUV的转换,得到YUV数据
int flip = 0;
/*读取到的图像数据是倒序存放的,flip=0保证了RGB2YUV可以正确地对其转换*/
if (RGB2YUV(width, height, rgbBuf, yBuf, uBuf, vBuf, flip))
{
printf("RGB2YUV error\n");
exit(1);
}
//将yuv按顺序写入yuvfile文件
fwrite(yBuf, 1, width * height, yuvFile);
fwrite(uBuf, 1, (width * height) / 4, yuvFile);
fwrite(vBuf, 1, (width * height) / 4, yuvFile);
//打印宽高,方便yuv观看程序打开
printf("width is %i", width);
printf("\n");
printf("heightis %i", height);
printf("\n");
//清理内存
free(rgbBuf);
free(yBuf); free(uBuf); free(vBuf);
fclose(bmpFile);
fclose(yuvFile);
return 0;
}
特别:
因为bmp文件是倒序存储的。所以在读文件是应该从最后一行开始读,开始设置(!flip)=1,则执行以下条件
if (!flip) {
for (j = 0; j < y_dim; j ++)
{
y = y_buffer + (y_dim - j - 1) * x_dim;
u = u_buffer + (y_dim - j - 1) * x_dim;
v = v_buffer + (y_dim - j - 1) * x_dim;
for (i = 0; i < x_dim; i ++) {
g = b + 1;
r = b + 2;
*y = (unsigned char)( RGBYUV02990[*r] + RGBYUV05870[*g] + RGBYUV01140[*b]);
*u = (unsigned char)(- RGBYUV01684[*r] - RGBYUV03316[*g] + (*b)/2 + 128);
*v = (unsigned char)( (*r)/2 - RGBYUV04187[*g] - RGBYUV00813[*b] + 128);
b += 3;
y ++;
u ++;
v ++;
}
}
}
重点解释:假设图片是5*3的像素矩阵最后一行的地址为y_buffer+(3-1)*5
抽象成宽高分别x_dim、y_dim的循环则为:
ybuffer+(ydim−j−1)∗xdim
y = y_buffer + (y_dim - j - 1) * x_dim;
u = u_buffer + (y_dim - j - 1) * x_dim;
v = v_buffer + (y_dim - j - 1) * x_dim;
(2)BMP2RGB()函数
1.rgb的位数为24(8,8,8)
//确定像素的实际点阵数
w = (info_h.biWidth*info_h.biBitCount + 31) / 32 * 4;//w为实际一行的字节数
h = info_h.biHeight;//h为列数
//开辟实际字节数量的缓冲区,读数据,一次读取一个字节
u_int8_t* dataBuf = (u_int8_t*)malloc(w*h);
/*使用文件头的字节偏移属性bfOffBits
直接把文件指针定位到像素值数据的起始 */
fseek(pFile, file_h.bfOffBits, 0);
fread(dataBuf, 1, w*h, pFile);
unsigned char* data = dataBuf;
unsigned char* rgb = rgbBuf;
//开始写入rgb
int i, j;
for (j = 0; j < h; j++)//j控制行循环
{
for (i = 0; i < w; i += 3)//i控制列循环
{
*rgb = data[i + w*j];//B
*(rgb + 1) = data[i + w*j + 1];//G
*(rgb + 2) = data[i + w*j + 2];//R
rgb += 3;
}
}
//释放内存
free(dataBuf);
return 0;
}
实验结果:rgb的位数为24
- 补充说明
- fopen()vs2013提示安全错误解决办法:打开文件的属性页,单击“预处理器”将“_CRT_SECURE_NO_WARNINGS”复制在“预处理器定义中’,然后确定再单击应用即可。