一.JPEG简介
JPEG全称Joint Photographic Experts Group(联合图像专家组),它是一项数字图像压缩标准(ISO/IEC 10918),1992年提出。
JPEG是一种有损压缩的数字图像技术,它的核心算法是离散余弦变换(DCT)。
二.JPEG压缩技术
JPEG编码原理涉及到一些图像处理的知识,强烈推荐先看一下:图像与滤波。
JPEG编码过程如下图
[图片上传失败...(image-c3fa31-1583587032871)]
解码过程就是编码的逆向操作
[图片上传失败...(image-2ad7ec-1583587032871)]
2.1 块切割
JPEG标准在处理图片时会先把图片分割成一个个8x8像素的方块,后面的DCT、量化、熵编码都是针对单个方块的操作,编码的产物是这些方块的压缩数据。压缩数据经过解码还原成像素数据,然后将一个个方块拼成一整完整图片的像素数据送给显卡展示。
2.2 DCT
DCT(Discrete Cosine Transform)变换的全称是离散余弦变换,它能将时域信号转换成频域信号,其中低频部分集中在矩阵的左上角,高频部分集中在矩阵的右下角。它是傅里叶变换的一种变种,相比傅里叶变换,DCT变换函数只用实数,算法实现简单,所以广泛运用在图片压缩领域,JPEG就采用了二维DCT变换。
由于人眼对图片中的低频信息(色彩变化不明显,如图片的整体色调,物体轮廓)比较敏感,对高频信息不敏感(色彩变化剧烈,如物体的边缘、人脸上的小斑点),因此我们可以利用DCT变换把图片中高频和低频部分区分开来,然后将高频部分的数据进行压缩,这样就达到压缩图片的功能。
二维DCT变换的公式为:
- F(u, v) 代表DCT变换后坐标(u, v)的频率
- c(u)、c(v)可以认为是一个补偿系数,可以使DCT变换矩阵为正交矩阵
- f(i, j) 代表坐标(i, j)的像素数据
假如我们有一个8x8的像素数据:
运用DCT公式,我们把像素信号转换成如下频率信号:
左上区域存储的是低频信号,右下区域存储的是高频信号
2.2 量化
下面我们用一个50% quantily的JPEG量化表将频率数据量化:
量化公式如下:
- 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
所谓量化其实就是将频率/量化步长,将量化步长以内的精度信息丢失。可以观察到上表的左上角数值小,右下角数值大,因此这张量化表的作用就是屏蔽高频信息。最终的量化结果为:
2.3 熵编码
熵编码是一类编码规范,它要求编码过程中按熵原理不丢失任何信息。常见的熵编码有:香农编码、哈夫曼编码和算术编码。
JPEG先用RLE(run-length encoding,游程编码)编码将图像数据以“之字形”排列,如下图,这样可以尽可能的将频率为0的数据存储在一起。连续N个0,可以用一个0和一个长度N来表示,压缩效果很好,然后将剩下的位置使用霍夫曼编码。
以上就是JPEG编码的重要步骤。解码基本上就是上述步骤的逆向操作,就不多说了,这里只介绍一下IDCT
2.4 IDCT
IDCT即Inverse DCT,它就是DCT的逆向操作,将图像的频率数据转换成像素数据,公式如下:
- f(i, j) 坐标(i, j)的像素数据
- F(u, v) 坐标(u, v)的频率数据
- c(u)、c(v)可以认为是一个补偿系数,可以使DCT变换矩阵为正交矩阵
还是看上面的例子,我们先做一下反量化操作,即乘一下步长就可以了:
接着我们将上图的频率数据代入IDCT公式,最终我们得到还原后的像素数据:
三.Android实例
下面我们通过一个Android实例来看一下DCT转换的效果
为了方便理解,我这里用一张Y分量的yuv图片来演示,原图如下
二维DCT变换公式其实是一个矩阵变换公式,上面的公式是它的求和形式,效率比较低。开发中一般直接用矩阵运算替代,公式如下:
其中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);
// 一维数组转成二维数组
double[][] matrixData = MatrixUtils.toMatrixData(y, N);
// 构造yuv矩阵
RealMatrix signalMatrix = new Array2DRowRealMatrix(matrixData);
// 获取dct系数矩阵
RealMatrix dctMatrix = getDCTMatrix(N);
// 频域矩阵 = dct系统矩阵 * yuv矩阵 * dct系数转置矩阵
RealMatrix frequencyMatrix = dctMatrix.multiply(signalMatrix).multiply(dctMatrix.transpose());
return frequencyMatrix;
}
// 获取dct系数矩阵
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系数矩阵
RealMatrix dctMatrix = getDCTMatrix(N);
// 频域矩阵 = dct系数转置矩阵 * dct系统矩阵 * yuv矩阵
RealMatrix frequencyMatrix = dctMatrix.transpose().multiply(frequencyMatrix).multiply(dctMatrix);
return signalMatrix;
}
变换效果就是把上面的频域信号转成yuv信号,效果就跟原图一样(DCT变换过程是无损的,忽略运算过程中的误差)
3.3 分块DCT变换
将图片分为一个个8x8的方块,分别对这些方块做dct变换,代码就不展示了,直接看效果
3.4 量化
这里依然用JPEG 50% quantily量化表,效果如下
3.5 分块反DCT变换
将量化后的一个个8x8方块依次做IDCT变换,再拼成一整完整的图片
左边是原图,右边是量化之后的图,仔细看还是能发现右图有一些毛边,细节上不如左图清晰
项目地址:
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