总览
离散余弦变换,DCT。DCT 在图像视频音频压缩领域用得比 FFT 更多。由于其能量更集中的特性,适合拿来裁剪不重要的信息。
本文主要通过 DFT 到 DCT 的推导加深对 DCT 的理解。
最后给出了 DCT-II 的公式,以及一个 PyTorch 代码实现。
离散傅里叶变换 DFT
傅里叶变换。
离散傅里叶变换。
离散傅里叶变换借助欧拉公式,展开为两项。
记实部和虚部:
从 DFT 到 DCT
离散傅里叶变换中有两个特点很明了:实部 项是偶函数,虚部 项是奇函数。所以,若原信号 是全实数的偶函数信号,那么虚部 是可以省略的,如此便能抛弃虚部,能简化很多问题。
离散余弦变换就是利用了这个特性。虽然不能不负责任地假设一切实信号都是偶函数的,但可以通过延拓来创造一个偶函数。
如何构造?
设现有一长度为 的实数离散信号 ,其中 。现构造一个 ,满足
这个新信号关于 对称。只要往右平移 0.5,就是一个标准的实偶信号。借此写出该信号的傅里叶变换式,并使用对称性质和 进行变换和化简。
没有了虚数项 的负担,式子的变换非常轻松。现在只保留实部,写出最终的式子:
离散余弦变换 DCT
最常用的 DCT-II 形式如下。
其中 ,其他情况 。这样做主要是因为在工程应用上让矩阵正交。
2d-DCT 与代码实现
PyTorch 有原生的 FFT 实现但没有 DCT 的。在看论文 (2025) Mesoscopic Insights: Orchestrating Multi-scale & Hybrid Architecture for Image Manipulation Localization 的对应代码时看到的一段 2d-DCT 实现。大致如下。
class DCT(nn.Module):
def __init__(self):
super(self).__init__()
self.dct_matrix_h = None
self.dct_matrix_w = None
def create_dct_matrix(self, N):
n = torch.arange(N, dtype=torch.float32).reshape((1, N))
k = torch.arange(N, dtype=torch.float32).reshape((N, 1))
dct_matrix = torch.sqrt(torch.tensor(2.0 / N)) * torch.cos(math.pi * k * (2 * n + 1) / (2 * N))
dct_matrix[0, :] = 1 / math.sqrt(N)
return dct_matrix
def dct_2d(self, x):
H, W = x.size(-2), x.size(-1)
if self.dct_matrix_h is None or self.dct_matrix_h.size(0) != H:
self.dct_matrix_h = self.create_dct_matrix(H).to(x.device)
if self.dct_matrix_w is None or self.dct_matrix_w.size(0) != W:
self.dct_matrix_w = self.create_dct_matrix(W).to(x.device)
return torch.matmul(self.dct_matrix_h, torch.matmul(x, self.dct_matrix_w.t()))
def idct_2d(self, x):
H, W = x.size(-2), x.size(-1)
if self.dct_matrix_h is None or self.dct_matrix_h.size(0) != H:
self.dct_matrix_h = self.create_dct_matrix(H).to(x.device)
if self.dct_matrix_w is None or self.dct_matrix_w.size(0) != W:
self.dct_matrix_w = self.create_dct_matrix(W).to(x.device)
return torch.matmul(self.dct_matrix_h.t(), torch.matmul(x, self.dct_matrix_w))
挺直接的两次矩阵乘法。
参考来源
- DBinary,“详解离散余弦变换(DCT)”,zhuanlan.zhihu.com/p/85299446
- 芯动的信号,“DCT变换”,zhuanlan.zhihu.com/p/413252930