一.JPEG簡介
JPEG全稱Joint Photographic Experts Group(聯(lián)合圖像專家組),它是一項數(shù)字圖像壓縮標準(ISO/IEC 10918),1992年提出。
JPEG是一種有損壓縮的數(shù)字圖像技術(shù),它的核心算法是離散余弦變換(DCT)。
二.JPEG壓縮技術(shù)
JPEG編碼原理涉及到一些圖像處理的知識,強烈推薦先看一下:圖像與濾波。
JPEG編碼過程如下圖
[圖片上傳失敗...(image-c3fa31-1583587032871)]
解碼過程就是編碼的逆向操作
[圖片上傳失敗...(image-2ad7ec-1583587032871)]
2.1 塊切割
JPEG標準在處理圖片時會先把圖片分割成一個個8x8像素的方塊,后面的DCT、量化、熵編碼都是針對單個方塊的操作,編碼的產(chǎn)物是這些方塊的壓縮數(shù)據(jù)。壓縮數(shù)據(jù)經(jīng)過解碼還原成像素數(shù)據(jù),然后將一個個方塊拼成一整完整圖片的像素數(shù)據(jù)送給顯卡展示。
2.2 DCT
DCT(Discrete Cosine Transform)變換的全稱是離散余弦變換,它能將時域信號轉(zhuǎn)換成頻域信號,其中低頻部分集中在矩陣的左上角,高頻部分集中在矩陣的右下角。它是傅里葉變換的一種變種,相比傅里葉變換,DCT變換函數(shù)只用實數(shù),算法實現(xiàn)簡單,所以廣泛運用在圖片壓縮領(lǐng)域,JPEG就采用了二維DCT變換。
由于人眼對圖片中的低頻信息(色彩變化不明顯,如圖片的整體色調(diào),物體輪廓)比較敏感,對高頻信息不敏感(色彩變化劇烈,如物體的邊緣、人臉上的小斑點),因此我們可以利用DCT變換把圖片中高頻和低頻部分區(qū)分開來,然后將高頻部分的數(shù)據(jù)進行壓縮,這樣就達到壓縮圖片的功能。
二維DCT變換的公式為:
- F(u, v) 代表DCT變換后坐標(u, v)的頻率
- c(u)、c(v)可以認為是一個補償系數(shù),可以使DCT變換矩陣為正交矩陣
- f(i, j) 代表坐標(i, j)的像素數(shù)據(jù)
假如我們有一個8x8的像素數(shù)據(jù):
運用DCT公式,我們把像素信號轉(zhuǎn)換成如下頻率信號:
左上區(qū)域存儲的是低頻信號,右下區(qū)域存儲的是高頻信號
2.2 量化
下面我們用一個50% quantily的JPEG量化表將頻率數(shù)據(jù)量化:
量化公式如下:
- B(i, j) = round(G(i, j) / Q(i, j))
如:Q(0, 0) = round(-415.38 / 16) = -26
Q(0, 1) = round(-30.19 / 11) = -3
所謂量化其實就是將頻率/量化步長,將量化步長以內(nèi)的精度信息丟失??梢杂^察到上表的左上角數(shù)值小,右下角數(shù)值大,因此這張量化表的作用就是屏蔽高頻信息。最終的量化結(jié)果為:
2.3 熵編碼
熵編碼是一類編碼規(guī)范,它要求編碼過程中按熵原理不丟失任何信息。常見的熵編碼有:香農(nóng)編碼、哈夫曼編碼和算術(shù)編碼。
JPEG先用RLE(run-length encoding,游程編碼)編碼將圖像數(shù)據(jù)以“之字形”排列,如下圖,這樣可以盡可能的將頻率為0的數(shù)據(jù)存儲在一起。連續(xù)N個0,可以用一個0和一個長度N來表示,壓縮效果很好,然后將剩下的位置使用霍夫曼編碼。
以上就是JPEG編碼的重要步驟。解碼基本上就是上述步驟的逆向操作,就不多說了,這里只介紹一下IDCT
2.4 IDCT
IDCT即Inverse DCT,它就是DCT的逆向操作,將圖像的頻率數(shù)據(jù)轉(zhuǎn)換成像素數(shù)據(jù),公式如下:
- f(i, j) 坐標(i, j)的像素數(shù)據(jù)
- F(u, v) 坐標(u, v)的頻率數(shù)據(jù)
- c(u)、c(v)可以認為是一個補償系數(shù),可以使DCT變換矩陣為正交矩陣
還是看上面的例子,我們先做一下反量化操作,即乘一下步長就可以了:
接著我們將上圖的頻率數(shù)據(jù)代入IDCT公式,最終我們得到還原后的像素數(shù)據(jù):
三.Android實例
下面我們通過一個Android實例來看一下DCT轉(zhuǎn)換的效果
為了方便理解,我這里用一張Y分量的yuv圖片來演示,原圖如下
二維DCT變換公式其實是一個矩陣變換公式,上面的公式是它的求和形式,效率比較低。開發(fā)中一般直接用矩陣運算替代,公式如下:
其中X為yuv像素矩陣,Y為頻域信號矩陣
3.1 DCT變換
這里引用了apache的commons-math3庫來做矩陣運算
// dct變換
public static RealMatrix dct2(byte[] yuv, int N) {
// 拷貝N*N的Y分量
byte[] y = new byte[N * N];
System.arraycopy(yuv, 0, y, 0, y.length);
// 一維數(shù)組轉(zhuǎn)成二維數(shù)組
double[][] matrixData = MatrixUtils.toMatrixData(y, N);
// 構(gòu)造yuv矩陣
RealMatrix signalMatrix = new Array2DRowRealMatrix(matrixData);
// 獲取dct系數(shù)矩陣
RealMatrix dctMatrix = getDCTMatrix(N);
// 頻域矩陣 = dct系統(tǒng)矩陣 * yuv矩陣 * dct系數(shù)轉(zhuǎn)置矩陣
RealMatrix frequencyMatrix = dctMatrix.multiply(signalMatrix).multiply(dctMatrix.transpose());
return frequencyMatrix;
}
// 獲取dct系數(shù)矩陣
private static RealMatrix getDCTMatrix(int N) {
double matrixData[][] = new double[N][N];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
double factor = i == 0 ? Math.sqrt(1d / N) : Math.sqrt(2d / N);
matrixData[i][j] = factor * Math.cos((2 * j + 1) * i * Math.PI * 0.5d / N);
}
}
return new Array2DRowRealMatrix(matrixData);
}
變換之后效果如下:
3.2 IDCT變換
DCT反變換公式如下:
// dct反變換
public static double[][] idct2(RealMatrix frequencyMatrix, int N) {
// 獲取dct系數(shù)矩陣
RealMatrix dctMatrix = getDCTMatrix(N);
// 頻域矩陣 = dct系數(shù)轉(zhuǎn)置矩陣 * dct系統(tǒng)矩陣 * yuv矩陣
RealMatrix frequencyMatrix = dctMatrix.transpose().multiply(frequencyMatrix).multiply(dctMatrix);
return signalMatrix;
}
變換效果就是把上面的頻域信號轉(zhuǎn)成yuv信號,效果就跟原圖一樣(DCT變換過程是無損的,忽略運算過程中的誤差)
3.3 分塊DCT變換
將圖片分為一個個8x8的方塊,分別對這些方塊做dct變換,代碼就不展示了,直接看效果
3.4 量化
這里依然用JPEG 50% quantily量化表,效果如下
3.5 分塊反DCT變換
將量化后的一個個8x8方塊依次做IDCT變換,再拼成一整完整的圖片
左邊是原圖,右邊是量化之后的圖,仔細看還是能發(fā)現(xiàn)右圖有一些毛邊,細節(jié)上不如左圖清晰
項目地址:
Gitee:https://gitee.com/huaisu2020/Android-Live
Github:https://github.com/xh2009cn/Android-Live
參考文章:
http://www.ruanyifeng.com/blog/2017/12/image-and-wave-filters.html
https://en.wikipedia.org/wiki/JPEG