读取JPEG头文件中的huffman表
实验工具:UltrEdit、Matlab
实验目的:提取出JPEG头文件中的huffman表段,并构建huffman映射。
在讲实验前,先了解一下JPEG的格式。
一.JPEG格式
JPEG格式是一种常见的图像文件压缩格式,是一些未压缩图像(如Tiff格式、bmp格式、png格式等),在经过JPEG压缩后形成的图像文件。图像的内容上会有一些肉眼不可见的失真。
JPEG压缩的过程为:
1.将像素点分块(8x8);2.将块内的像素点做DCT变换,每个块内得到1个DC系数和63个AC系数;3.将DC系数和AC系数量化;4.将DC系数和AC系数根据定义的Huffman表编码成二进制的压缩数据(在编码之前,还要对DC系数和AC系数做一些变换,如DC系数是按差值保存,而AC系数是按行程长度码保存)。
因此,JPEG格式的图象文件可以分为两个部分,即头文件部分和压缩数据部分。头文件中保存着一些图像的基本信息,如图像大小、量化表、Huffman表等。JPEG的比特流结构如下所示:
图像以FFD8开始,以FFD9结尾。压缩数据段以FFDA开始,即FFD8-FFDA之间的是JPEG图像的头文件。Huffamn码段则以FFC4为开头。JPEG定义了一些标准的Huffman表(也可以使用自定义的Huffman表)。
一般来说,在彩色图像中,针对DC系数和AC系数一共定义了4张Huffman表,即亮度DC表、色度DC表、亮度AC表和色度AC表,而在灰度图像中,则只需要两张表,即亮度DC表和亮度AC表。
二、实验步骤
实验使用灰度JPEG图像。
1.将一张灰度的JPEG图像,用UltraEdit软件打开。
可以直接看到图像的十六进制格式。
可以看到图像以FFD8开头,也可以找到压缩数据段的开始标志FFDA。在头文件段中,可以找到两个FFC4。
第一段FFC4是DC系数的Huffman表,而第二段则是AC系数的Huffman表。
以AC系数的Huffman表段为例,在FFC4之后,00 B5 10是表信息。后面的16个,即00 02...一直到7D,表示编码成相应长度的码字数量,我们记为码段1。由00 02...7D可知,编码长度为1的有0个,编码长度为16的有125个(AC系数一共有162个编码)。在7D之后,即01 02 ...FA则是AC系数的VLC值,记为码段2。在构建huffman映射时,就是根据码段1和码段2形成的。
2.将由UltraEdit打开的这段十六进制文件保存为.TXT文件
这样就可以方便Python、MATLAB、C等读取。
3.下面开始介绍代码。
新建脚本文件,命名为read_huff.m,先将保存好的.TXT文件读入,并将其保存为cell。
clc;
clear;
filename='Lena_90.txt';
code=textread(filename,'%s');%读取图像十六进制文件
之后便可以读取其头文件。分别读取出DC系数和AC系数的huffman表段(舍弃了表信息),保存为huff_dc和huff_ac。
%读取头文件
for i=1:length(code)
if (char(code(i))=='FF')&(char(code(i+1))=='C4')&(char(code(i+4))=='00')
dc_first=i+5;
end
if (char(code(i))=='FF')&(char(code(i+1))=='C4')&(char(code(i+4))=='10')
dc_last=i-1;
end
end
for i=1:length(code)
if (char(code(i))=='FF')&(char(code(i+1))=='C4')&(char(code(i+4))=='10')
ac_first=i+5;
end
if (char(code(i))=='FF')&(char(code(i+1))=='DA')
ac_last=i-1;
end
end
huff_dc=code(dc_first:dc_last);%读出的dc系数huffman表段
huff_ac=code(ac_first:ac_last);%读出的ac系数huffman表段
新建一个函数文件,命名为huff_table.m,用来构建huffman映射(代码稍后再贴)。
调用huff_table.m,可以分别得到DC系数和AC系数的映射结果,即dc_code,dc_length,ac_code和ac_length,以AC系数的映射结果为例,ac_code表示VLC值映射成的编码(10进制),ac_length则表示编码长度。根据这两个结果,可以将ac_code转化为二进制形式。
huffval_dc和huffval_ac则是直接从表中读出来的原始值。huffval_ac是AC系数的VLC值,可以和ac_code_b形成一一对应。
huffval_dc=huff_dc(17:end);%dc系数的Huffman值
[dc_code,dc_length]=huff_table(huff_dc);
%将dc系数的Huffman代码转换为二进制
for i=1:length(dc_code)
dc_code_b(i)=string(dec2base(dc_code(i),2,dc_length(i)));
end
huffval_ac=huff_ac(17:end);%ac系数的Huffman值,即VLC值
%将ac系数的Huffman代码转换为二进制
[ac_code,ac_length]=huff_table(huff_ac);
for i=1:length(ac_code)
ac_code_b(i)=string(dec2base(ac_code(i),2,ac_length(i)));
end
%保存标准的Huffman表
save huffman.mat ac_code_b huffval_ac dc_code_b huffval_dc;
最终的结果(ac_code_b):
下面给出huff_table.m的代码:
function [ehufco, ehufsi] = huff_table(huffman)
%将JPEG中的huffman表转化为huffman_table,
%返回代码表ehufco和代码长度表ehufsi
bits=huffman(1:16);
huffval=huffman(17:end);
k=0;
j=1;
for i=1:16
for j=1:hex2dec(bits{i})
huffsize(k+1)=i;
k=k+1;
end
end
huffsize(k+1)=0;
lastk=k;
code=0;
k=1;
si=huffsize(1);
while huffsize(k)>0
huffcode(k)=code;
code=code+1;
k=k+1;
while huffsize(k)==si
huffcode(k)=code;
code=code+1;
k=k+1;
end
if huffsize(k)==0
break;
end
code=code*2;
si=si+1;
while huffsize(k)~=si
code=code*2;
si=si+1;
end
end
for k=1:lastk
% i=hex2dec(huffval{k});
ehufco(k)=uint32(huffcode(k));
ehufsi(k)=uint32(huffsize(k));
end
end