BMP格式文件转YUV文件 实验报告

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个字节组成,其结构如下。
部分名称大小(字节)内部标志
bfType2表明文件类型(BM)
bfSize4文件大小(单位为字节)
bfReserved12保留字,设置为0
bfReserved22保留字,设置为0
bfOffBits4从文件头开始到实际的图象数据之间的字节的偏移量

在声明header的头文件中定义如下所示。

typedef struct tagBITMAPFILEHEADER {
	WORD bfType; /* 说明文件的类型*/
	DWORD bfSize;/* 说明文件的大小,用字节为单位*/
	WORD bfReserved1; /* 保留,设置为0 */
	WORD bfReserved2; /* 保留,设置为0 */
	DWORD bfOffBits; /* 说明从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节偏移量*/
} BITMAPFILEHEADER;
  • 包含位图信息的位图信息头
    位图信息头包含了图像宽高等位图详细数据。
名称占用空间内容实际数据
biSize4字节位图信息头的大小,为400x28(40)
biWidth4字节位图的宽度(单位:像素)-
biHeight4字节位图的高度(单位:像素)-
biPlanes2字节固定值1
biBitCount2字节每个像素的位数视数据而定:1-黑白图,4-16色,8-256色,24-真彩色
biCompression4字节压缩方式,BI_RGB(0)为不压缩-
biSizeImage4字节位图全部像素占用的字节数与位图宽高数据有关
biXPelsPerMeter4字节水平分辨率(多为像素)-
biYPelsPerMeter4字节垂直分辨率(多为)-
biClrUsed4字节位图使用的颜色数-
biClrImportant4字节重要的颜色数,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)。对图片进行调整后,即得到了正确结果。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值