BMP2YUV

BMP2YUV文件转换实验报告

1.基本原理

(1)BMP文件组成结构

          BMP(Bitmap) 是一种标准图像文件格式,分为设备相关位图(DDB)和设备无关位图(DIB)。文件深度可选1bit、4bit、8bit、16bit及24bit。BMP文件在储存数据时,图像的扫描方式是按照从左到右、从下到上的顺序。

典型的BMP文件有位图头文件数据结构、位图信息数据结构、调色板、位图数据四部分组成,具体分析如下:

(a)位图头文件数据结构,包含了BMP图像文件的类型、显示内容等信息;
BITMAPFILEHEADER
typedef struct tagBITMAPFILEHEADER {
        WORD         bfType;            /* 说明文件的类型;  */
        DWORD      bfSize;              /* 说明文件的大小,用字节为单位  */
                                                      /*注意此处的字节序问题,bmp以小尾字节序存储*/
        WORD         bfReserved1;   /* 保留,设置为0 */
        WORD         bfReserved2;   /* 保留,设置为0 */
        DWORD      bfOffBits;         /* 说明从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节 偏移量 */
}   BITMAPFILEHEADER;
(b)位图信息数据结构,包含了BMP图像的宽、高、压缩方法和定义颜色等信息;
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;


(c)调色板,不是所有位图都需要调色板,例如真彩色图(图像深度24bit的BMP文件);
RGBQUAD
typedef struct tagRGBQUAD {
      BYTE    rgbBlue;           /*指定蓝色分量*/
      BYTE    rgbGreen;        /*指定绿色分量*/
      BYTE    rgbRed;            /*指定红色分量*/
      BYTE    rgbReserved;   /*保留,指定为0*/
} RGBQUAD;
(d)位图数据,这部分根据文件所选的位数有关,24位图直接使用RGB,比24bit小的文件则使用调色板中颜色的索引值。
以Winhex打开的4bitlena.bmp文件为例,一个BMP文件以位图文件头开始:



注:bmp以小尾字节序存储

紧接着是信息头:

 


       第三部分调色板,只有在1位图(单色),4位图(16色),8位图(256色)的情况下,此时会有颜色表。调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。数组中每个元素的类型是一个RGBQUAD结构。RGBQUAD结构体由4个字节型数据组成,所以一个RGBQUAD结构体只占用4字节空间,从左到右每个字节依次表示(蓝色,绿色,红色,未使用)。

 

以16色为例,这16个RGBQUAD结构体依次为:


       到此为止,正好达到从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节偏移量14+40+64=118字节。第四部分就是实际的图像数据。如果是真彩色图,图像数据就是实际的RGB值。图像的每一行由表示像素的连续字节组成,每一行的字节数取决于图像的颜色数目和用像素表示的图像宽度。每一行的字节数必须DWORD对齐,必须是4的整数倍。

 (2)字节序

       不同计算机采用不同的字节序存储数据,字节序分为小尾字(Little Endian)和大尾字节序 (Big Endian) 。Intel 处理器大多数使用小尾字节序 ,Motorola 处理器多数使用大尾字节序。小尾就是低位字节排放在内存的低端,高位字节排放在内存的高端,即“低位在前,高位在后”。大尾序与其相反,“高位在前,低位在后”。BMP文件是以Intel写入的,结合上述分析,为小尾字节序。

(3)彩色空间转换(RGB2YUV)

     RGB转YUV的公式可推出YUV转RGB的公式如下,
       R = Y + 1.4075(V - 128)
       G = Y -  0.3455(U - 128) - 0.7169(V - 128)
       B = Y + 1.779(U - 128)
      具体分析可见彩色空间转换实验报告。

2.实验流程分析

(a)程序初始化(打开两个文件bmp及yuv文件、定义变量和缓冲区等);
(b)读取BMP文件,抽取或生成RGB数据写入缓冲区;

(c)调用RGB2YUV的函数实现RGB到YUV数据的转换;
(d)写200帧的YUV文件;
(e)程序收尾工作(关闭文件,释放缓冲区);
(f)将生成的YUV文件用播放器打开,观看是否正确。

3.关键代码及分析

部分细节分析见代码注释
//判断像素的实际点阵数
    if ((info_h.biWidth / 8 * info_h.biBitCount) % 4) == 0
		w = info_h.biWidth;
	else
		w = (info_h.biWidth*info_h.biBitCount+31)/32*4;
	if ((info_h.biHeight%2) == 0)
		h = info_h.biHeight;
	else
		h = info_h.biHeight + 1;

	width = w/8*info_h.biBitCount;//width为实际一行的字节数
	height = h;//height为列数
      规定每一行的字节数必须DWORD对齐。首先判断像素的点阵数。一个字节是8位,每个像素所需字节数为info_h.biBitCount/8,图像每行的字节数  =W*info_h.biBitCount/8。如果每一行的字节数不是4的整数倍,需要在每行数据补零字节数为4-(W*info_h.biBitCount/8) Mod 4,补充后以像素为单位的宽度W = (info_h.biWidth*info_h.biBitCount+31)/32*4;
case 16://位与移位取像素数据转换为8bit的彩色分量
		if(info_h.biCompression == BI_RGB)
		{
			for (Loop = 0;Loop < height * width;Loop +=2)
			{
				*rgbDataOut = (Data[Loop]&0x1F)<<3;//0001 1111
				*(rgbDataOut + 1) = ((Data[Loop]&0xE0)>>2) + ((Data[Loop+1]&0x03)<<6);                                                                                     //1110 0000                       //0000 0011
				*(rgbDataOut + 2) = (Data[Loop+1]&0x7C)<<1;//0111 1100
				rgbDataOut +=3;
			}
		}


高位----------->低位 

          Data[loop] 

                      Data[loop+1]

        hgfe dcba

                ponm       lkji

 

 &0001  1111左移三位

            edcb  a000

             

*rgbDataOut =    edcb a000

          &1110  0000右移两位 

               00hg       F000

          &0000      0011 左移6位

             ji00         0000

*(rgbDataOut + 1)=   jihg f000

         

   &0111        1100左移1位

    0onm lk00

*(rgbDataOut + 2) = onml k000

      对于16bit的图像,当biCompression成员的值是BI_RGB时,它没有调色板。16位中,最低的5位(edcba)表示蓝色分量,中间的5位(jihgf)表示绿色分量,高的5位(onmik)表示红色分量,一共占用了15位,最高的一位(p)保留,设为0。这种格式也被称作555   16位位图。除此之外还有565 16位图,其中蓝绿红色分量分别占据5位、6位、5位。
switch(info_h.biBitCount)
		{
		case 1:
			mask = 0x80;//1000 0000当biBitCount=1时,8个像素占1个字节;
			break;
		case 2:
			mask = 0xC0;//1100 0000当biBitCount=2时,4个像素占1个字节;
			break;
		case 4:
			mask = 0xF0;// 1111 0000当biBitCount=4时,2个像素占1个字节;  
			break;
		case 8:
			mask = 0xFF;//1111 1111 当biBitCount=8时,1个像素占1个字节;
		}

		int shiftCnt = 1;

		while (mask)
			{
				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;
                                 //8bit图像直接读取下一字节
				if(info_h.biBitCount == 8)
					mask =0;
				else
					mask >>= info_h.biBitCount;
				rgbDataOut+=3;
				shiftCnt ++;
			}
         由于biBitCount的不同,一个字节所能表示的像素数也不同,所以需要掩码mask来掩蔽暂时不需要的bit位。8bit每次恰好读取一个字节,故不需要进行掩码移位。小于8bit的情况,以biBitCount=4bit为例,2个像素占1个字节,shiftCnt=1------>Data[Loop]低四位右移4位即第一个像素的颜色索引值--------->mask右移4位变成0F(0000 1111)---------->shiftCnt = 2   --->Data[Loop]高四位不移位(右移0位)即为第二个像素的颜色索引值。2bit和1bit的原理与4bit相似。

主函数
/*--------------main.cpp-------------*/
#include <stdio.h>
#include<windows.h>    
#include "bmp2yuv.h"
#define u_int8_t	unsigned __int8
#define u_int		unsigned __int32
#define u_int32_t	unsigned __int32

BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;

int main(int argc, char** argv)
{   //变量初始化
	u_int frameWidth = 352;			/* --width=<uint> */
	u_int frameHeight = 240;		/* --height=<uint> */
	bool flip = TRUE;				/* --flip */
	u_int size,i,k,j,t=0;
	char *a=NULL;
	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;
	//命令行参数
	k =atoi(argv[6]);//图像重复帧数
	yuvFileName = argv[7];

	/* open the YUV file */
	yuvFile = fopen(yuvFileName, "wb");
	if (yuvFile == NULL)
	{
		printf("cannot find yuv file\n");
		exit(1);
	}
	else
	{
		printf("The output yuv file is %s\n", yuvFileName);
	}

	for(j = 1 ;j<6;j++)
{
	bmpFileName = argv[j];
	/* open the bmp file */
	
	bmpFile = fopen(bmpFileName, "rb");
	if (bmpFile == NULL)
	{
		printf("cannot find bmp file\n");
		exit(1);
	}
	else
	{
		printf("The input bmp file is %s\n", bmpFileName);
	}
	//读取位图文件头
	 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);
}
else
{	
	a =(char *)&File_header.bfType; 
	printf("this is a %s\n",a);
	//printf("this is a %x\n",file_h.bfType);
}
  //读取信息头
 if(fread(&Info_header,sizeof(BITMAPINFOHEADER),1,bmpFile) != 1)
	{
		printf("read info header error!\n\n");
		return false;
	}
	//	end read header
	 frameWidth = Info_header.biWidth;			
	 frameHeight = Info_header.biHeight;
	 size = frameWidth * frameHeight;
	//开辟缓冲区
	rgbBuf = (u_int8_t*)malloc(size * 3);
	yBuf = (u_int8_t*)malloc(size);
	uBuf = (u_int8_t*)malloc(size / 4);
	vBuf = (u_int8_t*)malloc(size / 4);
	//从BMP文件中读取RGB
	BMPreadRGB(bmpFile,File_header,Info_header,rgbBuf);
	//RGB转YUV
	   if(RGB2YUV(frameWidth, frameHeight, rgbBuf, yBuf, uBuf, vBuf, flip))
		{
			printf("error");
			return 0;
					}
	//write yuv file
	for(i = 0 ;i < k; i++)
	{
		fwrite(yBuf, 1, frameWidth * frameHeight, yuvFile);
		fwrite(uBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
		fwrite(vBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
			printf("\r...%d", ++videoFramesWritten);

	}
	printf("\n%u %ux%u video frames written\n", videoFramesWritten, frameWidth, frameHeight);
	/* cleanup */
	free(rgbBuf);
	free(yBuf);
	free(uBuf);
	free(vBuf);
	fclose(bmpFile);
}
	fclose(yuvFile);
    system("pause");
	return(0);
}
       主函数依次进行了输入输出文件的打开,bmp文件头读取,如果bftype不等于4d42则不是bmp文件,如果是bmp文件,接着读取信息头,获取图像的宽和高,为开辟缓冲区做准备,先调用BMPreadRGB函数写RGB缓冲区,再调用RGB2YUV函数获取YUV数据块,最后写200帧yuv文件。特别注意,最后必须在200帧图像数据写完之后才能关闭yuvfile,否则每一循环yuvfile中的数据都会被更新一次,得到的只有40帧图像。关于命令行参数的设置详细可见彩色空间转换实验报告。
BMPreadRGB
void BMPreadRGB(FILE * pFile, BITMAPFILEHEADER &file_h, BITMAPINFOHEADER &info_h,unsigned char * rgbDataOut )
{
       u_int32_t w, h,width,height;
       u_int Loop,i,j;
       unsigned char mask,*dataBuf,*Data;

    //判断像素的实际点阵数
    if ((info_h.biWidth / 8 * info_h.biBitCount) % 4) == 0
		w = info_h.biWidth;
	else
		w = (info_h.biWidth*info_h.biBitCount+31)/32*4;
	if ((info_h.biHeight%2) == 0)
		h = info_h.biHeight;
	else
		h = info_h.biHeight + 1;

	width = w/8*info_h.biBitCount;//width为实际一行的字节数
	height = h;//height为列数

//开辟实际字节数量的缓冲区,读数据,一次读取一个字节
 dataBuf = (unsigned char*)malloc(width*height);
 Data = (unsigned char*)malloc(width*height);

fseek(pFile, file_h.bfOffBits, 0);
if(fread(dataBuf, 1, width*height, pFile) == 0)
	{
		printf("read file error!\n\n");
		exit(0);
	}
//倒序存放
for ( i = 0;i < height;i ++)
		for ( j = 0;j < width;j++)
		{
 			Data[i*width+j] = dataBuf[(height-i-1)*width+j];
		}

//根据不同像素位数执行不同操作
switch(info_h.biBitCount)
{
	case 24:
		memcpy(rgbDataOut,Data,height*width);//真彩色图直接取像素数据写rgb缓冲区
		if(dataBuf)
			free(dataBuf);
		if(Data)
			free(Data);
		return ;
	case 16://位与移位取像素数据转换为8bit的彩色分量
		if(info_h.biCompression == BI_RGB)
		{
			for (Loop = 0;Loop < height * width;Loop +=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;
			}
		}
		if(dataBuf)
			free(dataBuf);
		if(Data)
			free(Data);
		return ;
       default://对于小于等于8bit的bmp文件,首先需要位与移位取像素数据,查调色板,写RGB缓冲区
			   RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow(float(2),info_h.biBitCount));
			 if(!MakePalette(pFile,file_h,info_h,pRGB))
	              printf("No palette!");
             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)
			{
				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;
                                 //8bit图像直接读取下一字节
				if(info_h.biBitCount == 8)
					mask =0;
				else
					mask >>= info_h.biBitCount;
				rgbDataOut+=3;
				shiftCnt ++;
			}
		
    }   
	    if(dataBuf)
			free(dataBuf);
		if(Data)
			free(Data);
		if(pRGB)
		  free(pRGB);
			return ;

   }
}
MakePalette
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(float(2),info_h.biBitCount))
	{
		fseek(pFile,sizeof(BITMAPFILEHEADER)+info_h.biSize,0);
		fread(pRGB_out,sizeof(RGBQUAD),(unsigned int)pow(float(2),info_h.biBitCount),pFile);
		return true;
	}
	else
		return false;
}
RGB2YUV函数
float RGBYUV02990[256],RGBYUV05870[256],RGBYUV01140[256];
float RGBYUV01684[256],RGBYUV03316[256];
float RGBYUV04187[256],RGBYUV00813[256];

void initLookupTable();

int RGB2YUV (int x_dim, int y_dim, void *bmp, void *y_out, void *u_out, void *v_out, int flip)
{
	static int init_done = 0;

	long i, j, size;
	unsigned char *r, *g, *b;
	unsigned char *y, *u, *v;
	unsigned char *pu1, *pu2, *pv1, *pv2, *psu, *psv;
	unsigned char *y_buffer, *u_buffer, *v_buffer;
	unsigned char *sub_u_buf, *sub_v_buf;
	float tmpy,tmpu,tmpv;

	if (init_done == 0)
	{
		InitLookupTable();
		init_done = 1;
	}

	// check to see if x_dim and y_dim are divisible by 2
	if ((x_dim % 2) || (y_dim % 2)) return 1;
	size = x_dim * y_dim;

	// allocate memory
	y_buffer = (unsigned char *)y_out;
	sub_u_buf = (unsigned char *)u_out;
	sub_v_buf = (unsigned char *)v_out;
	u_buffer = (unsigned char *)malloc(size * sizeof(unsigned char));
	v_buffer = (unsigned char *)malloc(size * sizeof(unsigned char));
	if (!(u_buffer && v_buffer))
	{
		if (u_buffer) free(u_buffer);
		if (v_buffer) free(v_buffer);
		return 2;
	}

	b = (unsigned char *)bmp;
	y = y_buffer;
	u = u_buffer;
	v = v_buffer;

	// convert RGB to YUV
	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 ++;
			}
		}
	} else {
		for (i = 0; i < size; i++)
		{
			g = b + 1;
			r = b + 2;
				
			tmpy = RGBYUV02990[*r] + RGBYUV05870[*g] + RGBYUV01140[*b];
			if(tmpy>235)
			{
			 tmpy=235;
			}
			if(tmpy<16)
			{
			 tmpy=16;
			}
			*y = (unsigned char)tmpy;
			tmpu = - RGBYUV01684[*r] - RGBYUV03316[*g] + (*b)/2          + 128;
			if(tmpu>240)
			{
			 tmpu=240;
			}
			if(tmpu<16)
			{
			 tmpu=16;
			}
			*u = (unsigned char)tmpu;
			tmpv =  (*r)/2          - RGBYUV04187[*g] - RGBYUV00813[*b] + 128;
			if(tmpv>240)
			{
			 tmpv=240;
			}
			if(tmpv<16)
			{
			 tmpv=16;
			}
			*v = (unsigned char)tmpv;
			b += 3;
			y ++;
			u ++;
			v ++;
		}
	}

	// subsample UV
	for (j = 0; j < y_dim/2; j ++)
	{
		psu = sub_u_buf + j * x_dim / 2;
		psv = sub_v_buf + j * x_dim / 2;
		pu1 = u_buffer + 2 * j * x_dim;
		pu2 = u_buffer + (2 * j + 1) * x_dim;
		pv1 = v_buffer + 2 * j * x_dim;
		pv2 = v_buffer + (2 * j + 1) * x_dim;
		for (i = 0; i < x_dim/2; i ++)
		{
			*psu = (*pu1 + *(pu1+1) + *pu2 + *(pu2+1)) / 4;
			*psv = (*pv1 + *(pv1+1) + *pv2 + *(pv2+1)) / 4;
			psu ++;
			psv ++;
			pu1 += 2;
			pu2 += 2;
			pv1 += 2;
			pv2 += 2;
		}
	}
	free(u_buffer);
	free(v_buffer);
	return 0;
}
void InitLookupTable()
{
	int i;
	for (i = 0; i < 256; i++) RGBYUV02990[i] = (float)0.2990 * i;
	for (i = 0; i < 256; i++) RGBYUV05870[i] = (float)0.5870 * i;
	for (i = 0; i < 256; i++) RGBYUV01140[i] = (float)0.1140 * i;
	for (i = 0; i < 256; i++) RGBYUV01684[i] = (float)0.1684 * i;
	for (i = 0; i < 256; i++) RGBYUV03316[i] = (float)0.3316 * i;
	for (i = 0; i < 256; i++) RGBYUV04187[i] = (float)0.4187 * i;
	for (i = 0; i < 256; i++) RGBYUV00813[i] = (float)0.0813 * i;
}

4.实验结果

原始图片

       lena_1bit                             lena_4bit                             lena_8bit                      lena_16bit                         lena_24bit



用YUVPlayer打开1bit至24bit的lena.bmp生成的200帧yuv文件,与初始的bmp文件进行对比,图像正常显示。                                                                      


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值