数据压缩原理实验2_BMP2YUV文件转换

本文介绍了BMP图像文件格式的结构,包括位图文件头、位图信息头、调色板和位图数据等内容,并详细解析了一种将BMP图像转换为YUV格式的方法。

一、实验原理

BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB),使用非常广。它采用位映射存储格式,除了图像深度可选以外,在绝大多数应用中不采用其他任何压缩,因此,BMP文件所占用的空间很大。BMP文件的图像深度可选1bit,4bit,8bit,16bit及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。


BMP文件格式主要由下面四部分组成:

位图文件头数据结构、位图信息头数据结构、调色板和位图数据四部分组成

(1)位图文件头数据结构,它包含BMP图像文件的类型、显示内容等信息

typedef struct tagBITMAPFILEHEADER {
        WORD       bfType;            /* 说明文件的类型; 占2个字节  */
        DWORD      bfSize;            /* 说明文件的大小,用字节为单位;占4个字节 */
        WORD       bfReserved1;       /* 保留,设置为0; 占2个字节 */
        WORD       bfReserved2;       /* 保留,设置为0; 占2个字节 */
        DWORD      bfOffBits;         /* 说明从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节偏移量;占4个字节 */
}   BITMAPFILEHEADER;

(2)位图信息头数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息

typedef struct tagBITMAPINFOHEADER { 
        DWORD    biSize;       		/* 说明结构体所需字节数 */
        LONG     biWidth;   		/* 以像素为单位说明图像的宽度 (需注意的是其数值低位在前高位在后)*/
        LONG     biHeight;  		/* 以像素为单位说明图像的高度 */
        WORD     biPlanes;  	        /* 说明位面数,必须为1 */
        WORD     biBitCount; 	        /* 说明位数/像素,1、2、4、8、24 */
        DWORD    biCompression;	        /* 说明图像是否压缩及压缩类型  
					其中压缩方式: 0表示不压缩,1表示RLE8压缩,2表示RLE4压缩,3表示每个像素值由指定的掩码决定
					BI_RGB(不压缩),BI_RLE8(8位RLE压缩方式),BI_RLE4(4位RLE压缩方式),BI_BITFIELDS(位域存放方式)*/
        DWORD    biSizeImage;           /* 以字节为单位说明图像大小 ,必须是4的整数倍,图像数据大小不是4的倍数时用0填充补足*/
        LONG     biXPelsPerMeter;       /* 目标设备的水平分辨率,像素/米 */
        LONG     biYPelsPerMeter;       /*目标设备的垂直分辨率,像素/米 */
        DWORD    biClrUsed;             /* 说明图像实际用到的颜色数,如果为0,则颜色数为2的biBitCount次方 */
        DWORD    biClrImportant;  	/*说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。*/
	/*以上按顺序依次所占字节数为:4/4/4/2/2/4/4/4/4/4/4 共40个字节*/
}  BITMAPINFOHEADER;

(3)调色板,这个部分是可选的,调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。数组中每个元素的类型是一个RGBQUAD结构。有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板。

typedef struct tagRGBQUAD { 
       BYTE    rgbBlue;           /*指定蓝色分量*/
       BYTE    rgbGreen;          /*指定绿色分量*/
       BYTE    rgbRed;            /*指定红色分量*/
       BYTE    rgbReserved;       /*保留,指定为0,可能需要α*/
}  RGBQUAD;

(4)位图数据,这部分的内容根据BMP位图使用的位数不同而不同。对于用到调色板的位图,图像数据就是该像素颜色在调色板中的索引值(逻辑色)。对于真彩色图,图像数据就是实际的R, G, B值。图像的每一扫描行由表示图像像素的连续的字节组成,每一行的字节数取决于图像的颜色数目和用像素表示的图像宽度。规定每一扫描行的字节数必须是4的整倍数,也就是DWORD对齐的。扫描行是由底向上存储的,这就是说,阵列中的第一个字节表示位图左下角的像素,而最后一个字节表示位图右上角的像素。

下面用一张1位的图像的二进制显示来具体解释上述BMP文件格式


图像说明:该图为1位图像,其分辨率为512*512,大小为32.0625KB=32832B=(8040)H

文件格式说明:

      第1-2个字节42 4D表示图像类型为BMP         

      第3-6个字节 40 80 00 00 表示图像大小,可以看出在数据存放时采取低位在前高位在后的方式;

      第11-14个字节3E 00 00 00表示图像的字节偏移量为(3E)H,为62个字节,即14(文件头)+40(信息头)+8(调色板)=62

      第15-18个字节28 00 00 00表示信息头的结构体所需为(28)H,40个字节

      第19-22个字节00 02 00 00表示图像的宽为(02 00)H,512个像素

      第23-26个字节00 02 00 00表示图像的高为(02 00)H,512个像素

      第29-30个字节01 00表示图像为1位图像

      第37-40个字节 02 80 00 00 表示图像的大小为(00 00 80 20)H, 32800B其计算公式如下:


其中x,y分别是图像的宽和高,因为图像要求其中biSizeImage指的是实际图像数据的大小,以字节为单位。因为windows在进行行行扫描的时候,最小的单位是4个字节,所以当图片的宽度不是4的倍数的时候,需要在每行的后面补上缺少的字节,以0填充。因此在计算中宽必须是4的整数倍,如果不是整数倍,则取大于宽的离4的整数倍最近的数值,这样每行的像素可以整数次读取完成。


上图为将24位真彩图进行了转换成1位,4位,8位的图,可以看到颜色差别还是很明显的

二、实验流程及代码分析

具体的实验流程如下图所示


下面是main.cpp里的具体代码,代码分析加注在代码中

#include <stdio.h>
#include <windows.h> //该头文件里包括了程序中对bmp文件的一些调用函数,如 BITMAPFILEHEADER等,所以必不可少
#include "bmp2yuv.h"

void main(int argc, char *argv[])
{
	FILE *bmpFile = NULL, *yuvFile = NULL;
	BITMAPFILEHEADER File_header;  //直接读取图像的文件头
	BITMAPINFOHEADER Info_header;  //读取图像的信息头

	unsigned char * rgbBuf = NULL;
	unsigned char * yBuff = NULL;
	unsigned char * uBuff = NULL;
	unsigned char * vBuff = NULL;
	int flip = 0;   // 因为bmp文件的存储方式是从左到右、从下到上,因此flip值为0来使rgb2yuv时从下往上读入数据

	for (int k = 1; k < 6; k++)   //这里采取了循环,每次读取一个bmp文件,k来控制一共需要转换的文件数
	{
		//	open bmp & yuv file	
		if ((bmpFile = fopen(argv[k], "rb")) == NULL)  //一共5个bmp文件
		{
			printf("bmp file open failed!");
			exit(0);
		}
		if ((yuvFile = fopen(argv[6], "ab+")) == NULL)  //argv[6]中存放的是最后生成的视频文件
		{                                               //通过"ab+"来不断的往文件里写入
			printf("yuv file failed!");
			exit(0);
		}
		//	end open bmp & yuv file

		//	read file & info header 
		if (fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1)
		{
			printf("read file header error!");
			exit(0);
		}
		if (File_header.bfType != 0x4D42)
		{
			printf("Not bmp file!");
			exit(0);
		}
		if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)
		{
			printf("read info header error!");
			exit(0);
		}
		//	end read header

		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字节整数倍的图像大小。 */
		if ((Info_header.biHeight % 2) == 0)
			height = Info_header.biHeight;
		else
			height = Info_header.biHeight + 1;

		//开辟缓冲区
		rgbBuf = (unsigned char *)malloc(height*width * 3);
		memset(rgbBuf, 0, height*width * 3);  //memset函数是对较大的结构体或数组进行清零操作的快速操作
		yBuff = (unsigned char *)malloc(height*width);
		uBuff = (unsigned char *)malloc((height*width) / 4);
		vBuff = (unsigned char *)malloc((height*width) / 4);
		//结束开辟缓冲区

		printf("This is a %d bits image!\n", Info_header.biBitCount);
		printf("\nbmp size: \t%d X %d\n", Info_header.biWidth, Info_header.biHeight);

		ReadRGB(File_header, Info_header, bmpFile, rgbBuf);

		int i;
		if (RGB2YUV(width, height, rgbBuf, yBuff, uBuff, vBuff, flip))
		{
			printf("rgb2yuv error");
			exit(1);
		}
		for (i = 0; i < width*height; i++)
		{
			if (yBuff[i] < 16) yBuff[i] = 16;
			if (yBuff[i] > 235) yBuff[i] = 235;
		}
		for (i = 0; i < width*height / 4; i++)
		{
			if (uBuff[i] < 16) uBuff[i] = 16;
			if (uBuff[i] > 240) uBuff[i] = 240;
			if (vBuff[i] < 16) vBuff[i] = 16;
			if (vBuff[i] > 240) vBuff[i] = 240;
		}
		for (int m = 0; m < 40; m++)
		{
			fwrite(yBuff, 1, width * height, yuvFile);
			fwrite(uBuff, 1, (width * height) / 4, yuvFile);
			fwrite(vBuff, 1, (width * height) / 4, yuvFile);
		}
	}

	free(rgbBuf);
	free(yBuff);
	free(uBuff);
	free(vBuff);
	fclose(bmpFile);
	fclose(yuvFile);
}

下面是bmp2yuv.cpp里的具体代码,代码分析加注在代码中

#include <stdio.h>
#include <windows.h>  
#include <math.h>

/*检测图像实际数据开始位置和信息头结束位置是否重合,
  若中间还有2的biBitCount次方结构体RGBQUAQ的大小,那么说明中间有调色板 */
bool MakePalette(FILE * pFile, BITMAPFILEHEADER &file_h, BITMAPINFOHEADER & info_h, RGBQUAD *pRGB_out)
{
	if ((file_h.bfOffBits - sizeof(BITMAPFILEHEADER) - info_h.biSize) == sizeof(RGBQUAD)*pow(2, (float)info_h.biBitCount))
	{
		fseek(pFile, sizeof(BITMAPFILEHEADER) + info_h.biSize, 0);
		fread(pRGB_out, sizeof(RGBQUAD), (unsigned int)pow(2, (float)info_h.biBitCount), pFile);
		return true;
	}
	else
		return false;
}

void ReadRGB( BITMAPFILEHEADER & file_h, BITMAPINFOHEADER & info_h, FILE * pFile,unsigned char * rgbDataOut)
{
	unsigned long Loop, i, j, width, height, w, h;
	unsigned char mask, *Index_Data, *Data;

	if ((info_h.biWidth % 4) == 0)
		w = info_h.biWidth;
	else
		w = (info_h.biWidth*info_h.biBitCount + 31) / 32 * 4;
	/*加31再除以32后下取整,就保证了计算结果是离这个数最近的而且是比它大的32的倍数,
	也就保证了是4字节的整数倍。乘以4和行数,得到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!");
		exit(0);
	}

	for (i = 0; i < height; i++)
		for (j = 0; j < width; j++)
		{
			Data[i*width + j] = Index_Data[i*width + j];
		}

	RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow(2,(float)info_h.biBitCount));
												//debug:把unsigned char 改为unsigned int
	if (!MakePalette(pFile, file_h, info_h, pRGB))
		printf("No palette!");
	//当每像素为24位时,直接取像素数据写RGB缓冲区
	if (info_h.biBitCount == 24)  
	{
		memcpy(rgbDataOut, Data, height*width);
		free(Index_Data);
		free(Data);
		return;
	} 
	//当每像素为16位时,位与移位取像素数据转换为 8bit/彩色分量 写RGB缓冲区 
	if (info_h.biBitCount == 16)
	{
		for (Loop = 0; Loop < height * width ; Loop+=2)
		//debug:"Loop++"改为"Loop+=2";
		//loop每次循环中应该要逐次增加2,因为loop表示一个字节,而它应该每次16位,即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;
		}       //上述存储由BGR的顺序存储
	}
	//当每像素为8位及以下时,构造调色板,位与移位取像素数据,查调色板,写RGB缓冲区
	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)
		{	/* 其中while 循环的次数:1bit 每字节循环8次;2bit 4次;4bit 2次;8bit 1次 */
			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++;
		}
	}
	free(Index_Data);
	free(Data);
	free(pRGB);
}
//下面为rgb2yuv的程序,因与上个实验相同就不贴了,详见报告一

当每像素为16位的时候,位与移位取像素数据转换为8bit/彩色分量,写RGB缓冲区,情况与24bit不同,下面进行图示说明:


三、实验结果

以下为最终的视频截图,分别从1位,4位,8位,16位,24位,其分辨率都为512*512



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值