线性卷积与循环卷积:原理、计算步骤、相等条件与 MATLAB 实战(附完整验证代码)
摘要:万恶之源——线性卷积和循环卷积。线性卷积决定 LTI 系统的输出,长度是 L+M−1L+M-1L+M−1;循环卷积是 DFT 域点乘在时域的对应物,长度固定为 NNN。两者在 N≥L+M−1N \\ge L+M-1N≥L+M−1 时严格相等,否则循环卷积会发生“时域混叠”(回卷)。全文按“线性卷积 → 循环卷积 → 两者何时相等 → 进阶”四步组织:两种卷积各自给出定义、三种计算方法与配套的 MATLAB 演示;相等条件给出定理、临界对照与计算演示;进阶部分讲 FFT 快速卷积与自实现循环卷积。文中所有输出均为本机 MATLAB R2023b 的真实运行结果。
关键词:线性卷积 · 循环卷积 · 时域混叠 · DFT · 圆周卷积 · FFT · cconv · conv
目录
- 线性卷积:定义、计算与 MATLAB 演示
- 循环卷积:定义、计算与 MATLAB 演示
- 两者何时相等:时域混叠、计算演示与 MATLAB
- 进阶:FFT 快速卷积与自实现循环卷积
- 对比总结与结论
1. 线性卷积:定义、计算与 MATLAB 演示
1.1 定义与物理响应
一个 LTI(线性时不变)系统,输入 x[n]x[n]x[n]、单位冲激响应 h[n]h[n]h[n],输出就是线性卷积:
y[n]=x[n]∗h[n]=∑k=−∞+∞x[k] h[n−k]y[n] = x[n] * h[n] = \\sum_{k=-\\infty}^{+\\infty} x[k]\\,h[n-k]y[n]=x[n]∗h[n]=k=−∞∑+∞x[k]h[n−k]
它的物理含义非常直白:把输入拆成一串冲激,每个冲激激起一份被延迟、缩放过的 hhh,然后把所有响应叠起来。这就是"卷积"这个词里"卷"的来源——hhh 被"翻转"后沿时间轴滑动。
对于有限长因果序列 xxx(长度 LLL,下标 0∼L−10\\sim L-10∼L−1)和 hhh(长度 MMM,下标 0∼M−10\\sim M-10∼M−1):
y[n]=∑k=max(0, n−M+1)min(L−1, n)x[k] h[n−k],n=0,1,…,L+M−2y[n]=\\sum_{k=\\max(0,\\,n-M+1)}^{\\min(L-1,\\,n)} x[k]\\,h[n-k],\\quad n=0,1,\\dots,L+M-2y[n]=k=max(0,n−M+1)∑min(L−1,n)x[k]h[n−k],n=0,1,…,L+M−2
结果长度为 L+M−1L+M-1L+M−1。这个长度是后面一切的关键。
1.2 计算实例
x=[1, 2, 3, 4],h=[5, 6, 7]x = [1,\\ 2,\\ 3,\\ 4],\\qquad h = [5,\\ 6,\\ 7]x=[1, 2, 3, 4],h=[5, 6, 7]
即 L=4, M=3L=4,\\ M=3L=4, M=3,线性卷积长度 L+M−1=6L+M-1=6L+M−1=6,最小无混叠的循环卷积长度也是 666。
1.3 计算方法一:翻转-平移法(图解法)
这是最能体现物理动作上“卷”的方法:
代入本例题,用有效区间公式 k∈[max(0,n−M+1), min(L−1,n)]k\\in[\\max(0,n-M+1),\\ \\min(L-1,n)]k∈[max(0,n−M+1), min(L−1,n)]:
直观感受"翻转后滑动"的过程(· 表示该位置无值):
k : 0 1 2 3
x[k]: 1 2 3 4
h[-k]: 7 6 5 · ← 翻转后的 h
————————————————–
n=0 : 5 · · · y[0] = 1×5 = 5
n=1 : 6 5 · · y[1] = 1×6 + 2×5 = 16
n=2 : 7 6 5 · y[2] = 1×7 + 2×6 + 3×5 = 34
n=3 : · 7 6 5 y[3] = 2×7 + 3×6 + 4×5 = 52
n=4 : · · 7 6 y[4] = 3×7 + 4×6 = 45
n=5 : · · · 7 y[5] = 4×7 = 28
观察最后六行:翻转后的 hhh 每一行整体右移一格,滑出 xxx 定义域的部分(k<0k<0k<0 或 k>L−1k>L-1k>L−1)不参与相乘;项数按 1,2,3,3,2,11,2,3,3,2,11,2,3,3,2,1 变化,中间两项都取满 M=3M=3M=3 项。
1.4 计算方法二:竖式乘法(多项式乘法)
线性卷积在代数上就是多项式乘法:把序列看成多项式系数,x[k]x[k]x[k] 对应 x0+x1z−1+x2z−2+…x_0 + x_1 z^{-1} + x_2 z^{-2} + \\dotsx0+x1z−1+x2z−2+…。
1 2 3 4 ← x
× 5 6 7 ← h
——————————————–
5 10 15 20 ← x × h[0] = x × 5
6 12 18 24 ← x × h[1] = x × 6(右移 1 列)
7 14 21 28 ← x × h[2] = x × 7(右移 2 列)
——————————————–
5 16 34 52 45 28 ← 逐列相加(不进位!)
注意:逐列直接相加,不做进位(这是和普通十进制乘法的唯一区别,本质是多项式加法)。逐列验算:第 3 列 15+12+7=3415+12+7=3415+12+7=34,第 5 列 24+21=4524+21=4524+21=45,与 1.3 节结果一致。
这条路径顺便解释了一个经典结论:zzz 变换下的系统函数相乘,对应时域的卷积。
1.5 计算方法三:滑窗表(按公式代数展开,程序员的写法,n、k两层for循环)
直接按 y[n]=∑kx[k]h[n−k]y[n]=\\sum_k x[k]h[n-k]y[n]=∑kx[k]h[n−k] 列表,最不容易出错:
| 0 | h[0]x[0]h[0]x[0]h[0]x[0] | 5×15\\times15×1 | 5 |
| 1 | h[1]x[0]+h[0]x[1]h[1]x[0] + h[0]x[1]h[1]x[0]+h[0]x[1] | 6×1+5×26\\times1+5\\times26×1+5×2 | 16 |
| 2 | h[2]x[0]+h[1]x[1]+h[0]x[2]h[2]x[0] + h[1]x[1] + h[0]x[2]h[2]x[0]+h[1]x[1]+h[0]x[2] | 7×1+6×2+5×37\\times1+6\\times2+5\\times37×1+6×2+5×3 | 34 |
| 3 | h[2]x[1]+h[1]x[2]+h[0]x[3]h[2]x[1] + h[1]x[2] + h[0]x[3]h[2]x[1]+h[1]x[2]+h[0]x[3] | 7×2+6×3+5×47\\times2+6\\times3+5\\times47×2+6×3+5×4 | 52 |
| 4 | h[2]x[2]+h[1]x[3]h[2]x[2] + h[1]x[3]h[2]x[2]+h[1]x[3] | 7×3+6×47\\times3+6\\times47×3+6×4 | 45 |
| 5 | h[2]x[3]h[2]x[3]h[2]x[3] | 7×47\\times47×4 | 28 |
规律:第 nnn 行是 hhh 的反序与 xxx 的对应片段做点积,项数为 min(n+1, M, L+M−1−n)\\min(n+1,\\ M,\\ L+M-1-n)min(n+1, M, L+M−1−n),即先增、到 MMM 后持平、再减。
1.6 两个校验技巧
计算完了用下面两条快速验错:
- 面积守恒:∑ny[n]=(∑kx[k])(∑mh[m])\\sum_n y[n]=\\left(\\sum_k x[k]\\right)\\left(\\sum_m h[m]\\right)∑ny[n]=(∑kx[k])(∑mh[m])
本例:5+16+34+52+45+28=1805+16+34+52+45+28=1805+16+34+52+45+28=180,而 (1+2+3+4)×(5+6+7)=10×18=180(1+2+3+4)\\times(5+6+7)=10\\times18=180(1+2+3+4)×(5+6+7)=10×18=180 ✓
- 端点法:y[0]=x[0]h[0]y[0]=x[0]h[0]y[0]=x[0]h[0],y[L+M−2]=x[L−1]h[M−1]y[L+M-2]=x[L-1]h[M-1]y[L+M−2]=x[L−1]h[M−1]
本例:y[0]=1×5=5y[0]=1\\times5=5y[0]=1×5=5 ✓,y[5]=4×7=28y[5]=4\\times7=28y[5]=4×7=28 ✓
1.7 MATLAB 演示:线性卷积的计算过程
把三种计算方法都写成程序,和官方函数对拍(两个本地函数 slide_conv、poly_conv 见文末完整脚本):
%% ================= 第 1 节演示:线性卷积的计算过程 =================
% 1) 官方函数两条路
y_conv = conv(x, h); % 线性卷积
y_filter = filter(h, 1, [x, zeros(1, M–1)]); % FIR 滤波视角(要补零才对得齐)
% 2) 计算方法一:翻转-平移法(逐点滑动点积)
y_slide = slide_conv(x, h);
% 3) 计算方法二:竖式乘法(错位相加,不进位)
y_poly = poly_conv(x, h);
fprintf('[线性卷积] conv(x,h) = %s\\n', mat2str(y_conv));
fprintf('[线性卷积] filter(h,1,[x 0 0]) = %s\\n', mat2str(y_filter));
fprintf('[线性卷积] 翻转-平移法 = %s\\n', mat2str(y_slide));
fprintf('[线性卷积] 竖式乘法(错位) = %s\\n', mat2str(y_poly));
fprintf('[线性卷积] 四种算法完全一致 = %d\\n', …
isequal(y_conv, y_filter, y_slide, y_poly));
fprintf('[线性卷积] 校验面积守恒:sum(y) = %d,sum(x)*sum(h) = %d\\n', …
sum(y_conv), sum(x)*sum(h));
fprintf('[线性卷积] 校验端点:y(1) = %d,y(end) = %d\\n', y_conv(1), y_conv(end));
% 逐项展开(对应 1.3 节的计算表)
fprintf('\\n n 有效 k 区间 逐项乘积\\n');
for n = 0:L+M–2
k1 = max(0, n–M+1); k2 = min(L–1, n);
terms = arrayfun(@(k) sprintf('%d*%d', x(k+1), h(n–k+1)), k1:k2, …
'UniformOutput', false);
fprintf(' %2d [%d, %d]%8s %s = %d\\n', n, k1, k2, '', …
strjoin(terms, ' + '), y_conv(n+1));
end
fprintf('\\n');
真实运行输出:
例题:x 长度 L = 4,h 长度 M = 3,Nmin = L+M-1 = 6
[线性卷积] conv(x,h) = [5 16 34 52 45 28]
[线性卷积] filter(h,1,[x 0 0]) = [5 16 34 52 45 28]
[线性卷积] 翻转-平移法 = [5 16 34 52 45 28]
[线性卷积] 竖式乘法(错位) = [5 16 34 52 45 28]
[线性卷积] 四种算法完全一致 = 1
[线性卷积] 校验面积守恒:sum(y) = 180,sum(x)*sum(h) = 180
[线性卷积] 校验端点:y(1) = 5,y(end) = 28
n 有效 k 区间 逐项乘积
0 [0, 0] 1*5 = 5
1 [0, 1] 1*6 + 2*5 = 16
2 [0, 2] 1*7 + 2*6 + 3*5 = 34
3 [1, 3] 2*7 + 3*6 + 4*5 = 52
4 [2, 3] 3*7 + 4*6 = 45
5 [3, 3] 4*7 = 28
四条路径结果完全一致——“翻转平移 / 竖式错位 / 滑窗点积”本来就是一回事。注意 filter 的输出长度默认等于输入长度,想看完整的线性卷积结果,要在输入尾部补 M−1M-1M−1 个零(即 filter(h,1,[x, zeros(1,M-1)])),否则尾部 M−1M-1M−1 个样本会被截掉。
2. 循环卷积:定义、计算与 MATLAB 演示
2.1 定义
两个 NNN 点序列的 NNN 点循环卷积:
yc[n]=∑k=0N−1x[k] h[(n−k) mod N],n=0,1,…,N−1y_c[n]=\\sum_{k=0}^{N-1} x[k]\\,h[(n-k)\\bmod N],\\quad n=0,1,\\dots,N-1yc[n]=k=0∑N−1x[k]h[(n−k)modN],n=0,1,…,N−1
注意两个前提:
输出长度永远是 NNN,这是它和线性卷积最表面的区别。
2.2 DFT 世界的“乘法”:为什么要用循环卷积
DFT 有一条极其漂亮的性质——循环卷积定理:
DFT{x⊛h}=DFT{x}⋅DFT{h}\\rm DFT\\lbrace x \\circledast h\\rbrace = \\rm DFT\\lbrace x\\rbrace \\cdot \\rm DFT\\lbrace h\\rbraceDFT{x⊛h}=DFT{x}⋅DFT{h}
即:时域的循环卷积 ↔ 频域的逐点乘积。这正是 FFT 快速卷积的理论基础,也是信道均衡、谱估计、以及 OFDM 循环前缀设计里到处出现循环卷积的原因。
但要注意它的定义域变了:序列被“卷”到长度为 NNN 的圆周上,长度超过 NNN 的部分会绕回开头。这不是数学游戏,而是 NNN 点 DFT 隐含的前提——NNN 点 DFT 把序列视为周期为 NNN 的周期序列的一个周期。周期序列之间做卷积,出来的自然是循环卷积。
2.3 三个等价视角(理解循环卷积的关键)
视角 A:圆周滑动。 把 hhh 首尾相接绕成一个长度为 NNN 的圆环,把 xxx 也铺在圆环上。求 yc[n]y_c[n]yc[n] 时,让 hhh 沿圆环循环移位 nnn 格,然后与 xxx 逐点相乘求和。滑出圆环的部分不会"掉下去",而是从另一头转回来——这就是"循环"的含义。
视角 B:周期延拓后取主值。 把 xxx、hhh 都以 NNN 为周期延拓成周期序列,它们的(线性)卷积仍是周期为 NNN 的周期序列,取它的**主值区(n=0,…,N−1n=0,\\dots,N-1n=0,…,N−1)**就是循环卷积:
yc[n]=yp[n],n=0,1,…,N−1y_c[n]=y_p[n],\\quad n=0,1,\\dots,N-1yc[n]=yp[n],n=0,1,…,N−1
其中 ypy_pyp 是两个周期序列的(线性)卷积,它本身也是周期为 NNN 的周期序列。
视角 C:DFT 点乘。 这是工程上最常用的视角:
yc[n]=IDFT{DFT{x}⋅DFT{h}}y_c[n]=\\text{IDFT}\\left\\{\\text{DFT}\\{x\\}\\cdot\\text{DFT}\\{h\\}\\right\\}yc[n]=IDFT{DFT{x}⋅DFT{h}}
三个视角完全等价。视角 B 直接给出了第 3 节的相等条件,视角 C 给出了 FFT 快速实现。
2.4 计算方法一:折叠法(最推荐)
这是计算最快、也最能揭示混叠机理的方法,三步:
例题仍取 x=[1,2,3,4]x=[1,2,3,4]x=[1,2,3,4]、h=[5,6,7]h=[5,6,7]h=[5,6,7],线性卷积 ylin=[5,16,34,52,45,28]y_{lin}=[5,16,34,52,45,28]ylin=[5,16,34,52,45,28](下标 0~5)。
(1) N=6N=6N=6,即 N=L+M−1N=L+M-1N=L+M−1,临界情况
下标 0∼50\\sim50∼5 对 6 取模后都是自己,无折叠:
yc=[5, 16, 34, 52, 45, 28]y_c=[5,\\ 16,\\ 34,\\ 52,\\ 45,\\ 28]yc=[5, 16, 34, 52, 45, 28]
与线性卷积完全相同。
(2) N=5N=5N=5,比临界值小 1
下标 5 折回到 0:yc[0]=5+28=33y_c[0]=5+28=33yc[0]=5+28=33,其余不变,得 yc=[33, 16, 34, 52, 45]y_c=[33,\\ 16,\\ 34,\\ 52,\\ 45]yc=[33, 16, 34, 52, 45]。线性卷积最后一个元素 28 绕回到最前面,把 5 冲成了 33。
(3) N=4N=4N=4,已经压到下限 max(L,M)=4\\max(L,M)=4max(L,M)=4
下标 4 折回到 0、下标 5 折回到 1:
yc[0]=ylin[0]+ylin[4]=5+45=50yc[1]=ylin[1]+ylin[5]=16+28=44yc[2]=ylin[2]=34,yc[3]=ylin[3]=52\\begin{aligned} y_c[0] &= y_{lin}[0] + y_{lin}[4] = 5 + 45 = 50\\\\ y_c[1] &= y_{lin}[1] + y_{lin}[5] = 16 + 28 = 44\\\\ y_c[2] &= y_{lin}[2] = 34,\\quad y_c[3] = y_{lin}[3] = 52 \\end{aligned}yc[0]yc[1]yc[2]=ylin[0]+ylin[4]=5+45=50=ylin[1]+ylin[5]=16+28=44=ylin[2]=34,yc[3]=ylin[3]=52
yc=[50, 44, 34, 52]y_c=[50,\\ 44,\\ 34,\\ 52]yc=[50, 44, 34, 52]
用示意图看 N=5N=5N=5 的折叠过程:
线性卷积 y_lin [ 5 16 34 52 45 28 ]
下标 0 1 2 3 4 5
| | | | | |
下标 mod 5 0 1 2 3 4 0
| | | | | |
v v v v v v
折叠相加:
y_c[0] = 5 + 28 = 33
y_c[1] = 16 = 16
y_c[2] = 34 = 34
y_c[3] = 52 = 52
y_c[4] = 45 = 45
——————————
循环卷积 y_c [ 33 16 34 52 45 ] ← 尾巴被"卷"回了开头
2.5 计算方法二:定义法(下标取模,逐 n 列表)
如果不想先算线性卷积,也可以直接用定义。取 N=5N=5N=5,补零后 xp=[1,2,3,4,0]x_p=[1,2,3,4,0]xp=[1,2,3,4,0]、hp=[5,6,7,0,0]h_p=[5,6,7,0,0]hp=[5,6,7,0,0]。
对每个 nnn,依次取 k=0,1,2,3,4k=0,1,2,3,4k=0,1,2,3,4 时需要乘的 hp[(n−k) mod 5]h_p[(n-k)\\bmod 5]hp[(n−k)mod5]:
| 0 | [5, 0, 0, 7, 6] | 1×5+2×0+3×0+4×7+0×61\\times5 + 2\\times0 + 3\\times0 + 4\\times7 + 0\\times61×5+2×0+3×0+4×7+0×6 | 33 |
| 1 | [6, 5, 0, 0, 7] | 1×6+2×5+3×0+4×0+0×71\\times6 + 2\\times5 + 3\\times0 + 4\\times0 + 0\\times71×6+2×5+3×0+4×0+0×7 | 16 |
| 2 | [7, 6, 5, 0, 0] | 1×7+2×6+3×5+4×0+0×01\\times7 + 2\\times6 + 3\\times5 + 4\\times0 + 0\\times01×7+2×6+3×5+4×0+0×0 | 34 |
| 3 | [0, 7, 6, 5, 0] | 1×0+2×7+3×6+4×5+0×01\\times0 + 2\\times7 + 3\\times6 + 4\\times5 + 0\\times01×0+2×7+3×6+4×5+0×0 | 52 |
| 4 | [0, 0, 7, 6, 5] | 1×0+2×0+3×7+4×6+0×51\\times0 + 2\\times0 + 3\\times7 + 4\\times6 + 0\\times51×0+2×0+3×7+4×6+0×5 | 45 |
yc=[33, 16, 34, 52, 45]y_c=[33,\\ 16,\\ 34,\\ 52,\\ 45]yc=[33, 16, 34, 52, 45]
结果与折叠法完全一致 ✓。观察第二列:每一行就是把上一行整体右移一格,移出去的元素从最左边转回来——这就是"圆周滑动"的代数体现。
2.6 计算方法三:循环矩阵法(最适合写程序)
循环卷积可以写成一次矩阵乘法 yc=xpHy_c = x_p Hyc=xpH,其中 HHH 是一个 N×NN\\times NN×N 循环矩阵(circulant matrix),元素满足 H[k,n]=h[(n−k) mod N]H[k,n]=h[(n-k)\\bmod N]H[k,n]=h[(n−k)modN],k,n=0,1,…,N−1k,n=0,1,\\dots,N-1k,n=0,1,…,N−1。
其结构是"每一行都是上一行的循环右移一位",N=5N=5N=5 时形如:
H=[h0h4h3h2h1h1h0h4h3h2h2h1h0h4h3h3h2h1h0h4h4h3h2h1h0]H=\\begin{bmatrix} h_0 & h_4 & h_3 & h_2 & h_1\\\\ h_1 & h_0 & h_4 & h_3 & h_2\\\\ h_2 & h_1 & h_0 & h_4 & h_3\\\\ h_3 & h_2 & h_1 & h_0 & h_4\\\\ h_4 & h_3 & h_2 & h_1 & h_0 \\end{bmatrix}H=h0h1h2h3h4h4h0h1h2h3h3h4h0h1h2h2h3h4h0h1h1h2h3h4h0
本例取 N=5N=5N=5,hp=[5,6,7,0,0]h_p=[5,6,7,0,0]hp=[5,6,7,0,0],代入得:
H=[5670005670005677005667005]H=\\begin{bmatrix} 5 & 6 & 7 & 0 & 0\\\\ 0 & 5 & 6 & 7 & 0\\\\ 0 & 0 & 5 & 6 & 7\\\\ 7 & 0 & 0 & 5 & 6\\\\ 6 & 7 & 0 & 0 & 5 \\end{bmatrix}H=5007665007765000765000765
做一次矩阵乘法即可得到循环卷积:
[1, 2, 3, 4, 0]⋅H=[33, 16, 34, 52, 45][1,\\ 2,\\ 3,\\ 4,\\ 0]\\cdot H=[33,\\ 16,\\ 34,\\ 52,\\ 45][1, 2, 3, 4, 0]⋅H=[33, 16, 34, 52, 45]
与 2.4、2.5 节结果一致 ✓。
为什么是循环矩阵? 因为循环矩阵的每一行都是上一行的循环右移,这正好对应"hhh 在圆周上滑一位"。循环矩阵可以被 DFT 矩阵对角化(H=F−1ΛFH=F^{-1}\\Lambda FH=F−1ΛF),这就是"DFT 点乘实现循环卷积"的矩阵解释。
2.7 MATLAB 演示:循环卷积的计算过程
同样用程序把三种计算方法复现一遍(取 N=5N=5N=5):
%% ================= 第 2 节演示:循环卷积的计算过程 =================
N = 5;
fprintf('[循环卷积] N = %d 时的三种计算方法\\n', N);
yc_def = circ_conv_by_def(x, h, N); % 计算方法:定义式(下标取模,双重循环)
yc_fold = circ_conv_by_fold(x, h, N); % 计算方法:折叠法(线性卷积 → 下标 mod N → 相加)
yc_mat = circ_conv_by_mat(x, h, N); % 计算方法:循环矩阵法(一次矩阵乘法)
fprintf(' 定义式(mod) y_c = %s\\n', mat2str(yc_def));
fprintf(' 折叠法 y_c = %s\\n', mat2str(yc_fold));
fprintf(' 循环矩阵法 y_c = %s\\n', mat2str(yc_mat));
fprintf(' 三者完全一致 = %d\\n', isequal(yc_def, yc_fold, yc_mat));
% 折叠过程:线性卷积的下标如何折回 0 ~ N-1
fprintf('\\n 折叠明细(N = %d):\\n', N);
ylin = conv(x, h);
for i = 1:numel(ylin)
fprintf(' y_lin[%d] = %4d –> 下标 %d mod %d = %d\\n', …
i–1, ylin(i), i–1, N, mod(i–1, N));
end
% 循环矩阵
xp = [x, zeros(1, N–numel(x))];
hp = [h, zeros(1, N–numel(h))];
idx = mod((0:N–1) – (0:N–1).', N) + 1;
H = hp(idx);
fprintf('\\n 循环矩阵 H =\\n'); disp(H);
fprintf(' 定义法逐 n 的 h_p[(n-k) mod N] 取值:\\n');
for n = 0:N–1
fprintf(' n = %d : %s\\n', n, mat2str(hp(mod(n–(0:N–1), N)+1)));
end
fprintf('\\n');
真实运行输出:
[循环卷积] N = 5 时的三种计算方法
定义式(mod) y_c = [33 16 34 52 45]
折叠法 y_c = [33 16 34 52 45]
循环矩阵法 y_c = [33 16 34 52 45]
三者完全一致 = 1
折叠明细(N = 5):
y_lin[0] = 5 –> 下标 0 mod 5 = 0
y_lin[1] = 16 –> 下标 1 mod 5 = 1
y_lin[2] = 34 –> 下标 2 mod 5 = 2
y_lin[3] = 52 –> 下标 3 mod 5 = 3
y_lin[4] = 45 –> 下标 4 mod 5 = 4
y_lin[5] = 28 –> 下标 5 mod 5 = 0
循环矩阵 H =
5 6 7 0 0
0 5 6 7 0
0 0 5 6 7
7 0 0 5 6
6 7 0 0 5
定义法逐 n 的 h_p[(n-k) mod N] 取值:
n = 0 : [5 0 0 7 6]
n = 1 : [6 5 0 0 7]
n = 2 : [7 6 5 0 0]
n = 3 : [0 7 6 5 0]
n = 4 : [0 0 7 6 5]
三种方法结果完全一致,而且折叠明细把“混叠”看得清清楚楚:线性卷积下标 5 的 28 折回下标 0,与原来的 5 相加得到 33。这正是下一节要讲的时域混叠。
3. 两者何时相等:时域混叠、计算演示与 MATLAB
3.1 核心关系式:时域混叠
由 2.3 节的视角 B(周期延拓取主值),循环卷积就是周期延拓后卷积的主值序列。展开写就是:
yc[n]=∑m=−∞+∞ylin[n+mN]y_c[n]=\\sum_{m=-\\infty}^{+\\infty} y_{lin}[n+mN]yc[n]=m=−∞∑+∞ylin[n+mN]
也就是说:循环卷积把线性卷积以 NNN 为周期一段一段折叠起来相加。学名是时域混叠(time-domain aliasing),俗称回卷 / wraparound。2.4 节的折叠法算的正是这个式子。
3.2 相等条件(定理)
定理:设 xxx 长 LLL、hhh 长 MMM,线性卷积 yliny_{lin}ylin 长 L+M−1L+M-1L+M−1。则 NNN 点循环卷积 ycy_cyc 满足
yc[n]={ylin[n],0≤n≤L+M−20,L+M−2<n≤N−1y_c[n]=\\begin{cases} y_{lin}[n], & 0\\le n\\le L+M-2\\\\ 0, & L+M-2<n\\le N-1 \\end{cases}yc[n]={ylin[n],0,0≤n≤L+M−2L+M−2<n≤N−1
当且仅当 N≥L+M−1N\\ge L+M-1N≥L+M−1。
3.3 直观总结:临界长度
记临界长度 Nmin=L+M−1N_{\\min}=L+M-1Nmin=L+M−1:
| N>L+M−1N>L+M-1N>L+M−1 | 循环卷积 = 线性卷积 + 尾部补零(可以用来算线性卷积) |
| N=L+M−1N=L+M-1N=L+M−1 | 循环卷积 = 线性卷积(临界,恰好相等) |
| N<L+M−1N<L+M-1N<L+M−1 | 发生混叠,不能用来算线性卷积 |
| N<max(L,M)N<\\max(L,M)N<max(L,M) | 连输入本身都被折叠,循环卷积无意义 |
3.4 本题完整对照与 MATLAB 演示
本题 L=4, M=3L=4,\\ M=3L=4, M=3,Nmin=6N_{\\min}=6Nmin=6,ylin=[5,16,34,52,45,28]y_{lin}=[5,16,34,52,45,28]ylin=[5,16,34,52,45,28]:
| 4 | [50 44 34 52] | 混叠:下标 4→0、5→1 |
| 5 | [33 16 34 52 45] | 混叠:下标 5→0 |
| 6 | [5 16 34 52 45 28] | 完全相等(临界) |
| 7 | [5 16 34 52 45 28 0] | 相等 + 尾部补 1 个零 |
| 8 | [5 16 34 52 45 28 0 0] | 相等 + 尾部补 2 个零 |
把 NNN 从 4 调到 8,用程序把上面的结论验证一遍:
%% ============ 第 3 节演示:循环卷积何时等于线性卷积 ============
fprintf('[相等条件] 线性卷积 y_lin = %s(长度 %d)\\n', mat2str(ylin), numel(ylin));
fprintf(' Nmin = L+M-1 = %d\\n\\n', Nmin);
for N = 4:8
yc = circ_conv_by_def(x, h, N);
if N >= Nmin
if N == Nmin
flag = '相等(临界)';
else
flag = '相等(尾部补零)';
end
else
flag = '不相等(时域混叠)';
end
fprintf(' N = %d : y_c = %-30s %s\\n', N, mat2str(yc), flag);
end
fprintf('\\n N=5 的混叠细节:y_lin[5] = %d 折回下标 %d,所以 ', ylin(6), mod(5,5));
fprintf('y_c[0] = y_lin[0] + y_lin[5] = %d + %d = %d\\n', …
ylin(1), ylin(6), ylin(1)+ylin(6));
fprintf(' N=4 的混叠细节:y_lin[4] = %d 折回下标 %d,所以 ', ylin(5), mod(4,4));
fprintf('y_c[0] = %d + %d = %d;', ylin(1), ylin(5), ylin(1)+ylin(5));
fprintf('y_lin[5] = %d 折回下标 %d,所以 y_c[1] = %d + %d = %d\\n\\n', …
ylin(6), mod(5,4), ylin(2), ylin(6), ylin(2)+ylin(6));
% 用 FFT 点乘算循环卷积:N 不足时算出来的就是循环卷积
for N = [4, 5, 6]
yf = real(ifft(fft(x, N) .* fft(h, N)));
fprintf(' FFT 法 N=%d : ifft(fft(x,N).*fft(h,N)) = %s\\n', N, mat2str(yf, 8));
end
fprintf(' 注意 N=6 时与线性卷积一致,N=4/5 时是循环卷积(混叠)\\n\\n');
真实运行输出:
[相等条件] 线性卷积 y_lin = [5 16 34 52 45 28](长度 6)
Nmin = L+M-1 = 6
N = 4 : y_c = [50 44 34 52] 不相等(时域混叠)
N = 5 : y_c = [33 16 34 52 45] 不相等(时域混叠)
N = 6 : y_c = [5 16 34 52 45 28] 相等(临界)
N = 7 : y_c = [5 16 34 52 45 28 0] 相等(尾部补零)
N = 8 : y_c = [5 16 34 52 45 28 0 0] 相等(尾部补零)
N=5 的混叠细节:y_lin[5] = 28 折回下标 0,所以 y_c[0] = y_lin[0] + y_lin[5] = 5 + 28 = 33
N=4 的混叠细节:y_lin[4] = 45 折回下标 0,所以 y_c[0] = 5 + 45 = 50;y_lin[5] = 28 折回下标 1,所以 y_c[1] = 16 + 28 = 44
FFT 法 N=4 : ifft(fft(x,N).*fft(h,N)) = [50 44 34 52]
FFT 法 N=5 : ifft(fft(x,N).*fft(h,N)) = [33 16 34 52 45]
FFT 法 N=6 : ifft(fft(x,N).*fft(h,N)) = [5 16 34 52 45 28]
注意 N=6 时与线性卷积一致,N=4/5 时是循环卷积(混叠)
N=6N=6N=6 起循环卷积与线性卷积完全相等(N=7N=7N=7、N=8N=8N=8 只是尾部多补了零),而 N=4N=4N=4、N=5N=5N=5 的结果都被“折”过了。另外注意最后三行:FFT 点乘算出来的正是循环卷积——这就是“补零不足时 FFT 卷积会算错”的根源。
3.5 三个必须记住的数字
4. 进阶:FFT 快速卷积与自实现循环卷积
本节的代码同样在 MATLAB R2023b 上实测通过,不依赖任何工具箱,可直接复制运行。
4.1 函数清单
| conv(x,h) | 线性卷积 | 无 |
| filter(h,1,x) | 线性卷积(FIR 滤波视角) | 无 |
| cconv(x,h,N) | NNN 点循环卷积 | 需 Signal Processing Toolbox;N≥max(L,M)N\\ge\\max(L,M)N≥max(L,M) |
| ifft(fft(x,N).*fft(h,N)) | 用 DFT 算循环卷积 | N≥max(L,M)N\\ge\\max(L,M)N≥max(L,M) |
| xcorr、conv2 | 相关、二维卷积 | —— |
若你的 MATLAB 没有 cconv(需 Signal Processing Toolbox),用 4.4 节的自实现 circ_conv,结果完全相同。
4.2 用 FFT 做快速线性卷积(正确姿势)
%% 正确:补零到 Nfft >= L+M-1
Nmin = numel(x) + numel(h) – 1; % 6
Nfft = 2^nextpow2(Nmin); % 8
y_fft = real(ifft(fft(x,Nfft) .* fft(h,Nfft)));
err = max(abs(y_fft – [conv(x,h), zeros(1, Nfft–Nmin)]));
fprintf('Nfft = %d, y_fft = %s, 最大误差 = %g\\n', Nfft, mat2str(y_fft), err);
fprintf('y_fft(1) 的 17 位精度显示 = %.17g\\n', y_fft(1));
真实输出:
Nfft = 8, y_fft = [5 16 34 52 45 28 0 0], 最大误差 = 3.55271e-15
y_fft(1) 的 17 位精度显示 = 4.9999999999999964
三个要点:
is_zero = abs(y_fft) < 1e-10;
4.3 反例:补零不足会怎样
%% 错误:Nfft = 5 < L+M-1 = 6
y_bad = real(ifft(fft(x,5) .* fft(h,5)));
真实输出:
Nfft = 5, y_bad = [33 16 34 52 45] <– 被回卷了,根本不是线性卷积!
注意 [33 16 34 52 45] 恰好等于 3.4 节里 N=5N=5N=5 的循环卷积——这不是巧合:当 FFT 点数不足时,你算出来的就是循环卷积。这是实际工程中最常见的 bug 之一,结果长度看着“对”(甚至更短),数值却完全错。如果发现 FFT 卷积的结果和 conv 对不上,第一件事就是检查 FFT 点数是否 ≥L+M−1\\ge L+M-1≥L+M−1。
4.4 自实现循环卷积(不依赖工具箱)+ 循环矩阵
function y = circ_conv(x, h, N)
% N 点循环卷积:y[n] = sum_k x[k]*h[(n-k) mod N]
x = x(:).'; h = h(:).';
assert(N >= max(numel(x), numel(h)), …
'N 必须不小于两个输入序列的长度');
xp = [x, zeros(1, N – numel(x))]; % 补齐到 N 点
hp = [h, zeros(1, N – numel(h))];
y = zeros(1, N);
for n = 0:N–1
for k = 0:N–1
y(n+1) = y(n+1) + xp(k+1) * hp(mod(n–k, N) + 1);
end
end
end
循环矩阵法(对应 2.6 节):
N = 5;
xp = [x, zeros(1, N–numel(x))];
hp = [h, zeros(1, N–numel(h))];
idx = mod((0:N–1) – (0:N–1).', N) + 1; % H(k,n) = hp((n-k) mod N + 1)
H = hp(idx);
disp(H)
y_mat = xp * H;
真实输出:
循环矩阵 H =
5 6 7 0 0
0 5 6 7 0
0 0 5 6 7
7 0 0 5 6
6 7 0 0 5
xp * H = [33 16 34 52 45]
与 2.6 节手推的矩阵完全一致 ✓,且 xp * H 的结果与 cconv 相同。
5. 对比总结与结论
5.1 两种卷积对照总表
| 定义式 | y[n]=∑kx[k]h[n−k]y[n]=\\sum_k x[k]h[n-k]y[n]=∑kx[k]h[n−k],下标不取模 | yc[n]=∑k=0N−1x[k]h[(n−k) mod N]y_c[n]=\\sum_{k=0}^{N-1} x[k]h[(n-k)\\bmod N]yc[n]=∑k=0N−1x[k]h[(n−k)modN] |
| 结果长度 | L+M−1L+M-1L+M−1 | 固定为 NNN |
| 输入序列长度要求 | 无 | 两者都必须是 NNN 点(短者补零) |
| 频域对应 | DTFT 相乘(连续谱) | DFT 点乘(离散谱,严格成立) |
| 物理意义 | LTI 系统的零状态响应 | 周期序列的卷积 / 圆环上的加权叠加 |
| 相等条件 | —— | N≥L+M−1N \\ge L+M-1N≥L+M−1 |
| 典型 MATLAB 函数 | conv、filter | cconv、ifft(fft(x,N).*fft(h,N)) |
一句话记住:循环卷积 = 线性卷积以 NNN 为周期折叠相加。NNN 够大就折不上,两者相等。
5.2 五条结论
附:本文验证脚本
conv_demo.m(自包含,含全部本地函数,MATLAB 直接 Run 可复现本文所有输出):
%% conv_demo.m —— 线性卷积 vs 循环卷积:全文演示的完整验证脚本
% 环境:MATLAB R2023b(不依赖任何工具箱,可直接 Run 复现本文全部输出)
% 例题:x = [1 2 3 4](L=4),h = [5 6 7](M=3),Nmin = L+M-1 = 6
clc
x = [1 2 3 4];
h = [5 6 7];
L = numel(x); M = numel(h); Nmin = L + M – 1;
fprintf('例题:x 长度 L = %d,h 长度 M = %d,Nmin = L+M-1 = %d\\n\\n', L, M, Nmin);
%% ================= 第 1 节演示:线性卷积的计算过程 =================
% 1) 官方函数两条路
y_conv = conv(x, h); % 线性卷积
y_filter = filter(h, 1, [x, zeros(1, M–1)]); % FIR 滤波视角(要补零才对得齐)
% 2) 计算方法一:翻转-平移法(逐点滑动点积)
y_slide = slide_conv(x, h);
% 3) 计算方法二:竖式乘法(错位相加,不进位)
y_poly = poly_conv(x, h);
fprintf('[线性卷积] conv(x,h) = %s\\n', mat2str(y_conv));
fprintf('[线性卷积] filter(h,1,[x 0 0]) = %s\\n', mat2str(y_filter));
fprintf('[线性卷积] 翻转-平移法 = %s\\n', mat2str(y_slide));
fprintf('[线性卷积] 竖式乘法(错位) = %s\\n', mat2str(y_poly));
fprintf('[线性卷积] 四种算法完全一致 = %d\\n', …
isequal(y_conv, y_filter, y_slide, y_poly));
fprintf('[线性卷积] 校验面积守恒:sum(y) = %d,sum(x)*sum(h) = %d\\n', …
sum(y_conv), sum(x)*sum(h));
fprintf('[线性卷积] 校验端点:y(1) = %d,y(end) = %d\\n', y_conv(1), y_conv(end));
% 逐项展开(对应 1.3 节的计算表)
fprintf('\\n n 有效 k 区间 逐项乘积\\n');
for n = 0:L+M–2
k1 = max(0, n–M+1); k2 = min(L–1, n);
terms = arrayfun(@(k) sprintf('%d*%d', x(k+1), h(n–k+1)), k1:k2, …
'UniformOutput', false);
fprintf(' %2d [%d, %d]%8s %s = %d\\n', n, k1, k2, '', …
strjoin(terms, ' + '), y_conv(n+1));
end
fprintf('\\n');
%% ================= 第 2 节演示:循环卷积的计算过程 =================
N = 5;
fprintf('[循环卷积] N = %d 时的三种计算方法\\n', N);
yc_def = circ_conv_by_def(x, h, N); % 计算方法:定义式(下标取模,双重循环)
yc_fold = circ_conv_by_fold(x, h, N); % 计算方法:折叠法(线性卷积 → 下标 mod N → 相加)
yc_mat = circ_conv_by_mat(x, h, N); % 计算方法:循环矩阵法(一次矩阵乘法)
fprintf(' 定义式(mod) y_c = %s\\n', mat2str(yc_def));
fprintf(' 折叠法 y_c = %s\\n', mat2str(yc_fold));
fprintf(' 循环矩阵法 y_c = %s\\n', mat2str(yc_mat));
fprintf(' 三者完全一致 = %d\\n', isequal(yc_def, yc_fold, yc_mat));
% 折叠过程:线性卷积的下标如何折回 0 ~ N-1
fprintf('\\n 折叠明细(N = %d):\\n', N);
ylin = conv(x, h);
for i = 1:numel(ylin)
fprintf(' y_lin[%d] = %4d –> 下标 %d mod %d = %d\\n', …
i–1, ylin(i), i–1, N, mod(i–1, N));
end
% 循环矩阵
xp = [x, zeros(1, N–numel(x))];
hp = [h, zeros(1, N–numel(h))];
idx = mod((0:N–1) – (0:N–1).', N) + 1;
H = hp(idx);
fprintf('\\n 循环矩阵 H =\\n'); disp(H);
fprintf(' 定义法逐 n 的 h_p[(n-k) mod N] 取值:\\n');
for n = 0:N–1
fprintf(' n = %d : %s\\n', n, mat2str(hp(mod(n–(0:N–1), N)+1)));
end
fprintf('\\n');
%% ============ 第 3 节演示:循环卷积何时等于线性卷积 ============
fprintf('[相等条件] 线性卷积 y_lin = %s(长度 %d)\\n', mat2str(ylin), numel(ylin));
fprintf(' Nmin = L+M-1 = %d\\n\\n', Nmin);
for N = 4:8
yc = circ_conv_by_def(x, h, N);
if N >= Nmin
if N == Nmin
flag = '相等(临界)';
else
flag = '相等(尾部补零)';
end
else
flag = '不相等(时域混叠)';
end
fprintf(' N = %d : y_c = %-30s %s\\n', N, mat2str(yc), flag);
end
fprintf('\\n N=5 的混叠细节:y_lin[5] = %d 折回下标 %d,所以 ', ylin(6), mod(5,5));
fprintf('y_c[0] = y_lin[0] + y_lin[5] = %d + %d = %d\\n', …
ylin(1), ylin(6), ylin(1)+ylin(6));
fprintf(' N=4 的混叠细节:y_lin[4] = %d 折回下标 %d,所以 ', ylin(5), mod(4,4));
fprintf('y_c[0] = %d + %d = %d;', ylin(1), ylin(5), ylin(1)+ylin(5));
fprintf('y_lin[5] = %d 折回下标 %d,所以 y_c[1] = %d + %d = %d\\n\\n', …
ylin(6), mod(5,4), ylin(2), ylin(6), ylin(2)+ylin(6));
% 用 FFT 点乘算循环卷积:N 不足时算出来的就是循环卷积
for N = [4, 5, 6]
yf = real(ifft(fft(x, N) .* fft(h, N)));
fprintf(' FFT 法 N=%d : ifft(fft(x,N).*fft(h,N)) = %s\\n', N, mat2str(yf, 8));
end
fprintf(' 注意 N=6 时与线性卷积一致,N=4/5 时是循环卷积(混叠)\\n\\n');
%% ================= 第 4 节(进阶):FFT 快速卷积与自实现 =================
% 4.2 正确的 FFT 快速线性卷积
Nfft = 2^nextpow2(Nmin);
y_fft = real(ifft(fft(x, Nfft) .* fft(h, Nfft)));
err = max(abs(y_fft – [ylin, zeros(1, Nfft–numel(ylin))]));
fprintf('[进阶 4.2] Nfft = 2^nextpow2(%d) = %d\\n', Nmin, Nfft);
fprintf('[进阶 4.2] y_fft = %s\\n', mat2str(y_fft));
fprintf('[进阶 4.2] 与 conv 的最大误差 = %g\\n', err);
fprintf('[进阶 4.2] y_fft(1) 的 17 位精度显示 = %.17g\\n', y_fft(1));
% 4.3 反例:FFT 点数不足
y_bad = real(ifft(fft(x, 5) .* fft(h, 5)));
fprintf('[进阶 4.3] Nfft = 5 时 y_bad = %s <– 回卷成了循环卷积\\n\\n', mat2str(y_bad, 6));
% 4.4 自实现循环卷积(函数见文末)+ 循环矩阵
fprintf('[进阶 4.4] circ_conv(x,h,5) = %s\\n', mat2str(circ_conv(x, h, 5)));
fprintf('[进阶 4.4] circ_conv(x,h,6) = %s\\n', mat2str(circ_conv(x, h, 6)));
fprintf('[进阶 4.4] 循环矩阵法 xp*H = %s\\n', mat2str(xp*H));
fprintf('[进阶 4.4] 与 cconv 一致(若有工具箱)= %d\\n', …
~exist('cconv', 'file') || isequal(circ_conv(x,h,5), cconv(x,h,5)));
%% ============================== 本地函数 ==============================
function y = slide_conv(x, h)
% 计算方法一:翻转-平移法(逐 n 做滑动点积)
L = numel(x); M = numel(h);
y = zeros(1, L+M–1);
for n = 0:L+M–2
k1 = max(0, n–M+1); k2 = min(L–1, n);
for k = k1:k2
y(n+1) = y(n+1) + x(k+1) * h(n–k+1);
end
end
end
function y = poly_conv(x, h)
% 计算方法二:竖式乘法(多项式乘法,错位相加、不进位)
L = numel(x); M = numel(h);
y = zeros(1, L+M–1);
for i = 1:L
for j = 1:M
y(i+j–1) = y(i+j–1) + x(i) * h(j);
end
end
end
function y = circ_conv_by_def(x, h, N)
% 计算方法三:定义式 y[n] = sum_k x[k]*h[(n-k) mod N]
xp = [x, zeros(1, N–numel(x))];
hp = [h, zeros(1, N–numel(h))];
y = zeros(1, N);
for n = 0:N–1
for k = 0:N–1
y(n+1) = y(n+1) + xp(k+1) * hp(mod(n–k, N)+1);
end
end
end
function yc = circ_conv_by_fold(x, h, N)
% 计算方法四:折叠法(先线性卷积,再把下标 mod N 的元素相加)
ylin = conv(x, h);
yc = zeros(1, N);
for i = 1:numel(ylin)
n = mod(i–1, N) + 1;
yc(n) = yc(n) + ylin(i);
end
end
function yc = circ_conv_by_mat(x, h, N)
% 计算方法五:循环矩阵法(y_c = x_p * H)
xp = [x, zeros(1, N–numel(x))];
hp = [h, zeros(1, N–numel(h))];
idx = mod((0:N–1) – (0:N–1).', N) + 1;
H = hp(idx);
yc = xp * H;
end
function y = circ_conv(x, h, N)
% N 点循环卷积(不依赖任何工具箱)
x = x(:).'; h = h(:).';
assert(N >= max(numel(x), numel(h)), 'N 必须不小于两个输入序列的长度');
xp = [x, zeros(1, N–numel(x))];
hp = [h, zeros(1, N–numel(h))];
y = zeros(1, N);
for n = 0:N–1
for k = 0:N–1
y(n+1) = y(n+1) + xp(k+1) * hp(mod(n–k, N)+1);
end
end
end
环境:MATLAB R2023b,Windows。代码兼容 R2019b 及以上(assert、nextpow2、局部函数均为长期稳定特性)。
如果这篇文章恰好能帮到你,那是我的荣幸,欢迎点赞收藏;有疑问欢迎评论区讨论。


