本文主要是将先前的博客 离散傅里叶变换DFT、离散余弦变换DCT、离散正弦变换DST,原理与公式推导 从图片修改为 Markdown 脚本,方便读者浏览,同时增加了部分内容。但由于文章字符过多,无法全部放在一篇博客上,所以将之前的文章拆分为三部分,可通过以下链接查看其它部分。

  • 理解DCT与DST【一】:离散傅里叶变换
  • 理解DCT与DST【二】:离散余弦变换

3 离散正弦变换(Discrete Sine Transform, DST)

离散正弦变换类似于离散余弦变换,只是不同于离散余弦变换将有限长非对称实序列延拓为有限长对称实序列,离散正弦变换将有限长非对称实序列延拓为有限长反对称实序列。

由 1.5.5 节内容可知,对于一个有限长反对称实序列,其 DFT 序列乘上虚数单位 j 后同样是一个有限长反对称实序列。类似于有限长对称实序列,有限长反对称实序列的 DFT 的计算只需要用到序列的一半采样点数据,而且可以转化为只涉及正弦函数的实数运算,因此与离散余弦变换具有相似的计算复杂度。同样,离散正弦变换对应着非对称有限长实序列的反对称延拓序列的离散傅里叶变换的非冗余部分,不同的延拓方式对应着不同的延拓序列长度以及不同的离散傅里叶变换系数,因此也就对应着不同的离散正弦变换系数。根据 1.5.6 节的分析,离散傅里叶变换系数中的非冗余部分即独立分量的个数与原序列中的独立分量个数是一致的,因此离散正弦变换系数的个数也就对应着延拓序列中独立分量的个数。

数学上离散正弦变换总共定义了 8 种形式,且刚好与离散余弦变换的延拓方式相反,即将对称的地方改成反对称,将反对称的地方改成对称,从而对应着 8 种有限长非对称实序列的反对称延拓方式,分别用 DST-I ~ DST-VIII 来表示。在 H.266 视频编解码标准中,主要用到了 DST-VII,下面主要对其进行推导和分析,其他类型的 DST 只给出相应的延拓方式和对应的公式定义。

3.1 DST-VII

图 2-1 所示的有限长非对称实序列 java离散正弦波数据获取周期 离散正弦序列定义_非对称 的 DST-VII 反对称及周期延拓如图 3-1 所示。准确来说,延拓序列的公式化定义如式(3-1)所示,其中波浪线代表相应有限长序列的周期延拓。那么,有限长反对称实序列 java离散正弦波数据获取周期 离散正弦序列定义_图像处理_02 的长度为 java离散正弦波数据获取周期 离散正弦序列定义_傅里叶变换_03,而独立分量只有 java离散正弦波数据获取周期 离散正弦序列定义_算法_04 个,因此可以预见其 DFT 的独立分量也只有 java离散正弦波数据获取周期 离散正弦序列定义_算法_04

java离散正弦波数据获取周期 离散正弦序列定义_傅里叶变换_06



java离散正弦波数据获取周期 离散正弦序列定义_图像处理_07

图 3-1 DST-VII 延拓

java离散正弦波数据获取周期 离散正弦序列定义_傅里叶变换_08

图 3-2 DST-VII 延拓序列的 DFT 序列


根据前面的内容,有

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_09

可得到以下性质,

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_10

所以,有限长反对称实序列 java离散正弦波数据获取周期 离散正弦序列定义_图像处理_02java离散正弦波数据获取周期 离散正弦序列定义_傅里叶变换_03 点 DFT 中的独立分量确实只有 java离散正弦波数据获取周期 离散正弦序列定义_算法_04

根据以上性质,可以得到其逆变换为

java离散正弦波数据获取周期 离散正弦序列定义_算法_14

由此,定义 DST-VII 的计算公式如下,

java离散正弦波数据获取周期 离散正弦序列定义_图像处理_15

为了公式的一致性,将式(3-5)的系数平均分配到两边,则有

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_16

容易证明,类似于离散余弦变换,式(3-6)所定义的离散正弦变换同样属于正交变换,在此不再赘述。

3.2 DST-I

DST-I 延拓的公式化表示如下,

java离散正弦波数据获取周期 离散正弦序列定义_傅里叶变换_17

作为示例,图 2-1 所定义的有限长非对称实序列的 DST-I 反对称及周期延拓如图 3-3 所示,其对应的离散傅里叶变换序列如图 3-4 所示。



java离散正弦波数据获取周期 离散正弦序列定义_算法_18

图 3-3 DST-I 延拓

java离散正弦波数据获取周期 离散正弦序列定义_算法_19

图 3-4 DST-I 延拓序列的 DFT 序列


容易得到 DST-I 对应的离散正弦变换公式如下,

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_20

3.3 DST-II

图 2-1 所示有限长非对称实序列 java离散正弦波数据获取周期 离散正弦序列定义_非对称的 DST-II 反对称及周期延拓如图 3-5(a) 所示。由于序列右移了 1/2,所有采样点都处于非整数索引位置,因此需要对该序列进行 2 倍的插零上采样,得到离散序列如图 3-5(b) 所示。准确来说,延拓序列的公式化定义如式(3-9)所示,其中波浪线代表相应有限长序列的周期延拓,非整数索引位置默认为 0。图 3-5(b) 所示延拓序列的 DFT 序列如图 3-6 所示。

java离散正弦波数据获取周期 离散正弦序列定义_图像处理_22



java离散正弦波数据获取周期 离散正弦序列定义_图像处理_23

图 3-5 DST-II 延拓(a)及其插零上采样(b)

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_24

图 3-6 DST-II 延拓序列的 DFT 序列


容易可得 DST-II 对应的离散正弦变换公式如下,

java离散正弦波数据获取周期 离散正弦序列定义_图像处理_25

其中

java离散正弦波数据获取周期 离散正弦序列定义_图像处理_26

3.4 DST-III

DST-III 延拓的公式化表示如下,

java离散正弦波数据获取周期 离散正弦序列定义_傅里叶变换_27

作为示例,图 2-1 所定义的有限长非对称实序列的 DST-III 反对称及周期延拓如图 3-7 所示,其对应的离散傅里叶变换序列如图 3-8 所示。



java离散正弦波数据获取周期 离散正弦序列定义_算法_28

图 3-7 DST-III 延拓

java离散正弦波数据获取周期 离散正弦序列定义_非对称_29

图 3-8 DST-III 延拓序列的 DFT 序列


容易得到 DST-III 对应的离散正弦变换公式如下,

java离散正弦波数据获取周期 离散正弦序列定义_图像处理_30

其中

java离散正弦波数据获取周期 离散正弦序列定义_算法_31

3.5 DST-IV

图 2-1 所示有限长非对称实序列 java离散正弦波数据获取周期 离散正弦序列定义_非对称

java离散正弦波数据获取周期 离散正弦序列定义_傅里叶变换_33



java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_34

图 3-9 DST-IV 延拓(a)及其插零上采样(b)

java离散正弦波数据获取周期 离散正弦序列定义_非对称_35

图 3-10 DST-IV 延拓序列的 DFT 序列


容易可得 DST-IV 对应的离散正弦变换公式如下,

java离散正弦波数据获取周期 离散正弦序列定义_算法_36

3.6 DST-V

DST-V 延拓的公式化表示如下,

java离散正弦波数据获取周期 离散正弦序列定义_非对称_37

作为示例,图 2-1 定义的有限长非对称实序列的 DST-V 反对称及周期延拓如图 3-11 所示,其对应的离散傅里叶变换序列如图 3-12 所示。



java离散正弦波数据获取周期 离散正弦序列定义_图像处理_38

图 3-11 DST-V 延拓

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_39

图 3-12 DST-V 延拓序列的 DFT 序列


容易得到 DST-V 对应的离散正弦变换公式如下,

java离散正弦波数据获取周期 离散正弦序列定义_图像处理_40

3.7 DST-VI

图 2-1 的有限长非对称实序列 java离散正弦波数据获取周期 离散正弦序列定义_非对称

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_42

容易可得 DST-VI 对应的离散余弦变换公式如下,

java离散正弦波数据获取周期 离散正弦序列定义_图像处理_43



java离散正弦波数据获取周期 离散正弦序列定义_非对称_44

图 3-13 DST-VI 延拓(a)及其插零上采样(b)

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_45

图 3-14 DST-VI 延拓序列的 DFT 序列


3.8 DST-VIII

图 2-1 的有限长非对称实序列 java离散正弦波数据获取周期 离散正弦序列定义_非对称

java离散正弦波数据获取周期 离散正弦序列定义_非对称_47



java离散正弦波数据获取周期 离散正弦序列定义_算法_48

图 3-15 DST-VIII 延拓(a)及其插零上采样(b)

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_49

图 3-16 DST-VIII 延拓序列的 DFT 序列


容易可得 DST-VIII 对应的离散正弦变换公式如下,

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_50

其中,

java离散正弦波数据获取周期 离散正弦序列定义_傅里叶变换_51

附录 A DST的 8 种延拓方式



java离散正弦波数据获取周期 离散正弦序列定义_图像处理_52


附录 B DST 的 8 种变换公式

DST-I

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_53

DST-II

java离散正弦波数据获取周期 离散正弦序列定义_图像处理_54

DST-III

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_55

DST-IV

java离散正弦波数据获取周期 离散正弦序列定义_傅里叶变换_56

DST-V

java离散正弦波数据获取周期 离散正弦序列定义_傅里叶变换_57
DST-VI

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_58

DST-VII

java离散正弦波数据获取周期 离散正弦序列定义_java离散正弦波数据获取周期_59

DST-VIII

java离散正弦波数据获取周期 离散正弦序列定义_傅里叶变换_60

java离散正弦波数据获取周期 离散正弦序列定义_算法_61

附录 C DST 的 8 种变换对应的基图像



java离散正弦波数据获取周期 离散正弦序列定义_算法_62


DST 基图像生成脚本

# -*- coding: utf-8 -*-
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt

def dst1_kern(N):
    x = np.zeros([N, N])
    for k in range(N):
        x[k, :] = np.sin(np.pi * (k + 1) * (np.arange(N) + 1) / (N + 1))
    x *= np.sqrt(2. / (N + 1))
    return x

def dst2_kern(N):
    x = np.zeros([N, N])
    for k in range(N):
        x[k, :] = np.sin(np.pi * (k + 1) * (2 * np.arange(N) + 1) / (2 * N))
    x[-1, :] /= np.sqrt(2)
    x *= np.sqrt(2. / N)
    return x
    
def dst3_kern(N):
    x = np.zeros([N, N])
    for k in range(N):
        x[k] = np.sin(np.pi * (2 * k + 1) * (np.arange(N) + 1) / (2 * N))
    x[:, -1] /= np.sqrt(2)
    x *= np.sqrt(2. / N)
    return x

def dst4_kern(N):
    x = np.zeros([N, N])
    for k in range(N):
        x[k] = np.sin(np.pi * (2 * k + 1) * (2 * np.arange(N) + 1) / (4 * N))
    x *= np.sqrt(2. / N)
    return x

def dst5_kern(N):
    x = np.zeros([N, N])
    for k in range(N):
        x[k] = np.sin(2 * np.pi * (k + 1) * (np.arange(N) + 1) / (2 * N + 1))
    x *= np.sqrt(4. / (2 * N + 1))
    return x

def dst6_kern(N):
    x = np.zeros([N, N])
    for k in range(N):
        x[k] = np.sin(np.pi * (k + 1) * (2 * np.arange(N) + 1) / (2 * N + 1))
    x *= np.sqrt(4. / (2 * N + 1))
    return x

def dst7_kern(N):
    x = np.zeros([N, N])
    for k in range(N):
        x[k] = np.sin(np.pi * (2 * k + 1) * (np.arange(N) + 1) / (2 * N + 1))
    x *= np.sqrt(4. / (2 * N + 1))
    return x

def dst8_kern(N):
    x = np.zeros([N, N])
    for k in range(N):
        x[k] = np.sin(np.pi * (2 * k + 1) * (2 * np.arange(N) + 1) / (4 * N - 2))
    x[:, -1] /= np.sqrt(2)
    x[-1, :] /= np.sqrt(2)
    x *= np.sqrt(4. / (2 * N - 1))
    return x

if __name__ == '__main__':
    path = 'dst_img/basis/'
    num = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII']
    
    for n in range(1, 9):
        kern = globals().get('dst{}_kern'.format(n))(8)
        '''
        把变换矩阵按行分块,记第k行的转置为ak,那么 h=[a1.T, a2.T, ..., a8.T],
        用矩阵分块的知识得到 v.dot(h) 的每一个子块是 ai.dot(aj.T),
        这样一次性就可以得到64个基图像拼起来的64x64的总图像了。
        '''
        h = kern.reshape([1, -1])
        v = h.T
        img = v.dot(h)
        
        plt.figure(figsize=(8, 8))
        plt.imshow(img, 'gray')
        plt.savefig(path+'/dst8x8_{}_all.png'.format(n), dpi='figure')
        
        imgs = []
        for i in range(8):
            for j in range(8):
                imgs.append(img[8*i:8*i+8, 8*j:8*j+8])
                #imgs.append(kern[[i], :].T.dot(kern[[j], :])) # 这种方法就是基图像一个一个地求

        imgs = np.stack(imgs)
        vmin, vmax = np.min(imgs), np.max(imgs)
        
        plt.figure(figsize=(32, 33))
        for i in range(8):
            for j in range(8):
                plt.subplot(8, 8, i*8+j+1)
                plt.imshow(imgs[i*8+j], 'gray', vmin=vmin, vmax=vmax)
                plt.axis('off')
                plt.tight_layout()
        plt.subplots_adjust(0.02, 0.02, 0.98, 0.95, 0.1, 0.1)
        plt.suptitle('DST-'+num[n-1]+' basis', fontsize=48)
        plt.savefig(path+'/dst8x8_{}.png'.format(n), dpi=32)