JPEG编解码原理

一.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变换的公式为:

image
  • F(u, v) 代表DCT变换后坐标(u, v)的频率
  • c(u)、c(v)可以认为是一个补偿系数,可以使DCT变换矩阵为正交矩阵
  • f(i, j) 代表坐标(i, j)的像素数据



假如我们有一个8x8的像素数据:

image

运用DCT公式,我们把像素信号转换成如下频率信号:

image

左上区域存储的是低频信号,右下区域存储的是高频信号

2.2 量化

下面我们用一个50% quantily的JPEG量化表将频率数据量化:

image

量化公式如下:

  • 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

所谓量化其实就是将频率/量化步长,将量化步长以内的精度信息丢失。可以观察到上表的左上角数值小,右下角数值大,因此这张量化表的作用就是屏蔽高频信息。最终的量化结果为:

image

2.3 熵编码

熵编码是一类编码规范,它要求编码过程中按熵原理不丢失任何信息。常见的熵编码有:香农编码、哈夫曼编码和算术编码。
JPEG先用RLE(run-length encoding,游程编码)编码将图像数据以“之字形”排列,如下图,这样可以尽可能的将频率为0的数据存储在一起。连续N个0,可以用一个0和一个长度N来表示,压缩效果很好,然后将剩下的位置使用霍夫曼编码。

image

以上就是JPEG编码的重要步骤。解码基本上就是上述步骤的逆向操作,就不多说了,这里只介绍一下IDCT

2.4 IDCT

IDCT即Inverse DCT,它就是DCT的逆向操作,将图像的频率数据转换成像素数据,公式如下:

image
  • f(i, j) 坐标(i, j)的像素数据
  • F(u, v) 坐标(u, v)的频率数据
  • c(u)、c(v)可以认为是一个补偿系数,可以使DCT变换矩阵为正交矩阵

还是看上面的例子,我们先做一下反量化操作,即乘一下步长就可以了:

image

接着我们将上图的频率数据代入IDCT公式,最终我们得到还原后的像素数据:

image

三.Android实例

下面我们通过一个Android实例来看一下DCT转换的效果
为了方便理解,我这里用一张Y分量的yuv图片来演示,原图如下

image

二维DCT变换公式其实是一个矩阵变换公式,上面的公式是它的求和形式,效率比较低。开发中一般直接用矩阵运算替代,公式如下:

image
image

其中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);
    }    

变换之后效果如下:

JPEG编解码原理_第1张图片
image

3.2 IDCT变换

DCT反变换公式如下:

image
 // 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变换,代码就不展示了,直接看效果

image

3.4 量化

这里依然用JPEG 50% quantily量化表,效果如下

image

3.5 分块反DCT变换

将量化后的一个个8x8方块依次做IDCT变换,再拼成一整完整的图片


JPEG编解码原理_第2张图片
image

左边是原图,右边是量化之后的图,仔细看还是能发现右图有一些毛边,细节上不如左图清晰

项目地址:
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

你可能感兴趣的:(JPEG编解码原理)