欢迎光临
我们一直在努力

线性卷积与循环卷积的爱恨情仇

线性卷积与循环卷积:原理、计算步骤、相等条件与 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 计算方法一:翻转-平移法(图解法)

这是最能体现物理动作上“卷”的方法:

  • 变量替换:把 x[n]→x[k]x[n]\\to x[k]x[n]→x[k]、h[n]→h[k]h[n]\\to h[k]h[n]→h[k];
  • 翻转:把 h[k]h[k]h[k] 变成 h[−k]h[-k]h[−k](关于纵轴翻折);
  • 平移:把翻转后的序列右移 nnn 位,得到 h[n−k]h[n-k]h[n−k];
  • 相乘求和:与 x[k]x[k]x[k] 逐点相乘再求和,得到 y[n]y[n]y[n];对每个 nnn 重复。
  • 代入本例题,用有效区间公式 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​+x1​z−1+x2​z−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]=∑k​x[k]h[n−k] 列表,最不容易出错:

    nnn展开式数值代入求和
    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)∑n​y[n]=(∑k​x[k])(∑m​h[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−1​x[k]h[(n−k)modN],n=0,1,…,N−1

    注意两个前提:

  • 参与运算的 xxx、hhh 都必须是 NNN 点序列:短的尾部补零,长的要先做周期折叠;
  • 必须有 N≥max⁡(L,M)N\\ge\\max(L,M)N≥max(L,M),否则连输入本身都被折叠了,循环卷积失去物理意义(MATLAB 的 cconv 也会报错)。
  • 输出长度永远是 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 计算方法一:折叠法(最推荐)

    这是计算最快、也最能揭示混叠机理的方法,三步:

  • 先照常算线性卷积 yliny_{lin}ylin​(长度 L+M−1L+M-1L+M−1);
  • 把 yliny_{lin}ylin​ 的下标全部对 NNN 取模;
  • 把落到同一下标的元素相加,得到 NNN 点结果。
  • 例题仍取 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]:

    nnnhp[(n−k) mod 5]h_p[(n-k)\\bmod 5]hp​[(n−k)mod5](k=0,…,4k=0,\\dots,4k=0,…,4)逐项乘积yc[n]y_c[n]yc​[n]
    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​=xp​H,其中 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=​h0​h1​h2​h3​h4​​h4​h0​h1​h2​h3​​h3​h4​h0​h1​h2​​h2​h3​h4​h0​h1​​h1​h2​h3​h4​h0​​​

    本例取 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=​50076​65007​76500​07650​00765​​

    做一次矩阵乘法即可得到循环卷积:

    [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:

    NNN 与 Nmin⁡N_{\\min}Nmin​ 的关系结果
    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]:

    NNN循环卷积结果与线性卷积的关系
    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 三个必须记住的数字

  • L+M−1L+M-1L+M−1:线性卷积长度,也是循环卷积不产生混叠的最小 NNN;
  • max⁡(L,M)\\max(L,M)max(L,M):cconv 和循环卷积定义对 NNN 的下限,低于它直接报错;
  • 2^nextpow2(L+M-1):FFT 快速卷积时推荐的 FFT 点数(既要 ≥L+M−1\\ge L+M-1≥L+M−1,又要尽量取 2 的幂以发挥 FFT 速度)。

  • 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

    三个要点:

  • fft(x,Nfft) 里的第二个参数 NNN 就是补零,把序列变成 NNN 点;
  • NNN 必须 ≥L+M−1\\ge L+M-1≥L+M−1,否则 xxx 和 hhh 的 DFT 乘积反变换回来得到的是循环卷积;
  • 打印出来是整整齐齐的 [5 16 34 52 45 28 0 0],但把第一个元素放大到 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 两种卷积对照总表

    维度线性卷积 (Linear)循环卷积 (Circular / Cyclic)
    定义式 y[n]=∑kx[k]h[n−k]y[n]=\\sum_k x[k]h[n-k]y[n]=∑k​x[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−1​x[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 五条结论

  • 线性卷积是 LTI 系统的真实响应,输出长度 L+M−1L+M-1L+M−1,计算有翻转平移、竖式乘法、滑窗表三条路,用"面积守恒 + 端点法"验算。
  • 循环卷积是 DFT 点乘的时域对应物,长度固定 NNN,本质上就是"把线性卷积以 NNN 为周期折叠相加"。
  • 相等条件:N≥L+M−1N\\ge L+M-1N≥L+M−1,此时循环卷积 = 线性卷积(后面补零);否则发生时域混叠,尾部数据绕回开头,结果不可用。
  • 工程结论:用 FFT 做快速卷积时,FFT 点数至少取 L+M−1L+M-1L+M−1(通常取 2 的幂);序列很长时用重叠相加 / 重叠保留分块处理。
  • 反过来,OFDM 等场景故意利用循环卷积(靠循环前缀把线性卷积"变成"循环卷积),让频域信道补偿变得无比简单——从"避免混叠"到"利用混叠",这正是循环卷积的价值所在。

  • 附:本文验证脚本

    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、局部函数均为长期稳定特性)。


    如果这篇文章恰好能帮到你,那是我的荣幸,欢迎点赞收藏;有疑问欢迎评论区讨论。

    赞(0)
    未经允许不得转载:171主机测评 » 线性卷积与循环卷积的爱恨情仇
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址