bmp 转YUV420数据组 C语言实现

需求:需要将BMP中的数据转换成YUV420之后,再输出到数组中去;


一、BMP图像数据格式详解

    1.1 简介
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作扩展名)。
 
  1.2.BMP格式结构
BMP文件的数据按照从文件头开始的先后顺序分为四个部分:
◆ 位图文件头(bmp file header):  提供文件的格式、大小等信息
◆ 位图信息头(bitmap information):提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息
◆ 调色板(color palette):可选,如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表
◆ 位图数据(bitmap data):图像数据区
 
BMP图片文件数据表如下:

 

数据段名称 大小(byte) 开始地址 结束地址
位图文件头(bitmap-file header)
14 0000h 000Dh
位图信息头(bitmap-information header)
40 000Eh 0035h
调色板(color table)
由biBitCount决定 0036h 未知
图片点阵数据(bitmap data)
由图片大小和颜色定 未知 未知

 

 
  1.3.BMP文件头
BMP文件头结构体定义如下:
typedef struct tagBITMAPFILEHEADER
{ 
UINT16 bfType;        //2Bytes,必须为"BM",即0x424D 才是Windows位图文件
DWORD bfSize;         //4Bytes,整个BMP文件的大小
UINT16 bfReserved1;  //2Bytes,保留,为0
UINT16 bfReserved2;  //2Bytes,保留,为0
DWORD bfOffBits;     //4Bytes,文件起始位置到图像像素数据的字节偏移量
} BITMAPFILEHEADER;


 
BMP文件头数据表如下:

 

变量名 地址偏移 大小 作用说明
bfType 0000h 2Bytes
文件标识符,必须为"BM",即0x424D 才是Windows位图文件
‘BM’:Windows 3.1x, 95, NT,…  ‘BA’:OS/2 Bitmap Array  ‘CI’:OS/2 Color Icon   
‘CP’:OS/2 Color Pointer   ‘IC’:OS/2 Icon   
‘PT’:OS/2 Pointer
因为OS/2系统并没有被普及开,所以在编程时,你只需判断第一个标识“BM”就行
bfSize 0002h 4Bytes 整个BMP文件的大小(以位B为单位)
bfReserved1 0006h 2Bytes 保留,必须设置为0
bfReserved2 0008h 2Bytes 保留,必须设置为0
bfOffBits 000Ah 4Bytes 说明从文件头0000h开始到图像像素数据的字节偏移量(以字节Bytes为单位),以为位图的调色板长度根据位图格式不同而变化,可以用这个偏移量快速从文件中读取图像数据

 
  1.4.BMP信息头
BMP信息头结构体定义如下:
typedef struct _tagBMP_INFOHEADER
{
DWORD  biSize;    //4Bytes,INFOHEADER结构体大小,存在其他版本I NFOHEADER,用作区分
LONG   biWidth;    //4Bytes,图像宽度(以像素为单位)
LONG   biHeight;    //4Bytes,图像高度,+:图像存储顺序为Bottom2Top,-:Top2Bottom
WORD   biPlanes;    //2Bytes,图像数据平面,BMP存储RGB数据,因此总为1
WORD   biBitCount;         //2Bytes,图像像素位数
DWORD  biCompression;     //4Bytes,0:不压缩,1:RLE8,2:RLE4
DWORD  biSizeImage;       //4Bytes,4字节对齐的图像数据大小
LONG   biXPelsPerMeter;   //4 Bytes,用象素/米表示的水平分辨率
LONG   biYPelsPerMeter;   //4 Bytes,用象素/米表示的垂直分辨率
DWORD  biClrUsed;          //4 Bytes,实际使用的调色板索引数,0:使用所有的调色板索引
DWORD biClrImportant;     //4 Bytes,重要的调色板索引数,0:所有的调色板索引都重要
}BMP_INFOHEADER;


 
BMP信息头数据表如下:

 

变量名
地址偏移
大小
作用说明
biSize
000Eh
4Bytes
BNP信息头即BMP_INFOHEADER结构体所需要的字节数(以字节为单位)
biWidth
0012h
4Bytes
说明图像的宽度(以像素为单位)
biHeight
0016h
4Bytes
说明图像的高度(以像素为单位)。这个值还有一个用处,指明图像是正向的位图还是倒向的位图,该值是正数说明图像是倒向的即图像存储是由下到上;该值是负数说明图像是倒向的即图像存储是由上到下。大多数BMP位图是倒向的位图,所以此值是正值。
biPlanes
001Ah
2Bytes
为目标设备说明位面数,其值总设置为1
biBitCount
001Ch
2Bytes
说明一个像素点占几位(以比特位/像素位单位),其值可为1,4,8,16,24或32
biCompression
001Eh
4Bytes
说明图像数据的压缩类型,取值范围为:
0         BI_RGB 不压缩(最常用)
1         BI_RLE8 8比特游程编码(BLE),只用于8位位图
2         BI_RLE4 4比特游程编码(BLE),只用于4位位图
3         BI_BITFIELDS比特域(BLE),只用于16/32位位图
4          
biSizeImage
0022h
4Bytes
说明图像的大小,以字节为单位。当用BI_RGB格式时,总设置为0
biXPelsPerMeter
0026h
4Bytes
说明水平分辨率,用像素/米表示,有符号整数
biYPelsPerMeter
002Ah
4Bytes
说明垂直分辨率,用像素/米表示,有符号整数
biClrUsed 002Eh 4Bytes 说明位图实际使用的调色板索引数,0:使用所有的调色板索引
biClrImportant 0032h 4Bytes 说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。

 

 
1.5.BMP调色板
BMP调色板结构体定义如下:
typedef struct _tagRGBQUAD
{
BYTE  rgbBlue;       //指定蓝色强度
BYTE  rgbGreen;      //指定绿色强度
BYTE  rgbRed;        //指定红色强度
 BYTE  rgbReserved;  //保留,设置为0
} RGBQUAD;

1,4,8位图像才会使用调色板数据,16,24,32位图像不需要调色板数据,即调色板最多只需要256项(索引0 - 255)。
颜色表的大小根据所使用的颜色模式而定:2色图像为8字节;16色图像位64字节;256色图像为1024字节。其中,每4字节表示一种颜色,并以B(蓝色)、G(绿色)、R(红色)、alpha(32位位图的透明度值,一般不需要)。即首先4字节表示颜色号1的颜色,接下来表示颜色号2的颜色,依此类推。
颜色表中RGBQUAD结构数据的个数有biBitCount来确定,当biBitCount=1,4,8时,分别有2,16,256个表项。
当biBitCount=1时,为2色图像,BMP位图中有2个数据结构RGBQUAD,一个调色板占用4字节数据,所以2色图像的调色板长度为2*4为8字节。
当biBitCount=4时,为16色图像,BMP位图中有16个数据结构RGBQUAD,一个调色板占用4字节数据,所以16像的调色板长度为16*4为64字节。
当biBitCount=8时,为256色图像,BMP位图中有256个数据结构RGBQUAD,一个调色板占用4字节数据,所以256色图像的调色板长度为256*4为1024字节。
当biBitCount=16,24或32时,没有颜色表。
 
1.6.BMP图像数据区
位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数:
当biBitCount=1时,8个像素占1个字节;
当biBitCount=4时,2个像素占1个字节;
当biBitCount=8时,1个像素占1个字节;
当biBitCount=24时,1个像素占3个字节;
Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充,
一个扫描行所占的字节数计算方法:
DataSizePerLine= (biWidth* biBitCount+31)/8;
// 一个扫描行所占的字节数
DataSizePerLine= DataSizePerLine/4*4; // 字节数必须是4的倍数
位图数据的大小(不压缩情况下):
DataSize= DataSizePerLine* biHeight;
 
颜色表接下来位为位图文件的图像数据区,在此部分记录着每点像素对应的颜色号,其记录方式也随颜色模式而定,既2色图像每点占1位(8位为1字节);16色图像每点占4位(半字节);256色图像每点占8位(1字节);真彩色图像每点占24位(3字节)。所以,整个数据区的大小也会随之变化。究其规律而言,可的出如下计算公式:图像数据信息大小=(图像宽度*图像高度*记录像素的位数)/8。
(注:以上信息采集自互联网)

二、RGB 转换YUV公式
是网上找的:
Y =  0.299*R + 0.587*G + 0.114*B;
U = -0.169*R - 0.331*G + 0.5  *B + 128;     注: +128 的含义是让UV的范围处于整数区间(0-255)

V =  0.5  *R - 0.419*G - 0.081*B + 128;


三,BMP转YUV420

       程序参考:

http://blog.csdn.net/vacu_um/article/details/68572557?reload

这部分代码中,大量为直接转载,博主为:vacu_um

            

           2.1 读取bmp位图

static void ReadRGB(FILE *pFile, BITMAPFILEHEADER *file_h, 
						BITMAPINFOHEADER *info_h, unsigned char *rgbDataOut)  
{  
    unsigned long Loop, iLoop, jLoop, width, height, w, h;  
    unsigned char mask, *Index_Data, *Data;  
    int shiftCnt; 
	unsigned char index ;


	
	//保证是图像大小是4字节的整数倍,具体理由见下面的注释  
    if ((info_h->biWidth% 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;  
   
    //若是24位,则bmp中有效数据大小是长*宽*3字节  
    //若是16位,则bmp中有效数据大小是长*宽*2字节  
    //若是8位,则bmp中有效数据大小是长*宽字节  
    //若是4位,则bmp中有效数据大小是长*宽/2字节  
    //若是2位,则bmp中有效数据大小是长*宽/4字节  
    //若是1位,则bmp中有效数据大小是长*宽/8字节(这大概是为什么bmp图像的长必须是4的倍数,宽必须是2的倍数的原因吧。。。)  
    width = (w/ 8)* info_h->biBitCount;  
    height = h;  
   
    //倒序前数据缓存区  
    Index_Data = (unsigned char*)malloc(height*width);//buffer大小应该与bmp中有效数据大小相同 
    
    //倒序后数据缓存区,用于存放bmp中的有效数  
    Data = (unsigned char*)malloc(height*width);//buffer大小应该与bmp中有效数据大小相同  
   
    //文件指针定位到有效数据起始处,读取有效数据  
    fseek(pFile,file_h->bfOffBits,0);  
   	printf("file_h->bfOffBits = %d \n",file_h->bfOffBits);
    if(fread(Index_Data, height*width, 1, pFile) > height*width)  
    {  
        printf("readfile error!");  
        exit(0);  
    }  

    //倒序存放  
    for (iLoop= 0; iLoop < height; iLoop++)
    {
	    for (jLoop= 0; jLoop < width; jLoop++)  
	    {  
	        Data[iLoop*width + jLoop] =Index_Data[(height - iLoop - 1)*width  
	   
	            + jLoop];  
			
	    } 
	
    }
	
	
    //24位:直接把数据复制给输出缓存区  
    if (info_h->biBitCount== 24)  
    {  
        memcpy(rgbDataOut,Index_Data, height*width);  
        free(Index_Data);  
        free(Data);  
        return;  
    }  
   
    //非24位:解码生成rgb,需要调色板信息  
   
    //生成调色板数组,数组的下标,对应bmp文件中有效数据,通过下标对应查找,便可得到该数据对应的颜色  
    //debug by LiuDong:(unsignedlong long)pow(),pow前的强制类型转换不能转换成unsignedint、unsignedchar等  
    //因为pow((float)2, info_h.biBitCount)最大值是2^24,unsigned char最大能表示255,unsigned int最大能表示2^32  
    RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow((float)2,info_h->biBitCount));  
    /*一个单元代表一种颜色 
    调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。 
    数组中每个元素的类型是一个RGBQUAD结构。真彩色无调色板部分。 
    biBitCount;位数/像素,1、2、4、8、24.假如是16位,R占5位,G占5位,B占5位,空出一位,排列组合一共有2^15种颜色,其他位数同理。*/  
   
    //读取位图调色板数据  
    if(!MakePalette(pFile, file_h,info_h,pRGB))  
        printf("Nopalette!");  
   
    //16位:移位操作,从2字节中取出RGB信息,存到3字节中  
    if (info_h->biBitCount== 16)  
    {  
        for(Loop = 0; Loop < height * width; Loop += 2)  
        {  
            *rgbDataOut= (Data[Loop] & 0x1F) << 3;//B:用0001 1111取出低字节的右五位,再放到目标字节的高5位(通过右移3位),得到五位的B  
            *(rgbDataOut+ 1) = ((Data[Loop] & 0xE0) >> 2) + ((Data[Loop + 1] & 0x03)<< 6);//G:11100000取出低字节的左三位,00000011取出高字节的右两位,合并后,放到再放到目标字节的高5位,得到五位的G  
            *(rgbDataOut+ 2) = (Data[Loop + 1] & 0x7C) << 1; //R:0111 1100取出高字节的中间五位,再放到目标字节的高5位,得到5位的R  
   
            rgbDataOut+= 3;  
        }//RGB都各自位于字节的高5位  
    }  
   
    //1~8位:移位操作,从有限固定位中取出RGB信息,存到3字节中  
    //循环次数:有效数据字节数  
    for (Loop =0; LoopbiBitCount)  
        {  
        case 1://1000 0000,1位,黑白双色图  
            mask = 0x80;  
            break;  
        case 2://1100 0000,2位,4色图  
            mask = 0xC0;  
            break;  
        case 4://1111 0000,4位,16色图  
            mask = 0xF0;  
            break;  
        case 8://1000 0000,8位,256色图  
            mask = 0xFF;  
        }  
   
        shiftCnt = 1;//控制mask的移位,决定取字节中哪些数据  
   
        while(mask)//循环一次就是一个字节的解析过程  
        {  
            index = mask == 0xFF ? Data[Loop] : ((Data[Loop] & mask) >> (8 -shiftCnt * info_h->biBitCount));  
            //Loop代表第几个带转换的原始有效数据  
            //pRGB代表调色板  
            //8位:mask=1111 1111,index=data[Loop],即index为bmp中原始有效数据,直接对应调色板数组下标,得到相应颜色  
   
            //查找调色板,取出对应BGR存入目标buffer:rgbDataOut  
            *rgbDataOut= pRGB[index].rgbBlue;//B  
            *(rgbDataOut+ 1) = pRGB[index].rgbGreen;//G  
            *(rgbDataOut+ 2) = pRGB[index].rgbRed;//R  
   
            if(info_h->biBitCount== 8)  
                mask = 0;//如果是8位bmp,一次性取完一个字节的颜色数据,直接跳出循环即可  
            else  
                mask >>= info_h->biBitCount;//若是1位bmp,则一字节取8次数据;若是2位bmp,则一字节取4次数据;若是4位bmp,则一字节取2次数据  
            //debugby LiuDong  
            if(Loop == width*height - 1)  
            {  
                rgbDataOut= rgbDataOut + 3 - width*height*3;  
                break;  
            }  
            rgbDataOut+= 3;  
            shiftCnt++;  
        }  
    }  
    
    if(Index_Data)  
        free(Index_Data);  
    if (Data)  
        free(Data);  
    if (pRGB)  
        free(pRGB);  
}  

    2.2 RGB转YUV换函数

    
/ RGB数据转化为YUV数据   
// Ø  计算公式:Y=0.30R+0.59G+0.11B , U=0.493(B-Y) , V=0.877(R-Y) 
// 需要注意采用4:2:0格式,u的数据是y的数据的1/4,v的数据是y的数据的1/4,有一个下采样的过程 
static int RGB2YUV(unsigned long w,unsigned long h,unsigned char* rgbData, 
						unsigned char*y,unsigned char*u,unsigned char*v)  
{  
	unsigned char*ytemp = NULL;	
	unsigned char*utemp = NULL;	
	unsigned char*vtemp = NULL;	
	unsigned long i, nr, ng, nb, nSize;  
	unsigned long j;
	utemp = (unsigned char*)malloc(w*h);  
	vtemp = (unsigned char*)malloc(w*h);  
	printf(" RGB2YUV start \n");

	//对每个像素进行 rgb -> yuv的转换  
	for (i = 0,nSize = 0; nSize235)  
			y[i]= 235;	
	}  
	for (i = 0;i240)  
			u[i]= 240;	
		if(v[i]>240)  
			v[i]= 240;	
	}  

*/   
	if (utemp)	
		free(utemp);  
	if(vtemp)  
		free(vtemp);  
}



  四、程序流程

         bmp 转YUV420数据组 C语言实现_第1张图片

              4.1 程序流程代码:


    

int main(int argc, char *argv[])
{
	FILE *bmpFile = NULL;
	FILE *yuvFile = NULL;
	FILE *fp_dat = NULL; 
	char *file_namesp = NULL;
	char filename_bmp[50]={0};
	char filename_yuv[50]={0};
	char filename_dat[50]={0};
	unsigned char buff_head[14] = {0};
	int i;
	
	BITMAPFILEHEADER File_header;  
    BITMAPINFOHEADER Info_header;  
	int len = 0;
	unsigned long width, height;  
	
	if (argc != 2)
	{
		printf("usage: %s \r\n", argv[0]);
		return -1;
	}
	
	strcpy(filename_bmp,argv[1]);
	file_namesp = strstr(filename_bmp,".bmp");
	if(NULL == file_namesp)
	{
		printf("Please Use bmp \n");
		return -1;
	}
	else
	{	
		len = (file_namesp-filename_bmp);
		
		memcpy(filename_yuv,filename_bmp,len);
		memcpy(filename_dat,filename_bmp,len);
		
		memcpy(&filename_yuv[len],".yuv",4);
		memcpy(&filename_dat[len],".dat",4);
	}
	printf("%s  %s\n",filename_yuv,filename_dat);
	
	bmpFile= fopen(filename_bmp, "rb");
	if (bmpFile == NULL)
	{
		printf("open file(%s) failed\r\n", filename_bmp);
		return -1;
	}
	
	fseek(bmpFile, 0, SEEK_SET);
	
	//读取位图文件头
	if(fread(buff_head, 14, 1, bmpFile) > 14)  
	{  
		printf("read file header error!\n");	
		return -1;
	} 

	// 由于结构体4字节对齐问题,不能直接用结构体地址
	File_header.bfType = buff_head[0]|(buff_head[1]<<8);
	File_header.bfSize = buff_head[2]|(buff_head[3]<<8)|(buff_head[4]<<16)|(buff_head[5]<<24);
	File_header.bfOffBits = buff_head[10]|(buff_head[11]<<8)|(buff_head[12]<<16)|(buff_head[13]<<24);
	
	//判断文件类型  
	if(File_header.bfType != 0x4D42)  
	{  
	   printf("Notbmp file!\n");  
	   return -1;
	}  
	else  
	{  
	   printf("this is a bmp file\n");  
	}  
	  
	//读取位图信息头	
	if(fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) > sizeof(BITMAPINFOHEADER))  
	{  
	   printf("read info header error! \n");	
	  return -1;  
	}  
	
	//为了保证图像大小是4的倍数,每一行的像素数*每像素的位数=一行总位数	
	//一行总位数必须是32位的整数倍。	
	//为什么呢?这样只有才能保证一行数据量是4字节的整数倍。  
	//怎么做呢?(x+31)除以32,再下取整,得到结果以后,再乘以32,
	//就得到比原位数大且是32的倍数的数字,再除以8便得到字节数,肯定是4字节的整数倍。 
	if(Info_header.biWidth % 4 == 0)  
		width = Info_header.biWidth;  
	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;  

	InitLookupTable();
	
	//开辟缓冲区  
	rgbBuffer = (unsigned char*)malloc(height*width* 3);  
	yBuffer = (unsigned char*)malloc(height*width);  
	uBuffer = (unsigned char*)malloc(height*width/4);	
	vBuffer = (unsigned char*)malloc(height*width/4);	
	  
   //从bmp文件中读取rgb信息  
   ReadRGB(bmpFile, &File_header,&Info_header, rgbBuffer); 

   RGB2YUV(width, height, rgbBuffer,yBuffer, uBuffer, vBuffer);  
	  
   
   yuvFile = fopen(filename_yuv, "w");
   if (yuvFile == NULL)
   {
	   printf("open file(%s) failed\r\n", filename_yuv);
	   return -1;
   }   

   //将转好的yuv写到文件中  
   if(WriteYUV(yBuffer, uBuffer, vBuffer, width*height, yuvFile))  
	   printf("writeYUV file successful!\n");  
   else  
	   printf("writeYUV file failed!\n");  

	//  写入到dat文件;
	fp_dat = fopen(filename_dat, "w");
	if (fp_dat == NULL)
	{
		printf("open file(%s) failed\r\n", filename_dat);
		return -1;
	}
	
   	buffer_dat = malloc((width*height*3/2)*8);

   	WriteYUVtoDAT(yBuffer, uBuffer, vBuffer, width*height, fp_dat) ;

	fclose(fp_dat);
	fclose(yuvFile);
	fclose(bmpFile);

	if(NULL != buffer_dat)	
		free(buffer_dat);
	
	if(NULL != yBuffer)
		free(yBuffer);

	if(NULL != uBuffer)
   		free(uBuffer);

	if(NULL != vBuffer)
		free(vBuffer);

	if(NULL != rgbBuffer)
		free(rgbBuffer);
	
	return 0;
}

    4 .2  YUV文件存储函数

// .将YUV文件写入磁盘中   
char WriteYUV(unsigned char*Y,unsigned char*U,unsigned char*V,unsigned long size,FILE *outFile)	
{  
	if(fwrite(Y, 1, size,outFile)!= size)  
		return false;  
	
	if(fwrite(U, 1, size/ 4, outFile) != size/ 4)  
		return false;  
	
	if(fwrite(V, 1, size/ 4, outFile) != size/ 4)  
		return false;  
	
	return true;  
}


   4.3 YUV 转数组函数

// .将YUV数据写成数组
char WriteYUVtoDAT(unsigned char*Y,unsigned char*U,unsigned char*V,unsigned long size,FILE *datfile)	
{  
	unsigned long len = size;
	int i=0;
	int file_len=0;

// Y 分量
	for (i=0; i

   代码已经上传至CSDN下载:

         下载

你可能感兴趣的:(C语言)