JPEG Decoder (1/3)
簡介
Baseline JPEG Baseline編碼時依序將經過下面流程:
- RGB to Y'CbCr
- 2-D DCT
- Quantization
- Huffman encoding
最後Header、Huffman table、Quantization table和資料一起包裝成jpg。

出自An Introduction to Image Compression
1. RGB-to-Y'CbCr
參考 YCbCr: JPEG conversion,LenaRGB.raw
為 512x512 24bit RGB影像。
import numpy as np
def rgb2yuv(rgb):
rgb = rgb.astype(np.float64)
m = np.array([
[0.29900, -0.168736, 0.5],
[0.58700, -0.331264, -0.418688],
[0.11400, 0.5, -0.081312]
])
yuv = np.dot(rgb, m)
yuv[:, :, 1:] += 128
return yuv.astype(np.uint8)
def yuv2rgb(yuv):
yuv = yuv.astype(np.float64)
yuv[:, :, 1:] -= 128
m = np.array([
[1.000, 1.000, 1.000],
[0.000, -0.344136, 1.772],
[1.402, -0.714136, 0.000],
])
rgb = np.dot(yuv, m)
return rgb.astype(np.uint8)
with open("./LenaRGB.raw", "rb") as f:
original = [f.read(1)[0] for i in range(512*512*3)]
original = np.array(original, dtype=np.uint8).reshape((512, 512, 3))
image = original[:, :, :].copy()
img_yuv = rgb2yuv(image)
img_rgb = yuv2rgb(img_yuv)
Diff = np.abs(img_rgb.astype(np.int16) - image.astype(np.int16))
for idx, color in enumerate(("Red", "Green", "Blue")):
print(color)
unique, counts = np.unique(Diff[:,:,idx], return_counts=True)
for d, c in np.asarray((unique, counts)).T:
print(d, c , '{:.3f}%'.format(c/512**2*100))
assert np.allclose(image, img_rgb, atol=3, rtol=0)
測試,雖然肉眼分不出還原的圖與原圖差異,但從數據看來,值差異最多到3,以藍色最為明顯。造成誤差的原因是float、uint8間的轉換誤差。
DCT & IDCT
先定義JPEG使用的DCT式子,再推導出1D-DCT和2D-DCT。
DCT-II為最廣泛使用的DCT形式之一,用於JPEG壓縮,DCT依據定義不同而有不同形式。DCT-II式子如下:
為了在其化為矩陣時成為orthogonal matrix ,乘上 scaling factor: f 化為 orthonormalized DCT-II。

DCT-III為DCT-II的inverse,orthonormalized DCT-III如下:
現在簡單驗證一下乘上、係數不會影響到轉換,並以scipy.fftpack.dct
驗證結果是否一致。scipy.fftpack.dct
有提供幾個參數,設置norm='ortho'
使矩陣係數orthonormal。
結果符合預期。
1-D DCT
試著將DCT化成矩陣形式
- ,第k項元素代表
- 是NxN矩陣,為轉換時的係數,稱為DCT matrix
隨意列舉幾個DCT matrix:
不難看出是orthogonal matrix,根據orthogonal matrix的特性:。
在進行反轉換時原本要求的可代換為,得到。
驗證的orthogonality:
- 由於浮點數精度的問題,用
np.allclose
容忍一定範圍內的誤差
- orthogonal matrix
- 實數矩陣
- 方陣
- 行列皆為orthogonal unit vectors (orthonormal vectors)
2-D DCT
2-D DCT其中一個特性是seperable,也就是說可拆分成兩個1-D DCT
- 垂直方向對column進行DCT
- 水平方向對row進行DCT
以矩陣表示之
- 為image matrix
- 為經過2-D DCT的image matrix
以8x8 DCT matrix大小為例,兩次矩陣乘法的運算量十分驚人,如果我們將及進行向量化成一維矩陣(以表示),可以用Kronecker product將合併成一個矩陣。Kronecker product又被稱作matrix direct product,運算子,不具交換性,定義如下:


與向量化結合
依據以上式子改寫原式:
事先計算可增加DCT轉換效率。numpy
可協助做到Kronecker product
JPEG是由一小片一小片壓縮後的影像組合而成,以8x8 pixels為單位,進行量化、編碼,稱為Minimum Coded Unit (MCU)。因此2D-DCT矩陣以及量化矩陣也是8x8。
接著使用scipy.fftpack.dct
檢驗依dct_2
生成的8x8 2D-DCT矩陣是否一致,dct_2
是我們的JPEG Encoder用來計算2-D DCT的函式:
假設為8x8:
3. Quantization
量化是為了去除視覺上不重要的資訊。quality factor越高,生成的量化矩陣(quantization table)就多元素接近1,經過DCT轉換的區塊除上量化矩陣,保留的資料就越多。各家相機廠商使用的量化矩陣,皆有所不同。
實作上由於quality factor生成的量化矩陣可能有元素為0,需要將結果限制在[0,255]
import numpy as np
qt_luminance = np.array([
[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],
])
qt_chrominance = np.array([
[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],
])
def Quantize(input, qf, jpeg_quantiz_matrix):
factor = 5000/qf if (qf < 50) else 200-2*qf
jpeg_quantiz_matrix = np.maximum(np.floor(jpeg_quantiz_matrix*factor/100+.5),
np.ones((8, 8)))
print("QF={}".format(qf))
print(np.array2string(np.round(jpeg_quantiz_matrix, 3), prefix=''))
return (input // jpeg_quantiz_matrix)
Quantize(np.eye(8), 100, qt_luminance)
Quantize(np.eye(8), 95, qt_luminance)
Quantize(np.eye(8), 80, qt_luminance)
Standard JPEG quantization tables (Luminance)在不同quality factor下的量化矩陣
Reference