You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Python np.fft.fft()与C# Fourier.Forward()的FFT结果不一致?

IQ数据FFT结果差异问题解答

问题背景

我基于IQ数据绘制图表,在Python和C#中均通过I、Q分量构建复数数组作为输入:

  • Python:signal.append(complex(I[i], Q[i]))
  • C#:signal[i] = new Complex(I[i], Q[i])

但执行FFT后的结果存在明显差异:

  • Python:X=np.fft.fft(signal)
  • C#:Fourier.Forward(signal, FourierOptions.Default);

Python FFT结果示例

134.99774296753057+123.375408089594j  
0j    
-4.1321113197767545e-15-1.1168149738338684e-14j   
0j    
1.2906342661267445e-15+1.2220259526518618e-14j
0j    
-7.181755190543981e-15-2.5777990853015353e-15j
0j    

C# FFT结果示例

{(0.603728260168885, 0.55175159848022)}   
{(5.03413889952152E-15, 6.88380587394808E-13)}    
{(-1.93271009179521E-12, 1.21853222667336E-12)}   
{(3.28118559584034E-13, -1.75079741448138E-12)}
{(-6.73242997306789E-13, -6.27480109322981E-13)}  
{(-7.67671396112748E-14, -1.17023881533025E-12)}  
{(1.38179442802571E-12, 3.75998104929882E-13)}    
{(-3.81679745939109E-13, 8.32395031201847E-13)}

我需要弄清楚三个问题:

  1. 为何两者FFT结果不同?
  2. Python结果中偶数位置为何出现0j?
  3. 如何让结果一致?

最终目标是基于IQ数据绘制频域图与频谱图,输入为复数数组。


问题解答

1. 两者FFT结果不同的原因

核心差异是归一化方式不一致:

  • NumPy的np.fft.fft默认不做归一化,输出结果的幅值与输入信号的能量总和成正比。
  • 你使用的C# Fourier.Forward(推测为MathNet.Numerics库实现)默认会执行归一化,输出结果会除以FFT点数N,因此每个结果的幅值是NumPy结果的1/N倍。

相位的微小差异属于浮点精度误差,不影响核心结果。

2. Python结果中偶数位置为何出现0j

你的Python代码生成的是周期性重复的稀疏信号:每个周期内前0.05秒是chirp信号,剩余0.45秒为0,重复10次。这种信号的FFT结果会因正交性抵消,只有与重复频率、chirp带宽相关的频率点保留能量,其他频率点因信号的稀疏重复特性相互抵消为0(或极小的浮点误差值)。

3. 如何让结果一致

统一归一化方式即可:

方案一:修改Python代码添加归一化

将FFT结果除以信号总长度,对齐C#默认行为:

fftResult = np.fft.fft(signal) / len(signal)

方案二:修改C#代码取消归一化

调用Fourier.Forward时指定FourierOptions.NoScaling,对齐NumPy默认行为:

Fourier.Forward(signal, FourierOptions.NoScaling);

绘制频谱图时,建议对FFT结果取幅值(Python用np.abs(),C#用Magnitude),这样即使相位有微小差异,也不会影响可视化效果。


附完整代码

Python代码

import numpy as np
import matplotlib.pyplot as plt
import math

samplerate = 10000
duration = 0.05
totalsignalduration = 0.5
startFreq = 0
endFreq = 2000
repeat = 10

numSignalSamples = int(samplerate * totalsignalduration)
chirpsignal = [0] * (numSignalSamples * repeat)

FreqDelta = (endFreq - startFreq) / (samplerate * duration)

for nRepeat in range(repeat):
    for i in range(numSignalSamples):
        t = i / samplerate
        instantFreq = startFreq + FreqDelta * i if t <= duration else 0
        phase = 2 * math.pi * instantFreq * t
        chirpsignal[i + nRepeat * numSignalSamples] = complex(math.cos(phase), math.sin(phase)) if t <= duration else complex(0, 0)

# 构建复数信号
signal = np.array(chirpsignal, dtype=np.complex128)

# 执行FFT并归一化(对齐C#默认结果)
fftResult = np.fft.fft(signal) / len(signal)

# 计算频率轴(转kHz)
frequencies = np.arange(len(signal)) * samplerate / len(signal) / 1000

plt.figure()
plt.plot(frequencies, np.abs(fftResult))
plt.xlabel("Freq (kHz)")
plt.ylabel("Amplitude")
plt.show()

C#代码

using MathNet.Numerics.IntegralTransforms;
using MathNet.Numerics;
using ZedGraph;
using System;

public class ChirpProperty
{
    public double SamplingRate { get; set; }
    public double Duration { get; set; }
    public double TotalSignalDuration { get; set; }
    public double StartFreq { get; set; }
    public double StopFreq { get; set; }
    public int Repeat { get; set; }
}

public class ChirpSignalGenerator
{
    public void Generate(ref double[] I, ref double[] Q, ChirpProperty chirpProperty)
    {
        double sampleRate = chirpProperty.SamplingRate;
        double duration = chirpProperty.Duration;
        double totalDuration = chirpProperty.TotalSignalDuration;
        double startFreq = chirpProperty.StartFreq;
        double endFreq = chirpProperty.StopFreq;
        int numSignalSamples = (int)(sampleRate * totalDuration);
        Complex[] chirpSignal = new Complex[numSignalSamples * chirpProperty.Repeat];
        double freqDelta = (endFreq - startFreq) / (sampleRate * duration);

        for (int nRepeat = 0; nRepeat < chirpProperty.Repeat; nRepeat++)
        {
            for (int i = 0; i < numSignalSamples; i++)
            {
                double t = i / sampleRate;
                double instantFreq = t <= duration ? startFreq + freqDelta * i : 0;
                double phase = 2 * Math.PI * instantFreq * t;
                chirpSignal[i + nRepeat * numSignalSamples] = t <= duration ? new Complex(Math.Cos(phase), Math.Sin(phase)) : new Complex(0, 0);
            }
        }

        I = new double[chirpSignal.Length];
        Q = new double[chirpSignal.Length];
        for (int i = 0; i < chirpSignal.Length; i++)
        {
            I[i] = chirpSignal[i].Real;
            Q[i] = chirpSignal[i].Imaginary;
        }
    }
}

public class FFTOperation
{
    public Complex[] FFT(double[] I, double[] Q)
    {
        Complex[] signal = new Complex[I.Length];
        for (int i = 0; i < I.Length; i++)
        {
            signal[i] = new Complex(I[i], Q[i]);
        }

        // 取消归一化,对齐NumPy默认结果
        Fourier.Forward(signal, FourierOptions.NoScaling);
        return signal;
    }
}

public partial class MainForm : Form
{
    private ZedGraphControl SpectrumGraph;
    private double[] srcI;
    private double[] srcQ;
    private Complex[] spectrum;
    private ChirpProperty chirpProperty = new ChirpProperty
    {
        SamplingRate = 10000,
        Duration = 0.05,
        TotalSignalDuration = 0.5,
        StartFreq = 0,
        StopFreq = 2000,
        Repeat = 10
    };

    private void PlotSpectrum(Complex[] signal, ChirpProperty chirpProperty)
    {
        GraphPane graphPane = SpectrumGraph.GraphPane;
        graphPane.CurveList.Clear();
        double[] frequencies = new double[signal.Length];
        PointPairList pointList = new PointPairList();

        for (int i = 0; i < signal.Length; i++)
        {
            frequencies[i] = i * chirpProperty.SamplingRate / signal.Length;
            pointList.Add(frequencies[i], signal[i].Magnitude);
        }

        LineItem curve = graphPane.AddCurve("Spectrum", pointList, System.Drawing.Color.Blue, SymbolType.None);
        graphPane.XAxis.Title.Text = "Freq (Hz)";
        graphPane.YAxis.Title.Text = "Amplitude";
        SpectrumGraph.AxisChange();
        SpectrumGraph.Invalidate();
    }

    private void btnGenerateChirpIQ_Click(object sender, EventArgs e)
    {
        try
        {
            ChirpSignalGenerator chirpGen = new ChirpSignalGenerator();
            FFTOperation fft = new FFTOperation();

            chirpGen.Generate(ref srcI, ref srcQ, chirpProperty);
            spectrum = fft.FFT(srcI, srcQ);
            PlotSpectrum(spectrum, chirpProperty);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}

内容的提问来源于stack exchange,提问作者123456

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 20:40:54