Files
thejinchao.github.io/docs/blog/2025/02/JPEG002.md
T
2025-02-22 19:54:50 +08:00

125 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "JPEG算法解密(二)"
tags: 压缩 图像 程序 算法
---
# JPEG算法解密(二)
### 步骤三:离散余弦变换
----
这次我们来介绍JPEG算法中的核心内容,离散余弦变换(Discrete cosine transform),简称DCT。
离散余弦变换属于傅里叶变换的另外一种形式,没错,就是大名鼎鼎的傅里叶变换。傅里叶是法国著名的数学家和物理学家,1807年,39岁的傅里叶在他的一篇论文里提出了一个想法,他认为*任何周期性的函数,都可以分解为为一系列的三角函数的组合*,这个想法一开始并没有得到当时科学界的承认,比如当时著名的数学家拉格朗日提出质疑,三角函数无论如何组合,都无法表达带有“尖角”的函数,一直到1822年拉格朗日死后,傅里叶的想法才正式在他的著作《热的解析理论》一书中正式发表。
![](/images/2014/08/jpeg_20.jpg)
金子总会闪光,傅里叶变换如今广泛应用于数学、物理、信号处理等等领域,变换除了它在数学上的意义外,还有其哲学上的伟大意义,那就是,世上任何复杂的事物,都可以分解为简单的事物的组合,而这个过程只需要借助数学工具就可以了。但是当年拉格朗日的质疑是正确的,三角函数的确无法表达出尖角形状的函数,不过只要三角函数足够多,可以无限逼近最终结果。比如下面这张动图,就动态描述了一个矩形方波,是如何做傅里叶分析的。
<p align="center">
<img src="/images/2014/08/jpeg_22.jpg" width="45%">
<img src="/images/2014/08/jpeg_21.gif" width="45%">
</p>
当我们要处理的不再是函数,而是一堆离散的数据时,并且这些数据是对称的话,那么傅里叶变化出来的函数只含有余弦项,这种变换称为离散余弦变换。举个例子,有一组一维数据$[x_0,x_1,x_2,\ldots,x_{n-1}]$,那么可以通过DCT变换得到$n$个变换级数$F_i$
$$\begin{aligned}
F_m&=\sum_{k=0}^{n-1}x_k\cos\left[\frac{\pi}{n}m(k+\frac{1}{2})\right] \\
m&=0,1,\ldots,n-1
\end{aligned}\tag{2.1}
$$
此时原始数据$x_m$可以通过离散余弦变换变化的逆变换(IDCT)表达出来
$$\begin{aligned}
x_m&=\frac{F_0}{n}+\sum_{k=1}^{n-1}\left[\frac{2F_k}{n}\cos\left[\frac{\pi}{n}(m+\frac{1}{2})k\right]\right]\\
m&=0,1,\ldots,n-1
\end{aligned}\tag{2.2}
$$
也就是说,经过DCT变换,可以把一个数组分解成数个数组的和,如果我们数组视为一个一维矩阵,那么可以把结果看做是一系列矩阵的和
$$\begin{aligned}
&\left[x_0,x_1,x_2,\ldots,x_{n-1}\right]=\frac{F_0}{n}\left[1,1,1,\ldots,1\right]\\
&+\small{\frac{2F_1}{n}\left[\cos{\frac{\pi}{2n}},\cos{\frac{3\pi}{2n}},\cos{\frac{5\pi}{2n}},\ldots,\cos{\frac{(2n-1)\pi}{2n}}\right]}\\
&+\small{\frac{2F_2}{n}\left[\cos{\frac{2\pi}{2n}},\cos{\frac{6\pi}{2n}},\cos{\frac{10\pi}{2n}},\ldots,\cos{\frac{2(2n-1)\pi}{2n}}\right]}\\
&+\small{\frac{2F_3}{n}\left[\cos{\frac{3\pi}{2n}},\cos{\frac{9\pi}{2n}},\cos{\frac{15\pi}{2n}},\ldots,\cos{\frac{3(2n-1)\pi}{2n}}\right]}\\
&+\ldots\\
&+\small{\frac{2F_{n-1}}{n}\left[\cos{\frac{(n-1)\pi}{2n}},\cos{\frac{2(n-1)\pi}{2n}},\cos{\frac{3(n-1)\pi}{2n}},\ldots,\cos{\frac{(n-1)(2n-1)\pi}{2n}}\right]}\\
\end{aligned}\tag{2.3}
$$
举个例子,我们有一个长度为8的数字,内容为[50,55,67,80,-10,-5,20,30],在数轴上展示的话,没有任何明显的规律
![](/images/2014/08/jpeg_25.gif "[50,55,67,80,-10,-5,20,30]")
经过DCT转换,得到8个级数为[287.0,106.3,14.2,-110.8,9.2,65.7,-8.2,-43.9],根据公式2.3把这个数组转换为8个新的数组的和,再使用图像来表达的话,就可以发现DCT转换的有趣之处了
![](/images/2014/08/jpeg_26.gif "数组0:[35.9,35.9,35.9,35.9,35.9,35.9,35.9,35.9]")
![](/images/2014/08/jpeg_27.gif "数组1:[26.0,22.1,14.8,5.2,-5.2,-14.8,-22.1,-26.1]")
![](/images/2014/08/jpeg_28.gif "数组2:[3.3,1.4,-1.4,-3.3,-3.3,-1.4,1.4,3.3]")
![](/images/2014/08/jpeg_29.gif "数组3:[-23.0,5.4,27.2,15.4,-15.4,-27.2,-5.4,23.0]")
![](/images/2014/08/jpeg_30.gif "数组4:[1.6,-1.6,-1.6,1.6,1.6,-1.6,-1.6,1.6]")
![](/images/2014/08/jpeg_31.gif "数组5:[9.1,-16.1,3.2,13.6,-13.6,-3.2,16.1,-9.1]")
![](/images/2014/08/jpeg_32.gif "数组6:[-0.8,1.9,-1.9,0.8,0.8,-1.9,1.9,-0.8]")
![](/images/2014/08/jpeg_33.gif "数组7:[-2.1,6.1,-9.1,10.8,-10.8,9.1,-6.1,2.1]")
奥妙之处在于,经过DCT,数据中隐藏的规律被发掘了出来,杂乱的数据被转换成几个工整变化的数据。DCT转换后的数组中第一个是一个直线数据,因此又被称为“直流数据”,简称DC,后面的数据被称为“交流数据”,简称AC,这个称呼起源于信号分析中的术语。
在JPEG压缩过程中,经过颜色空间的转换,每一个8X8的图像块,在数据上表现为3个8X8的矩阵,紧接着我们对这三个矩阵做一个二维的DCT转换,二维的DCT转换公式为
$$\small{\begin{aligned}
F(u,v)&=\alpha(u)\cdot\alpha(v)\cdot \sum_{x=0}^{7}\sum_{y=0}^{7}f(x,y)\cos\left(\frac{2x+1}{16}u\pi\right)\cos\left(\frac{2y+1}{16}v\pi\right)\quad u,v=0,1,2,\ldots,7\\
\alpha(u)&=\begin{cases}1/\sqrt{8}, & \text{when u}=0\\
1/2, & \text{when u}\neq 0\end{cases}
\end{aligned}}\tag{2.4}
$$
DCT的威力究竟有多大,我们可以做一个实际的测试,比如一个所有数值都一样的矩阵,经过DCT转换后,将所有级数组合成一个新的矩阵
$$
\small{\begin{bmatrix}
100&100&100&100&100&100&100&100\\
100&100&100&100&100&100&100&100\\
100&100&100&100&100&100&100&100\\
100&100&100&100&100&100&100&100\\
100&100&100&100&100&100&100&100\\
100&100&100&100&100&100&100&100\\
100&100&100&100&100&100&100&100\\
100&100&100&100&100&100&100&100
\end{bmatrix}\overset{\text{DCT}}{\Longrightarrow}
\begin{bmatrix}
800&0&0&0&0&0&0&0\\
0&0&0&0&0&0&0&0\\
0&0&0&0&0&0&0&0\\
0&0&0&0&0&0&0&0\\
0&0&0&0&0&0&0&0\\
0&0&0&0&0&0&0&0\\
0&0&0&0&0&0&0&0\\
0&0&0&0&0&0&0&0
\end{bmatrix}
}
$$
可以看到,经过DCT转换,矩阵的“能量”被全部集中在左上角上的直流分量F(0,0)上,其他位置都变成了0。
在实际的JPEG压缩过程中,由于图像本身的连贯性,一个8X8的图像中的数值一般不会出现大的跳跃,经过DCT转换会有类似的效果,左上角的直流分量保存了一个大的数值,其他分量都接近于0,我们以Lenna左上角第一块图像的Y分量为例,经过变换的矩阵为
$$
\small{\begin{bmatrix}
34&34&34&33&34&28&35&32\\
34&34&34&33&34&28&35&32\\
34&34&34&33&34&28&35&32\\
34&34&34&33&34&28&35&32\\
34&34&34&33&34&28&35&32\\
36&36&29&27&33&31&30&31\\
32&32&35&30&32&33&31&27\\
30&30&27&28&30&30&28&29\\
\end{bmatrix}\overset{\text{DCT}}{\Longrightarrow}
\begin{bmatrix}
257.1&6.4&2.5&-0.3&0.4&0.1&-6.0&6.9\\
8.4&0.0&0.5&-0.5&1.9&3.4&-4.2&3.3\\
-5.3&-1.0&-1.4&1.3&-0.7&-0.5&2.1&-1.7\\
2.4&1.7&1.5&1.5&-0.6&-1.5&0.2&0.4\\
-1.1&-1.6&-0.2&-1.8&1.6&1.2&-1.4&-0.1\\
1.4&0.9&-1.9&-0.1&-2.0&0.9&1.5&0.7\\
-2.0&-0.1&3.1&2.0&1.8&-2.7&-0.9&-1.3\\
1.5&-0.2&-2.3&-1.9&-1.0&2.3&0.3&1.1\\
\end{bmatrix}
}
$$
可以看到,数据经过DCT变化后,被明显分成了直流分量和交流分量两部分,为后面的进一步压缩起到了充分的铺垫作用,可以说是整个JPEG中最重要的一步,后面我们会介绍数据量化。