您的位置:首页 > 其它

数据压缩实验二:bmp2yuv

2017-04-03 13:33 489 查看
一、实验原理

1.BMP文件的组成结构

BMP(全称 Bitmap)是 Windows 操作系统中的标准图像文件格式,可以分成两类:设备

相关位图(DDB)和设备无关位图(DIB),使用非常广。它采用位映射存储格式,除了图像

深度可选以外,在绝大多数应用中不采用其他任何压缩,因此,BMP 文件所占用的空间很大。

BMP 文件的图像深度可选 lbit、4bit、8bit、16bit 及 24bit。BMP 文件存储数据时,图像的扫

描方式是按从左到右、从下到上的顺序。由于 BMP 文件格式是 Windows 环境中交换与图有

关的数据的一种标准,因此在 Windows 环境中运行的图形图像软件都支持 BMP 图像格式。

典型的 BMP 图像文件由四部分组成:

(1)位图头文件数据结构

     它包含 BMP 图像文件的类型、显示内容等信息

位图文件头主要包括:

typedef struct tagBITMAPFILEHEADER

{

WORD bfType; /* 说明文件的类型 */

DWORD bfSize; /* 说明文件的大小,用字节为单位 */

WORD bfReserved1; /* 保留,设置为 0 */

WORD bfReserved2; /* 保留,设置为 0 */

DWORD bfOffBits; /* 说明从BITMAPFILEHEADER 结构开始到实际的图像数据之间的字节偏移量 */ } BITMAPFILEHEADER;

(2)位图信息数据结构

它包含有 BMP 图像的宽、高、压缩方法,以及定义颜色等信息

位图信息头结构体如下:

 typedef struct tagBITMAPINFOHEADER

 {

DWORD biSize; /* 说明结构体所需字节数 */

LONG biWidth; /* 以像素为单位说明图像的宽度 */

LONG biHeight; /* 以像素为单位说明图像的高速 */

WORD biPlanes; /* 说明位面数,必须为 1 */

WORD biBitCount; /* 说明位数/像素,1、2、4、8、24 */

DWORDbiCompression; /* 说明图像是否压缩及压缩类型 BI_RGB,BI_RLE8,BI_RLE4, BI_BITFIELDS */

DWORD biSizeImage; /* 以字节为单位说明图像大小,必须是 4 的整数倍*/

LONG biXPelsPerMeter; /*目标设备的水平分辨率,像素/米 */

  LONG biYPelsPerMeter; /*目标设备的垂直分辨率,像素/米 */

DWORDbiClrUsed; /* 说明图像实际用到的颜色数,如果为 0,则颜色数为 2 的 biBitCount 次方 */

DWORD biClrImportant; /*说明对图像显示有重要影响的颜色索引的数目,如果是 0,表示都重要。*/ } BITMAPINFOHEADER;

(3)调色板

         这个部分是可选的,调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed 和 biBitCount 字段。数组中每个元素的类型是一个 RGBQUAD 结构。真彩色无调色板部分。

调色板结构如下:

 typedef struct tagRGBQUAD

{

BYTE rgbBlue; /*指定蓝色分量*/

BYTE rgbGreen; /*指定绿色分量*/

BYTE rgbRed; /*指定红色分量*/

BYTE rgbReserved; /*保留,指定为 0*/ } RGBQUAD;

(4)实际位图数据

       这部分的内容根据 BMP 位图使用的位数不同而不同,在 24 位图中直接使用 RGB,而其他的小于 24 位的使用调色板中颜色索引值,图像数据就是该像素颜色在调色板中的索引值(逻辑色)。对于真彩色图,图像数据就是实际的 R、G、B

值。图像的每一扫描行由表示图像像素的连续的字节组成,每一行的字节数取决于图像的颜

色数目和用像素表示的图像宽度。规定每一扫描行的字节数必须是 4 的整倍数,也就是

DWORD 对齐的。扫描行是由底向上存储的,这就是说,阵列中的第一个字节表示位图左下

角的像素,而最后一个字节表示位图右上角的像素。

2.字节序

不同的计算机系统采用不同的字节序存储数据,同样一个 4 字节的 32 位整数,在内存中

存储的方式不同。字节序分为小尾字节序(Little Endian)和大尾字节序(Big Endian)。Intel 处理

器大多数使用小尾字节序,Motorola 处理器大多数使用大尾(Big Endian)字节序。

小尾就是低位字节排放在内存的低端,高位字节排放在内存的高端,即所谓的“低位在

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

在实现 BMP 文件头信息的写入时,需要注意整数保存时的字节序。例如:文件大小是以

Intel 序保存的。在编程前先用二进制打开方式观察BMP 文件各个部分的数据存储格式。

打开测试所用第一张24位图文件,以二进制打开观察如下图,可见,此bmp文件为小尾字节序(高位在后,低位在前):

二、实验流程

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

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

                              

3.调用RGB2YUV的函数实现RGB到YUV数据的转换(如实验一所示)

4.写YUV文件

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

三、关键代码及分析

Main.cpp

int main(int argc, char** argv)
{
/*变量定义 */
u_intframeWidth = 352
4000
; /*--width=<uint> */
u_intframeHeight = 240; /*--height=<uint> */
boolflip = FALSE; /*--flip 的值为0时,可倒序读入数据*/
unsignedint i;
BITMAPFILEHEADERFile_header;
BITMAPINFOHEADERInfo_header;
char*bmpFileName[9] = { NULL };
char*yuvFileName = NULL;
FILE*bmpFile[9] = { NULL };
FILE*yuvFile = NULL;
u_int8_t*bmpBuf = NULL;
unsignedchar * rgbout = NULL;
u_int8_t*yBuf = NULL;
u_int8_t*uBuf = NULL;
u_int8_t*vBuf = NULL;
u_int32_tvideoFramesWritten = 0;
//设置每一幅图像出现的帧数,共210帧
intN[9] = {15,15,15,15,30,30,30,30,30 };

//通过n值的设置实现多幅图像读入
for(int n = 1; n < 10; n++)
bmpFileName[n- 1] = argv
;
yuvFileName= argv[10];

/*打开 BMP 文件 */
for(int x = 0; x < 9; x++){
for(int m = 0; m < N[x]; m++){
bmpFile[x]=fopen(bmpFileName[x],"rb");
if(bmpFile[x] == NULL)
{
printf("cannotfind bmp file\n");
exit(1);
}
else
{
printf("Theinput bmp file is %s\n", bmpFileName[x]);
}
// 读取各位图文件头与信息头数据
if(fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile[x]) != 1)
{
printf("readfile header error!");
exit(0);
}
if(File_header.bfType != 0x4D42)
{
printf("Notbmp file!");
exit(0);
}
else
{
printf("thisis a %c%c\n", File_header.bfType);
}
if(fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile[x]) != 1)
{
printf("readinfo header error!");
exit(0);
}
// 结束位图文件头与信息头读取

// 调色板函数
boolMakePalette(FILE * pFile, BITMAPFILEHEADER &file_h, BITMAPINFOHEADER &info_h, RGBQUAD *pRGB_out);
//读取实际位图数据函数
voidReadRGB(FILE * pFile, BITMAPFILEHEADER & file_h, BITMAPINFOHEADER &info_h, unsigned char * rgbDataOut);

frameWidth= Info_header.biWidth;
frameHeight= Info_header.biHeight;
/*打开YUV文件,以可新建追加方式写入数据 */
yuvFile=fopen(yuvFileName,"ab");
if (yuvFile == NULL)
{
printf("cannotfind yuv file\n");
exit(1);
}
else
{
printf("Theoutput yuv file is %s\n", yuvFileName);
}

/*开辟每帧输入缓冲区 */
bmpBuf= (u_int8_t*)malloc(frameWidth * frameHeight * 3);
rgbout= (unsigned char*)malloc(frameWidth * frameHeight * 3);
/*开辟每帧输出缓冲区*/
yBuf= (u_int8_t*)malloc(frameWidth * frameHeight);
uBuf= (u_int8_t*)malloc((frameWidth * frameHeight) / 4);
vBuf= (u_int8_t*)malloc((frameWidth * frameHeight) / 4);

if(bmpBuf == NULL || yBuf == NULL || uBuf == NULL || vBuf == NULL)
{
printf("noenought memory\n");
exit(1);
}
//从位图文件中读取实际位图数据到输入缓冲区
ReadRGB(bmpFile[x],File_header, Info_header,rgbout);
memcpy(bmpBuf,rgbout, frameWidth*frameHeight * 3);
//调用RGB2YUV函数实现格式转化
if(BMP2YUV(frameWidth, frameHeight, bmpBuf, yBuf, uBuf, vBuf, flip))
{
printf("error");
return0;
}
//控制输出图像值溢出
for(i = 0; i < frameWidth*frameHeight; i++)
{
if(yBuf[i] < 16) yBuf[i] = 16;
if(yBuf[i] > 235) yBuf[i] = 235;
}

for(i = 0; i < frameWidth*frameHeight / 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;
}
//将输出YUV缓冲区数据写入YUV文件中
fwrite(yBuf,1, frameWidth * frameHeight, yuvFile);
fwrite(uBuf,1, (frameWidth * frameHeight) / 4, yuvFile);
fwrite(vBuf,1, (frameWidth * frameHeight) / 4, yuvFile);

/*释放内存 */
if(rgbout)
free(rgbout);
if(bmpBuf)
free(bmpBuf);
if(yBuf)
free(yBuf);
if(uBuf)
free(uBuf);
if(vBuf)
free(vBuf);
if(bmpFile)
fclose(bmpFile[x]);
if(yuvFile)
fclose(yuvFile);
}
}
return(0);
}
获取RGB数据函数
void ReadRGB(FILE * pFile, BITMAPFILEHEADER& file_h, BITMAPINFOHEADER & info_h, unsigned char * rgbDataOut)
{
unsignedlong Loop, width, height, w, h;
unsignedchar mask, *Index_Data;
boolMakePalette(FILE * pFile, BITMAPFILEHEADER &file_h, BITMAPINFOHEADER &info_h, RGBQUAD *pRGB_out);
// w为计算机内位图文件所存字节数,为4字节的整数倍,当图像一行数据存储不是4字节的整数倍时, 计算机会自动填充至4字节整数倍,在读取数据过程中须注意填充的无效数据
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 ;
height= h;
printf("zheshi%ld\n%ld\n",width,height);
Index_Data= (unsigned char *)malloc(height*width);

//找到位图文件中的实际位图数据并获取数据
fseek(pFile,file_h.bfOffBits, 0);
if(fread(Index_Data, height*width, 1, pFile) != 1)
{
printf("readfile error!\n\n");
exit(0);
}
//根据每像素位数不同进行不同数据操作
switch(info_h.biBitCount)
{
case24://24 位图中直接使用 RGB数据
memcpy(rgbDataOut,Index_Data, height*width);
if(Index_Data)
free(Index_Data);
return;
case16://16位图中对2字节RGB数据进行移位使每个像素的RGB值转化为3字节数据,以ps中x1-r5-g5-b5格式为例
if(info_h.biCompression == BI_RGB)
{
/*16->24,则逐个使每个像素的实际RGB数据指针Index_Data移动两个字节时,
输出RGB数据指针需移动三个字节*/
for(Loop = 0; Loop < height * width; Loop += 2)
{
*rgbDataOut= (Index_Data[Loop] & 0x1F) << 3;
*(rgbDataOut+ 1) = ((Index_Data[Loop] & 0xE0) >> 2) +
((Index_Data[Loop+ 1] & 0x03) << 6);
*(rgbDataOut+ 2) = (Index_Data[Loop + 1] & 0x7C) << 1;

rgbDataOut+= 3;
}
}
if(Index_Data)
free(Index_Data);
return;
default://位数为1-8位数据需调用调色板中颜色索引值
//构造调色板,开辟相应内存空间
RGBQUAD*pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow(float(2),info_h.biBitCount));
int temp = sizeof(pRGB);
if(!MakePalette(pFile, file_h, info_h, pRGB))
printf("Nopalette!\n\n");
//根据位数不同设置不同的移位偏移量,实现分别获取每比特数/每像素对应的数据值

switch(info_h.biBitCount)
{
case1:
mask= 0x80;
break;
case2:
mask= 0xC0;
break;
case4:
mask= 0xF0;
break;
case8:
mask= 0xFF;
}
//对每个像素的RGB值进行颜色索引

for (Loop = 0; Loop<height*width; Loop++)
{
unsignedchar mloop=mask;
intshiftCnt = 1;
if(Loop%(info_h.biWidth*info_h.biBitCount/8)==0)
Loop+=(width-info_h.biWidth*info_h.biBitCount/8);//防止读取填充的无效数据读入
//逐字节查找颜色,当mloop为0时,结束循环,到下一个字节再进行颜色索引
while(mloop)
{
//printf("ccc%d",mask);
//index即为调色板中颜色索引值
unsignedchar index = (mloop == 0xFF) ? Index_Data[Loop] :
((Index_Data[Loop]& mloop) >> (8 - shiftCnt * info_h.biBitCount));
//如果位数为8,则此字节数据即为调色板颜色索引值
*rgbDataOut= pRGB[index].rgbBlue;
*(rgbDataOut+ 1) = pRGB[index].rgbGreen;
*(rgbDataOut+ 2) = pRGB[index].rgbRed;
rgbDataOut+= 3;
shiftCnt++;
if(mloop==1)
break;
//如果位数为8,不用再对数据进行移位
if(info_h.biBitCount == 8)
mloop= 0;
else//否则,继续移位至此字节下一个RGB数据
mloop>>= info_h.biBitCount;

}
}
if(Index_Data)
free(Index_Data);

}

}
创建调色板函数:
bool MakePalette(FILE * pFile,BITMAPFILEHEADER &file_h, BITMAPINFOHEADER & info_h, RGBQUAD *pRGB_out)
{
//由位图信息数据与位图实际数据之间差值得到调色板数据空间大小为(2^n)(n为位数)
if((file_h.bfOffBits - sizeof(BITMAPFILEHEADER) - info_h.biSize) ==sizeof(RGBQUAD)*(unsigned int)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);
returntrue;
}
else
returnfalse;
}
其余操作同实验一

四、实验结果及分析
遇到的问题:

在单位bmp文件的转化过程中,因为实验所取为600x800像素文件,一行数据的存储为75字节,非4字节整数倍,一开始忽略此问题,导致读入计算机自动填充的无效数据,使得数据无法正确读入,程序跑飞,后来加入一行代码 if(Loop%(info_h.biWidth*info_h.biBitCount/8)==0)
Loop+=(width-info_h.biWidth*info_h.biBitCount/8);判断每读取完一行有效实际数据后,即跳过无效填充数据,跳至下一行有效数据头,便可解决此问题。
图片对比如下:







由图像对比可知,实验所采用的600x800bmp文件均成功转化,并形成一个210帧yuv文件。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: