IQ 信号 FFT 频谱分析 实现原理详细步骤IQ 数据说明一、单路实信号表示完全不用正交两路核心特点仅使用一路实数采样不区分 I/Q 两路信号时域实采样Real Sampling直接对中频 / 射频信号进行实数采样s(t)A(t)cos(ω0​tφ(t))相位信息隐含在信号过零点中直观可见的只有幅度包络。典型应用AM 检波、基础频谱分析、示波器观测。幅度 / 包络Envelope表达式A(t)I2Q2​缺陷完全丢失相位信息无法用于 QAM、PSK 等调制方式的解调。相位Phase表达式φ(t)arctan2(Q,I)典型应用FM/PM 解调、相位同步。功率 / 平方律检测P(t)I2Q2与幅度仅相差一个平方关系同样不携带独立相位信息。信号链路与 IQ 正交基本概念天线接收的原始射频RF为单路高频实信号本身不存在正交结构。混频降频后得到中频IF仍为单路实信号经正交处理后成为中频正交信号IF IQ。继续混频至 0Hz得到零中频Zero-IF即常用的 IQ 正交基带数据。本振区分余弦本振 → I 路正弦本振 → Q 路步骤 1原始 IQ 数据读取文件解析原理说明IQ 信号在 txt 文件中以实数交替存储I0, Q0, I1, Q1, …, In, Qn需先读取文件并校验数据有效性确保能正确拆分 I/Q 分量。代码对应逻辑import numpy as np # 读取txt中所有实数数据 x np.loadtxt(filename) # 数据合法性校验 if x.size 2: raise ValueError(数据太短至少需要2个数I0,Q0。) if x.size % 2 ! 0: x x[:-1] # 奇数长度时截断最后一个数保证I/Q成对工程考量校验数据长度避免因数据残缺导致后续 I/Q 拆分失败截断奇数长度数据是「容错设计」兼容实际采集时可能的末尾数据丢失问题。步骤 2I/Q 分量拆分与复数信号构造原理说明IQ 信号的本质是复信号I 为实部Q 为虚部需从交替的实数序列中拆分 I、Q 通道再组合为复数形式才能正确表征射频信号的相位和幅度信息。代码对应逻辑# 步长2取所有I分量第0、2、4...位 i x[0::2].astype(np.float64) # 步长2取所有Q分量第1、3、5...位 q x[1::2].astype(np.float64) # 组合为复信号实部I虚部Q iq i 1j * q # 转为complex64平衡精度与内存 return iq.astype(np.complex64)工程考量先转float64再计算避免低精度运算导致的误差最终转complex64复数单精度足够满足射频信号分析需求且内存占用仅为complex128的一半。步骤 3IQ 信号预处理去直流分量原理说明直流分量信号均值会导致频谱中零频处出现强峰值掩盖有用信号需通过「减去信号均值」消除直流分量。代码对应逻辑# 统一数据类型 y np.asarray(iq, dtypenp.complex64) if remove_dc: # 去直流减去复信号的均值实部/虚部分别去直流 y y - np.mean(y)原理补充复信号均值计算np.mean(y) mean(Re(y)) 1j*mean(Im(y))减去均值后实部I和虚部Q的直流分量均被消除。步骤 4窗函数选择与生成原理说明直接对非周期信号做 FFT 会产生「频谱泄露」能量扩散到相邻频率窗函数通过「加权信号两端」使信号平滑过渡到 0降低泄露不同窗函数的泄露抑制 / 频率分辨率 trade-off 不同。代码对应逻辑def make_window(window, n): # 兼容大小写输入如Hann和hann window window.lower() if window in (hann, hanning): return np.hanning(n) # 汉宁窗默认泄露抑制好 if window hamming: return np.hamming(n) # 汉明窗主瓣更窄 if window blackman: return np.blackman(n) # 布莱克曼窗泄露抑制最优分辨率最低 if window in (rect, rectangle): return np.ones(n) # 矩形窗无加权分辨率最高泄露最严重工程考量统一转为小写提升接口易用性避免因输入大小写导致的错误窗长度与信号长度一致保证每个采样点都能被加权。步骤 5FFT 点数自适应确定原理说明FFT 的计算效率在点数为 2 的幂时最高基 2-FFT 算法需自动选择「≥信号长度且≥1024」的最小 2 的幂兼顾计算效率和频谱分辨率。代码对应逻辑if nfft is None: # 计算≥max(n,1024)的最小2的幂 nfft 1 int(np.ceil(np.log2(max(n, 1024))))原理补充np.log2(max(n,1024))计算目标点数的对数np.ceil()向上取整保证点数≥目标值1 整数等价于2^整数快速得到 2 的幂。步骤 6信号加窗处理原理说明将 IQ 复信号与窗函数逐点相乘使信号两端平滑衰减降低频谱泄露加窗后信号的能量会被窗函数加权后续幅值归一化需考虑窗函数的能量。代码对应逻辑# 生成窗函数 w make_window(window, n).astype(np.float64) # 复信号逐点乘窗函数I/Q同时加权 xw (x * w).astype(np.complex64)工程考量窗函数转float64保证加权运算的精度结果转complex64维持数据类型一致性。步骤 7FFT 计算与频谱移位原理说明FFT 默认输出的频谱是「0 频→正频→负频」排列需用fftshift将其移位为「负频→0 频→正频」符合人对频率轴的直观认知复信号 FFT 的结果是复数其模绝对值代表频率分量的幅值。代码对应逻辑# FFT移位中心为0频 X np.fft.fftshift(np.fft.fft(xw, nnfft))原理补充np.fft.fft(xw, nnfft)对加窗后的信号做 nfft 点 FFT信号长度 nfft 时自动补零nfft 时截断fftshift将 FFT 结果的左右半部分交换使 0 频位于频谱中心。步骤 8幅值归一化与 dB 转换原理说明幅值归一化将幅值缩放到 [0,1] 范围方便不同信号的频谱对比dB 转换将线性幅值转为对数刻度dB更符合人耳 / 仪器对信号强度的感知公式20×log10(A)A 为幅值。代码对应逻辑# 归一化到[0,1] mag np.abs(X) / (np.max(np.abs(X)) 1e-12) # 转为dB值 mag_db 20.0 * np.log10(mag 1e-12)工程考量加1e-12避免除以 0幅值全为 0 时或对数计算中出现log10(0)会得到 - inf归一化到最大值消除信号绝对幅值的影响聚焦频谱形状。步骤 9频率轴生成与结果格式化原理说明频率轴需与 FFT 结果一一对应fftfreq根据采样率和 FFT 点数生成频率刻度再用fftshift移位保证与频谱中心对齐结果转为 JSON 字符串方便跨平台 / 跨语言传输如前端绘图、网络接口返回。代码对应逻辑import json # 生成频率轴范围[-fs/2, fs/2)间隔fs/nfft f np.fft.fftshift(np.fft.fftfreq(nfft, d1.0 / fs_hz)) # 格式化为JSON字符串 result json.dumps({ frequency_hz: f.tolist(), # numpy数组转列表JSON不支持数组 magnitude_db: mag_db.tolist() })原理补充fftfreq(nfft, d1/fs_hz)生成原始频率轴范围 [0, fs)间隔fs/nfftfftshift后频率轴变为 [-fs/2, fs/2)对应负频→0 频→正频的频谱排列。总结核心流程文件读取→I/Q 拆分→预处理去直流→加窗→FFT 计算→频谱移位→幅值归一化dB→频率轴生成→结果格式化关键优化自动选择 2 的幂作为 FFT 点数提升效率、加窗抑制频谱泄露、容错处理数据长度校验、防除零 / 对数错误工程设计统一数据类型complex64、兼容大小写窗函数输入、JSON 格式化结果适配跨平台使用。