一、图片格式分析
(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等等?
五、最终呈现