BMP格式文件转YUV文件
文章目录
前言
BMP(Bitmap-File)图形文件是Windows采用的图形文件格式,在Windows环境下运行的所有图像处理软件都支持BMP图像文件格式。Windows系统内部各图像绘制操作都是以BMP为基础的。Windows 3.0以前的BMP图文件格式与显示设备有关,因此把这种BMP图像文件格式称为设备相关位图DDB(device-dependent bitmap)文件格式。Windows 3.0以后的BMP图像文件与显示设备无关,因此把这种BMP像文件格式称为设备无关位图DIB(device-independent bitmap)格式(注:Windows 3.0以后,在系统中仍然存在DDB位图,像BitBlt这种函数就是基于DDB位图的,只不过如果你想将图像以BMP格式保存到磁盘文件中时,微软极力推荐你以DIB格式保存),目的是为了让Windows能够在任何类型的显示设备上显示所存储的图像。BMP位图文件默认的文件扩展名是BMP或者bmp,有时也会以.DIB或.RLE作扩展名。
这种格式的特点是包含的图像信息较丰富,几乎不进行压缩,但由此导致了它与生俱来的缺点–占用磁盘空间过大。
本实验目的则是在理解了BMP文件格式以及读写方法的基础上,将多个BMP文件转化为YUV文件,同时依次进行播放,从而实现多个BMP图片在一个YUV文件中依次播放的效果。
一、BMP文件主要结构
1、文件头
BMP文件的文件头包括:
- 包含整整体文件信息的位图文件头
位图文件头内部由14个字节组成,其结构如下。
部分名称 | 大小(字节) | 内部标志 |
---|---|---|
bfType | 2 | 表明文件类型(BM) |
bfSize | 4 | 文件大小(单位为字节) |
bfReserved1 | 2 | 保留字,设置为0 |
bfReserved2 | 2 | 保留字,设置为0 |
bfOffBits | 4 | 从文件头开始到实际的图象数据之间的字节的偏移量 |
在声明header的头文件中定义如下所示。
typedef struct tagBITMAPFILEHEADER {
WORD bfType; /* 说明文件的类型*/
DWORD bfSize;/* 说明文件的大小,用字节为单位*/
WORD bfReserved1; /* 保留,设置为0 */
WORD bfReserved2; /* 保留,设置为0 */
DWORD bfOffBits; /* 说明从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节偏移量*/
} BITMAPFILEHEADER;
- 包含位图信息的位图信息头
位图信息头包含了图像宽高等位图详细数据。
名称 | 占用空间 | 内容 | 实际数据 |
---|---|---|---|
biSize | 4字节 | 位图信息头的大小,为40 | 0x28(40) |
biWidth | 4字节 | 位图的宽度(单位:像素) | - |
biHeight | 4字节 | 位图的高度(单位:像素) | - |
biPlanes | 2字节 | 固定值 | 1 |
biBitCount | 2字节 | 每个像素的位数 | 视数据而定:1-黑白图,4-16色,8-256色,24-真彩色 |
biCompression | 4字节 | 压缩方式,BI_RGB(0)为不压缩 | - |
biSizeImage | 4字节 | 位图全部像素占用的字节数 | 与位图宽高数据有关 |
biXPelsPerMeter | 4字节 | 水平分辨率(多为像素) | - |
biYPelsPerMeter | 4字节 | 垂直分辨率(多为) | - |
biClrUsed | 4字节 | 位图使用的颜色数 | - |
biClrImportant | 4字节 | 重要的颜色数,0代表所有颜色都重要 | 一般为0 |
在声明header的头文件中定义如下所示。
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;
2、颜色表(Color Table)
是BMP图像的可选部分,与前面位图信息中biBitCount相关联。
在声明header的头文件中定义如下所示。
typedef struct tagRGBQUAD {
BYTE rgbBlue; /*指定蓝色分量*/
BYTE rgbGreen; /*指定绿色分量*/
BYTE rgbRed; /*指定红色分量*/
BYTE rgbReserved; /*保留,指定为0*/
} RGBQUAD;
二、材料准备
在轮番使用了截图与裁剪、bmp格式转换、PhotoShop以及Windows画图工具调整图片色深格式之后,得到此次BMP转YUV实验的五张**即炫酷又能展现我开车水平**的图片素材。
(大众集团光宗耀祖之车型们)
(集齐四台,人生赢家)
(此即为人生赢家之低调生活之车辆→Audi RS6 YYDS!)
四张图片宽高均为1436*810,且色位为256。
三、转换代码实现
1、文件头定义
在项目下的头文件中,声明了bmp格式文件的头部。
header.h文件
#pragma once
#include<iostream>
#include<stdlib.h>
using namespace std;
void transferyuv(char inputFile[], char outputFile[], int n);
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef long LONG;
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;
2、转换过程函数
在transferyuv的cpp代码文件中进行转换。收取文件参数之后,在函数中直接进行文件读取、格式检查、转换以及写入yuv文件的操作。
其中规定
#include"header.h"
#pragma warning(disable : 4996)
void transferyuv(char inFile[], char outFile[], int n)
{
BITMAPFILEHEADER File_Header;
BITMAPINFOHEADER Info_Header;
RGBQUAD rgbquad[256];
FILE* bmp_fp1 = fopen(inFile, "rb");
FILE* yuv_fp2;
//先检查文件格式和文件头,同时读入文件头与位图信息头
if (fread(&File_Header, sizeof(BITMAPFILEHEADER), 1, bmp_fp1) != 1)
{
printf("文件读取错误!");
exit(0);
}
if (File_Header.bfType != 0x4D42)
{
printf("输入文件不是bmp文件,请检查!");
exit(0);
}
if (fread(&Info_Header, sizeof(BITMAPINFOHEADER), 1, bmp_fp1) != 1)
{
printf("文件头格式错误。");
exit(0);
}
//输出文件(第一次为新写文件,第二次为向后添加)
if (n == 0)
{
yuv_fp2 = fopen(outFile, "wb+");
}
else
{
yuv_fp2 = fopen(outFile, "ab+");
}
//获取图像宽高
int width = Info_Header.biWidth;
int height = Info_Header.biHeight;
if (n == 0)
{
cout << "图像宽:" << width << endl;
cout << "图像高:" << height << endl;
}
//打印图像信息
cout << endl;
cout << "第" << n + 1 << "幅输入图像" << endl;
int colorsPalette = (File_Header.bfOffBits - sizeof(tagBITMAPFILEHEADER) - sizeof(tagBITMAPINFOHEADER)) / sizeof(tagRGBQUAD);
//若有调色板则读入调色板
if (colorsPalette > 0) {
for (int i = 0; i < colorsPalette; i++)
{
fread(&rgbquad[i], 1, sizeof(tagRGBQUAD), bmp_fp1);
}
cout << "调色板数量:" << colorsPalette << endl;
}
else {
cout << "文件无调色板" << endl;
}
unsigned char* buffer1 = (unsigned char*)malloc(width * height * 3);
unsigned char* buffer2 = (unsigned char*)malloc(width * height * 3 / 2);
fread(buffer1, 1, width * height * 3, bmp_fp1);
//按照先遍历行、再遍历列的方式进行读写
for (int i(0) ; i < height; i++)
{
for (int j(0); j < width; j++)
{
//按照BMP从下至上、从左至右的顺序进行参数调整
int real_i = height - 1 - i;
int r=0;
int g = 0;
int b = 0;
//读取调色板RGB值或直接读取
if (colorsPalette > 0)
{
r = rgbquad[*(buffer1 + width * i + j)].rgbRed;
g = rgbquad[*(buffer1 + width * i + j)].rgbGreen;
b= rgbquad[*(buffer1 + width * i + j)].rgbBlue;
}
else {
r = *(buffer1 + (width * i + j) * 3 + 2);
g = *(buffer1 + (width * i + j) * 3 + 1);
b = *(buffer1 + (width * i + j) * 3);
}
//计算亮度Y
int Y=0;
Y = int(0.299 * r + 0.587 * g + 0.114 * b);
*(buffer2 + width * real_i + j) = Y;
//按照4:2:0采样格式写入YUV
if (real_i % 2 == 0 && j % 2 == 0)
{
int U = int(-0.1684 * r - 0.3316 * g + 0.5 * b + 128);
int V = int(0.5 * r - 0.4187 * g - 0.0813 * b + 128);
*(buffer2+ width * height + width / 2 * real_i / 2 + j / 2) = U;
*(buffer2+ width * height * 5 / 4 + width / 2 * real_i / 2 + j / 2) = V;
}
}
}
//写入数据
for (int i(0); i < 50; i++) {
fwrite(buffer2, 1, width * height * 3 / 2, yuv_fp2);
}
free(buffer1);
free(buffer2);
fclose(bmp_fp1);
fclose(yuv_fp2);
}
3、主函数
对主函数传入的参数格式进行调用。
参数(int argc, char argv[])中,表示参数个数的argc参数数值为2+[bmp文件数],argv[]中第一个参数固定为项目exe文件自身,第二个参数定为输出文件,而后面的参数依次为写入的bmp文件。
所以第i个bmp文件表示即为argv[i + 2]。
main函数
#include"header.h"
int main(int argc, char** argv) {
int num = argc - 2;
for (int i = 0; i < num; i++)
{
transferyuv(argv[i + 2], argv[1], i);
}
}
4、项目调试
在项目调试中传入参数并应用,进行项目生成。
在项目的debug文件夹下即看到项目生成的exe文件。
运行exe文件并重新传入参数(不知为何),即可进行转换。
5、效果测试
《从高调的人生赢家转换成了低调的人生赢家》
四、问题总结
1、在起初生成项目过程中,发现因错误提醒而中止的情况。
官方给出的解释是fopen函数出了点问题,将C4996警告屏蔽掉即可。
#pragma warning(disable : 4996)
2、在生成过程中,某次的生成出现了很鬼畜的情况。
通过与同学讨论和检查错误,发现问题出在选择4:2:0格式的情况下,图像大小不满足长为4的倍数,宽为2的倍数(此时大小选成了1438*810)。对图片进行调整后,即得到了正确结果。