ComicEnhancerPro 系列教程十八:JPG文件長度與質量


作者:馬健
郵箱:stronghorse_mj@hotmail.com
主頁:http://www.comicer.com/stronghorse/
發布:2017.07.23

教程十八:JPG文件長度與質量

眾所周知,JPG是一種“有損”壓縮格式,與PNG等無損壓縮格式相比,最大的問題是:如果反復壓縮,會造成圖像質量逐漸退化。所以在對JPG文件進行處理,並且輸出仍然選擇JPG格式的情況下,很多人都會問同樣的一個問題:如何才能在盡情享受有損壓縮帶來的較小文件長度的便利前提下,盡量避免圖像質量退化?

為了解決這個問題,從v4.13開始,除傳統的JPG質量系數外,在CEP中還允許對JPEG質量進行更精細的控制,界面如下圖所示:


圖1

總體來說,允許對三個方面的參數進行控制:

  1. 是否使用原JPG文件的質量系數,其實就是是否要復制原JPG文件的量化表(Quantization Tables)。如果您覺得轉存后的JPG文件長度與原JPG文件長度相差太多,請勾選此項。
  2. 顏色采樣,就是指定是否要進行子采樣(sub-sampling),或通俗點說壓縮的時候是否要把顏色信息丟掉一半或3/4,解碼的時候再用插值法找補回來。如果丟,毫無疑問文件長度會減小,但顏色退化就是不可避免的;如果不丟,顏色是保住了,但文件長度就要大一些。“自動”的解釋見CEP使用說明,其實就是學Photoshop,根據用戶指定的質量系數判斷在用戶的心目中是質量更重要還是文件長度更重要,然后自動決定是否 對顏色縮水。
  3. 是否對JPG進行優化編碼。這個對JPG文件質量無影響,但對文件長度有影響,參見教程二十的量化測試。

如果三個“優先”都被勾選,那么當您用CEP打開一個JPG文件,然后什么也不做就另存為另外一個JPG文件,則兩個文件的長度和質量應該差別都不大——注意我說的是“不大”,不是“沒有”。長度差異一個DIR命令就知道,圖像差異可以用眼睛看,也可以用CEP v4.13開始提供的“圖像比較”功能進行量化比較,詳見教程二十

如果想深入了解這些參數究竟如何影響JPG文件長度與質量,可以閱讀著名小眾軟件JPEGsnoop的作者Calvin Hass寫的系列文章,以下是其中關鍵的幾篇:
JPEG Compression, Quality and File Size
JPEG Color Space Conversion Error
JPEG Chroma Subsampling
JPEG Compression Quality from Quantization Tables
JPEG Huffman Coding Tutorial
What is an Optimized JPEG?

為便於后續理解,我先從上面第一篇文章中摘取對JPG壓縮過程的說明,做無責任翻譯(“無責任”的意思就是胡言亂語,各路專家不必較真,也謝絕較真):

  1. 色彩空間轉換(Color Space Conversion)。彩色圖像首先要經歷色彩空間轉換,從RGB(紅/綠/藍)轉換成YCbCr(亮度/藍色度/紅色度)。這種色彩空間轉換促使后續過程使用不同的量化表(quantization tables),其中一個量化表用於亮度(luminance),另一個量化表用於色度(chrominance)。譯注:除RGB轉YCbCr外,JPEG中還有一種常見轉換是CMYK色彩空間轉YCCK色彩空間。灰度圖像沒有色彩信息,也就不存在色彩空間轉換。
  2. 分段切塊(Segmentation into Blocks)。原始圖像數據被按照8×8的像素尺寸切分成小塊,稱為最小編碼單元(Minimum Coded Unit,MCU)。這意味着JPEG壓縮算法嚴重依賴於這些塊邊界的位置和排列。
  3. 離散余弦變換(Discrete Cosine Transformation,DCT)。圖像從空間域(空域)轉換成頻率域(頻域)表示。這可能是整個過程中最亂和最難以理解的一步。簡單而言,圖像內容被轉換成用一系列諧波(正弦波)合成的數學表示。例如,二進制串 101010 可以表示為每兩個像素重復一次的諧波,串 1100110011可以表示為每4個像素重復一次,串 1001101011可以表示為幾個諧波之和。現在請想象沿着X和Y方向做這種波動方程(也稱為DCT基函數)的映射。
  4. 量化(Quantization)。在DCT步驟中生成的波動方程,按照從低頻成分(在圖像塊中要跨越很長距離才發生變化的部分)到高頻成分(可能每個像素都會有變化的成分) 的順序存儲。眾所周知人眼對低頻信息的誤差要比對高頻信息的更敏感。JPEG壓縮算法拋棄了很多這種高頻(多半是噪聲)細節,同時保持變化緩慢的圖像信息。這個過程是通過把所有方程系數按照量化表中的對應值進行分類,然后再圓整到最近的整數值。具有較小的系數或在量化表中有較大除數的成分將被圓整成零。質量設置得越低,量化表中的除數越大,導致被圓整成零的可能性也越大。反之,最高的質量設置將產生全部值都是1的量化表,意味着所有DCT數據都可以保留。
    這里需要注意的重要一點是此步驟所使用的量化表,對幾乎所有數碼相機和軟件包而言都是不同的(譯注:在IJG代碼中,質量系數100%將產生全1的量化表。PS軟件即使最高質量12也產生不了全1的量化表,相機固件就更不用說,要追求無損不如用RAW,可以參閱教程十九中給出的數據,或主要相機廠家、IrfanView的量化表)。由於這是對壓縮或重新壓縮所產生的“誤差”貢獻最大的因素,因此在不同的壓縮器/源之間轉存時都要忍受 (量化表不一致所帶來的)圖像退化的痛苦。相機廠家獨立地選擇一個任性的“圖像質量”名稱(或級別)來代表他們所設計的64值量化矩陣,因此在不同的廠家甚至相同的廠家之間都不能 按名字進行比較(如佳能的“Fine”與尼康的“Fine”)。
  5. “之”字掃描(Zigzag Scan)。量化產生的矩陣會有很多零。質量設置越低,矩陣中的零就越多。把矩陣從左上角開始按照“之”字掃描,重新排列成64元素的向量,其順序將是從低頻成分到高頻成分。由於高頻成分最有可能被圓整成零, 因此這個64元素的向量通常以一串零結尾。這對下一步非常重要。
  6. 直流分量上的差分脈沖調制編碼(DPCM on DC component)。在前述分塊基礎上,均值(整個8×8塊的均值,直流分量)的變化被按照與前一個塊的均值之差進行編碼。這被稱為差分脈沖調制編碼(Differential Pulse Code Modulation,DPCM)。
  7. 交流分量上的行程編碼(RLE on AC components)。對於64值向量中的每一個值(交流分量),采用行程編碼(Run Length Encoding,RLE)來存儲。由於1×64向量中含有大量的零,高效的存儲方法是存非零值及非零值之間零的數量。RLE存儲的是“跳過”(skip)與“值”(value),其中“跳過”是 在分量之前零的數量,“值”是下一個非零分量。
  8. 熵編碼/霍夫曼編碼(Entropy Coding / Huffman Coding)。創建一個字典,用來對經常用到的值串進行縮位表示。常用串/模板使用較短的編碼(只有幾個bit的編碼),而不常使用的串則采用較長的編碼。所用的字典(霍夫曼表,Huffman table)存儲在文件里,因此很容易查找字典把編碼后的bit串恢復成原始值,參見JPEG Huffman Coding tutorial

如果上面的翻譯還是看得眼暈,可以再去看看hesays寫的中文文章《JPEG圖像編碼解碼》,或《常用多媒體文件格式與壓縮標准解析》(姜楠,王健編著,電子工業出版社,2005.05,SSID=11417553)的第5章。

對於JPEG有損壓縮的誤差來源,Calvin Hass認為最大的來源是量化過程。用戶可以通過選擇壓縮系數或質量系數對量化表進行控制,從而影響最終JPEG文件的文件長度和質量。其他來源,如色彩空間轉換的舍入誤差,其重要性都不如量化誤差。因此對JPEG進行無損轉存的唯一方法是不要經過解壓過程,直接對MCU進行操作以避開量化過程。在IJG提供的命令行程序jpegtran中,就是通過直接操作MCU實現JPEG文件的無損90度旋轉、 鏡像翻轉、裁剪(左上角必須是MCU的邊界)等,我懷疑收費軟件cPicture中的JPEG無損旋轉、裁剪就是學它的,只不過加了圖形界面。

我個人的看法:

  1. JPG文件的質量退化源自其“有損”壓縮過程,其中的關鍵環節包括:色彩空間轉換(從RGB色彩空間轉換至YCbCr色彩空間, 從CMYK色彩空間轉換至CYYK色彩空間)、色彩信息縮水(我一直覺得sub-sampling翻譯成“子采樣”實在太過文縐縐,聽起來蛋疼得緊,既然現在不是在寫學術論文,以后還是意譯成更通俗的“縮水”吧)、離散余弦變換(DCT)結果通過量化表進行量化
  2. 色彩空間轉換的誤差來源是浮點運算后的取整圓整(轉換公式請百度“RGB轉YCbCr”), 即計算過程中用到了浮點數,但最終結算結果要圓整成不超過255的整數。這是人無法控制的事情,即使您把JPG質量系數設置為100%或Photoshop中的12,也管不到這一步。所以指望常規JPG可以無損壓縮的人,看到這里 就可以死心了。 當然這種誤差可能會小到肉眼看不出來的程度,參見教程二十里的測試。
  3. 色彩信息縮水是基於這樣一個心理學模型:與色彩的變化相比,人類對明暗的對比或變化更敏感。所以JPG壓縮的第一步就是色彩空間轉換,即把色彩與明暗混為一體的RGB色彩空間轉換成明暗、色彩分離的YCbCr色彩空間,其中Y是明暗 (亮度)信息,Cb、Cr是顏色(色度)信息,然后在編碼時就可以把Cb、Cr信息沿縱、橫方向縮減以減少需編碼的信息量從而減小文件長度,解碼時再用插值算法恢復成原大小,反正插值誤差肉眼也不大看得出來。當然這種縮減也是講檔次的,比較有追求的就干脆不縮減,比如Photoshop里質量7以上 或CEP里質量系數90%以上;稍微差一點的就只在一個方向上進行縮減,另外一個方向不變,即顏色信息量縮減為1/2,比如尼康、佳能相機直出的JPEG文件 或CEP里質量系數70%~89%;比較不講究的就在縱、橫方向上同時縮減為原來的1/2,即信息量縮減為原來的1/4以獲得最小文件長度,比如手機,Photoshop里質量1~6,或早期版本的CEP就是這么玩的。
    當然,以上兩點只有彩色圖像才會有,灰度圖像沒有色彩信息,自然不需要轉換或縮減色彩。
    Photoshop、相機、手機的色度縮水系數參見《教程十九:用JpegQuality看JPG文件的壓縮參數》
    在Calvin Hass的其它文章中都提到了色度縮水的影響,但不知道為什么在JPEG Compression, Quality and File Size中沒有提。
  4. 常規JPG質量系數控制的其實是DCT量化表,即把質量系數按公式轉換成一個倍率系數(factor),與JPG標准里推薦的標准量化表相乘,作為量化時的除數。所以如果 能解碼出JPG文件里存放的量化表,並且知道從質量系數到量化表的計算過程,其實是可以反算出壓縮JPG文件時所選的質量系數的。但實際上只有開源的代碼庫 (如IJG)才真正知道其計算過程,商業的Photoshop等反算出來的質量系數只能說是“參考”。但即使是僅供參考的質量系數,也馬馬虎虎可以用來比較兩個文件最后一次壓縮的時候誰壓得更狠一點。
  5. DCT量化之后的編碼過程對圖像質量無影響(參見教程二十的證明),但優化編碼、漸進(progressive)編碼在一定程度上可以減小文件長度,只不過編碼時要先額外對圖像掃描一遍甚至多遍才能找准優化方向,所以耗時略長。

JPEGsnoop的作者Calvin Hass更進一步認為:

  1. 如果轉存JPG文件時繼續使用原JPG文件的量化表、色彩縮減倍數,則可以認為轉存過程是無損(Recompress Losslessly)。這就是在CEP的JPG選項里“優先使用原JPG質量系數”、“優先使用原JPG采樣參數”的含義。當然從我個人的理解,這個“無損”其實也不是真正的無損,比如原JPG的色彩信息是縱、橫各縮水一半,轉存時仍然要再次縮水,您說是有損還是無損?所以按照我的理解,應該是這樣說:如果繼續沿用原JPG文件的量化表、色彩縮減倍數,可以在轉存時獲得與原JPG文件差不多的文件長度和質量。 具體證明過程請參見教程二十
  2. 因為很多軟件、相機所使用的量化表和色彩縮水倍數都各有其特色,所以Calvin Hass認為這代表了JPG生成器的指紋(fingerprint), 在JPEGsnoop中也用這兩個參數來識別生成JPG文件的軟件、相機,其他人則用這個功能來識別圖像是原生的還是被P過的。不過我個人覺得JPEGsnoop的輸出實在是太令人眼花繚亂了,所以做了一個簡化的軟件JpegQuality,並且與CEP做了集成,可以直接從CEP啟動以查看當前JPG文件的壓縮參數 ,包括質量系數、顏色縮水倍數等,詳見《教程十九:用JpegQuality看JPG文件的壓縮參數》

針對上面的第1點,除CEP外,在我的其它一些軟件中也采用了類似的技術進行JPG文件轉存,包括:

  • Pdg2Pic:在“JPG文件修復”中,以前要手工指定重新壓縮的JPG質量系數,從v4.09開始改成從原JPG文件中讀取量化表、顏色縮水倍數,不再需要手工指定JPG質量系數。如果從源JPG文件中讀取不到這些參數,只能說JPG文件損壞得很徹底,誰來了也修不好了。
  • FreePic2Pdf:Acrobat(我測試的版本是Adobe Acrobat XI Pro)對PDF中嵌入的JPG圖像支持是有限的,某些用IJG能正常解碼的JPG文件如果直接嵌入PDF,在Acrobat中解碼時會出現錯位。在將圖像轉成PDF時,如果檢測到這種文件,就需要重新編碼,然后再嵌入PDF。重新編碼后自然希望文件長度、質量基本不變。
  • PdfToy:在導出PDF中的JPG圖像時,有時希望實現自動反相(反白),否則直接導出的圖像可能是黑漆漆一片。這個完全無損是不可能的,只能是老老實實解碼、反白、重新編碼,仍然要求重新編碼后文件長度、質量基本不變。

在上面的敘述中,JPG質量系數即量化表對JPG質量的影響可能大家還比較好理解,但顏色縮水對質量的影響可能就有人要較真了。這里提供一個例子,先是原圖(出自MyReader使用說明書的插圖):


圖2

在CEP中打開后JPG質量系數點到95,顏色采樣方式選“自動”,其實就是不縮水,另存為JPG后的結果:


圖3

在JPG質量系數不變的情況下,只是把顏色采樣方式從“自動”改成“手動”,然后勾選“水平方向縮一半”和“垂直方向縮一半”,則效果變成:


圖4

把圖片從網頁里另存出來,用看圖軟件來回切換着看,可以看出顏色縮水后文件長度確實比不縮水的要小,但即使質量系數95%,紅色仍然顯得黯淡了許多(圖4)。而顏色不縮水的JPG圖像與原始PNG圖像相比則肉眼基本看不出差距 (圖3)。但不論是否縮水,JPG文件長度都比原PNG文件(圖2)更大,這是因為我故意選擇了一張不適合用JPG文件格式存儲的人工(artificial)圖像,一方面其中的漸變部分更容易看出差距,另一方面是想說明JPG格式並不是包打天下的,總有其適用范圍(如顏色數較多的自然圖像,包括風光、人物照片等),也有其不適用的范圍(如顏色數較少的人工圖像),算是給狂熱的 堅持JPG格式可以包打天下的JPG偏執狂們澆點冷水。

雜七雜八寫到這里,可能還是有人拎不清在CEP里究竟應該怎么設置JPG質量,以下是一些建議:

  • CEP的設計目標本來就不是給專家用的,所以如果實在拎不清,就還是用缺省設置吧,在大多數情況下可以獲得相對均衡的結果:高質量系數時確保顏色無損失,低質量系數時則文件長度優先。
  • 如果處理從PDG轉出來的JPG后感覺文件長度膨脹得太厲害,有點難以接受,就把三個“優先”選項全部都勾選。CX的JPG質量系數不是一般人敢選的,還是直接copy它吧。
  • 如果您真的對JPG質量很在意,就保持其它選項為缺省,尤其是“顏色采樣方式”一定要選“自動”,然后增加JPG質量系數,直到100%。
  • 如果想嘗試不同的顏色縮減比例的效果,可以把“顏色采樣方式”改成“手動”,然后改變“水平方向縮一半”、“垂直方向縮一半”選項試試看。

附錄 IJG中從質量系數到量化表的計算過程的源代碼

在使用IJG把圖像壓縮成JPG時,通常調用下面的代碼設置質量系數(出自IJG的示例源代碼cjpeg.c)

 /* Set quantization tables for selected quality. */
/* Some or all may be overridden if -qtables is present. */
jpeg_set_quality(cinfo, quality, force_baseline);

而jpeg_set_quality函數及其所調用函數的實現如下:

GLOBAL(void)
jpeg_set_quality (j_compress_ptr cinfo, int quality, boolean force_baseline)
/* Set or change the 'quality' (quantization) setting, using default tables.
* This is the standard quality-adjusting entry point for typical user
* interfaces; only those who want detailed control over quantization tables
* would use the preceding three routines directly.
*/
{
/* Convert user 0-100 rating to percentage scaling */
quality = jpeg_quality_scaling(quality);

/* Set up standard quality tables */
jpeg_set_linear_quality(cinfo, quality, force_baseline);
}
GLOBAL(int)
jpeg_quality_scaling (int quality)
/* Convert a user-specified quality rating to a percentage scaling factor
* for an underlying quantization table, using our recommended scaling curve.
* The input 'quality' factor should be 0 (terrible) to 100 (very good).
*/
{
/* Safety limit on quality factor. Convert 0 to 1 to avoid zero divide. */
if (quality <= 0) quality = 1;
if (quality > 100) quality = 100;

/* The basic table is used as-is (scaling 100) for a quality of 50.
* Qualities 50..100 are converted to scaling percentage 200 - 2*Q;
* note that at Q=100 the scaling is 0, which will cause jpeg_add_quant_table
* to make all the table entries 1 (hence, minimum quantization loss).
* Qualities 1..50 are converted to scaling percentage 5000/Q.
*/
if (quality < 50)
quality = 5000 / quality;
else
quality = 200 - quality*2;

return quality;
}
GLOBAL(void)
jpeg_set_linear_quality (j_compress_ptr cinfo, int scale_factor,
boolean force_baseline)
/* Set or change the 'quality' (quantization) setting, using default tables
* and a straight percentage-scaling quality scale. In most cases it's better
* to use jpeg_set_quality (below); this entry point is provided for
* applications that insist on a linear percentage scaling.
*/
{
/* These are the sample quantization tables given in JPEG spec section K.1.
* The spec says that the values given produce "good" quality, and
* when divided by 2, "very good" quality.
*/
static const unsigned int std_luminance_quant_tbl[DCTSIZE2] = {
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68, 109, 103, 77,
24, 35, 55, 64, 81, 104, 113, 92,
49, 64, 78, 87, 103, 121, 120, 101,
72, 92, 95, 98, 112, 100, 103, 99
};
static const unsigned int std_chrominance_quant_tbl[DCTSIZE2] = {
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
};

/* Set up two quantization tables using the specified scaling */
jpeg_add_quant_table(cinfo, 0, std_luminance_quant_tbl,
scale_factor, force_baseline);
jpeg_add_quant_table(cinfo, 1, std_chrominance_quant_tbl,
scale_factor, force_baseline);
}
GLOBAL(void)
jpeg_add_quant_table (j_compress_ptr cinfo, int which_tbl,
const unsigned int *basic_table,
int scale_factor, boolean force_baseline)
/* Define a quantization table equal to the basic_table times
* a scale factor (given as a percentage).
* If force_baseline is TRUE, the computed quantization table entries
* are limited to 1..255 for JPEG baseline compatibility.
*/
{
JQUANT_TBL ** qtblptr;
int i;
long temp;

/* Safety check to ensure start_compress not called yet. */
if (cinfo->global_state != CSTATE_START)
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);

if (which_tbl < 0 || which_tbl >= NUM_QUANT_TBLS)
ERREXIT1(cinfo, JERR_DQT_INDEX, which_tbl);

qtblptr = & cinfo->quant_tbl_ptrs[which_tbl];

if (*qtblptr == NULL)
*qtblptr = jpeg_alloc_quant_table((j_common_ptr) cinfo);

for (i = 0; i < DCTSIZE2; i++) {
temp = ((long) basic_table[i] * scale_factor + 50L) / 100L;
/* limit the values to the valid range */
if (temp <= 0L) temp = 1L;
if (temp > 32767L) temp = 32767L; /* max quantizer needed for 12 bits */
if (force_baseline && temp > 255L)
temp = 255L; /* limit to baseline range if requested */
(*qtblptr)->quantval[i] = (UINT16) temp;
}

/* Initialize sent_table FALSE so table will be written to JPEG file. */
(*qtblptr)->sent_table = FALSE;
} 

從jpeg_quality_scaling函數可以看出,用戶指定的質量(quality)系數[1, 100]被分段線性映射成了[0, 1000),然后在jpeg_set_linear_quality函數中作為乘數與預設(推薦)的量化表相乘。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM