BMP2YUV实验报告



1.实验基本原理

BMP文件格式

BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB),使用非常广。它采用位映射存储格式,除了图像深度可选以外,在绝大多数应用中不采用其他任何压缩,因此,BMP文件所占用的空间很大。BMP文件的图像深度可选lbit、4bit、8bit、16bit及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。
典型的BMP图像文件由四部分组成:
(1)位图头文件数据结构BITMAPFILEHEADER,它包含BMP图像文件的类型、显示内容等信息;
(2)位图信息数据结构BITMAPINFOHEADER,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
(3)调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板。调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsedbiBitCount字段。数组中每个元素的类型是一个RGBQUAD结构。

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

结构体BITMAPFILEHEADER,BITMAPINFOHEADER,RGBQUAD定义在windows.h库中,可以直接调用。

#include<windows.h> 
BITMAPFILEHEADER

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

BITMAPINFOHEADER

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;

RGBQUAD

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

查看BMP文件二进制流

24bitBMP文件

格式说明:

前14字节为FILEHEADER空间,之后40字节为INFOHEADER空间,24bitBMP为真彩色图像,无调色板,因此INFOHEADER空间之后为位图数据。

第1-2字节:42 4D表示图像为BMP格式

第3-6字节:表示图像大小,为倒序存储,实际大小应为00 00 E0 01

第11-14字节:表示偏移量36H=54,即文件头+信息头一共54字节

第19-22字节:倒序存储图像宽度,(02 80H)=640像素

第23-36字节:倒序存储图像高度,(01 E0H)=480像素

8bitBMP文件

格式说明:

前14字节为FILEHEADER空间,之后40字节为INFOHEADER空间,8bitBMP不是真彩色图像,有调色板。

调色板空间字节数= sizeof(RGBQUAD)*pow(2, (float)info_h.biBitCount))

字节序

不同的计算机系统采用不同的字节序存储数据,同样一个4字节的32位整数,在内存中存储的方式不同。字节序分为小尾字节序(Little Endian)和大尾字节序(Big Endian)Intel处理器大多数使用小尾字节序,Motorola处理器大多数使用大尾(Big Endian)字节序。

小尾就是低位字节排放在内存的低端,高位字节排放在内存的高端,即所谓的低位在前,高位在后。大尾就是高位字节排放在内存的低端,低位字节排放在内存的高端,即所谓的高位在前,低位在后TCP/IP各层协议将字节序定义为大尾,因此TCP/IP协议中使用的字节序通常称之为网络字节序。

在实现BMP文件头信息的写入时,需要注意整数保存时的字节序。


2.实验流程分析


程序初始化(打开两个文件、定义变量和缓冲区等)

读取BMP文件,抽取或生成RGB数据写入缓冲区

调用RGB2YUV的函数实现RGBYUV数据的转换

YUV文件

程序收尾工作(关闭文件,释放缓冲区)


3.关键代码及分析

main.cpp

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <math.h>
#include<windows.h> 
#include "rgb2yuv.h"
#include "readrgb.h"


#define u_int8_t	unsigned __int8
#define u_int		unsigned __int32
#define u_int32_t	unsigned __int32

int main(int argc, char** argv)
{
	bool flip = FALSE;				/* --flip */
	unsigned int i;
	int width = 0, height = 0;
	/* internal variables */
	char* bmpFileName = NULL;
	char* yuvFileName = NULL;
	FILE* bmpFile = NULL;
	FILE* yuvFile = NULL;
	u_int8_t* rgbBuf = NULL;
	u_int8_t* yBuf = NULL;
	u_int8_t* uBuf = NULL;
	u_int8_t* vBuf = NULL;
	u_int32_t videoFramesWritten = 0;

	//打开5幅bmp图像并生成1个YUV文件
	for (int k = 1; k < 6; k++)            //argv[0]=命令名,argv[1]=参数1,argv[2]=参数2,….argv[n]=参数n
	{
		if ((bmpFile = fopen(argv[k], "rb")) == NULL)
		{
			printf("bmp file open failed!");
			exit(0);
		}
		if ((yuvFile = fopen(argv[6], "ab+")) == NULL)  //ab+:以附加方式打开可读写的二进制文件。若文件不存在,则会建立该文件,
		{                                               //如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。
			printf("yuv file failed!");
			exit(0);
		}


		BITMAPFILEHEADER File_header;
		BITMAPINFOHEADER 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);
		}

		//保证行扫描为4字节的整数倍
		int width, height;
		if (((Info_header.biWidth * Info_header.biBitCount / 8))%4 == 0)
			width = Info_header.biWidth * Info_header.biBitCount / 8;
		else
			width = (Info_header.biWidth*Info_header.biBitCount + 31) / 32 * 4;
			//width,height定义为整型,此式保证计算结果是距离这个数最近的4字节的整数倍
		if ((Info_header.biHeight % 2) == 0)
			height = Info_header.biHeight;
		else
			height = Info_header.biHeight + 1;
			//保证为偶数列
	
		rgbBuf = (u_int8_t*)malloc(Info_header.biWidth * Info_header.biHeight * 3);//为bmp中的图像数据开辟buffer

		yBuf = (u_int8_t*)malloc(Info_header.biWidth * Info_header.biHeight);
		uBuf = (u_int8_t*)malloc((Info_header.biWidth * Info_header.biHeight) / 4);
		vBuf = (u_int8_t*)malloc((Info_header.biWidth * Info_header.biHeight) / 4);
		memset(rgbBuf, 0, Info_header.biWidth * Info_header.biHeight * 3);         //memset:对结构体或者数组快速清零的函数

		if (rgbBuf == NULL || yBuf == NULL || uBuf == NULL || vBuf == NULL)
		{
			printf("no enought memory\n");
			exit(1);
		}

		ReadRGB(bmpFile, File_header, Info_header, rgbBuf); //读取bmp文件中的图像数据

		if (RGB2YUV(Info_header.biWidth, Info_header.biHeight, rgbBuf, yBuf, uBuf, vBuf, flip))
		{
			printf("rgb2yuv error");
			exit(1);
		}

		//归一化处理
		for (i = 0; i < unsigned int(Info_header.biWidth * Info_header.biHeight); i++)
		{
			if (yBuf[i] < 16) yBuf[i] = 16;
			if (yBuf[i] > 235) yBuf[i] = 235;
		}

		for (i = 0; i < unsigned int(Info_header.biWidth * Info_header.biHeight / 4); i++)
		{
			if (uBuf[i] < 16) uBuf[i] = 16;
			if (uBuf[i] > 240) uBuf[i] = 240;

			if (vBuf[i] < 16) vBuf[i] = 16;
			if (vBuf[i] > 240) vBuf[i] = 240;
		}

		//每个文件写40帧
		for (int j = 0; j < 40; j++)
		{
			fwrite(yBuf, 1, Info_header.biWidth * Info_header.biHeight, yuvFile);
			fwrite(uBuf, 1, (Info_header.biWidth * Info_header.biHeight) / 4, yuvFile);
			fwrite(vBuf, 1, (Info_header.biWidth * Info_header.biHeight) / 4, yuvFile);
		}

		printf("\n %u,%ux%u video frames written\n",Info_header.biBitCount,Info_header.biWidth, Info_header.biHeight);
		memset(yBuf, 0, Info_header.biWidth * Info_header.biHeight);
		memset(uBuf, 0, Info_header.biWidth * Info_header.biHeight / 4);
		memset(vBuf, 0, Info_header.biWidth * Info_header.biHeight / 4);
	}
	//释放缓冲区,关闭文件
	free(yBuf);
	free(uBuf);
	free(vBuf);
	free(rgbBuf);
	fclose(bmpFile);
	fclose(yuvFile);
	
	getchar();
	return(0);
}

readRGB.cpp

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <math.h>
#include<windows.h> 
#include "readrgb.h"

//检测信息头结束位置是否与图像数据起始位置重合,若中间有(2的biBitCount次方)*(结构体RGBQUAD大小)的空间,
//则说明存在调色板,即图像不是真彩色。
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);//从pFile指向的文件中取 sizeof(RGBQUAD)*pow(2, (float)info_h.biBitCount)
		                                                                                        //个字节数存入pRGB_out指向的空间
		return true;
	}
	else
		return false;
}
void ReadRGB(FILE* bFile , BITMAPFILEHEADER & File_header, BITMAPINFOHEADER & Info_header, unsigned char* rgbdata)
{
	unsigned long Loop, width, height;
	unsigned char mask = 0, *tempData;

	if (((Info_header.biWidth * Info_header.biBitCount / 8)) % 4 == 0)
		width = Info_header.biWidth * Info_header.biBitCount / 8;
	else
		width = (Info_header.biWidth*Info_header.biBitCount + 31) / 32 * 4;

	if ((Info_header.biHeight % 2) == 0)
		height = Info_header.biHeight;
	else
		height = Info_header.biHeight + 1;

	tempData = (unsigned char *)malloc(width * height);

	fseek(bFile, File_header.bfOffBits, 0);
	fread(tempData, 1, width * height, bFile);  //将数据读入临时buffer,倒序存储,注意在rgb2yuv时也要倒序处理,即flip=0

	if (Info_header.biBitCount == 24)//若图像深度为24(无调色板),则数据直接写入rgb缓冲区
	{
		memcpy(rgbdata, tempData, width * height);
		free(tempData);
		return;
	}

	RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow(2, (float)Info_header.biBitCount));
	if (!MakePalette(bFile, File_header, Info_header, pRGB))
		printf("No palette!");

	if (Info_header.biBitCount == 16)
	{
		for (Loop = 0; Loop < width * height; Loop += 2)   //unsigned long Loop:一字节长度
			                                               //图像深度为16,位与移位取像素数据转换为8bit / 彩色分量,写入rgb缓冲区
		{
			*rgbdata = (tempData[Loop] & 0x1F) << 3;
			*(rgbdata + 1) = ((tempData[Loop] & 0xE0) >> 2) + ((tempData[Loop + 1] & 0x03) << 6);
			*(rgbdata + 2) = (tempData[Loop + 1] & 0x7C) << 1;
			rgbdata += 3;
		}
	}
	for (Loop = 0; Loop < width * height; Loop++)//图像深度为8及8以下,构造调色板,位与移位取像素数据,查调色板,写入rgb缓冲区
	{
		switch (Info_header.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) //循环mask次:1bit每字节循环8次,2bit4次,4bit2次
		{
			unsigned char index = mask == 0xFF ? tempData[Loop] : ((tempData[Loop] & mask) >> (8 - shiftCnt * Info_header.biBitCount));

			*rgbdata = pRGB[index].rgbBlue;
			*(rgbdata + 1) = pRGB[index].rgbGreen;
			*(rgbdata + 2) = pRGB[index].rgbRed;

			if (Info_header.biBitCount == 8)
				mask = 0;
			else
				mask >>= Info_header.biBitCount;
			rgbdata += 3;
			shiftCnt++;
		}
	}
	free(tempData);
	free(pRGB);
}

rgb2yuv代码在此不再赘述

4.实验结果及分析

图像大小均为640*480,顺序为24bit,16bit,8bit,4bit,1bit处理后的结果

(实验图片来自新浪微博@伊吹鸡腿子)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值