JPEG( Joint Photographic Experts Group)是用于连续色调静态图像压缩的一种标准,文件后缀名为.jpg或.jpeg,是最常用的图像文件格式。其主要是采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式。它的特点在于够将图像压缩在很小的储存空间,但不可避免地一定程度上会造成图像数据的损伤。尤其是使用过高的压缩比例。使用JPEG方法将使最终解压缩后恢复的图像质量降低,如果追求高品质图像,则不宜采用过高的压缩比例。
然而,JPEG编码可以用有损压缩方式去除冗余的图像数据。换句话说,就是可以用较少的磁盘空间得到较好的图像品质。而且JPEG是一种很灵活的格式,具有调节图像质量的功能,它允许用不同的压缩比例对文件进行压缩,支持多种压缩级别,压缩比率通常在10:1到40:1。JPEG格式压缩的主要是高频信息,对色彩的信息保留较好,适合应用于互联网;它可减少图像的传输时间,支持24位真彩色;也普遍应用于需要连续色调的图像中。
本实验则在已经对JPEG的原理进行学习理解的基础上,对C语言实现的JPEG分析和解码程序进行分析,进行JPEG向YUV的转换,对JPEG文件的等信息进行分析。
JPEG文件格式是JPEG(联合图像专家组)标准的产物,该标准由ISO与CCI TT(国际电报电话咨询委员会)共同制定,是面向连续色调静止图像的一种压缩标准。其压缩步骤大致分为四步:
颜色转换
由于JPEG只支持YUV颜色模式,而不支持RGB颜色模式,所以在将彩色图像进行压缩之前,必须先对颜色模式进据转换。转换完成之后还需要进行数据采样。一般采用的采样比例是2:1:1或4:2:2。由于在执行了此项工作之后,每两行数据只保留一行,因此,采样后图像数据量将压缩为原来的一半。
DCT变换
DCT(DiscreteConsineTransform)是将图像信号在频率域上进行变换,分离出高频和低频信息的处理过程。然后再对图像的高频部分(即图像细节)进行压缩,以达到压缩图像数据的目的。首先将图像划分为多个8*8的矩阵。然后对每一个矩阵作DCT变换(变换公式此略)。变换后得到一个频率系数矩阵,其中的频率系数都是浮点数。
量化
由于在后面编码过程中使用的码本都是整数,因此需要对变换后的频率系数进行量化,将之转换为整数。由于进行数据量化后,矩阵中的数据都是近似值,和原始图像数据之间有了差异,这一差异是造成图像压缩后失真的主要原因。
编码
编码采用两种机制:一是0值的行程长度编码;二是熵编码(EntropyCoding)。在JPEG中,采用曲徊序列,即以矩阵对角线的法线方向作“之”字排列矩阵中的元素。这样做的优点是使得靠近矩阵左上角、值比较大的元素排列在行程的前面,而行程的后面所排列的矩阵元素基本上为0值。行程长度编码是非常简单和常用的编码方式,在此不再赘述。编码实际上是一种基于统计特性的编码方法。在JPEG中允许采用HUFFMAN编码或者算术编码。
本实验则对JPEG的DCT变换、量化和编码操作进行学习与代码分析。
此实验使用实验素材中的图片进行 .jpg文件结构分析。使用HexFlex软件进行十六进制分析。
JPEG文件数据以数据段(Segment)为单位,以高位在前低位在后的形式进行存储,不同的段以首部两个字节进行标志,且大多段的有效数据长度(不含段头)均在段头后的2个字节进行标识。不同的段其段头两字节有特殊标识。
JPEG文件不同段类型分为:
应用程序保留标记 APP0
固定标记为0xFFE0,其中包含如图所示字段。
同时部分JPEG文件可能还含有APPn标记,以携带图片对应的应用程序信息(摄制设备等)。其中n的值为1-15,对应的固定标记为0xFFE1-0xFFEF。
定义量化表 DQT
固定标记为0xFFDB,在JPEG文件中通常存在两个,分别为亮度信息的量化表与色度信息的量化表。每个量化表的长度一般为0x0043(若文件中两量化表合在一起长度则为0x0084)。
定义霍夫曼表 DHT
固定标记为0xFFC4,后面两字节为DHT段长度。
DHT段第五字节则是霍夫曼表ID和类型。其中高四位为表类型,0和1分别代表当前表为直流分量霍夫曼表和交流分量霍夫曼表。低四位则为表ID,表示当前表为第几个表。
其后的16字节则代表霍夫曼编码后对应码长的码字个数。霍夫曼码长为1至16的码字个数分别由这16个字节进行表示。
再其后的字节则为编码内容,表示每个符字对应的权值。
上图即为示例文件中某个ID为0的直流分量霍夫曼表。可看到此表中2-16位码字应共有1+2+5+3+3+3+2+5+3+4+2+2+2+1+5=43个。选中后面表示每种码字的权值,可看到的确位43个。
而其中权值含义则为在解码过程中,此码字后的数据码字的位长。根据位长读取紧随其后的码字,即可进行解码。
定义扫描起点 SOS
固定标志为0xFFDA,除段长度2字节外,存储包含颜色分量信息和压缩图像数据。
图中0xFFDA后即为此段数据。
JPEG编码的过程如下图所示。而解码则是编码的逆过程。
如图所示。过程分别为:
Level Offset 零偏置
对于灰度级峰值为2n的图片,通过将像素减去2n-1,将无符号数转换为有符号数,从而使像素的绝对值出现3位10进制的概率大大减少,缩小数据范围。
8×8 DCT变换
对每个单独的图像分量,进行8×8块为单位的DCT变换,将系数向矩阵左上位置进行集中,以适合后续的交流直流分量分别编码的方式。
Uniform Scalar Quantization 量化表量化
对亮度信息和色度信息分别根据量化表进行量化。
编码
编码分为上下两路,分别对量化后的系数矩阵作直流分量和交流分量的编码。
其中直流分量采用DPCM(差分脉冲调制编码)的形式进行编码之后,再对编码所得的预测差值进行霍夫曼熵编码。
而对量化矩阵中的交流分量,由于DCT变换后的系数矩阵能量大多数存在于左上角,故使用之字形扫描。
而对之字形扫描之后的数据进行游程编码得到(RRRR,SSSS)格式的游程码数据,再对数据进行固定格式的霍夫曼编码,从而得到最终编码数据。
解码则是对编码的逆操作,其步骤大致为:
实验通过对JPEG文件解码工程进行调试,对工程运行逻辑进行分析和理解,并加入实验内容,以实现实验目的。
工程jpeg_dec共有三个头文件 stdint.h、tinypeg.h和tinyjpeg-internal.h,以及三个代码代码.c文件 jidctflc.c、loadjpeg.c和tinyjpeg.c。
① 霍夫曼码表结构体
//霍夫曼码表
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];
};
② 图像块结构体
//一个8×8的块(DCT变换及量化编码单元)
struct component
{
unsigned int Hfactor; //水平采样因子
unsigned int Vfactor; //垂直采样因子
float* Q_table; /* Pointer to the quantisation table to use */
struct huffman_table* AC_table; //交流DCT系数的哈夫曼码表指针
struct huffman_table* DC_table; //直流DCT系数的哈夫曼码表指针
short int previous_DC; //前一个块的直流系数(用于进行预测差分编码)
short int DCT[64]; //存储当前块的8×8 DCT系数
#if SANITY_CHECK
unsigned int cid;
#endif
};
③ 解码信息结构体
//文件基本解码信息
struct jdec_private
{
/* Public variables */
uint8_t* components[COMPONENTS];
unsigned int width, height; /* Size of the image */
unsigned int flags;
/* Private variables */
const unsigned char* stream_begin, * stream_end; //文件最为流式数据的开始和结束
unsigned int stream_length;
/* Pointer to the current stream */
const unsigned char* stream; //指向文件流的指针
unsigned int reservoir, nbits_in_reservoir;
struct component component_infos[COMPONENTS];
/* quantization tables */
float Q_tables[COMPONENTS][64]; //量化表
/* Huffman Tables */
struct huffman_table HTDC[HUFFMAN_TABLES]; //直流系数霍夫曼表
struct huffman_table HTAC[HUFFMAN_TABLES]; //交流系数霍夫曼表
int default_huffman_table_initialized;
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]; //存储反DCT变换之后的分量(4:2:0)
jmp_buf jump_state;
/* Internal Pointer use for colorspace conversion, do not modify it !!! */
uint8_t* plane[COMPONENTS];
/* 添加:用于存放直流系数(DCT[0])和交流系数(DCT[1])的数组 */
int* dc, * ac; //用于读取的指针
unsigned char* outdc, * outac; //用于输出的指针
};
实验中对JPEG进行解码等主要操作均在tinyjpeg.c文件中。此实验通过代码运行逻辑顺序进行分析理解,并在过程中通过添加代码完成实验要求。
① 解析JPEG文件数据段
对读取过程中的文件段头(两字节)标志进行读取,并判断段的种类,调用相应的函数进行操作。
//判断jpeg文件
int tinyjpeg_parse_header(struct jdec_private* priv, const unsigned char* buf, unsigned int size)
{
int ret;
/* Identify the file */
if ((buf[0] != 0xFF) || (buf[1] != SOI)) //以0xFFD8开始
snprintf(error_string, sizeof(error_string), "Not a JPG file ?\n");
priv->stream_begin = buf + 2; //跳过0xFFD8头,读取文件头
priv->stream_length = size - 2; //除去0xFFD8头的长度,为文件流总长度
priv->stream_end = priv->stream_begin + priv->stream_length; //利用头指针和长度直接定位文件尾指针
ret = parse_JFIF(priv, priv->stream_begin);
return ret;
}
读取函数parse_JFIF()如下。
static int parse_JFIF(struct jdec_private* priv, const unsigned char* stream)
{
int chuck_len;
int marker;
int sos_marker_found = 0;
int dht_marker_found = 0;
const unsigned char* next_chunck;//为了实现块的系数的预测编码,定义临近块指针
/* Parse marker */
while (!sos_marker_found)
{
if (*stream++ != 0xff)
goto bogus_jpeg_format;
/* Skip any padding ff byte (this is normal) */
while (*stream == 0xff)
stream++;
marker = *stream++;
chuck_len = be16_to_cpu(stream);
next_chunck = stream + chuck_len;
switch (marker)//按照段头标志进行识别
{
case SOF:
if (parse_SOF(priv, stream) < 0)
return -1;
break;
case DQT:
if (parse_DQT(priv, stream) < 0)
return -1;
break;
case SOS:
if (parse_SOS(priv, stream) < 0)
return -1;
sos_marker_found = 1;
break;
case DHT:
if (parse_DHT(priv, stream) < 0)
return -1;
dht_marker_found = 1;
break;
case DRI:
if (parse_DRI(priv, stream) < 0)
return -1;
break;
default:
#if TRACE
fprintf(p_trace, "> Unknown marker %2.2x\n", marker);
fflush(p_trace);
#endif
break;
}
stream = next_chunck; //下一个数据块,继续进行扫描
}
if (!dht_marker_found) {
#if TRACE
fprintf(p_trace, "No Huffman table loaded, using the default one\n");
fflush(p_trace);
#endif
build_default_huffman_tables(priv);
}
#ifdef SANITY_CHECK
if ((priv->component_infos[cY].Hfactor < priv->component_infos[cCb].Hfactor)
|| (priv->component_infos[cY].Hfactor < priv->component_infos[cCr].Hfactor))
snprintf(error_string, sizeof(error_string), "Horizontal sampling factor for Y should be greater than horitontal sampling factor for Cb or Cr\n");
if ((priv->component_infos[cY].Vfactor < priv->component_infos[cCb].Vfactor)
|| (priv->component_infos[cY].Vfactor < priv->component_infos[cCr].Vfactor))
snprintf(error_string, sizeof(error_string), "Vertical sampling factor for Y should be greater than vertical sampling factor for Cb or Cr\n");
if ((priv->component_infos[cCb].Hfactor != 1)
|| (priv->component_infos[cCr].Hfactor != 1)
|| (priv->component_infos[cCb].Vfactor != 1)
|| (priv->component_infos[cCr].Vfactor != 1))
snprintf(error_string, sizeof(error_string), "Sampling other than 1x1 for Cr and Cb is not supported");
#endif
return 0;
bogus_jpeg_format:
#if TRACE
fprintf(p_trace, "Bogus jpeg format\n");
fflush(p_trace);
#endif
return -1;
}
② DQT段操作函数
对DQT段进行解析,以得到量化表信息。同时加入代码,来输出所有的量化矩阵。
/* 解析DQT(0xFFDB),获取量化矩阵元素 */
static int parse_DQT(struct jdec_private* priv, const unsigned char* stream)
{
int qi;
float* table; //指向量化表元素
const unsigned char* dqt_block_end;//指向量化表尾
#if TRACE
fprintf(p_trace, "> DQT marker\n");
fflush(p_trace);
#endif
dqt_block_end = stream + be16_to_cpu(stream); //读取文件数据中第一和第二字节,从而得出长度
stream += 2; //跳过长度信息的两字节,指向有效数据
/* 是否已经达到了尾部 */
while (stream < dqt_block_end)
{
qi = *stream++;
#if SANITY_CHECK
//高四位为量化位数,此字节为0x00为8位,0x01为16位
if (qi >> 4)
snprintf(error_string, sizeof(error_string), "16 bits quantization table is not supported\n");
//低四位为量化表ID
if (qi > 4)
snprintf(error_string, sizeof(error_string), "No more 4 quantization table is supported (got %d)\n", qi);
#endif
table = priv->Q_tables[qi]; //量化表元素数据
build_quantization_table(table, stream);
/* 以之字形扫描顺序输出量化矩阵 */
FILE* fp = fopen("table.txt", "a");
if (fp == NULL)
{
printf("Fail to open file!\n");
exit(0);
}
fprintf(fp, "量化表\n");
for (int i = 0; i < 64; i++)
{
if (i != 0 && i % 8 == 0)
fprintf(fp, "\n");
fprintf(fp, "%f\t ", table[i]);
}
printf(fp, "\n");
stream += 64;
}
#if TRACE
fprintf(p_trace, "< DQT marker\n");
fflush(p_trace);
#endif
return 0;
}
③ SOF段操作函数
/* 解析SOF0(FFC0),得到图像基本数据 */
static int parse_SOF(struct jdec_private* priv, const unsigned char* stream)
{
int i, width, height, nr_components, cid, sampling_factor;
int Q_table;
struct component* c;
#if TRACE
fprintf(p_trace, "> SOF marker\n");
fflush(p_trace);
#endif
print_SOF(stream);
//不包含FFC0头情况下,第三四字节为图像高度,第五六字节为图像宽度,后面紧跟一字节为颜色分量数(03)
height = be16_to_cpu(stream + 3);
width = be16_to_cpu(stream + 5);
nr_components = stream[7];
#if SANITY_CHECK
if (stream[2] != 8)
snprintf(error_string, sizeof(error_string), "Precision other than 8 is not supported\n");
if (width > JPEG_MAX_WIDTH || height > JPEG_MAX_HEIGHT)
snprintf(error_string, sizeof(error_string), "Width and Height (%dx%d) seems suspicious\n", width, height);
if (nr_components != 3)
snprintf(error_string, sizeof(error_string), "We only support YUV images\n");
if (height % 16)
snprintf(error_string, sizeof(error_string), "Height need to be a multiple of 16 (current height is %d)\n", height);
if (width % 16)
snprintf(error_string, sizeof(error_string), "Width need to be a multiple of 16 (current Width is %d)\n", width);
#endif
stream += 8; //指针继续向后走
for (i = 0; i < nr_components; i++) {
cid = *stream++; //颜色分量ID
sampling_factor = *stream++; //水平和垂直采样因子
Q_table = *stream++; //颜色分量的量化表序号
c = &priv->component_infos[i]; //指向该分量的结构体指针
#if SANITY_CHECK
c->cid = cid;
if (Q_table >= COMPONENTS)
snprintf(error_string, sizeof(error_string), "Bad Quantization table index (got %d, max allowed %d)\n", Q_table, COMPONENTS - 1);
#endif
c->Vfactor = sampling_factor & 0xf; //取低四位为垂直采样因子
c->Hfactor = sampling_factor >> 4; //取高四位为水平采样因子
c->Q_table = priv->Q_tables[Q_table]; //以量化表序号获取量化表
#if TRACE
fprintf(p_trace, "Component:%d factor:%dx%d Quantization table:%d\n",
cid, c->Hfactor, c->Hfactor, Q_table);
fflush(p_trace);
#endif
}
//存储图像宽高信息
priv->width = width;
priv->height = height;
#if TRACE
fprintf(p_trace, "< SOF marker\n");
fflush(p_trace);
#endif
return 0;
}
④ DHT段操作函数
通过解析DHT数据段,来得到霍夫曼码表信息,同时加入代码输出所有霍夫曼码表。
/* DHT数据解析(0xFFC4,一般为两个),存储霍夫曼码表 */
static int parse_DHT(struct jdec_private* priv, const unsigned char* stream)
{
unsigned int count, i;
unsigned char huff_bits[17]; //码长上限为16,此数组存储特定码长的码字个数
int length, index;
length = be16_to_cpu(stream) - 2; //除去2字节标记代码后得到的表长
stream += 2; /* Skip length */
#if TRACE
fprintf(p_trace, "> DHT marker (length=%d)\n", length);
fflush(p_trace);
#endif
/* 在表不为空的情况下读取 */
while (length > 0) {
index = *stream++;
huff_bits[0] = 0;
count = 0;
for (i = 1; i < 17; i++) {
huff_bits[i] = *stream++; //统计特定码长的码字个数
count += huff_bits[i]; // count个字节对应的就是每个字符对应的权值
}
#if SANITY_CHECK
if (count >= HUFFMAN_BITS_SIZE)
snprintf(error_string, sizeof(error_string), "No more than %d bytes is allowed to describe a huffman table", HUFFMAN_BITS_SIZE);
if ((index & 0xf) >= HUFFMAN_TABLES)
snprintf(error_string, sizeof(error_string), "No more than %d Huffman tables is supported (got %d)\n", HUFFMAN_TABLES, index & 0xf);
#if TRACE
fprintf(p_trace, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);
fflush(p_trace);
#endif
#endif
/* 添加:以txt文件输出所有的HUFFMAN码表 */
FILE* fp = fopen("huffman.txt", "a");
if (fp == NULL)
{
printf("Fail to open file!\n");
exit(0); //退出程序(结束程序)
}
// 高四位为1是交流分量表
if (index & 0xf0)
{
fprintf(fp, "huffman_table AC%d号表\n", (index & 0xf));
build_huffman_table(huff_bits, stream, &priv->HTAC[index & 0xf]); //建立霍夫曼表
}
else
{
fprintf(fp, "huffman_table DC%d号表\n", (index & 0xf));
build_huffman_table(huff_bits, stream, &priv->HTDC[index & 0xf]);
}
length -= 1;
length -= 16;
length -= count;
stream += count; //向后继续推进,直到length为0
}
#if TRACE
fprintf(p_trace, "< DHT marker\n");
fflush(p_trace);
#endif
return 0;
}
⑤ SOS段操作函数
/* 解析SOS(0xFFDA),以得到颜色分量的霍夫曼表序号(对应DHT中的序号数据) */
static int parse_SOS(struct jdec_private* priv, const unsigned char* stream)
{
unsigned int i, cid, table;
unsigned int nr_components = stream[2]; //读取颜色分量数(仅支持3)
#if TRACE
fprintf(p_trace, "> SOS marker\n");
fflush(p_trace);
#endif
#if SANITY_CHECK
if (nr_components != 3)
snprintf(error_string, sizeof(error_string), "We only support YCbCr image\n");
#endif
stream += 3;
for (i = 0; i < nr_components; i++) { //对每个颜色进行表查找和处理
cid = *stream++; //表ID
table = *stream++; //表数据
#if SANITY_CHECK
if ((table & 0xf) >= 4)
snprintf(error_string, sizeof(error_string), "We do not support more than 2 AC Huffman table\n");
if ((table >> 4) >= 4)
snprintf(error_string, sizeof(error_string), "We do not support more than 2 DC Huffman table\n");
if (cid != priv->component_infos[i].cid)
snprintf(error_string, sizeof(error_string), "SOS cid order (%d:%d) isn't compatible with the SOF marker (%d:%d)\n",
i, cid, i, priv->component_infos[i].cid);
#if TRACE
fprintf(p_trace, "ComponentId:%d tableAC:%d tableDC:%d\n", cid, table & 0xf, table >> 4);
fflush(p_trace);
#endif
#endif
//查找的量化表数据进行取值
priv->component_infos[i].AC_table = &priv->HTAC[table & 0xf]; //低四位为AC
priv->component_infos[i].DC_table = &priv->HTDC[table >> 4]; //高四位为DC
}
priv->stream = stream + 3;
#if TRACE
fprintf(p_trace, "< SOS marker\n");
fflush(p_trace);
#endif
return 0;
}
⑥ 对读取结构体数据进行处理
在此函数中,对每个块的水平和垂直采样情况进行解析,计算MCU。同时通过调用不同的MCU读取和处理函数进行数据处理。
同时在此函数中添加代码以实现输出原JPEG图像的直流分量图像和其中一个交流分量图像。
//主要解码过程
int tinyjpeg_decode(struct jdec_private* priv, int pixfmt)
{
unsigned int x, y, xstride_by_mcu, ystride_by_mcu;
unsigned int bytes_per_blocklines[3], bytes_per_mcu[3];
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;
if (setjmp(priv->jump_state))
return -1;
/* To keep gcc happy initialize some array */
bytes_per_mcu[1] = 0;
bytes_per_mcu[2] = 0;
bytes_per_blocklines[1] = 0;
bytes_per_blocklines[2] = 0;
decode_mcu_table = decode_mcu_3comp_table;
switch (pixfmt) {
case TINYJPEG_FMT_YUV420P:
colorspace_array_conv = convert_colorspace_yuv420p;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t*)malloc(priv->width * priv->height);
if (priv->components[1] == NULL)
priv->components[1] = (uint8_t*)malloc(priv->width * priv->height / 4);
if (priv->components[2] == NULL)
priv->components[2] = (uint8_t*)malloc(priv->width * priv->height / 4);
bytes_per_blocklines[0] = priv->width;
bytes_per_blocklines[1] = priv->width / 4;
bytes_per_blocklines[2] = priv->width / 4;
bytes_per_mcu[0] = 8;
bytes_per_mcu[1] = 4;
bytes_per_mcu[2] = 4;
break;
case TINYJPEG_FMT_RGB24:
colorspace_array_conv = convert_colorspace_rgb24;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t*)malloc(priv->width * priv->height * 3);
bytes_per_blocklines[0] = priv->width * 3;
bytes_per_mcu[0] = 3 * 8;
break;
case TINYJPEG_FMT_BGR24:
colorspace_array_conv = convert_colorspace_bgr24;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t*)malloc(priv->width * priv->height * 3);
bytes_per_blocklines[0] = priv->width * 3;
bytes_per_mcu[0] = 3 * 8;
break;
case TINYJPEG_FMT_GREY:
decode_mcu_table = decode_mcu_1comp_table;
colorspace_array_conv = convert_colorspace_grey;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t*)malloc(priv->width * priv->height);
bytes_per_blocklines[0] = priv->width;
bytes_per_mcu[0] = 8;
break;
default:
#if TRACE
fprintf(p_trace, "Bad pixel format\n");
fflush(p_trace);
#endif
return -1;
}
/* 添加:分别输出DC分量图像和AC分享图像 */
FILE* DC_image = fopen("DCimage.yuv", "wb");
FILE* AC_image = fopen("ACimage.yuv", "wb");
priv->dc = (int*)malloc(sizeof(int) * (priv->width * priv->height) / 64); // 每个8x8的块仅有一个直流分量
priv->ac = (int*)malloc(sizeof(int) * (priv->width * priv->height) / 64);// 因为只输出某一AC值图像,所以AC和DC所占空间相同
xstride_by_mcu = ystride_by_mcu = 8; //初始化为4:4:4,即MCU宽高比为1:1,均为8像素
if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {
//Y分量垂直和水平的采样因子相等(4:4:4),每个MCU仅包含一个Y分量,且MCU的宽和高不变
decode_MCU = decode_mcu_table[0];
convert_to_pixfmt = colorspace_array_conv[0];
#if TRACE
fprintf(p_trace, "Use decode 1x1 sampling\n");
fflush(p_trace);
#endif
}
else if (priv->component_infos[cY].Hfactor == 1) {
//水平采样为1,垂直采样为2(4:2:2),每个MCU包含2个Y分量,且MCU的高需要增加一倍
decode_MCU = decode_mcu_table[1];
convert_to_pixfmt = colorspace_array_conv[1];
ystride_by_mcu = 16;
#if TRACE
fprintf(p_trace, "Use decode 1x2 sampling (not supported)\n");
fflush(p_trace);
#endif
}
else if (priv->component_infos[cY].Vfactor == 2) {
//水平采样和垂直采样均为2(4:2:0或4:1:1),每个MCU包含4个Y分量,且MCU的宽和高都需要增加一倍
decode_MCU = decode_mcu_table[3];
convert_to_pixfmt = colorspace_array_conv[3];
xstride_by_mcu = 16;
ystride_by_mcu = 16;
#if TRACE
fprintf(p_trace, "Use decode 2x2 sampling\n");
fflush(p_trace);
#endif
}
else {
//水平采样和垂直采样均为2(另一种4:2:2),每个MCU包含4个Y分量,且MCU的宽需要增加一倍
decode_MCU = decode_mcu_table[2];
convert_to_pixfmt = colorspace_array_conv[2];
xstride_by_mcu = 16;
#if TRACE
fprintf(p_trace, "Use decode 2x1 sampling\n");
fflush(p_trace);
#endif
}
resync(priv);
/* Don't forget to that block can be either 8 or 16 lines */
bytes_per_blocklines[0] *= ystride_by_mcu;
bytes_per_blocklines[1] *= ystride_by_mcu;
bytes_per_blocklines[2] *= ystride_by_mcu;
bytes_per_mcu[0] *= xstride_by_mcu / 8;
bytes_per_mcu[1] *= xstride_by_mcu / 8;
bytes_per_mcu[2] *= xstride_by_mcu / 8;
/* 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)
{
// 每解码一个8x8的宏块,将支流系数(DCT[0])和交流系数(DCT[1])分别赋值给定义的dc和ac参数
int i = 0;
if (i < priv->width * priv->height / 64)
{
priv->dc[i] = priv->component_infos[0].DCT[0];
priv->ac[i] = priv->component_infos[0].DCT[1];
}
i++;
decode_MCU(priv); //进行MCU内的解码
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) //查找RST标记
return -1;
}
}
}
}
#if TRACE
fprintf(p_trace, "Input file size: %d\n", priv->stream_length + 2);
fprintf(p_trace, "Input bytes actually read: %d\n", priv->stream - priv->stream_begin + 2);
fflush(p_trace);
#endif
/* 根据DCT的能量守恒特性,反DCT之后的DC取值最大可达到8*256,且反差分编码后数据可能为负数。因此需要对得到的直流和交流系数进行处理,使其值分布在[0,255]之间*/
priv->outdc = (unsigned char*)malloc(sizeof(unsigned char) * (priv->width * priv->height) / 64);
priv->outac = (unsigned char*)malloc(sizeof(unsigned char) * (priv->width * priv->height) / 64);
int dcmax = priv->dc[0];
int acmax = priv->ac[0];
int dcmin = priv->dc[0];
int acmin = priv->ac[0];
for (int j = 0; j < priv->width * priv->height / 64; j++)
{
if (priv->dc[j] > dcmax)
dcmax = priv->dc[j];
if (priv->dc[j] < dcmin)
dcmin = priv->dc[j];
if (priv->ac[j] > acmax)
acmax = priv->ac[j];
if (priv->ac[j] < acmin)
acmin = priv->ac[j];
}
// 数据范围处理
for (int k = 0; k < priv->width * priv->height / 64; k++)
{
priv->outdc[k] = (unsigned char)(255 * (priv->dc[k] - dcmin) / (dcmax - dcmin));
priv->outac[k] = (unsigned char)(255 * (priv->ac[k] - acmin) / (acmax - acmin));
}
fwrite(priv->outdc, 1, priv->width * priv->height / 64, DC_image);
fwrite(priv->outac, 1, priv->width * priv->height / 64, AC_image);
return 0;
}
对工程进行生成和运行,可得到实验输出结果:
实验中向txt文件中对JPEG文件的所有霍夫曼码表进行了输出。
其中包括:
实验中输出了原图像中的直流分量图像和交流分量图像,并对两图像的概率密度分布进行了绘图。
直流分量 | 交流分量 | |
---|---|---|
输出图像 | ||
概率分布 |