news 2026/10/14 1:42:04

基2 DIT-FFT完全指南:从数学推导到工程实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基2 DIT-FFT完全指南:从数学推导到工程实现

写频域滤波笔记写到第四篇,总算要直面整个流程里最绕不开的算法了。做频域处理,不管你是滤波、频谱分析还是做相关运算,第一步几乎都是把时域信号变成频域数据;而这个变换如果还靠从定义出发硬算DFT,点数稍微大一点就只能干瞪眼。所以这篇笔记把基2时间抽取FFT(Radix-2 Decimation-In-Time FFT,下文简称基2 DIT-FFT)从头到尾捋一遍,从数学拆解、蝶形运算、位反转到能跑的代码,再到调试中那些教科书不会写的坑,一次性讲透。这篇文章适合正在学数字信号处理的学生、打算自己从零实现FFT的工程师,以及所有被DFT运算量折磨过的人。

1. 从DFT到FFT:为什么你的程序里需要基2时间抽取

1.1 直接算DFT的成本到底有多高

先把最基础的公式放出来。对长度为N的序列x(n),DFT定义是:

X(k) = Σ x(n)·W_N^(nk), W_N = e^(-j2π/N)

直接按这个公式算一个X(k),要做N次复数乘法;算完全部N个点,总共N²次复数乘法。N=64的时候是4096次,勉强能忍;N=1024的时候就已经到104.8万次复数乘法;到了N=4096,直接冲上1677万次。这还没算加法,加法的量级同样是N²。

我当年第一次用科学计算软件做频谱分析,拿到4096点的数据,按最笨的公式用双重循环去算,等了好一会儿才出结果。你以为这不算什么,可这只是4096点。换成一段几秒钟的音频,一帧就是1024点、几千帧数据,这种运算量级到了工程上完全没法落地。

FFT的价值就是把N²压到(N/2)·log₂N。同样1024点,FFT只需要5120次复数乘法,差了大约205倍。N越大这个差距越夸张,4096点的时候接近683倍。所以说,FFT不是"锦上添花的优化",而是DFT能真正走出数学定义、进入实际系统的那道门槛。

我把几个典型N值下的运算量列个表,方便直观感受:

N直接DFT复数乘法基2 FFT复数乘法加速比
864125.3
16256328
64409619221.3
25665536102464
102410485765120204.8
40961677721624576682.7

1.2 为什么是"基2",为什么叫"时间抽取"

基2的含义很直白:要求序列长度N必须是2的整数次幂,即N=2^M。这个约束看似苛刻,实际上换来了极其规整的算法结构——每一级分解都把问题切成两半,一共切M级,每一级的运算模式完全一样,非常适合用统一的蝶形单元描述,也方便硬件流水线和软件循环实现。

时间抽取(Decimation-In-Time,DIT)指的是分解角度:把输入序列x(n)按时间下标n的奇偶拆成两个半长序列,分别求各自的DFT,再通过蝶形运算合成完整结果。与之对应的是频率抽取(DIF),它拆的是输出频谱X(k)的奇偶点。两种方案蝶形数量一样,区别只在位反转出现在输入还是输出。下表是两者的对比:

对比项DIT(时间抽取)DIF(频率抽取)
拆分对象输入序列的奇偶下标输出频谱的奇偶下标
位反转位置输入:先位反转再做蝶形输出:先做蝶形再位反转
蝶形结构上支路a+b·W,下支路a-b·W上支路a+b,下支路(a-b)·W
工程常见度教材最常见,实现直观部分硬件实现偏好

这里有个内在原因值得说一下:DFT的核函数W_N^(nk)在n和k两个维度上都有周期性和对称性,正是这些冗余,让"拆一半再合并"成为可能。你可以把FFT理解成一种分治策略,类似归并排序:先把问题切成两半,各自解决,再花O(N)的成本合并结果。每一层合并都是O(N),一共log₂N层,总复杂度就是O(N log₂N)。这不是什么魔法,只是用结构换掉了冗余计算。

2. 基2时间抽取的数学推导:这一步是整个算法的灵魂

2.1 按奇偶把序列拆成两半

假设N=2^M,把x(n)的下标分成偶数n=2r和奇数n=2r+1,其中r=0,1,...,N/2-1。代入DFT定义:

X(k) = Σ x(2r)·W_N^(2rk) + Σ x(2r+1)·W_N^((2r+1)k)

关键的一步在这里:W_N^(2rk) = (W_N²)^(rk) = W_(N/2)^(rk)。因为W_N² = e^(-j4π/N) = e^(-j2π/(N/2)),恰好就是N/2点DFT的旋转因子。于是可以写成:

X(k) = G(k) + W_N^k·H(k)

其中G(k)是偶数下标序列x(2r)的N/2点DFT,H(k)是奇数下标序列x(2r+1)的N/2点DFT。

这个式子有一个问题:G(k)和H(k)都是以N/2为周期的,它们只有N/2个独立值,可X(k)有N个点。剩下N/2个点怎么补?利用W_N^(k+N/2) = -W_N^k,立刻得到:

X(k+N/2) = G(k) - W_N^k·H(k), k = 0,1,...,N/2-1

这就把N点DFT完整地表示成了两个N/2点DFT的组合,一正一负各得一半点。

2.2 蝶形运算的诞生

上面两个式子合并,就是著名的蝶形运算单元。它的含义是:两个输入a和b,其中b先乘上旋转因子W_N^k,然后加法和减法各出一条输出:

  • 上支路输出:a + b·W_N^k
  • 下支路输出:a - b·W_N^k

一个蝶形单元只做1次复数乘法、2次复数加减法。整个N点FFT里,每一级都有N/2个蝶形,一共M=log₂N级。

这里有一个非常实用的性质:蝶形运算天然支持原地计算(in-place)。也就是说,a+b·W写回输入a的存储位置,a-b·W写回输入b的存储位置,不需要额外数组。前提是这一级的两个输入算完之后不会被别的地方再引用——在DIT的结构里这个条件是成立的,因为每个蝶形只消费自己的两个输入。很多FFT库内存占用小,靠的就是这个性质。

蝶形数量顺手算一下:N=8时,log₂8=3级,每级4个蝶形,共12个蝶形运算;N=1024时,10级,每级512个,共5120个。和前面表的数字完全对得上。

2.3 多级分解:8点FFT的完整信号流

两个N/2点DFT还不是终点。G(k)和H(k)各自本身也是DFT,只要长度依然是2的幂,就可以继续按奇偶拆。以8点为例:

  • 第一级拆成两个4点DFT:偶数序列x0,x2,x4,x6,以及奇数序列x1,x3,x5,x7。
  • 每个4点DFT再拆成两个2点DFT。
  • 2点DFT就是最原始的蝶形:两个输入配W_2^0=1,输出和与差。

从信号流结构上看,8点FFT是3级、每级4个蝶形。第一级蝶形旋转因子都是W_2^0=1,第二级用W_4^0和W_4^1,第三级用W_8^0、W_8^1、W_8^2、W_8^3。每级使用的旋转因子数量翻倍,前一级数量减半。

这里有一个极其关键的规律:第s级(从1开始数)的蝶形分组跨度为2^(s-1),组内两路输入的间隔正好等于组跨度,而该级需要的旋转因子是W_(2^s)^k,其中k=0,...,2^(s-1)-1。代码实现时,每一级的旋转因子集合是固定的,所以蝶形可以从左到右顺序扫描,每个小组内w从1开始逐步乘上W_(2^s)。这个"小组内重置w"的细节很容易写漏,后面调试章节我会专门讲。

为了加深理解,我把4点FFT的完整过程手算一遍,方便对照你自己的代码。设输入x=[a,b,c,d]。

第一步,位反转。4点的位反转顺序是[0,2,1,3],所以数组变成[a,c,b,d]。

第二步,第一级(len=2),两组蝶形,因子都是1:

  • 第0组:(a,c) 输出 a+c, a-c
  • 第2组:(b,d) 输出 b+d, b-d

数组变成[a+c, a-c, b+d, b-d]。

第三步,第二级(len=4),一组蝶形,因子为W_4^0=1和W_4^1=-j:

  • X0 = (a+c) + (b+d) = a+b+c+d
  • X2 = (a+c) - (b+d) = a-b+c-d
  • X1 = (a-c) + (-j)(b-d) = a-c - j(b-d)
  • X3 = (a-c) - (-j)(b-d) = a-c + j(b-d)

拿直接DFT验证一下:X1 = a - jb - c + jd = a-c+j(d-b),跟上面结果一致;X3 = a + jb - c - jd = a-c+j(b-d),也一致。算法结构正确。

这个手算过程价值很大,强烈建议初学者在写代码之前先拿4点和8点各推一遍,比直接抄代码理解深得多。你甚至可以把中间数组打印出来,对着上面的推导检查每一级的输出。

3. 位反转:FFT里最容易写错的一环

3.1 为什么顺序会乱

细心的人会注意到,4点FFT第一步就把[a,b,c,d]变成了[a,c,b,d],这就是位反转。它的来源是反复的奇偶抽取:第一次抽取决定下标二进制的最低位,第二次抽取决定次低位,抽M次就相当于从最低位开始逐位读取下标。

用8点举例。原始下标0到7的三位二进制是000、001、010、011、100、101、110、111。第一级按奇偶抽取,偶数下标(最低位为0)去上半区,奇数下标(最低位为1)去下半区;第二级再按第二位分;第三级按最高位分。最终排列顺序恰好就是"从最低位到最高位重新读"的结果,即二进制位反转。

原始序号二进制位反转反转后序号
00000000
10011004
20100102
30111106
41000011
51011015
61100113
71111117

所以8点FFT的输入顺序是[x0,x4,x2,x6,x1,x5,x3,x7]。记住这个顺序,后面调试时非常有用。

3.2 位反转的实现技巧

实现位反转有几种常见写法。第一种是按位移的直观版本,适合理解和教学:对每个i,把它拆成M位二进制,再逐位颠倒拼回去。

unsigned int bit_reverse_value(unsigned int i, unsigned int M) { unsigned int rev = 0; for (unsigned int j = 0; j < M; j++) { rev = (rev << 1) | ((i >> j) & 1u); } return rev; }

然后遍历i从0到N-1,把x[i]和x[rev]交换。注意只在i<rev时交换,否则交换两次又换回去了。这个版本清晰直观,缺点是每个点都要做M次移位,总体多出N·log₂N次整数操作。对大多数应用无所谓,但在追求性能的场合不够优雅。

第二种是原地交换的经典写法,也是很多教材和库里的版本。它用一个变量j作为"当前应该匹配的反转位置":

void bit_reverse(double complex *x, int n) { int j = 0; for (int i = 0; i < n; i++) { if (i < j) { double complex tmp = x[i]; x[i] = x[j]; x[j] = tmp; } int m = n >> 1; while (j >= m && m >= 1) { j -= m; m >>= 1; } j += m; } }

这段代码第一次看会有点绕,它的本质是模拟一个M位计数器,从最高位开始"加1并进位",从而产生反转序列。以N=8为例,j的演变是0→4→2→6→1→5→3→7,恰好就是位反转顺序。i从0走到7,当i<j时交换,正好把每对错位元素换回来。

我在实际测试中发现,第二种写法翻车概率明显更高,主要问题出在边界条件。建议先在纸上把N=4、N=8的情况跑一遍,确认j的序列正确,再往工程代码里放。另外提醒一句:处理前先加个长度断言,确保n是2的幂,否则这个循环在非2的幂输入下可能越界或死循环。

4. 从公式到可运行代码:完整实现详解

4.1 C语言实现

蝶形运算和位反转都清楚了,剩下的就是把它们串起来。下面是一个完整的基2 DIT-FFT的C语言实现,使用复数支持,长度作为参数传入:

#include <stdio.h> #include <math.h> #include <complex.h> #ifndef M_PI #define M_PI 3.14159265358979323846 #endif void bit_reverse(double complex *x, int n) { int j = 0; for (int i = 0; i < n; i++) { if (i < j) { double complex tmp = x[i]; x[i] = x[j]; x[j] = tmp; } int m = n >> 1; while (j >= m && m >= 1) { j -= m; m >>= 1; } j += m; } } void fft_radix2_dit(double complex *x, int n) { bit_reverse(x, n); for (int len = 2; len <= n; len <<= 1) { double angle = -2.0 * M_PI / len; double complex wlen = cos(angle) + I * sin(angle); int half = len >> 1; for (int i = 0; i < n; i += len) { double complex w = 1.0 + 0.0 * I; for (int k = 0; k < half; k++) { double complex u = x[i + k]; double complex v = x[i + k + half] * w; x[i + k] = u + v; x[i + k + half] = u - v; w *= wlen; } } } }

逐层解释一下。最外层循环len从2逐步翻倍到N,代表当前DFT子块的长度,也就是这一级的蝶形规模。每一级进去以后,先算这一级的旋转因子wlen = W_len = e^(-j2π/len),然后以len为跨度遍历每一组蝶形。组内k从0到half-1,w从1开始,每处理一个蝶形就乘一次wlen,这样w依次就是W_len^0、W_len^1、...、W_len^(half-1)。每个蝶形取u、v两个输入,v乘以w之后做加减,结果写回原位。

这段代码最需要注意的一个点:内层循环的w必须在每个小组开始时重置为1。只看代码可能觉得这是理所当然的,但实际写的时候,一旦把w的初始化提到外层循环之外,后一组蝶形就会用错旋转因子,输出频谱看起来结构还在,但每一段都错位。我见过好几个人栽在这里,排查很久才发现是w没有重置。

验证代码可以用单位冲激做测试:x[0]=1、其余为0时,FFT结果应全为1。再把x[n]=cos(2π·2n/8)代入,应该在频点k=2和k=6出现幅度为4的峰值(对幅度1的实余弦,峰值应为N/2=4)。这两个测试用例简单又可靠,建议作为任何FFT实现的第一道自测。

4.2 Python实现与验证

Python的优点是复数和列表操作都很自然,适合快速验证算法理解。下面这个实现跟C的逻辑完全一致:

import math def fft_radix2_dit(x): n = len(x) assert n > 0 and (n & (n - 1)) == 0, "长度必须是2的幂" a = list(x) j = 0 for i in range(n): if i < j: a[i], a[j] = a[j], a[i] m = n >> 1 while j >= m and m >= 1: j -= m m >>= 1 j += m length = 2 while length <= n: half = length >> 1 angle = -2.0 * math.pi / length wlen = complex(math.cos(angle), math.sin(angle)) for i in range(0, n, length): w = 1 + 0j for k in range(half): u = a[i + k] v = a[i + k + half] * w a[i + k] = u + v a[i + k + half] = u - v w *= wlen length <<= 1 return a

写成Python之后,可以用你手头任何可信的参考FFT实现做对照。我自己写过一个测试:对N=16的随机复数序列,上面的实现和参考实现的结果最大误差在1e-14量级,说明算法结构没问题。

有一点经验想分享:用Python验证算法时,别用过于简化的写法,尽量让结构跟目标语言一一对应。这样验证通过后再翻译到其他语言,几乎可以机械照搬,不容易引入新的逻辑错误。我的习惯是先写Python做正确性验证,再翻译成C做性能验证,最后才考虑查表、循环展开这类优化。

4.3 旋转因子该查表还是该递推

上面的代码每一级都重新算cos和sin得到wlen,然后靠复数乘法递推出组内所有w。这个方案代码简单,但有两种情况需要改。

第一,N非常大。每级只调用一次cos/sin,整个变换总共调log₂N次,这个开销其实可忽略。真正需要注意的是w逐次乘出来的浮点误差累积。对N到几万级别,双精度下的误差仍在可接受范围;但如果做超长序列或者对精度极其敏感的数值计算,建议改成直接查表读取W_N^k的精确值。

第二,追求极致速度的嵌入式或实时场景。最常用的做法是预计算一张长度为N/2的旋转因子表twiddle[k]=e^(-j2πk/N),然后每一级按下标规律直接索引。规律我在2.3节提过:第s级需要的因子是W_(2^s)^k,换算成W_N的下标就是k·(N/2^s)。比如N=8时,第二级(len=4)需要W_4^0、W_4^1,对应W_8^0、W_8^2,索引是0和2,正好是k乘上N/len=2得到的。查表法避免重复计算三角函数,代价是占一块常驻内存,以及把下标规律写对。

这里给一个折中建议:绝大多数PC上跑的频域滤波应用,递推法完全够用。真正常见的性能瓶颈往往不是三角函数,而是内存访问模式和循环结构。等你确认profiling结果确实卡在三角函数上,再动手改成查表不迟。过早优化在这个算法上收益很低,反而容易引入下标错误。

5. 实际调试中的常见坑与排查思路

5.1 输入长度不是2的幂

这是基2 FFT最硬性的前提。长度不符合时,代码要么越界,要么给出完全错误的结果。工程上处理方式很直接:把数据补零到下一个2的幂。比如实际帧长是1000,就补到1024。

这里有个流传很广的误解:补零能"提高频率分辨率"。严格说,补零不会改变信号本身的频谱信息,它只是让DFT在更密的频点上采样,相当于对原有频谱做插值。原来两个靠得很近的谱峰,补零后依然分不开,因为决定分辨率的是数据本身的有效时长,而不是补零后的点数。判断依据很简单:如果某个峰在补零前就已经存在,补零只是让它更光滑;如果补零前看不到,补零之后也基本不会有新的物理谱峰冒出来。补零能做的,是在栅栏效应恰好把峰藏在两个频点之间时,帮你确认峰的位置,但峰宽信息依然是原始的。

做频域滤波时,补零还有一个副作用:序列长度变了,频率轴的bin间隔Δf=fs/N也跟着变。如果你把滤波器响应H[k]按补零后的k下标设计,务必保证频率轴换算正确,否则截止频率会整体偏移。我的习惯是:所有涉及频率轴的操作都用"归一化频率k/N"来写,这样补零与否都不容易出错。

5.2 输出频谱乱序或镜像

频谱输出混乱,十有八九是位反转没做对,或者做了两次。判断方法非常简单:输入一个单位冲激x[0]=1、其余为0,正确的FFT输出应该是所有点都等于1。如果输出看起来像被打乱的1,问题就在位反转。另一种快速诊断是用单频正弦:输出应该在理论频点附近出现两个对称的峰,如果峰的位置错乱但整体能量分布尚在,也是位反转问题。

还有一类情况是旋转因子符号写反。FFT的W_N是e^(-j2π/N),如果写成了e^(+j2π/N),得到的结果是DFT的共轭。对实数输入,效果相当于频谱左右镜像,也就是k和N-k对调。这在幅度谱上完全看不出来,因为幅度谱是一样的。排查办法:拿一个已知的复数测试序列,比如x[n]=e^(j2πn/N),看谱峰是否落在k=1处。如果是,因子符号就对了;如果落在k=N-1处,符号反了。

5.3 幅度值整体偏大或偏小

频域滤波往往会涉及实数信号的幅度读数。直接FFT得到的X(k)不做归一化,它的大小和序列长度N线性相关。对一个幅度为A的实正弦,理想情况下在正频点k0和负频点N-k0各出现一个幅度为A·N/2的峰。想从频谱读出信号幅度,需要把峰值除以N/2;如果只统计单边谱,还要记住负频那半能量也在。

很多人第一次做频谱分析,拿FFT结果的幅值直接当信号幅值看,发现一个幅度1的信号频谱峰高达512甚至更多,以为算法错了。其实算法没错,只是忘了归一化。反向变换同理:IFFT如果缺了1/N那一步,重建信号的幅度就会放大N倍。

关于缩放还有一个容易踩的坑:不同库的归一化约定不一样,有的做1/N,有的做1/√N,有的干脆不做。如果你在项目里混用不同来源的FFT/IFFT实现,务必先跑一个"FFT→IFFT"的往返测试,确认最后能还原原始序列。这类约定问题跟数学正确性无关,纯属接口规范,但工程上最容易出事故的恰恰是这种隐性约定。

5.4 逆变换回不到原始信号

做频域滤波的标准链路是:FFT → 频谱相乘 → IFFT。如果滤波后波形明显异常,先排查三个点。

第一,IFFT实现是否正确。基2 DIT-FFT的代码只要把旋转因子符号取反、最后整体除以N,就得到IFFT。也就是把wlen换成e^(+j2π/len),并且在最后对所有点乘1/N。如果只改了符号忘了除N,输出幅度会放大N倍。

第二,滤波器的频率响应是否和数据排列方式匹配。FFT输出前N/2个点是正频率0到fs/2,后N/2个点是负频率。有些H[k]设计只考虑了前一半,后一半没有对称赋值,导致频谱不对称,逆变换出来要么变成复数,要么波形严重失真。实数信号的频域滤波必须保证H[k]=conj(H[N-k]),也就是共轭对称,输出才能仍是实信号。

第三,循环卷积的边界效应。FFT乘法对应的时域运算是循环卷积,不是线性卷积。直接把两块数据拼起来滤波,边界处会有环绕污染。正确的做法是用overlap-add或overlap-save分块处理,这在下一节展开。

6. 与频域滤波的衔接:FFT准备好了,下一步做什么

6.1 滤波链路的基本形态

基2 DIT-FFT学到这里,频域滤波的骨架已经齐了。一条最基本的链路是:

x[n] → FFT → X[k] → 乘H[k] → Y[k] → IFFT → y[n]

H[k]就是滤波器的频率响应。如果做低通,理想情况下H[k]在截止频率以内为1、以外为0。但现实里直接这样设计会带来两个问题:一是频谱上的硬台阶对应时域无限长的sinc冲击响应,截断后产生吉布斯振铃;二是前面提到的循环卷积会让滤波结果在块边界出现wrap-around。所以工程上的频域滤波,H[k]通常设计成带过渡带的平滑形状,配合窗函数使用,同时用分块策略消除边界效应。

这块内容其实在我这个系列前面的笔记里已经铺垫了一部分,比如怎么由想要的频率响应构造H[k],怎么考虑群延迟和不加窗的振铃,这篇就不再重复。但有一点和FFT本身直接相关:滤波器响应H[k]的长度通常要求对齐FFT长度。如果你用补零后的N点做FFT,H[k]也要定义在同样的N点上,并保持共轭对称。

6.2 分块处理的工程考量

流式数据无法一次性全序列FFT,只能分块。分块长度的选择其实是在几个互相拉扯的因素里找平衡:太长,单次变换的延迟和内存都变大,旋转因子表的压力也变大;太短,位反转和循环的固定开销摊销不出去,总体效率下降。工程上通常先定延迟预算,再倒推FFT长度。

如果滤波器冲击响应长度为L,数据块长度为M,为了线性卷积正确,FFT长度N至少要达到M+L-1,一般取下一个2的幂。overlap-add的做法是每块输入补零到N,与H[k]相乘后IFFT,得到M+L-1个有效输出样本,其中前M个拼到输出流里,后面L-1个留到下一块,与下一块结果的前L-1个样本叠加。overlap-save则是直接对重叠块做循环卷积,丢弃被污染的头部,只保留后面干净的部分。两种方法本质上都是在处理"循环卷积不等于线性卷积"这个核心矛盾。

我实践中更常用的参数选择是:先确定可接受的处理延迟,再反推FFT长度。比如语音或音频实时处理,延迟要求通常在几十毫秒以内,按采样率48kHz算,1024点FFT对应的块时长约21.3ms,是常见选择。如果对延迟不敏感,比如离线处理一段录音,直接用整段补零做一次FFT往往最简单,只要数据量在内存可接受范围内。

6.3 关于性能优化和算法选型的一点个人经验

最后说点个人意见。基2 DIT-FFT是理解所有FFT变体的最佳切入点,但它未必是工程性能最优的算法。实际库常见的还有基4、混合基、分裂基,以及针对实数的实FFT、针对特定平台的向量化实现。学习路径上建议先把基2彻底吃透,之后再去看优化变体,你会发现它们全是同一套分治思想的不同切法,无非是在减少旋转因子乘法次数和提高访存局部性之间做权衡。

性能优化方面,如果N固定且会反复做变换,建议把旋转因子表、位反转顺序表都预先算好,运行时只查表。位反转本身也可以离线算好索引数组,省掉每次都跑一遍交换逻辑。这些优化的前提是正确性已经完全验证过。我的做法始终是:先有一个朴素但正确、结构清晰的版本跑通全流程,再逐项做profiling,针对真正耗时的环节动手优化。

FFT这个算法好就好在,它的正确性可以用非常简单的测试用例锁定:单位冲激、单频正弦、已知复数序列、往返重建。把这四个测试写进自动化流程里,后面无论是改旋转因子策略、换位反转实现,还是移植到别的语言,回归起来都非常省心。

这篇笔记写到这,基2 DIT-FFT从数学推导、位反转、蝶形实现到调试方法就算完整过了一遍。我个人最大的体会是:这个算法真正难的不是公式,而是把公式翻译成代码时那些细小的约定——w在哪里重置、位反转放在输入还是输出、归一化在哪个方向。把这些约定一个个钉死,后续频域滤波的工程实现就会顺畅很多。下一篇笔记就可以放心地把滤波器设计部分铺开讲了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 1:41:52

AD7768-1BCPZ 24位同步采样ADC:架构解析与硬件设计实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:40:46

DevExpress 13.1/13.2 授权激活与部署排查指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:40:42

Zabbix 4.0 数据采集实战:Agent、SNMP、JMX 与性能调优

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:40:08

3天从85%降到20%!这3个AI智能降重工具让我顺利通过盲审

还记得上周三凌晨两点&#xff0c;当我第三次收到知网AIGC检测报告时&#xff0c;手心都在冒汗——85%的AI相似度&#xff0c;40%的查重率&#xff0c;这意味着我的毕业论文根本达不到盲审要求。导师直接在我的初稿上批注“学术合规性存疑&#xff0c;建议重写”。距离最终答辩…

作者头像 李华
网站建设 2026/10/14 1:39:45

NRF52832低功耗蓝牙GAP开发实战:广播、扫描、连接与参数调优

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:39:12

macOS为2K显示器启用HiDPI的底层原理与稳定实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华