第四周 实验 bmp转YUV文件

小磊z

关注

阅读 30

2022-04-19

一、图片格式分析

(1)BMP

BMP文件格式:Bitmap位图。

文件数据分为四个部分:

bmp文件头(bmp file header):提供文件的格式、大小等信息。    14byte

位图信息头(bitmap information):提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息。 40 byte

调色板(color palette):可选,如使用索引来表示图像,调色板就是索引与其对应颜色的映射表。

位图数据(bitmap data):实际数据。

具体展开:

文件头14byte:

前两个字节分别为'B'、'M'。 3、4、5、6字节表示文件尺寸; 7,8字节表示保留字1 ; 9、10字节表示 保留字2 ; 11、12、13、14字节表示 位图数据部分相对于文件首的起始偏移量。

因为定义了起始偏移量,因此理论上,可以在调色板之后和数据部分之前任意填充。

图像参数信息:

15、16、17、18字节 , 表示当前结构体的大小, 40字节或 56字节。

19、20、21、22 字节, 表示图像宽度(单位像素)

23、24、25、26字节,表示图像高度(单位像素)

27、28字节,永远是1

29、30字节,表示每像素占用的位数,即bpp

31、32、33、34  字节表示压缩方式,

0 —— RGB方式
1 —— 8bpp的run-length-encoding方式
2 —— 4bpp的run-length-encoding方式
3 —— bit-fields方式

35、36、37、38 表示图像的尺寸(字节数)

39、40、41、42字节表示水平分辨率;

43、44、45、46字节表示垂直分辨率。

47、48、49、50表示引用色彩数,若为0,表示颜色数为2的每像素位数次方。

51、52、53、54表示关键色彩数。若为0,表示都很重要。

调色板:

当bpp小于等于8,BMP使用调色板记录色彩信息。调色板存在时,图像数据块中存储的只是各个像素的色彩在调色板中的索引值,必须通过在调色板中查表,才能获知各个像素的真实信息。

bpp=1,调色板中只有两个色彩值。

bpp=4或者8,可使用的色彩数为16种或256种,但是实际采用取决于关键色彩数。

当需要使用的色彩超过256种,bpp》=16;此时不再使用调色板。

采用RGB或者Bit-Fields;

16 bpp-RGB:16bits种,低5位标识Green分量;中5位为Green分量;高5位为Red分量,最后1位无意义。顺序从低到高为B-G-R。

24 bpp-RGB:真彩位图,8 8 8 B-G-R。

32 bpp-RGB:8 8 8 8 ,最高8位标识透明度Alpha分量。

BF后续再细致了解。

图像数据块

注意的是BMP原点是左下角,但现在的图像基本是以图像左上角为原点存储数据,如果按照现在的方式读取,在崔志方向相反。

在读取BMP位图数据时,应该把第一行位图数据放在存储矩阵的最后一行。

(2)YUV

YUV格式有两大类:Plane和Packed;

Plane:先连续存储所有的像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。

Packed:每个像素点的YUV是连续交错存储的。

存储格式:

YUVY格式、UYVY格式、YUV422P格式(Plane模式)、YV12,YU12格式(即YUV420P格式)、NV12,NV21格式(Y和UV分为两个Plane,UV交错存储)

二、系统整体设计

实验用图片:

这里图片全部为1280像素*720像素,不足部分做了补足。

主题为“我喜欢的事物”

来源网图,侵删

整体思路:

各部分通过结构体去定义,先读取bmp文件头,输出bmp文件的相关信息;

然后读取调色板、信息头等信息,判断bmp文件的位图数据读取方式,然后读取、转换成YUV格式。

三、具体实现

bmp.h;

#include<iostream>
#include "stdio.h"
#include<windows.h>
#pragma pack(2)

int bmpwidth;
int bmpheight;
int bbp; 
int len;

typedef struct bmpHeader{
	WORD Type;				//文件类型 
	DWORD Size;				//文件大小,字节单位 
	WORD Reservered1;		//保留字,设置为0 
	WORD Reservered2;		//保留字,设置为0 
	DWORD Offbyte;			//偏移量,至位图数据 
}Header; 

typedef struct bmpInf{
	DWORD Size;				//当前 位图信息头 字节数 一般为40或者56 
	DWORD bmpwidth;			//图像的宽度,单位:像素 
	DWORD bmpheight;		//图像的高度,单位:像素 
	WORD Reservered;		//位面数,比如为1 
	WORD bbp;				//每像素占用的位数 
	DWORD Saveway;			//压缩方式 
	DWORD ImageSize;		//图像尺寸。字节数 
	LONG Xpels;				//水平分辨率 
	LONG Ypels;				//垂直分辨率 
	DWORD ClourUsed;		//引用色彩数 
	DWORD ClourCounted;		//关键色彩数 
}Inf; 

typedef struct bmpRGB{
	BYTE rgbBlue;
	BYTE rgbGreen;
	BYTE rgbRed;
	BYTE rgbReserved;
}RGB;

typedef struct bmpData{
	BYTE red;
	BYTE green;
	BYTE blue;
}Data;

bmptoyuv.h 

#include"bmp.h"
using namespace std;
int bmptoyuv(int frame,char inputFile[],int n)
{
	FILE *fout = fopen("I LIKE.yuv","ab+");
	if(fopen==NULL) 
	{
		cout<<"读取失败"<<endl; 
		return 0;
	}
	else 
		cout<<"读取成功"<<endl;
	FILE *f = fopen(inputFile,"rb+"); 
	if(f==NULL) 
	{
		cout<<"读取失败"<<endl; 
		return 0;
	}
	else 
	cout<<"读取成功"<<endl; 
	Header bmpheader;
	fread(&bmpheader,sizeof(bmpHeader),1,f);
	if(bmpheader.Type!=19778)//注意:此处M在高位,B在低位,转换后为 01001101 01000010,即19778; 
	{
		cout<<"不是bmp文件"<<endl; 
		return 0;
	}
	else 
		cout<<"为BMP文件,文件的大小为"<<float(bmpheader.Size)/1024/1024<<"Mb"<<endl; 
		
	Inf bmpinf;
	fread(&bmpinf,sizeof(Inf),1,f);     //读取信息头 
	bbp=bmpinf.bbp;
	bmpwidth=bmpinf.bmpwidth;
	bmpheight=bmpinf.bmpheight; 
	
	unsigned char *output_buffer=(unsigned char *)malloc(bmpwidth*bmpheight*3/2);
	if(bbp>=16)
	{
		cout<<"无调色板,直接读取"<<endl;
		if(bbp==16)
		{
			int len=bmpwidth*bmpheight*2;    //2bit
			unsigned char *f_buffer=(unsigned char *)malloc(len);
			fread(f_buffer,sizeof(unsigned char),len,f);
			for(int j=0;j<bmpheight;j++)
			{
				int height_real=bmpheight-j-1;
				int R,G,B;
				for(int i=0;i<bmpwidth;i++)
				{
					B = *(f_buffer+(bmpwidth*j+i)*2)>>3;   //前5位为B 
					G = (*(f_buffer+(bmpwidth*j+i)*2)<<5>>2)|(*(f_buffer+(bmpwidth*j+i)*2+1)>>6);  //中5位为G 
					R = *(f_buffer+(bmpwidth*j+i)*2+1)>>1<<3;  //后5位为R 
					int Y = int(0.299*R+0.587*G+0.114*B);
					*(output_buffer+bmpwidth*height_real+i) = Y;
					
					if(height_real%2==0&&i%2==0)
					{
						int U,V; 
						U = int(-0.1684*R-0.3316*G+0.5*B+128);
						V = int(0.5*R-0.4187*G-0.0813*B+128);
						*(output_buffer+bmpwidth*bmpheight+bmpwidth/2*height_real/2+i/2) = U;
						*(output_buffer+bmpwidth*bmpheight*5/4+bmpwidth/2*height_real/2+i/2) = V;
					}
					}
				}
			}
		if(bbp==24)   //24位时 
		{
			int len=bmpwidth*bmpheight*3;
			unsigned char *f_buffer=(unsigned char *)malloc(len);
			fread(f_buffer,sizeof(unsigned char),len,f);
			for(int j=0;j<bmpheight;j++)
			{
				int height_real=bmpheight-j-1;
				int R,G,B;
				for(int i=0;i<bmpwidth;i++)
				{
					B = *(f_buffer+(bmpwidth*j+i)*3);
					G = *(f_buffer+(bmpwidth*j+i)*3+1);
					R = *(f_buffer+(bmpwidth*j+i)*3+2);
					int Y = int(0.299*R+0.587*G+0.114*B);
					*(output_buffer+bmpwidth*height_real+i) = Y;
						
					if(height_real%2==0&&i%2==0)
					{
						int U,V; 
						U = int(-0.1684*R-0.3316*G+0.5*B+128);
						V = int(0.5*R-0.4187*G-0.0813*B+128);
						*(output_buffer+bmpwidth*bmpheight+bmpwidth/2*height_real/2+i/2) = U;
						*(output_buffer+bmpwidth*bmpheight*5/4+bmpwidth/2*height_real/2+i/2) = V;
					}
					}
				}
			}
		if(bbp==32)  //32位时 
		{
			int len=bmpwidth*bmpheight*4;
			unsigned char *f_buffer=(unsigned char *)malloc(len);
			fread(f_buffer,sizeof(unsigned char),len,f);
			for(int j=0;j<bmpheight;j++)
			{
				int height_real=bmpheight-j-1;
				int R,G,B;
				for(int i=0;i<bmpwidth;i++)
				{
					B = *(f_buffer+(bmpwidth*j+i)*4);
					G = *(f_buffer+(bmpwidth*j+i)*4+1);
					R = *(f_buffer+(bmpwidth*j+i)*4+2);
					int Y = int(0.299*R+0.587*G+0.114*B);
					*(output_buffer+bmpwidth*height_real+i) = Y;
						
					if(height_real%2==0&&i%2==0)
					{
						int U,V; 
						U = int(-0.1684*R-0.3316*G+0.5*B+128);
						V = int(0.5*R-0.4187*G-0.0813*B+128);
						*(output_buffer+bmpwidth*bmpheight+bmpwidth/2*height_real/2+i/2) = U;
						*(output_buffer+bmpwidth*bmpheight*5/4+bmpwidth/2*height_real/2+i/2) = V;
					}
					}
				}
			}
		}
		else 
		{
			cout<<"有调色板,读取调色板"<<endl;
			RGB rgb[bmpinf.ClourUsed];
			for(int k=0;k<bmpinf.ClourUsed;k++)
			{
				fread(&rgb[k],4,1,f);
			}
			int len=bmpwidth*bmpheight;
			unsigned char *f_buffer=(unsigned char *)malloc(len);
			fread(f_buffer,sizeof(unsigned char),len,f);
			for(int j=0;j<bmpheight;j++)
			{
				int height_real=bmpheight-j-1;
				int R,G,B;
				for(int i=0;i<bmpwidth;i++)
				{
					B = rgb[*(f_buffer+(bmpwidth*j+i))].rgbBlue;
					G = rgb[*(f_buffer+(bmpwidth*j+i))].rgbGreen;
					R = rgb[*(f_buffer+(bmpwidth*j+i))].rgbRed;
					int Y = int(0.299*R+0.587*G+0.114*B);
					*(output_buffer+bmpwidth*height_real+i) = Y;
						
					if(height_real%2==0&&i%2==0)
					{
						int U,V; 
						U = int(-0.1684*R-0.3316*G+0.5*B+128);
						V = int(0.5*R-0.4187*G-0.0813*B+128);
						*(output_buffer+bmpwidth*bmpheight+bmpwidth/2*height_real/2+i/2) = U;
						*(output_buffer+bmpwidth*bmpheight*5/4+bmpwidth/2*height_real/2+i/2) = V;
					}
				}
			}
		}
		for(int i=0;i<frame;i++)
			fwrite(output_buffer,1,bmpheight*bmpwidth*3/2,fout);
		fclose(f); 
		fclose(fout);
		return 0;
} 

main.cpp 

#include"bmptoyuv.h"
#include<stdlib.h>
int main(int argc, char* argv[]) {
	FILE *fout = fopen("I LIKE.yuv","wb+");
	for(int i=2;i<=argc;i++)
	{
		cout<<argv[i]<<endl;
		int Y=bmptoyuv(atoi(argv[1]),argv[i],1);
    	if(Y!=0)
		{
		    cout<<"输出错误"<<endl; 
			return 0;
		}
	}

	return 0;
}

四、过程中所遇和解决问题

(1)结构体默认对齐方式

现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址是某个数k(通常它为4或者8)的倍数,这就是所谓的内存对齐。

而对于结构体中的各个成员,第一个成员位于偏移为0的位置,以后的每个数据成员的偏移必须是min(#pragama pack)制定的数的倍数。

64位操作系统上64位编译器:默认8字节对齐。

64位操作系统上32位编译器:默认8字节对齐;

32位操作系统上32位编译器:默认4字节对齐。

解决方式:#pragama pack()设定。

(2)调色板的进一步理解

以3bit为例,24位。

此时,R\G\B对应的色域空间为256*256*256;当图像所需色域空间不大时活可以提供的位数不足时。

此时就以调色板作为一个索引表。

可以将调色板想象成一个数组,每个调色板的大小为4字节,按蓝、绿、红存储一个颜色值。

此时图像数据块的一个字节对应一个调色板中的4字节,即一个颜色。

(3)提出问题:YUV文件并没有文件头之类,是如何识别是YUV文件的,仅仅是后缀?

像之前分析过的Tiff, RIFF、AVI、WAV等,都通过文件头或者FOURCC等来描述图片或者视频的信息。

那我们从何处getYUV文件的信息?

是444还是420等等?

五、最终呈现

 

 

精彩评论(0)

0 0 举报