一、實驗原理
1. JPEG編碼原理
- DCT變換:能量守恒,能量集中,去相關
從框圖可以看出先進行電平偏移再進行8x8塊的DCT變換。level offset的原因在于Y的範圍為(0,255),而U、V的範圍為(-128,128),是以Y的電平應該減去128,再分為8x8的塊進行DCT變換。
DCT變換的基本思路是将圖像分解為8×8的子塊或16×16的子塊,并對每一個子塊進行單獨的DCT變換,然後對變換結果進行量化、編碼。随着子塊尺寸的增加,算法的複雜度急劇上升,是以,實用中通常采用8×8的子塊進行變換。
對每個單獨的彩色圖像分量,把整個分量圖像分成8×8的圖像塊,如圖所示,并作為兩維離散餘弦變換。
- 基于人眼特性量化DCT系數
選擇的是中平型均勻量化器,因為人眼對亮度信号比對色差信号更敏感,是以使用了兩種量化表:亮度量化值和色差量化值。
根據人眼的視覺特性(對低頻敏感,對高頻不太敏感)對低頻分量采取較細的量化,對高頻分量采取較粗的量化。如果原始圖象中細節豐富,則去掉的資料較多,量化後的系數與量化前差别較大。
- 對量化後的DCT系數F(u,v)資料進行熵編碼
DC系數編碼
由于直流系數 F(0,0)反映了該子圖像中包含的直流成分,通常較大,又由于兩個相鄰的子圖像的直流系數通常具有較大的相關性,是以對 DC 系數采用內插補點脈沖編碼(DPCM),即對本像素塊直流系數與前一像素塊直流系數的內插補點進行無損編碼。
AC系數編碼
AC編碼進行了之字形掃描:由于經DCT變換後,系數大多數集中在左上角,即低頻分量區,是以采用Z字形按頻率的高低順序讀出,可以出現很多連零的機會。可以使用遊程編碼。尤其在最後,如果都是零,給出EOB (End of Block)即可。
遊程編碼:系數序列分組,将非零系數和它前面的相鄰的全部零系數分在一組内;每組用兩個符号表示[(Run,Size),(Amplitude)],Amplitude:表示非零系數的幅度值;Run:表示零的遊程即零的個數;Size:表示非零系數的幅度值的編碼位數;
2. JPEG解碼
與編碼相反
過程 |
---|
解碼Huffman資料 |
解碼DC內插補點 |
重構量化後的系數 |
DCT逆變換 |
丢棄填充的行/列 |
反0偏置 |
對丢失的CbCr分量內插補點(下采樣的逆過程) |
YCbCr→RGB |
3. JPEG檔案格式
格式 | 全稱 | 說明 | 标記代碼 | 固定值 |
---|---|---|---|---|
SOI | Start of Image | 圖像開始 | 2位元組 | 0xFFD8 |
APP0 | Application | 應用程式保留标記0 | 2位元組 | 0xFFE0 |
DQT | Define Quantization Table | 定義量化表 | 2位元組 | 0xFFDB |
SOF0 | Start of Frame | 幀圖像開始 | 2位元組 | 0xFFC0 |
DHT | Define Huffman Table | 定義哈夫曼表 | 2位元組 | 0xFFC4 |
SOS | Start of Scan | 掃描開始 12位元組 | 2位元組 | 0xFFDA |
EOI | End of Image | 圖像結束 | 2位元組 | 0xFFD9 |
DQT 定義量化表
具體字段 | 位元組數 | 說明 |
---|---|---|
資料長度 | 2位元組 | 字段①和多個字段②的總長度 |
量化表 | 資料長度-2位元組 | 具體内容包括以下兩項 |
精度及量化表ID | 1位元組 | 高4位:精度,隻有兩個可選值(0:8位;1:16位) 低4位:量化表ID,取值範圍為0~3 |
表項 | (64×(精度+1))位元組 | 例如8位精度的量化表,其表項長度為64×(0+1)=64位元組 |
DHT,定義哈夫曼表
具體字段 | 位元組數 | 說明 |
---|---|---|
資料長度 | 2位元組 | |
huffman表 | 資料長度-2位元組 | 具體内容包括以下内容 |
表ID和表類型 | 1位元組 | 高4位:類型,隻有兩個值可選(0:DC直流;1:AC交流) 低4位:哈夫曼表ID,注意,DC表和AC表分開編碼 |
不同位數的碼字數量 | 16位元組 | |
編碼内容 | 16個不同位數的碼字數量之和(位元組) |
二、實驗流程
實驗流程 |
---|
逐漸調試JPEG解碼器程式。将輸入的JPG檔案進行解碼,将輸出檔案儲存為可供YUVViewer觀看的YUV檔案。 |
了解程式設計的整體架構。 |
了解三個結構體的設計目的。 struct huffman_table、struct component、struct jdec_private |
了解在視音頻編解碼調試中TRACE的目的和含義:會打開和關閉TRACE、會根據自己的要求修改TRACE。 |
以txt檔案輸出所有的量化矩陣和所有的HUFFMAN碼表。 |
輸出DC圖像并經過huffman統計其機率分布。 |
輸出某一個AC值圖像并統計其機率分布。 |
三、關鍵代碼分析
- 将輸出檔案儲存為可供YUVViewer觀看的YUV檔案
tinyjpeg.h
enum tinyjpeg_fmt {
TINYJPEG_FMT_GREY = ,
TINYJPEG_FMT_BGR24,
TINYJPEG_FMT_RGB24,
TINYJPEG_FMT_YUV420P,
TINYJPEG_FMT_YUV420ALL,//////////////////////////////////edit by lee
};
loadjpeg.c
int main(int argc, char *argv[])
{
int output_format = TINYJPEG_FMT_YUV420ALL;//将輸出的格式換為yuv檔案格式
...
input_filename = argv[current_argument];
if (strcmp(argv[current_argument+],"yuv420p")==)
output_format = TINYJPEG_FMT_YUV420P;
else if (strcmp(argv[current_argument+],"rgb24")==)
output_format = TINYJPEG_FMT_RGB24;
else if (strcmp(argv[current_argument+],"bgr24")==)
output_format = TINYJPEG_FMT_BGR24;
else if (strcmp(argv[current_argument+],"grey")==)
output_format = TINYJPEG_FMT_GREY;
else if (strcmp(argv[current_argument + ], "yuv420all") == )//add yuv file bu lee
output_format = TINYJPEG_FMT_YUV420ALL;
else
exitmessage("Bad format: need to be one of yuv420p, rgb24, bgr24, grey\n");
...
}
int load_multiple_times(const char *filename, const char *outfilename, int output_format)
{
......
/* Save it */
switch (output_format)
{
case TINYJPEG_FMT_RGB24:
case TINYJPEG_FMT_BGR24:
write_tga(outfilename, output_format, width, height, components);
break;
case TINYJPEG_FMT_YUV420P:
write_yuv(outfilename, width, height, components);
break;
case TINYJPEG_FMT_GREY:
write_pgm(outfilename, width, height, components);
case TINYJPEG_FMT_YUV420ALL://
write_pgm(outfilename, width, height, components);//add yuv file 2017 5 16 by lee
break;
}
......
}
int convert_one_image(const char *infilename, const char *outfilename, int output_format)
{
......
/* Save it */
switch (output_format)
{
case TINYJPEG_FMT_RGB24:
case TINYJPEG_FMT_BGR24:
write_tga(outfilename, output_format, width, height, components);
break;
case TINYJPEG_FMT_YUV420P:
/////////////////////////////////////////you can create yourself/////////////////////////////////
write_yuv(outfilename, width, height, components);
break;
case TINYJPEG_FMT_GREY:
write_pgm(outfilename, width, height, components);
case TINYJPEG_FMT_YUV420ALL:
write_yuv_file(outfilename, width, height, components);/////////add by lee
break;
}
}
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
......
switch (pixfmt) {
case TINYJPEG_FMT_YUV420ALL://add by lee
case TINYJPEG_FMT_YUV420P:
colorspace_array_conv = convert_colorspace_yuv420p;
if (priv->components[] == NULL)
priv->components[] = (uint8_t *)malloc(priv->width * priv->height);
if (priv->components[] == NULL)
priv->components[] = (uint8_t *)malloc(priv->width * priv->height/);
if (priv->components[] == NULL)
priv->components[] = (uint8_t *)malloc(priv->width * priv->height/);
bytes_per_blocklines[] = priv->width;
bytes_per_blocklines[] = priv->width/;
bytes_per_blocklines[] = priv->width/;
bytes_per_mcu[] = ;
bytes_per_mcu[] = ;
bytes_per_mcu[] = ;
break;
......
}
輸出yuv檔案
static void write_yuv_file(const char *filename, int width, int height, unsigned char **components)//output yuv file 2017 5 16 by lee
{
FILE *F;
char temp[];
snprintf(temp, , "%s.yuv", filename);
F = fopen(temp, "wb");
fwrite(components[], width, height, F);
fwrite(components[], width*height / , , F);
fwrite(components[], width*height / , , F);
}
- 以txt檔案輸出所有的量化矩陣和所有的HUFFMAN碼表
tinyjpeg.h
#define QTXT //add by lee 2017 5 16
#define QTXTFILE "quan_jpeg.txt"//add by lee 2017 5 16
tinyjpeg.c
輸出huffman碼表
static void build_huffman_table(const unsigned char *bits, const unsigned char *vals, struct huffman_table *table)
{
......
#if TRACE
fprintf(p_trace,"val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
fflush(p_trace);
#endif
#if QTXT
fprintf(quan, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
fflush(quan);
#endif
......
}
輸出量化碼表
static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
......
for (i=; i<; i++) {
for (j=; j<; j++) {
#if QTXTedit by lee 2017 5 16
fprintf(quan,"%d ",ref_table[*zz]); //ref_table[*zz]存的即為量化碼表,直接輸出
fflush(quan);
#endif
*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
}
#if QTXT/edit by lee 2017 5 16
fprintf(quan,"\n");
fflush(quan);
#endif
}
......
}
輸出txt檔案中DQT碼表的頭尾
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
......
#if TRACE
fprintf(p_trace,"> DQT marker\n");
fflush(p_trace);
#endif
#if QTXTedit by lee 2017 5 16
fprintf(quan, "> DQT marker\n");
fflush(quan);
#endif
#if TRACE
fprintf(p_trace,"< DQT marker\n");
fflush(p_trace);
#endif
#if QTXT//edit by lee 2017 5 16
fprintf(quan, "< DQT marker\n");
fflush(quan);
#endif
......
}
- 輸出DC圖像和某一個AC值圖像
tinyjpeg.h
void DCiamge(struct jdec_private *priv);//存儲DC系數
void ACiamge(struct jdec_private *priv);//存儲AC系數
void outputDCAC(struct jdec_private *priv);//輸出圖像
tinyjpeg.c
開空間以及調用函數
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
..................................................
priv->DCImage = (int *)malloc(sizeof(int)*priv->width * priv->height/);//edit by lee 2017 5 18
priv->ACImage = (int *)malloc(sizeof(int)*priv->width * priv->height/);//edit by lee 2017 5 18
outputDCAC(priv);//edit by lee 2017 5 20
.......................................
}
/*
* output DC
* edit by lee 2017 5 20
*/
static void DCiamge(struct jdec_private *priv)
{
static long i = ;//i表示圖像中DCT塊的個數
if (i<priv->height*priv->width / )//8x8的塊
{
priv->DCImage[i] = priv->component_infos[cY].DCT[];//cY=0
}
i++;
}
/*
* output AC
* edit by lee 2017 5 20
*/
static void ACiamge(struct jdec_private *priv)
{
static long int i = ;
if (i<priv->height*priv->width / )
priv->ACImage[i] = priv->component_infos[cY].DCT[];//此處選取的是DCT[64]的第二個ac系數,而其實取值為(1,63)皆可
i++;
}
/*
* output DCACimage
* edit by lee 2017 5 20
*/
static void outputDCAC(struct jdec_private *priv)
{
int i = ;
int dcmax, dcmin;//記錄下DC值中最大最小值友善歸一化
int acmax, acmin;//記錄下AC值中最大最小值友善歸一化
//此處歸一化有兩種方法,一種全部除以8,一種用最大最小值歸一化,此處選擇後者,保證比例準确
unsigned char *temp;//記錄歸一化後的值友善輸出
/*設定初值*/
dcmin = priv->DCImage[];
dcmax = priv->DCImage[];
acmin = priv->ACImage[];
acmax = priv->ACImage[];
temp = (unsigned char*)malloc(priv->height*priv->width / );//找出最大最小值并臨時存放值
for (i = ; i < (priv->height*priv->width / ); i++)
{
if (priv->DCImage[i] > dcmax)
dcmax = priv->DCImage[i];
if (priv->DCImage[i] < dcmin)
dcmin = priv->DCImage[i];
if (priv->ACImage[i] > acmax)
acmax = priv->ACImage[i];
if (priv->ACImage[i] < acmin)
acmin = priv->ACImage[i];
}//循環找出最大最小值
for (i = ; i < (priv->height*priv->width / ); i++)
{
temp[i] = (unsigned char) *(priv->DCImage[i] - dcmin) / (dcmax - dcmin);
}
fwrite(temp, , priv->width*priv->height / , DFILE);//DC系數以及寫出dc系數進DFILE
for (i = ; i < (priv->height*priv->width / ); i++)
{
temp[i] = (unsigned char)*(priv->ACImage[i] - acmin)/ (acmax - acmin);
}
fwrite(temp, , priv->width*priv->height / , AFILE);//DC系數以及寫出dc系數進DFILE
if (temp) free(temp);
}
類似的函數都需要在Y分量中加入DCiamge和ACiamge函數
static void decode_MCU_1x1_1plane(struct jdec_private *priv)
{
// Y
process_Huffman_data_unit(priv, cY);
DCiamge(priv);//edit by lee 2017 5 20
ACiamge(priv);//edit by lee 2017 5 20
IDCT(&priv->component_infos[cY], priv->Y, );
........
}
下省略
四、實驗結果
- DQT表和huffman編碼碼表
- DC系數的YUV圖像和AC系數之一的圖像
- 蚊子噪聲
高頻銳利截止,因為變換後的高頻系數很小,量化後值為0,高頻截止,導緻蚊子噪聲。
原始圖檔:
壓縮後的圖像
可以看出壓縮比越大,蚊子噪聲越明顯。