JPEG(Joint Photographic Experts Group)负责静止图像编码国际标准的制定,JPEG标准支持两种图像建立模式:
渐进建立(progressive build-up)
顺序建立(sequential build-up)
JPEG标准可采用以下四种压缩操作模式:
① 基于DCT的顺序型操作模式
② 基于DCT的渐进型操作模式
③ 基于DPCM的无损编码(顺序型)操作模式
④ 基于多分辨率编码的(渐进型)操作模式
JPEG规定了四种运行模式以满足不同的需要:
基于DPCM的无损编码模式:压缩比可达2:1,编解码通过一次扫描完成,保证解码后可完全精确恢复到原图像采样值;
基于DCT的有损顺序编码模式:压缩比可达10:1以上;
基于DCT的递增编码模式:编解码需要多次扫描完成,扫描效果从粗糙到精细,逐级递进;
基于DCT的分层编码模式:图像在多个空间多种分辨率进行编码,可以根据需要只对低分辨率数据作解码放弃高分辨率信息。
实际应用中第二种居多,本实验主要探究第二种压缩编码模式。
颜色空间:JPEG标准本身并没有规定颜色空间,只是对各分量分别编码,实现中通常将高度相关的RGB颜色空间转换到相关性较小的YUV颜色空间。图像的主要信息包括在Y通道,UV分量更平滑,容易压缩。人眼对色度分量不敏感,对色度分量可以进行下采样如4:2:2。
- JPEG编解码原理
编码步骤:
(1) 预处理
图像分块
分层思想:将图像分割成互不重叠的矩形块,每一个像块作为一个独立的单元进行变换和编解码。在图片像素数据流中,信息可以被分为一段接一段的最小编码单元(Minimum Coded Unit,MCU)数据流。所谓MCU,是图像中一个正方矩阵像素的数据。
电平偏置Level Offset:无符号数变为有符号数,将绝对值大的数出现的概率大大减少。
(2)DCT
离散余弦变换是一类正交变换。利用DCT去除数据之间的相关性以此去掉冗余信息。
(3)量化 Quantization
采用中平型均匀量化器。量化步距是按照系数所在位置与颜色分量来确定
因为人眼对亮度信号比色差信号更敏感,因此使用了两种量化表:亮度量化值和色度量化值;根据人眼的视觉特性(对低频敏感对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化。如果原始图像中细节丰富,则去掉的数据较多,量化后的系数与量化前差别较大;反之,细节少的原始图像在压缩时去掉的数据少些。
真正的量化表=缩放因子×基本量化表
(4)AC系数的之字形扫描 Zigzag scan
经DCT变换后系数大多数集中在左上角,即低频分量区。因此采用字形按频率的高低顺序读出,可以出现很多连零的机会,尤其在最后给出EOB(End Of Block)即可,将二维数组转化为一维数组。
(5)对DC系数进行DPCM编码
经过DCT变换之后得到的DC直流系数特点:系数的数值比较大;相邻图像块的DC系数值变化不大,存在冗余。基于此,JPEG算法使用了差分脉冲编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码。对DIFF用Huffman编码分成类别,类似指数Golomb编码,类别ID:一元码编码;类内索引:采用定长码。
(6)对AC系数进行RLE游程-幅度编码
为了消除码字中的统计冗余,采用[(Run,size),(Amplitude)]进行AC系数编码。
(7)VLC熵编码 - JPEG解码器
- JPEG文件格式
数据存储方式:最常用JPEG文件交换格式JPEG File Interchange Format JFIF
文件大致分成两个部分:标记码Tag和压缩数据
实验步骤
- 理解3个结构体的设计目的
struct huffman_table
{
/* Fast look up table, using HUFFMAN_HASH_NBITS bits we can have directly the symbol,
* if the symbol is <0, then we need to look into the tree table */
short int lookup[HUFFMAN_HASH_SIZE];//存储
/* code size: give the number of bits of a symbol is encoded */
unsigned char code_size[HUFFMAN_HASH_SIZE];//存储对应码字长度的数组
/* some place to store value that is not encoded in the lookup table
* FIXME: Calculate if 256 value is enough to store all values
*/
uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];
};
struct component
{
unsigned int Hfactor;//水平采样因子
unsigned int Vfactor;//垂直采样因子
float *Q_table; /* Pointer to the quantisation table to use */
struct huffman_table *AC_table;
struct huffman_table *DC_table;
short int previous_DC; /* Previous DC coefficient */ //前一个宏块DC值
short int DCT[64]; /* DCT coef */ //dct变换后的64个参数
#if SANITY_CHECK
unsigned int cid;
#endif
};
typedef void (*decode_MCU_fct) (struct jdec_private *priv);
typedef void (*convert_colorspace_fct) (struct jdec_private *priv);
struct jdec_private //最上层结构体,MCU块层次,包含了该MCU的信息和元素
{
/* Public variables */
uint8_t *components[COMPONENTS];//指向该MCU所包含的宏块指针
unsigned int width, height; /* Size of the image */
unsigned int flags;
/* Private variables */
const unsigned char *stream_begin, *stream_end;//数据流的开始和结束
unsigned int stream_length;//数据流长度
const unsigned char *stream; /* Pointer to the current stream */
unsigned int reservoir, nbits_in_reservoir;
struct component component_infos[COMPONENTS];//所包含的块
float Q_tables[COMPONENTS][64]; /* quantization tables */
struct huffman_table HTDC[HUFFMAN_TABLES]; /* DC huffman tables */
struct huffman_table HTAC[HUFFMAN_TABLES]; /* AC huffman tables */
int default_huffman_table_initialized;//若不包含Huffman码表时所用的缺省码表
int restart_interval;
int restarts_to_go; /* MCUs left in this restart interval */
int last_rst_marker_seen; /* Rst marker is incremented each time */
/* Temp space used after the IDCT to store each components */
uint8_t Y[64*4], Cr[64], Cb[64];//三个彩色分量最基本MCU块大小 8x8x4、8x8、8x8
jmp_buf jump_state;
/* Internal Pointer use for colorspace conversion, do not modify it !!! */
uint8_t *plane[COMPONENTS];
};
2.理解在视音频编解码调试中TRACE的目的和含义, 会打开和关闭TRACE。会根据自己的要求修改TRACE 。输出YUV文件如下:
3.以txt文件输出所有的量化矩阵和所有的HUFFMAN码表:
4.输出直流交流图像:
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
/*add by dyq*/
priv->DC_Image = (short int *)malloc(sizeof(short int)*priv->height*priv->width / 64);
priv->AC_Image = (short int *)malloc(sizeof(short int)*priv->height*priv->width / 64);
/* end */
unsigned int x, y, xstride_by_mcu, ystride_by_mcu;
unsigned int bytes_per_blocklines[3], bytes_per_mcu[3];
/*
typedef void (*decode_MCU_fct) (struct jdec_private *priv);
typedef void (*convert_colorspace_fct) (struct jdec_private *priv);
这两个函数里面结构体作为函数参数实现面向过程程序设计中又分层次的思想。
*/
decode_MCU_fct decode_MCU;
const decode_MCU_fct *decode_mcu_table;
const convert_colorspace_fct *colorspace_array_conv;
convert_colorspace_fct convert_to_pixfmt;
/*...省略部分代码*/
decode_mcu_table = decode_mcu_3comp_table;
return -1;
}
//选择MCU的长宽
xstride_by_mcu = ystride_by_mcu = 8;
if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1)
{
decode_MCU = decode_mcu_table[0];
convert_to_pixfmt = colorspace_array_conv[0];
} else if (priv->component_infos[cY].Hfactor == 1) {
decode_MCU = decode_mcu_table[1];
convert_to_pixfmt = colorspace_array_conv[1];
ystride_by_mcu = 16;
} else if (priv->component_infos[cY].Vfactor == 2) {
decode_MCU = decode_mcu_table[3];
convert_to_pixfmt = colorspace_array_conv[3];
xstride_by_mcu = 16;
ystride_by_mcu = 16;
} else {
decode_MCU = decode_mcu_table[2];
convert_to_pixfmt = colorspace_array_conv[2];
xstride_by_mcu = 16;
}
resync(priv);
/*...省略部分代码*/
/* Just the decode the image by macroblock (size is 8x8, 8x16, or 16x16) */
for (y=0; y < priv->height/ystride_by_mcu; y++)
{
//trace("Decoding row %d\n", y);
priv->plane[0] = priv->components[0] + (y * bytes_per_blocklines[0]);
priv->plane[1] = priv->components[1] + (y * bytes_per_blocklines[1]);
priv->plane[2] = priv->components[2] + (y * bytes_per_blocklines[2]);
for (x=0; x < priv->width; x+=xstride_by_mcu)
{
//在此处取直流分量和交流分量进行输出
getDAC(priv, xstride_by_mcu, ystride_by_mcu);//add by dyq
decode_MCU(priv);//huffman 解码
convert_to_pixfmt(priv);//变换
priv->plane[0] += bytes_per_mcu[0];
priv->plane[1] += bytes_per_mcu[1];
priv->plane[2] += bytes_per_mcu[2];
if (priv->restarts_to_go>0)
{
priv->restarts_to_go--;
if (priv->restarts_to_go == 0)
{
priv->stream -= (priv->nbits_in_reservoir/8);
resync(priv);
if (find_next_rst_marker(priv) < 0)
return -1;
}
}
}
}
/* add by dyq */
//全部取出后作范围调整[0,255]
adjustDAC(priv, xstride_by_mcu, ystride_by_mcu);
/* end */
return 0;
}
static void getDAC(struct jdec_private *priv, int xstride_by_mcu , int ystride_by_mcu)
{
static long int i = 0;
//因为宏块为8*8 因此DC图像应该为实际图像大小除以宏块大小
if (i < priv->height*priv->width / (xstride_by_mcu * ystride_by_mcu) )
{
priv->DC_Image[i] = priv->component_infos[0].DCT[0];
priv->AC_Image[i] = priv->component_infos[0].DCT[1];
}
i++;
}
static void adjustDAC(struct jdec_private *priv, unsigned int xstride_by_mcu, unsigned int ystride_by_mcu)
{
int i;
short int min_AC, max_AC, min_DC, max_DC, size;
unsigned char *temp_AC, *temp_DC;
size = priv->height*priv->width / (xstride_by_mcu * ystride_by_mcu);
temp_AC = (unsigned char*)malloc(sizeof(unsigned char)*size);
temp_DC = (unsigned char*)malloc(sizeof(unsigned char)*size);
//将AC/DC分量范围调整到[0-255]
min_AC = priv->AC_Image[0];
max_AC = priv->AC_Image[0];
min_DC = priv->DC_Image[0];
max_DC = priv->DC_Image[0];
//遍历寻找最大最小值,进行中心化操作
for (i = 0; i < size; i++)
{
if (priv->AC_Image[i] < min_AC)
min_AC = priv->AC_Image[i];
if (priv->AC_Image[i] > max_AC)
max_AC = priv->AC_Image[i];
if (priv->DC_Image[i] < min_DC)
min_DC = priv->DC_Image[i];
if (priv->DC_Image[i] > max_DC)
max_DC = priv->DC_Image[i];
}
//范围调整
for (i = 0; i < size; i++)
{
temp_AC[i] = (unsigned char)(255 * (priv->AC_Image[i] - min_AC) / (max_AC - min_AC));
temp_DC[i] = (unsigned char)(255 * (priv->DC_Image[i] - min_DC) / (max_DC - min_DC));
}
fwrite(temp_AC, 1, size, AC_FILE);
fwrite(temp_DC, 1, size, DC_FILE);
free(temp_AC);
free(temp_DC);
}
得到结果如下:
5.输出DC图像并经过huffman统计其概率分布
Number | DC image | Frequency Distribution Histogram |
---|---|---|
DCT[0] |
6.输出某一个AC值图像并统计其概率分布。
Number | AC image | Frequency Distribution Histogram |
---|---|---|
DCT[1] | ||
DCT[3] | ||
DCT[32] |