原创文章,转载请注明:JPEG概述和头分析(C源码) By Lucio.Yang
部分内容来自:w285868925,JPEG压缩标准
1.JPEG概述
JPEG是一个压缩标准,又可分为标准 JPEG、渐进式JPEG及JPEG2000三种:
①标准JPEG:以24位颜色存储单个光栅图像,是与平台无关的格式,支持最高级
别的压缩,不过,这种压缩是有损耗的。此类型图片在网页下载时只能由上而下依序显 示图片,直到图片资料全部下载完毕,才能看到全貌。
②渐进式 JPEG:渐进式JPG为标准JPG的改良格式,支持交错,可以在网页下载时,先呈现出图片的粗略外观后,再慢慢地呈现出完整的内容,渐进式JPG的文件 比标准JPG的文件要来得小。
③JPEG2000:新一代的影像压缩法,压缩品质更好,其压缩率比标准JPEG高约30%左右,同时支持有损 和无损压缩。一个极其重要的特征在于它能实现渐进传输,即先传输图像的轮廓,然后逐步传输数据,让图像由朦胧到清晰显示。
2.压缩算法
必要性:大数据量的图象信息会给存储器的存储容量,通信干线信道的带宽,以及计算机的处理速度增加极大的压力。单纯靠增加存储器容量,提高信道带宽以及计算机的处理速度等方法来解决这个问题是不现实的,这时就要考虑压缩。
压缩可分为两大类:第一类压缩过程是可逆的,也就是说,从压缩后的图象能够完全恢复出原来的图象,信息没有任何丢失,称为无损压缩;第二类压缩过程是不可逆的,无法完全恢复出原图象,信息有一定的丢失,称为有损压缩。
压缩编码的方法有很多,主要分成以下四大类:(1)象素编码;(2)预测编码;(3)变换编码;(4)其它方法。
选择哪一类压缩,要折衷考虑,尽管我们希望能够无损压缩,但是通常有损压缩的压缩比(即原图象占的字节数与压缩后图象占的字节数之比,压缩比越大,说明压缩效率越高)比无损压缩的高。
JPEG专家组开发了两种基本的压缩算法,一种是采用以离散余弦变换(Discrete Cosine Transform,DCT)为基础的有损压缩算法,另一种是采用以预测技术为基础的无损压缩算法。使用有损压缩算法时,在压缩比为25:1的情况下,压缩后还原得到的图像与原始图像相比较,非图像专家难于找出它们之间的区别,因此得到了广泛的应用。例如,在VCD和DVD-Video电视图像压缩技术中,就使用JPEG的有损压缩算法来取消空间方向上的冗余数据 为了在保证图像质量的前提下进一步提高压缩比,近年来JPEG专家组正在制定JPEG2000标准,这个标准中将采用小波变换(Wavelet)算法。
JPEG压缩是有损压缩,它利用了人的视角系统的特性,使用量化和无损压缩编码相结合来去掉视角的冗余信息和数据本身的冗余信息。
压缩编码大致分成三个步骤:
1、使用正向离散余弦变换(Forward
Discrete Cosine Transform,FDCT)把空间域表示的图变换成频率域表示的图。
2、使用加权函数对DCT系数进行量化,这个加权函数对于人的视觉系统是最佳的。
3、使用霍夫曼可变字长编码器对量化系数进行编码。
3.标记段简介
压缩后的JPEG文件大体上可以分成以下两个部分:标记码(Tag)加压缩数据。
下面简单介绍标记段。
标记段的结构一般为:
SOI
DQT
DRI
SOF0
DHT
SOS
…
EOI
标记码由两个字节组成,高字节为0XFF,每个标记码之前可以填上个数不限的填充字节0XFF。
下面介绍一些常用的标记码的结构及其含义。
(1)SOI(Start of Image)
标记结构 字节数
0XFF 1
0XD8 1
可作为JPEG格式的判据(JFIF还需要APP0的配合)
(2)APP0(Application)
标记结构 字节数 意义
0XFF 1
0XE0 1
Lp 2 APP0标记码长度,不包括前两个字节0XFF,0XE0
Identifier 5 JFIF识别码 0X4A,0X46,0X49,0X46,0X00
Version 2 JFIF版本号 可为0X0101或者0X0102
Units 1 单位,等于零时表示未指定,为1表示英寸,为2表示
厘米
Xdensity 2 水平分辨率
Ydensity 2 垂直分辨率
Xthumbnail 1 水平点数
Ythumbnail 1 垂直点数
RGB0 3 RGB的值
RGB1 3 RGB的值
…
RGBn 3 RGB的值,n=Xthumbnail*Ythumbnail
APP0是JPEG保留给Application所使用的标记码,而JFIF将文件的相关信息定义在此标记中。
(3)DQT(Define Quantization Table)
标记结构 字节数 意义
0XFF 1
0XDB 1
Lq 2 DQT标记码长度,不包括前两个字节0XFF,0XDB
(Pq,Tq) 1 高四位Pq为量化表的数据精确度,Pq=0时,Q0~Qn的
值为8位,Pq=1时,Qt的值为16位,Tq表示量化表的
编号,为0~3。在基本系统中,Pq=0,Tq=0~1,也就是
说最多有两个量化表。
Q0 1或2 量化表的值,Pq=0时;为一个字节,Pq=1时,为两个
字节
Q1 1或2 量化表的值,Pq=0时;为一个字节,Pq=1时,为两个
字节
…
Qn 1或2 量化表的值,Pq=0时,为一个字节;Pq=1时,为两个
字节。n的值为0~63,表示量化表中64个值(之字形排
列)
(4)DRI(Define Restart Interval)
标记结构 字节数 意义
0XFF 1
0XDD 1
Lr 2 DRI标记码长度,不包括前两个字节0XFF,0XDD
Ri 2 重入间隔的MCU个数,Ri必须是一MCU行中MCU
(5)SOF(Start of Frame) 在基本系统中,只处理SOF0
标记结构 字节数 意义
0XFF 1
0XC0 1
Lf 2 SOF标记码长度,不包括前两个字节0XFF,0XC0
P 1 基本系统中,为0X08
Y 2 图象高度
X 2 图象宽度
Nf 1 Frame中的成分个数,一般为1或3,1代表灰度图,3
代表真彩图
C1 1 成分编号1
(H1,V1) 1 第一个水平和垂直采样因子
Tq1 1 该量化表编号
C2 1 成分编号2
(H2,V2) 1 第二个水平和垂直采样因子
Tq2 1 该量化表编号
…
Cn 1 成分编号n
(Hn,Vn) 1 第n个水平和垂直采样因子
Tqn 1 该量化表编号
(6)DHT(Define Huffman Table)
标记结构 字节数 意义
0XFF 1
0XC4 1
Lh 2 DHT标记码长度,不包括前两个字节0XFF,0XC4
(Tc,Th) 1
L1 1
L2 1
…
L16 1
V1 1
V2 1
…
Vt 1
(7)SOS(Start of Scan)
标记结构 字节数 意义
0XFF 1
0XDA 1
Ls 2 DHT标记码长度,不包括前两个字节0XFF,0XDA
Ns 1
Cs1 1
(Td1,Ta1) 1
Cs2 1
(Td2,Ta2) 1
…
CsNs 1
(TdNs,TaNs) 1
Ss 1
Se 1
(8)EOI(End of Image) 结束标志
标记结构 字节数 意义
0XFF 1
0XD9 1
4.头信息分析实例
/* * File Name: jpegDe.c * Author: 576632108@qq.com lucio * Date: 2014.11.5 * Description: analyze the head info of jpeg */ #include <stdio.h> #include <stdlib.h> typedef unsigned short WORD; typedef unsigned long DWORD; typedef long LONG; int main(int argc, char **argv){ FILE *fp; WORD window; WORD temp; int i=0,j=0,k=0; int found_count=0; int mark_head=0xFF; int mark_part[8]={0xD8,0xE0,0xDB,0xDD,0xC0,0xC4,0xDA,0xD9}; char mark_str[8][10]={"SOI","APP0","DQT","DRI","SOF0","DHT","SOS","EOI"}; int head,part; int temp_head,temp_part; int done=0; fp=fopen("KD.jpg","rb"); if(fp==0){ printf("Can not open the file to read. "); return -1; } while( 1 ){ fseek(fp,i,SEEK_SET); if(fread(&window,2,1,fp)!=1){ printf("Can not read the info of the info header. "); fclose(fp); return -1; } head=window-(window/0x100)*0x100;//靠前的部分是高位 part=window/0x100;//靠后的部分是低位 if( head==mark_head ){ for ( j=0;j<8;j++ ){ if( part==mark_part[j] ){ printf("%s: %X-%X ",mark_str[j],head,part); if( j==1 ){//APP0 //此处不需要fseek,因为读的过程中指针移动 //Lp if(fread(&temp,2,1,fp)!=1){ printf("Can not read the info of the info header. "); fclose(fp); return -1; } temp_head=temp-(temp/0x100)*0x100; temp_part=temp/0x100; temp=temp_head+temp_part; printf(" Lp: %d ",temp); //Identifier printf(" Identifier: "); for( k=0;k<3;k++ ){ if(fread(&temp,2,1,fp)!=1){ printf("Can not read the info of the info header. "); fclose(fp); return -1; } temp_head=temp-(temp/0x100)*0x100; temp_part=temp/0x100; if( k!=2 ) printf("%02X-%02X-",temp_head,temp_part); else printf("%02X",temp_head); } printf(" "); //Version fseek(fp,-1,SEEK_CUR); if(fread(&temp,2,1,fp)!=1){ printf("Can not read the info of the info header. "); fclose(fp); return -1; } temp_head=temp-(temp/0x100)*0x100; temp_part=temp/0x100; temp=temp_head+temp_part; printf(" Version: %02X-%02X ",temp_head,temp_part); //Units if(fread(&temp,1,1,fp)!=1){ printf("Can not read the info of the info header. "); fclose(fp); return -1; } printf(" Units: %02X ",temp); //Xdensity if(fread(&temp,2,1,fp)!=1){ printf("Can not read the info of the info header. "); fclose(fp); return -1; } temp_head=temp-(temp/0x100)*0x100; temp_part=temp/0x100; temp=temp_head+temp_part; printf(" Xdensity: %d ",temp); //Ydensity if(fread(&temp,2,1,fp)!=1){ printf("Can not read the info of the info header. "); fclose(fp); return -1; } temp_head=temp-(temp/0x100)*0x100; temp_part=temp/0x100; temp=temp_head+temp_part; printf(" Ydensity: %d ",temp); //Xthumbnail if(fread(&temp,2,1,fp)!=1){ printf("Can not read the info of the info header. "); fclose(fp); return -1; } temp_head=temp-(temp/0x100)*0x100; temp_part=temp/0x100; printf(" Xthumbnail: %d ",temp_head); //Ythumbnail printf(" Ythumbnail: %d ",temp_part); } i+=1; if( j==7 ) done=1; break; }//if }//for }//if i++; if( done==1 ) break; } fclose(fp); system("pause"); return 0; }