JPEG图像压缩探索_DCT变换的应用

目录

为什么写这篇文章:

一、傅里叶变换简介

二、DCT是什么

三、DCT在图像有损压缩中的应用

1、图像有损压缩的依据点:

2、使用DCT进行图像有损压缩的具体操作:

3、使用IDCT进行图像解压缩的具体操作:

三、DCT算法的Java实现_AndroidDemo的Gitee地址:

四、libjpeg中对应的DCT代码实现:

五、引用资料:


为什么写这篇文章:

        因为自从作为安卓客户端开发以来,开始接触到一些音频视频、图像处理方面的知识,逐渐激发了对其浓厚的兴趣,因此希望能从最底层的理论上开始学习并形成对它们的相关理论的个人理解,只有对这些底层数学知识和工程知识有充分的理解,才有可能未来成为该领域的专家,并做出自己的创新,而不是一直被别人的SDK牵着鼻子走。

        另外,其实作为一名Android客户端开发,假如有一天Android不行了,那么在安卓SDK的调用上积累的大部分经验就完全清0,我认为这对整个职业生涯是非常有害的,因此我认为我无论是处于兴趣,还是居安思危,我都必须去学知识架构比较稳定的知识。此时我发现如果结合兴趣来看的话,音频、视频、编解码、图形图像和AI是最接近我所想要的知识领域,他们都基于数学和物理的定理,不会像前端框架、客户端各种轮子、后端Spring框架等基于别人轮子上存活的领域那样很容易被推翻后,整个知识领域被推倒重来,所有花进去的时间随着框架的更新而再次变得从0开始,这就是不稳定的知识领域。但稳定的知识领域,无论是嵌入式、硬件开发、游戏开发、图形图像其知识是稳定可叠加的,每累积多一点知识,技能就增强一分,例如从普通的基于法线向量的光照,再到光线追踪,用到的数学知识都是相近的,都是三角函数为主,最多就是多了概率而已,然而这些数学知识不会像客户端API一样说改就改,而是证明过正确,就不会再变动,而是不断会有新知识往上叠加和改良而已,所以这就是稳定的知识的领域了,只要学会了,就是自己的,而不像SDK性质的知识积累那样,总被SDK的开发者和所谓的时代潮流牵着鼻子走。

        也正因为这些稳定领域的知识需要一定程度的数学相关的基本功,所以我不打算只满足于FFMPEG、libJPEG等库的使用上进行学习,而是从音频视频、图形图像的基本数学原理结合它们的实现代码进行学习,把这些知识,内化成自己的内功,才有可能开创自己的创新。

一、傅里叶变换简介

        每一个复杂的波形,都可以分解为多个简单波形的叠加,这就是傅里叶变换的基本原则。这里并不打算详细地说明傅里叶变换究竟是什么,只需要知道这个原则,基于这个原则,就可以把时域信号变成频域信号,这时记录一个信号并不需要高密度地采样并记录每个采样点的值,而是记录信号各频率的分量的振幅、相位即可,还原时使用三角函数生成波形并重新相加即可。

二、DCT是什么

        DCT全称为Discrete Cosine Transform,即离散余弦变换。其原则与傅里叶变换相近,都是把目标信号从复杂的时域信号,分解为不同频率强度的频域信号。就像上面对傅里叶的简介一样,复杂信号是简单信号的叠加,所以DCT的基本原理就是,通过多个不同强度不同频率的DCT基信号,即可叠加“拼装”为原本的信号,因此实际记录的时候就不再需要记录复杂的原始信号,而是记录DCT的基即可。并且信号转为DCT的频域信号后,左上角表达了其低频信号强度,右下角表达了其高频信号强度,使得信号的频率分量和强度一目了然,没有原始信号的纷繁复杂。

        对比二维离散傅里叶变换的高低频域以散布于四角,DCT变换其高低频的分布比较一目了然,越接近左上角低频,越接近右下角越高频,因此更适合用于做频域缩减实现图像有损压缩。

三、DCT在图像有损压缩中的应用

1、图像有损压缩的依据点:

        很多时候,我们肉眼观看一个图像,主要关注的是其大面积的色块,而精细部分则关注不多(有阿斯伯格现象的人则可能相反,但大部分人确实如此)。因此,图像中的精细部分即使做了一定程度的削弱,也不影响一副图像的观感,这就是JPEG等有损的图像格式最根本的原理。

        对于图像处理来说,细节部分一般具有亮度起伏大、较短的尺度下亮度变化次数多的特点,转换为数学方式的描述,就是图像细节部分一般为“高频信号”,因此,如果能使用“分频器”,把图像的“时域信号”转化为“频域信号”,即可实现筛选走一部分高频信号,只保留高频信号,使得表达图像时需要的空间减少。

2、使用DCT进行图像有损压缩的具体操作:

        我们先看DCT和逆DCT(IDCT)的公式:

(引用自《数字图像与视频处理》一书) 

        先看DCT公式1,u,v为我们需要得出DCT变换的频域结果的矩阵下标,f(x,y)为原8x8信号矩阵的值。

公式大致意义(重点):

        DCT变换基表可以理解为一个从(8*8)的二维余弦信号从最低频到最高频的穷举表,频率随着u和v的增大而增大。把8*8大小的二维时域信号矩阵,乘以(u, v)指定频率的基表单元矩阵,并对结果矩阵求和,即可得到输入信号f(x, y)对指定(u, v)频率的权值——这个值的大小表示了和对应(u, v)频率的相关性。把这些权值从(0, 0)到(8, 8),也就是从左到右、从上至下放到一个8*8大小的新表F(u, v)中,就可以把二维时域信号,转化为关于不同频率的相关性权值频域信号,并且从(0, 0)到(8, 8)频率以此递进,从而可以方便地进行频率筛选,这是原始二维信号做不到的。

       而其逆向操作,就是利用频率相关性权值表F(u, v),把不同权值比例的8*8频率表叠加为一个8*8的实际画面。

        根据u,v的递增,的频率会越发增大,直到(u,v)为(7,7)时达到每一个像素都和周围的像素有差异为止,(u, v)从(0,0)到(7,7)的频率基图像如下所示:

        计算代码:

        

    public void calcDCTBase(int u, int v) {
        this.mU = u;
        this.mV = v;
        double c_u = 1;
        double c_v = 1;
        if (u == 0 && v == 0) {
            c_u = c_v = 1f / Math.sqrt(2);
        }
        for (int y = 0; y < 8; y ++) {
            for (int x = 0; x < 8; x ++) {
                double base = c_u * c_v * Math.cos(((2 * x + 1) * u * Math.PI / 16f)) * Math.cos(((2 * y + 1) * v * Math.PI / 16f));
                mDCTBaseMatrix[x][y] = base;
            }
        }
        invalidate();
    }

       之前说过,任何复杂的周期信号都可以简单的周期函数叠加而得。所以我们的输入图像,也可以通过不同强度的不同频率的基图像叠加而成。然后我们的二维图像信号f(x, y)通过乘以(0,0)~(u,v)不同频率的基函数,产生的结果就是信号f(x,y)和(0,0)~(u,v)的基图像的相关性图像,把从(0,0)~(u,v)这个范围的这个相关性结果求和后放到对应的结果矩阵的(u,v)位置,最后便得到了图像f(x,y)对应基图像从(0,0)~(u,v)不同频率的相关性(权值)图像。

        最后,根据人眼的视觉特性,可以使用量化表使得肉眼敏感的低频信号使用较小的量化间隔值来确保进度,对肉眼不敏感的高频信号使用大的量化间隔粗糙量化即可,达到数据压缩的目的——高频信号量化后几乎成了0了。我使用的量化表如图:

如输入图像f(x,y):

DCT变化并使用量化表量化后得到输出:

        可以发现这副图像大部分信号和低频的几个基图像相关性最强,此时我们就已经把图像从时域转化为频域,并且压缩了高频信号了。

计算代码:

    /**对输入的8*8矩阵信号,分别乘以从最低频(u,v为(0,0)处的DCT基)到最高频的DCT(u,v为(u,v)处的DCT基)基矩阵并求和,
     * 求和结果进行亮度量化后,放到输出矩阵对应坐标(u,v)处**/
    private void signalToDCTSignalTrans(int inputMatrix[], double outputMatrix[][]) {
        for (int u = 0; u < 8; u ++) {
            for (int v = 0; v < 8; v ++) {
                double base = 0;
                for (int x = 0; x < 8; x ++) {
                    for (int y = 0; y < 8; y ++) {
                        base += inputMatrix[y * 8 + x] * mDCTBaseMatrix[u][v].getSignalDCTBaseVal(x, y);
                    }
                }
                outputMatrix[u][v] = 1f / 4f * base / Constant.DCT_BRIGHTNESS_TRANS_TABLE[u * 8 + v];
            }
        }
    }

3、使用IDCT进行图像解压缩的具体操作:

        因为任何复杂的周期信号都可以简单的周期函数叠加而得,因此,按照DCT图像(0,0)~(u,v)存储的每个和基图像相关性值(权值),乘以对应的基图像,叠加(求和)后得到的值就是时域信号,也就是源图像。

        把刚刚得到的DCT图像通过逆亮度量化和IDCT之后,就得到了图像:

        

        可以看到图像经历过变换之后,因为量化误差的原因,产生了一定程度的误差。这也是为什么JPEG图像经过多次解压再重压缩之后图像质量会越来越差的原因。

        计算代码:

    /**IDCT变换,把DCT变换后的频域信号重新转化为时域信号**/
    private void DCTSignalInverseDCTToSignal() {
        for (int y = 0; y < 8; y ++) {
            for (int x = 0; x < 8; x ++) {
                double base = 0;
                for (int u = 0; u < 8; u++) {
                    for (int v = 0; v < 8; v++) {
                        //逆亮度量化,逆DCT
                        base += mDCTTransResult[u][v] * Constant.DCT_BRIGHTNESS_TRANS_TABLE[u * 8 + v] * mDCTBaseMatrix[u][v].getSignalDCTBaseVal(x, y);
                    }
                }
                mDCTReverseTransResult[x][y] = 1f / 4f * base;
            }
        }
    }

        总体运作图像:

  

三、DCT算法的Java实现_AndroidDemo的Gitee地址:

lvlv/AndroidDemo大全 - Gitee.com

(为了呈现整个过程,突然花了心思做了写自定义控件)

四、libjpeg中对应的DCT代码实现:

//todo:

五、引用资料:

1、《数字图像与视频处理》 (机械工业出版社 卢官明、唐贵进、崔子冠 编)

2、Discrete Cosine Transform

  • 7
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值