图像格式转换实验报告
实验1 图像格式转换实验报告
学 号:
姓
班 级:
一、实验目的 掌握两种以上图像的格式,重点掌握BMP 图像格式。
二、实验原理:
1、JPEG 文件的解码过程。
①.读入文件的相关信息
按照上述的JPEG 文件数据存储方式,把要解码的文件的相关信息一一读出,为接下来的解码工作做好准备。参考方法是,设计一系列的结构体对应各个标记,并存储标记内表示的信息。其中图像长宽、多个量化表和哈夫曼表、水平/垂直采样因子等多项信息比较重要。以下给出读取过程中的两个问题。
1)整个文件的大体结构
JFIF 格式的JPEG 文件(*.jpg)的一般顺序为:
SOI(0xFFD8)
APP0(0xFFE0)
[APPn(0xFFEn)]可选
DQT(0xFFDB)
SOF0(0xFFC0)
DHT(0xFFC4)
SOS(0xFFDA)
压缩数据
EOI(0xFFD9)
2)字的高低位问题
JPEG 文件格式中,一个字(16位)的存储使用的是 Motorola 格式, 而不是 Intel 格式。也就是说, 一个字的高字节(高8位)在数据流的前面, 低字节(低8位)在数据流的后面,与平时习惯的Intel 格式不一样。.
3)读出哈夫曼表数据
在标记段DHT 内,包含了一个或者多个的哈夫曼表。 不同位数的码字数量JPEG 文件的哈夫曼编码只能是1~16位。这个字段的16个字节分别表示1~16位的编码码字在哈夫曼树中的个数。编码内容这个字段记录了哈夫曼树中各个叶子结点的权。所以,上一字段(不同位数的码字数量)的16个数值之和就应该是本字段的长度,也就是哈夫曼树中叶子结点个数。
4)建立哈夫曼树
读出哈夫曼表的数据后,就要建立哈夫曼树。
②.初步了解图像数据流的结构
a) 在图片像素数据流中,信息可以被分为一段接一段的最小编码单元(Minimum CodedUnit ,MCU )数据流。所谓MCU ,是图像中一个正方矩阵像素的数据。矩阵的大小是这样确定的:查阅标记SOF0,可以得到图像不同颜色分量的采样因子,即Y 、Cr 、Cb 三个分量各自的水平采样因子和垂直采样因子。大多图片的采样因子为4:1:1或1:1:1。其中,4:1:1即(2*2):(1*1):(1*1));1:1:1即(1*1):(1*1):(1*1)。记三个分量中水平采样因子最大值为Hmax ,垂直采样因子最大值为Vmax ,那么单个MCU 矩阵的宽就是Hmax*8像素,高就是Vmax*8像素。
如果,整幅图像的宽度和高度不是MCU 宽度和高度的整数倍,那么编码时会些数
值填充进去,保证解码过程中MCU 的完整性(解码完成后,可直接忽视图像宽度和高度外的数据)。在数据流中,MCU 的排列方法是从左到右,从上到下。
b) 每个MCU 又分为若干个数据单元。数据单元的大小必定为8*8,所以每个MCU 的数据单元个数为Hmax*Vmax。另外JPEG 的压缩方法与BMP 文件有所不同,它不是把每个像素的颜色分量连续存储在一起的,而是把图片分成Y ,Cr ,Cb 三张子图,然后分别压缩。而三个颜色分量的采样密度(即采样因子)可能一样(例如1:1:1)也可能不一样(例如4:1:1)。每个MCU 内部,数据的顺序是Y 、Cr 、Cb 。如果一个颜色分量有多个数据单元,则顺序是从左到右,从上到下。
③.颜色分量单元的内部解码
1)理论说明
“颜色分量单元”是笔者为说明问题而建立的概念,指的是MCU 中某个颜色分量
的一个8*8数据块,例如上面提到的Y 1 、Cr 1、Cb 1 都是一个颜色分量单元。图像数据流是以位(bit )为单位存储信息的。并且内部的数据都是在编码时通过正向离散余弦变换(FDCT )进行时空域向频率域变换而得到的结果,所以对于每个颜色分量单元都应该由两部分组成:1个直流分量和63个交流分量。解码的过程其实就是哈夫曼树的查找过程。
颜色分量单元内部综合运用了RLE 行程编码和哈夫曼编码来压缩数据。每个像素的数据流由两部分构成:编码和数值,并且两者基本以互相隔开方式出现(除非该编码的权值为零)。具体读入单个颜色分量单元的步骤如下:
a )从此颜色分量单元数据流的起点开始一位一位的读入,直到读入的编码与该分量直流哈夫曼树的某个码字(叶子结点)一致,然后用直流哈夫曼树查得该码字对应的权值。权值(共8位)表示该直流分量数值的二进制位数,也就是接下来需要读入的位数。
b )继续读入位数据,直到读入的编码与该分量交流哈夫曼树的某个码字(叶子结点)一致,然后用交流哈夫曼树查得该码字对应的权值。权值的高4位表示当前数值前面有多少个连续的零,低4位表示该交流分量数值的二进制位数,也就是接下来需要读入的位数。
c )不断重复步骤b ,直到满足交流分量数据结束的条件。而结束条件有两个,只要满足其中一个即可:①当读入码字的权值为零,表示往后的交流变量全部为零;②已经读入63个交流分量。
d )各个数值的译码是按下表进行的:
④.直流系数的差分编码
把所有的颜色分量单元按颜色分量(Y 、Cr 、Cb )分类。每一种颜色分量内,相邻的两个颜色分量单元的直流变量是以差分来编码的。也就是说,通过步骤3解码出来的直流变量数值只是当前颜色分量单元的实际直流变量减去前一个颜色分量单元的实际
直流变量。也就是说,当前直流变量要通过前一个颜色分量单元的实际(非解码)直流分量来校正:
DCn=DCn-1+Diff
其中Diff 为差分校正变量,也就是直接解码出来的直流系数。但如果当前颜色分量单元是第一个单元,则解码出来的直流数值就是真正的直流变量。
⑤.反量化
不同的颜色分量使用不同的量化表,这个可以从标记段SOF 中的颜色分量信息字段查得。一般是Y 分量使用量化表0,而Cr 、Cb 两个分量共同使用量化表1。
反量化的过程比较简单。只需要对8*8的颜色分量单元的64个值逐一乘以对应的量化表内位置相同的值则可。图像内全部的颜色分量单元都要进行反量化。
⑥.反Zig-zag 编码
如果将反量化后的每个8*8颜色分量单元的每个元素编号,如下图4,那么各反Zig-zag 编码的过程就是把矩阵元素按图5重新排列。
关于量化和反Zig-zag 编码的先后顺序,笔者查阅的几份资料有不同的见解。经过实践试验,解码的过程中,是应该直接用文件提供的量化表反量化矩阵数据,再将其反Zig-zag 编码才能正确解码。
⑦.隔行的正负纠正
这个问题比较特别,因为在笔者认真阅读的几份资料中都没有提及此问题。而是笔者通过对已知图像进行JPEG 编码压缩,然后和该图的JPEG 文件数据对比发现的问题。具体原因不明。
实际上,就是必须对每个颜色分量单元的奇数行(每个颜色分量单元有8行,假设把它按0、1、……、6、7编出行号),即1、3、5、7行,进行取相反数操作(正的变负,负的变正)。
⑧.反离散余弦变换
之前提到,文件中的数据是在编码时通过正向离散余弦变换(FDCT )进行时空域向频率域变换而得到的结果,所以现在解码就必须将其反向离散余弦变换(IDCT ),就是把颜色分量单元矩阵中的频率域数值向时空域转换。并且,原来的频率域的矩阵大小为8*8,则经过反向离散余弦变换后,时空域的矩阵仍然是8*8。
设正负纠正后的频率域矩阵为F[u][v],而反向离散余弦变换后的矩阵为f[i][j],其中0≤u,v,i,j≤7。
⑨.YCrCb 向RGB 转换
要在屏幕上显示图像,就必须以RGB 模式表示图像的颜色。所以,解码时需要把YCrCb 模式向RGB 模式转换。
正如前面提到,并不是每种颜色分量的采样因子都一样,所以转换时需要注意。如果采样因子是1:1:1,则每一个像素点的3个颜色分量都被采样,所以没有问题。但
4:1:1的采样因子就不一样了。由“初步了解图像数据流的结构”一节中对4:1:1的采样因子的分析,可以知道一个MCU 里有4个Y 分量单元,而Cr 分量和Cb 分量各自只有1个分量单元。以图2为例,仅有的一个Cr 分量单元(红色的64个采样点)应该平铺用于4个Y 分量单元,即左上角16个值用于Y1,右上角16个值用于Y2,左下角16个值用于Y5,右下角16个值用于Y6。换句话说,一个Cr 采样点服务于4个Y 采样点。对于Cb 分量,道理一样。
另外,由于离散余弦变化要求定义域的对称,所以在编码时把RGB 的数值范围从[0,255]统一减去128偏移成[-128,127]。因此解码时必须为每个分量加上128。具体公式如下:
R=Y +1.402*Cb +128;
G=Y-0.34414*Cr -0.71414*Cb +128;
B=Y +1.772*Cb +128;
还有一个问题,通过变换得出的R 、G 、B 值可能超出了其定义域,所以要作出检查。如果大于255,则截断为255;如果小于0,则截断为0。
2、BMP 文件格式
① BMP文件头:BMP 文件头数据结构含有BMP 文件的类型、文件大小和位图起始位置等信息。 typedef struct tagBITMAPFILEHEADER{
WORD bfType; // 位图文件的类型,必须为BM
DWORD bfSize; // 位图文件的大小,以为单位
WORD bfReserved1; // 位图文件保留字,必须为0
WORD bfReserved2; // 位图文件保留字,必须为0
DWORD bfOffBits; // 位图数据的起始位置,以相对于位图文件头的偏移量表示,以为单位
} BITMAPFILEHEADER;
② 位图信息头:BMP 位图信息头数据用于说明位图的尺寸等信息。
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; // 本结构所占用数
LONGbiWidth; // 位图的宽度,以像素为单位
LONGbiHeight; // 位图的高度,以像素为单位
WORD biPlanes; // 目标设备的级别,必须为1
WORD biBitCount// 每个像素所需的位数,必须是1(双色),4(16色) ,8(256色) 或24(真彩色) 之一
DWORD biCompression; // 位图压缩类型,必须是 0(不压缩),1(BI_RLE8压缩
类型) 或2(BI_RLE4压缩类型) 之一
DWORD biSizeImage; // 位图的大小,以为单位
LONG biXPelsPerMeter; // 位图水平分辨率,每米像素数
LONG biYPelsPerMeter; // 位图垂直分辨率,每米像素数
DWORD biClrUsed;// 位图实际使用的颜色表中的颜色数
DWORD biClrImportant;// 位图显示过程中重要的颜色数
} BITMAPINFOHEADER;
③、 颜色表:颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD 类型的结构,定义一种颜色。
typedef struct tagRGBQUAD {
BYTE rgbBlue;// 蓝色的亮度(值范围为0-255)
BYTE rgbGreen; // 绿色的亮度(值范围为0-255)
BYTE rgbRed; // 红色的亮度(值范围为0-255)
BYTE rgbReserved;// 保留,必须为0
} RGBQUAD;
位图信息头和颜色表组成位图信息,BITMAPINFO 结构定义如下:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader; // 位图信息头
RGBQUAD bmiColors[1]; // 颜色表
} BITMAPINFO;
④ 位图数据:位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右, 扫描行之间是从下到上。位图的一个像素值所占的数:
当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;
三、实验操作
通过改变main 函数里下面语句,可以改变输入图片的名字:
if((hfjpg=_lopen("flower.jpg ",OF_READ))==HFILE_ERROR)
通过改变main 函数里下面语句,可以改变输出图片的名字:
hfbmp=_lcreat("bmp_pic.bmp",0);
直接运行就可以生成bmp 文件。