在MATLAB的世界里,`conv` 函数是进行卷积运算的得力助手。而卷积,这个在信号处理、图像处理、概率论以及许多其他领域都闪耀着光芒的数学概念,也有其经典的定义公式。理解 `conv` 函数与卷积公式之间的联系,就如同理解一把锋利的工具如何精确地执行一项严谨的数学任务。
卷积公式:数学的基石
我们先从数学本身说起。对于两个离散时间序列(或者说向量) $x[n]$ 和 $h[n]$,它们的卷积,记作 $y[n] = (x h)[n]$,其定义公式为:
$$ y[n] = sum_{k=infty}^{infty} x[k] h[nk] $$
这个公式告诉我们,要计算输出序列 $y[n]$ 在某个时刻 $n$ 的值,我们需要将输入序列 $x$ 的一个点 $x[k]$ 与另一个序列 $h$ 的一个点 $h[nk]$ 相乘,然后对所有可能的 $k$ 求和。
这里面有几个关键的点需要体会:
“翻转”与“滑动”: 观察 $h[nk]$,这意味着我们将序列 $h$ 沿着时间轴(或者索引轴)进行翻转(将 $h[k]$ 变成 $h[k]$),然后再进行平移(将 $h[k]$ 变成 $h[nk]$,其中 $n$ 是平移的量)。
“相乘”与“求和”: 对于每一个平移后的 $h$(记作 $h_{n}[k] = h[nk]$),我们计算 $x[k]$ 和 $h_{n}[k]$ 在各个 $k$ 上的乘积,然后将所有这些乘积加起来,得到 $y[n]$。
这个过程可以想象成:
1. 将序列 $h$ 沿时间轴翻转,得到 $h_{rev}[k] = h[k]$。
2. 将翻转后的序列 $h_{rev}$ 向右(或向左,取决于你的定义和偏好)滑动一个单位,得到 $h_{rev}[km]$。
3. 在每个滑动的位置 $m$ 上,将 $x[k]$ 和 $h_{rev}[km]$ 对应相乘(即 $x[k] cdot h[km]$),然后将所有乘积加起来。这个总和就是输出 $y[m]$。
仔细对照一下,我们的公式 $y[n] = sum_{k=infty}^{infty} x[k] h[nk]$ 其实是将 $h$ 翻转($h[k]$)后再向右平移 $n$ 个单位($h[nk]$)。
MATLAB 的 `conv` 函数:代码的实现
MATLAB 的 `conv(a, b)` 函数,用于计算向量 `a` 和向量 `b` 的卷积。如果 `a` 和 `b` 分别是长度为 $M$ 和 $N$ 的向量,那么 `conv(a, b)` 的结果是一个长度为 $M+N1$ 的向量。
MATLAB 的 `conv` 函数默认执行的是 线性卷积。这意味着它实际上是在处理有限长度的序列,并且假设这些序列在有限的区间之外都是零。
`conv` 函数是如何实现卷积公式的?
MATLAB 的 `conv` 函数在内部是如何工作的,可以类比于上面描述的“翻转、滑动、相乘、求和”的过程。为了更直观,我们以两个短向量为例:
假设向量 `a` 是 $[a_0, a_1, a_2]$,代表序列 $x[n] = {a_0, a_1, a_2}$ (当 $n=0,1,2$ 时),其他时候为0。
假设向量 `b` 是 $[b_0, b_1]$,代表序列 $h[n] = {b_0, b_1}$ (当 $n=0,1$ 时),其他时候为0。
根据卷积公式,我们要计算 $y[n] = sum_{k=infty}^{infty} x[k] h[nk]$。
由于 $x[k]$ 和 $h[nk]$ 都是有限长度的,非零的部分是有限的。
$x[k]$ 非零当 $k in {0, 1, 2}$。
$h[nk]$ 非零当 $nk in {0, 1}$,即 $k in {n, n1}$。
为了使 $x[k]h[nk]$ 非零,我们需要 $k$ 同时满足这两个条件:
$k in {0, 1, 2}$ 且 $k in {n, n1}$。
这要求 ${0, 1, 2} cap {n, n1}$ 非空。
当 $n=0$, $k in {0} cap {0,1}$,即 $k=0$。$y[0] = x[0]h[0] = a_0 b_0$。
当 $n=1$, $k in {0,1} cap {1,0}$,即 $k=0,1$。$y[1] = x[0]h[1] + x[1]h[0] = a_0 b_1 + a_1 b_0$。
当 $n=2$, $k in {1,2} cap {2,1}$,即 $k=1,2$。$y[2] = x[1]h[1] + x[2]h[0] = a_1 b_1 + a_2 b_0$。
当 $n=3$, $k in {2} cap {3,2}$,即 $k=2$。$y[3] = x[2]h[1] = a_2 b_1$。
所以,输出向量 `y` 应该是 $[a_0 b_0, a_0 b_1 + a_1 b_0, a_1 b_1 + a_2 b_0, a_2 b_1]$。
这个向量的长度是 $3+21=4$,这与 MATLAB 的结果长度一致。
深入理解 `conv` 函数的内部机制 (类比)
`conv` 函数的实现,可以通过一个“滑动窗口”或“乘法累加”的矩阵乘法来理解。
1. Toeplitz 矩阵表示:
一个常用的方法是将其中一个向量(比如 $b$)展开成一个 Toeplitz 矩阵(或者更准确地说,是 Hankel 矩阵的一部分,但在卷积中我们常说到 Toeplitz)。这个矩阵的每一行都是由 $b$ 向量循环(或者说平移)产生的。
以 `a = [a0, a1, a2]` 和 `b = [b0, b1]` 为例:
为了得到长度为 $M+N1$ 的输出,我们需要将较短的向量(这里是 `b`)进行“填充”,以便构建一个合适的矩阵。
假设我们用 `b` 来构建一个矩阵,让 `a` 作为列向量乘以它。为了让输出长度是 $3+21=4$,我们需要一个 $3 imes 4$ 的矩阵。
我们可以将 `b` 沿对角线排列,并补零:
```
b_matrix = [ b0 b1 0 0
0 b0 b1 0
0 0 b0 b1 ]
```
或者,我们也可以将 `a` 沿对角线排列,并补零,让 `b` 作为列向量。为了得到长度为 4 的输出,我们需要一个 $4 imes 3$ 的矩阵。
(注意:MATLAB 的 `conv` 实现更接近于把 `b`(或者 `a`,取决于哪个长)变成一个 Toeplitz 结构,然后与另一个向量相乘。)
更直观地,可以考虑以下方式:
将 `a` 视为一个列向量,并进行“扩展”:
```
a_col = [ a0 ]
[ a1 ]
[ a2 ]
```
将 `b` 扩展成一个矩阵,其列由 $b$ 向量的“移位”版本组成:
```
b_matrix = [ b0 0 0
b1 b0 0
0 b1 b0
0 0 b1 ]
```
(这里的矩阵结构稍微调整一下,使其能与 `a` 相乘得到正确结果。
更精确地说,为了计算 $y[n] = sum x[k]h[nk]$,我们可以考虑 $y$ 的每个元素。
$y[0] = x[0]h[0]$
$y[1] = x[0]h[1] + x[1]h[0]$
$y[2] = x[0]h[2] + x[1]h[1] + x[2]h[0]$ ... (注意 $h$ 的索引)
MATLAB 的实现更像是:
将 `b` 向量(长度 N)通过补零扩展成一个长度为 M+N1 的向量,然后基于这个向量构建一个 $M imes (M+N1)$ 的矩阵。
假设 `a` 长 M,`b` 长 N。
`conv(a, b)` 的结果长度 L = M+N1。
MATLAB 内部会构建一个 $M imes L$ 的矩阵 `A`。
`A` 的每一行 `i` 包含了 `a(i)` 乘以一个移位后的 `b` 向量(补零)。
具体来说,`A(i, :)` 包含 $a_i cdot b[0], a_i cdot b[1], dots, a_i cdot b[N1]$,并在适当的位置补零。
让我们回到 `a = [a0, a1, a2]` (M=3) 和 `b = [b0, b1]` (N=2)。
输出长度 L = 4。
我们需要构建一个 $3 imes 4$ 的矩阵,让它乘以一个包含 `b` 的向量(或者反之)。
更贴切的理解方式是:
将 `a` 向量看作输入。
将 `b` 向量(我们称之为“核”或“滤波器”)看作是在“扫描”`a`。
但这里不是简单的滑动窗口。`conv` 的过程是将 `b` 翻转,然后滑动。
MATLAB 的 `conv` 函数本质上是实现了多项式乘法。
如果 $A(z) = a_0 + a_1 z^{1} + a_2 z^{2}$ 是由向量 `a` 构成的 Z 变换。
如果 $B(z) = b_0 + b_1 z^{1}$ 是由向量 `b` 构成的 Z 变换。
那么卷积 $y[n]$ 对应的 Z 变换是 $Y(z) = A(z)B(z)$。
$Y(z) = (a_0 + a_1 z^{1} + a_2 z^{2})(b_0 + b_1 z^{1})$
$= a_0 b_0 + a_0 b_1 z^{1} + a_1 b_0 z^{1} + a_1 b_1 z^{2} + a_2 b_0 z^{2} + a_2 b_1 z^{3}$
$= a_0 b_0 + (a_0 b_1 + a_1 b_0) z^{1} + (a_1 b_1 + a_2 b_0) z^{2} + a_2 b_1 z^{3}$
对应的序列就是 $[a_0 b_0, a_0 b_1 + a_1 b_0, a_1 b_1 + a_2 b_0, a_2 b_1]$。
这完美契合了我们之前手动计算的结果。
MATLAB 的 `conv` 函数就是通过高效地执行这种多项式乘法来实现的。它内部的算法(通常是基于 FFT 的算法,当向量长度较大时)能够快速计算出这个乘积的多项式系数,从而得到卷积结果。
总结 `conv` 函数与卷积公式的关系
1. 数学定义驱动: `conv` 函数的功能直接源于卷积的数学定义公式 $y[n] = sum_{k=infty}^{infty} x[k] h[nk]$。它被设计出来就是为了精确计算这个公式的值。
2. 有限长度处理: 在实际应用中,我们处理的是有限长度的向量。`conv` 函数会自动假设这些向量在区间外的值为零,并根据卷积公式计算有限的非零输出。
3. “翻转、滑动、相乘、求和”的体现: 虽然 `conv` 函数的底层实现可能非常高效(例如使用 FFT),但其逻辑完全符合“翻转一个序列,然后滑动它,在每个位置上将两个序列的对应元素相乘并累加”的直观过程。
4. 多项式乘法类比: `conv` 函数的计算过程可以看作是两个多项式(由输入向量的系数构成)的乘法。`conv` 函数的输出向量就是这个乘积多项式的系数。
5. 输出长度: `conv(a, b)` 的输出长度为 `length(a) + length(b) 1`,这是由卷积公式在有限长度序列上展开得到的必然结果。
总而言之,MATLAB 的 `conv` 函数是卷积公式在计算机编程中的一种精确而高效的实现。它为用户提供了一个简单易用的接口,隐藏了背后的复杂计算,但其核心是忠实地执行卷积的数学定义。理解卷积公式,就能明白 `conv` 函数为什么会产生这样的结果,以及它在不同应用场景中的含义。