您的位置:首页 > 其它

数据压缩原理实验2_BMP2YUV文件转换

2017-03-28 16:09 579 查看
一、实验原理

BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB),使用非常广。它采用位映射存储格式,除了图像深度可选以外,在绝大多数应用中不采用其他任何压缩,因此,BMP文件所占用的空间很大。BMP文件的图像深度可选1bit,4bit,8bit,16bit及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。

BMP文件格式主要由下面四部分组成:

位图文件头数据结构、位图信息头数据结构、调色板和位图数据四部分组成

(1)位图文件头数据结构,它包含BMP图像文件的类型、显示内容等信息

typedef struct tagBITMAPFILEHEADER {
WORD       bfType;            /* 说明文件的类型; 占2个字节  */
DWORD      bfSize;            /* 说明文件的大小,用字节为单位;占4个字节 */
WORD       bfReserved1;       /* 保留,设置为0; 占2个字节 */
WORD       bfReserved2;       /* 保留,设置为0; 占2个字节 */
DWORD      bfOffBits;         /* 说明从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节偏移量;占4个字节 */
}   BITMAPFILEHEADER;


(2)位图信息头数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息

typedef struct tagBITMAPINFOHEADER {
DWORD    biSize;       		/* 说明结构体所需字节数 */
LONG     biWidth;   		/* 以像素为单位说明图像的宽度 (需注意的是其数值低位在前高位在后)*/
LONG     biHeight;  		/* 以像素为单位说明图像的高度 */
WORD     biPlanes;  	        /* 说明位面数,必须为1 */
WORD     biBitCount; 	        /* 说明位数/像素,1、2、4、8、24 */
DWORD    biCompression;	        /* 说明图像是否压缩及压缩类型
其中压缩方式: 0表示不压缩,1表示RLE8压缩,2表示RLE4压缩,3表示每个像素值由指定的掩码决定
BI_RGB(不压缩),BI_RLE8(8位RLE压缩方式),BI_RLE4(4位RLE压缩方式),BI_BITFIELDS(位域存放方式)*/
DWORD    biSizeImage;           /* 以字节为单位说明图像大小 ,必须是4的整数倍,图像数据大小不是4的倍数时用0填充补足*/
LONG     biXPelsPerMeter;       /* 目标设备的水平分辨率,像素/米 */
LONG     biYPelsPerMeter;       /*目标设备的垂直分辨率,像素/米 */
DWORD    biClrUsed;             /* 说明图像实际用到的颜色数,如果为0,则颜色数为2的biBitCount次方 */
DWORD    biClrImportant;  	/*说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。*/
/*以上按顺序依次所占字节数为:4/4/4/2/2/4/4/4/4/4/4 共40个字节*/
}  BITMAPINFOHEADER;


(3)调色板,这个部分是可选的,调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。数组中每个元素的类型是一个RGBQUAD结构。有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板。

typedef struct tagRGBQUAD {
BYTE    rgbBlue;           /*指定蓝色分量*/
BYTE    rgbGreen;          /*指定绿色分量*/
BYTE    rgbRed;            /*指定红色分量*/
BYTE    rgbReserved;       /*保留,指定为0,可能需要α*/
}  RGBQUAD;


(4)位图数据,这部分的内容根据BMP位图使用的位数不同而不同。对于用到调色板的位图,图像数据就是该像素颜色在调色板中的索引值(逻辑色)。对于真彩色图,图像数据就是实际的R, G, B值。图像的每一扫描行由表示图像像素的连续的字节组成,每一行的字节数取决于图像的颜色数目和用像素表示的图像宽度。规定每一扫描行的字节数必须是4的整倍数,也就是DWORD对齐的。扫描行是由底向上存储的,这就是说,阵列中的第一个字节表示位图左下角的像素,而最后一个字节表示位图右上角的像素。

下面用一张1位的图像的二进制显示来具体解释上述BMP文件格式



图像说明:该图为1位图像,其分辨率为512*512,大小为32.0625KB=32832B=(8040)H

文件格式说明:

      第1-2个字节42 4D表示图像类型为BMP   
     
      第3-6个字节 40 80 00 00 表示图像大小,可以看出在数据存放时采取低位在前高位在后的方式;
      第11-14个字节3E 00 00 00表示图像的字节偏移量为(3E)H,为62个字节,即14(文件头)+40(信息头)+8(调色板)=62
      第15-18个字节28 00 00 00表示信息头的结构体所需为(28)H,40个字节
      第19-22个字节00 02 00 00表示图像的宽为(02 00)H,512个像素
      第23-26个字节00 02 00 00表示图像的高为(02 00)H,512个像素
      第29-30个字节01 00表示图像为1位图像
      第37-40个字节 02 80 00 00 表示图像的大小为(00 00 80 20)H, 32800B其计算公式如下:



其中x,y分别是图像的宽和高,因为图像要求其中biSizeImage指的是实际图像数据的大小,以字节为单位。因为windows在进行行行扫描的时候,最小的单位是4个字节,所以当图片的宽度不是4的倍数的时候,需要在每行的后面补上缺少的字节,以0填充。因此在计算中宽必须是4的整数倍,如果不是整数倍,则取大于宽的离4的整数倍最近的数值,这样每行的像素可以整数次读取完成。



上图为将24位真彩图进行了转换成1位,4位,8位的图,可以看到颜色差别还是很明显的

二、实验流程及代码分析

具体的实验流程如下图所示



下面是main.cpp里的具体代码,代码分析加注在代码中

#include <stdio.h>
#include <windows.h> //该头文件里包括了程序中对bmp文件的一些调用函数,如 BITMAPFILEHEADER等,所以必不可少
#include "bmp2yuv.h"

void main(int argc, char *argv[])
{
FILE *bmpFile = NULL, *yuvFile = NULL;
BITMAPFILEHEADER File_header;  //直接读取图像的文件头
BITMAPINFOHEADER Info_header;  //读取图像的信息头

unsigned char * rgbBuf = NULL;
unsigned char * yBuff = NULL;
unsigned char * uBuff = NULL;
unsigned char * vBuff = NULL;
int flip = 0;   // 因为bmp文件的存储方式是从左到右、从下到上,因此flip值为0来使rgb2yuv时从下往上读入数据

for (int k = 1; k < 6; k++)   //这里采取了循环,每次读取一个bmp文件,k来控制一共需要转换的文件数
{
//	open bmp & yuv file
if ((bmpFile = fopen(argv[k], "rb")) == NULL)  //一共5个bmp文件
{
printf("bmp file open failed!");
exit(0);
}
if ((yuvFile = fopen(argv[6], "ab+")) == NULL)  //argv[6]中存放的是最后生成的视频文件
{                                               //通过"ab+"来不断的往文件里写入
printf("yuv file failed!");
exit(0);
}
//	end open bmp & yuv file

//	read file & info header
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);
}
if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)
{
printf("read info header error!");
exit(0);
}
//	end read header

int width, height;
if ((Info_header.biWidth % 4) == 0)
width = Info_header.biWidth;
else
width = (Info_header.biWidth*Info_header.biBitCount + 31) / 32 * 4;
/*加31再除以32后下取整,就保证了计算结果是离这个数最近的而且是比它大的32的倍数,
也就保证了是4字节的整数倍。乘以4和行数,得到4字节整数倍的图像大小。 */
if ((Info_header.biHeight % 2) == 0)
height = Info_header.biHeight;
else
height = Info_header.biHeight + 1;

//开辟缓冲区
rgbBuf = (unsigned char *)malloc(height*width * 3);
memset(rgbBuf, 0, height*width * 3);  //memset函数是对较大的结构体或数组进行清零操作的快速操作
yBuff = (unsigned char *)malloc(height*width);
uBuff = (unsigned char *)malloc((height*width) / 4);
vBuff = (unsigned char *)malloc((height*width) / 4);
//结束开辟缓冲区

printf("This is a %d bits image!\n", Info_header.biBitCount);
printf("\nbmp size: \t%d X %d\n", Info_header.biWidth, Info_header.biHeight);

ReadRGB(File_header, Info_header, bmpFile, rgbBuf);

int i;
if (RGB2YUV(width, height, rgbBuf, yBuff, uBuff, vBuff, flip))
{
printf("rgb2yuv error");
exit(1);
}
for (i = 0; i < width*height; i++)
{
if (yBuff[i] < 16) yBuff[i] = 16;
if (yBuff[i] > 235) yBuff[i] = 235;
}
for (i = 0; i < width*height / 4; i++)
{
if (uBuff[i] < 16) uBuff[i] = 16;
if (uBuff[i] > 240) uBuff[i] = 240;
if (vBuff[i] < 16) vBuff[i] = 16;
if (vBuff[i] > 240) vBuff[i] = 240;
}
for (int m = 0; m < 40; m++)
{
fwrite(yBuff, 1, width * height, yuvFile);
fwrite(uBuff, 1, (width * height) / 4, yuvFile);
fwrite(vBuff, 1, (width * height) / 4, yuvFile);
}
}

free(rgbBuf);
free(yBuff);
free(uBuff);
free(vBuff);
fclose(bmpFile);
fclose(yuvFile);
}

下面是bmp2yuv.cpp里的具体代码,代码分析加注在代码中

#include <stdio.h>
#include <windows.h>
#include <math.h>

/*检测图像实际数据开始位置和信息头结束位置是否重合,
若中间还有2的biBitCount次方结构体RGBQUAQ的大小,那么说明中间有调色板 */
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(2, (float)info_h.biBitCount))
{
fseek(pFile, sizeof(BITMAPFILEHEADER) + info_h.biSize, 0);
fread(pRGB_out, sizeof(RGBQUAD), (unsigned int)pow(2, (float)info_h.biBitCount), pFile);
return true;
}
else
return false;
}

void ReadRGB( BITMAPFILEHEADER & file_h, BITMAPINFOHEADER & info_h, FILE * pFile,unsigned char * rgbDataOut)
{
unsigned long Loop, i, j, width, height, w, h;
unsigned char mask, *Index_Data, *Data;

if ((info_h.biWidth % 4) == 0)
w = info_h.biWidth;
else
w = (info_h.biWidth*info_h.biBitCount + 31) / 32 * 4;
/*加31再除以32后下取整,就保证了计算结果是离这个数最近的而且是比它大的32的倍数,
也就保证了是4字节的整数倍。乘以4和行数,得到4字节整数倍的图像大小。 */
if ((info_h.biHeight % 2) == 0)
h = info_h.biHeight;
else
h = info_h.biHeight + 1;

width = w / 8 * info_h.biBitCount;  //宽
height = h;  //高

Index_Data = (unsigned char *)malloc(height*width);
Data = (unsigned char *)malloc(height*width);

fseek(pFile, file_h.bfOffBits, 0);
if (fread(Index_Data, height*width, 1, pFile) != 1)
{
printf("read file error!");
exit(0);
}

for (i = 0; i < height; i++)
for (j = 0; j < width; j++)
{
Data[i*width + j] = Index_Data[i*width + j];
}

RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow(2,(float)info_h.biBitCount));
//debug:把unsigned char 改为unsigned int
if (!MakePalette(pFile, file_h, info_h, pRGB))
printf("No palette!");
//当每像素为24位时,直接取像素数据写RGB缓冲区
if (info_h.biBitCount == 24)
{
memcpy(rgbDataOut, Data, height*width);
free(Index_Data);
free(Data);
return;
}
//当每像素为16位时,位与移位取像素数据转换为 8bit/彩色分量 写RGB缓冲区
if (info_h.biBitCount == 16)
{
for (Loop = 0; Loop < height * width ; Loop+=2)
//debug:"Loop++"改为"Loop+=2";
//loop每次循环中应该要逐次增加2,因为loop表示一个字节,而它应该每次16位,即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;
}       //上述存储由BGR的顺序存储
}
//当每像素为8位及以下时,构造调色板,位与移位取像素数据,查调色板,写RGB缓冲区
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)
{	/* 其中while 循环的次数:1bit 每字节循环8次;2bit 4次;4bit 2次;8bit 1次 */
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;
if (info_h.biBitCount == 8)
mask = 0;
else
mask >>= info_h.biBitCount;
rgbDataOut += 3;
shiftCnt++;
}
}
free(Index_Data);
free(Data);
free(pRGB);
}
//下面为rgb2yuv的程序,因与上个实验相同就不贴了,详见报告一


当每像素为16位的时候,位与移位取像素数据转换为8bit/彩色分量,写RGB缓冲区,情况与24bit不同,下面进行图示说明:



三、实验结果

以下为最终的视频截图,分别从1位,4位,8位,16位,24位,其分辨率都为512*512

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: