OpencvSharp 算子学习教案之 - Cv2.Dft
2026/6/10 6:15:40 网站建设 项目流程

OpencvSharp 算子学习教案之 - Cv2.Dft

大家好,Opencv在很多工程项目中都会用到,而OpencvSharp则是以C#开发与实现的Opencv操作库,对.NET开发人员友好,但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳,因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案,供大家参考学习。

Cv2.Dft

  • 教案版本:V1.0
  • 面向对象:OpenCvSharp 初学者
  • 所属模块:core
  • 源码位置:OpenCvSharp/Cv2/Cv2_core.cs:2887

摘要:Dft 会把时域信号转换成频域频谱。本文用一个 1x4 实数向量演示完整复数输出,并说明为什么前向变换通常要和 Idft、Scale 配合,才能完成正确的往返验证。

1. 函数名称(带参数签名)

publicstaticvoidDft(InputArraysrc,OutputArraydst,DftFlagsflags=DftFlags.None,intnonzeroRows=0)

2. 函数用途

Cv2.Dft的作用,是把时域中的信号变成频域中的频谱。

它最常见的用途有:

  1. 做频谱分析,观察信号里有哪些频率成分。
  2. 做快速卷积和相关。
  3. 在图像处理中进入频域后再做滤波。

对初学者来说,最重要的知识点是:实数输入并不一定直接返回一张普通的实数矩阵。默认情况下,OpenCV 可能使用压缩的 CCS 格式;如果你想直接观察每个频率点,最好显式打开DftFlags.ComplexOutput

3. 函数公式

1D DFT 的基本公式是:

Xk=∑n=0N−1xne−j2πkn/N X_k = \sum_{n=0}^{N-1} x_n e^{-j2\pi kn/N}Xk=n=0N1xnej2πkn/N

其中:

  • xnx_nxn是时域信号;
  • XkX_kXk是频域频谱;
  • NNN是采样点数;
  • jjj表示虚数单位。

如果再做逆变换,并且开启DFT_SCALE,那么可以写成:

xn=1N∑k=0N−1Xkej2πkn/N x_n = \frac{1}{N} \sum_{k=0}^{N-1} X_k e^{j2\pi kn/N}xn=N1k=0N1Xkej2πkn/N

本文示例使用的信号是:

x=[1,2,3,4] x = [1, 2, 3, 4]x=[1,2,3,4]

它的完整复数频谱是:

X=[10, −2+2j, −2, −2−2j] X = [10,\,-2+2j,\,-2,\,-2-2j]X=[10,2+2j,2,22j]

4. 函数原理说明

Cv2.Dft可以理解成下面几步:

  1. 检查输入是否为浮点型实数或复数矩阵。
  2. 根据flags决定输出是完整复数、实数,还是压缩的 CCS 格式。
  3. 对输入做离散傅里叶变换。
  4. 把结果写入dst
  5. 如果传入nonzeroRows,OpenCV 会把它当成性能优化提示,用在卷积、相关等场景里。

这里最容易混淆的是输出格式。对初学者来说,建议先把DftFlags.ComplexOutput记住:它会把实数输入直接变成完整的复数频谱,这样每个频率点的实部和虚部都能清楚看到。

5. 参数含义解析

参数名类型必填含义
srcInputArray输入信号,可以是实数或复数矩阵
dstOutputArray输出频谱,形状和类型受 flags 影响
flagsDftFlags变换标志,默认None
nonzeroRowsint只把前几行视为非零时的性能提示

补充说明:

  1. DftFlags.ComplexOutput适合做教学演示和频谱可视化。
  2. DftFlags.RealOutput只适合逆变换。
  3. DftFlags.Scale常和逆变换一起使用,避免结果比原信号大一倍或NNN倍。
  4. DftFlags.Rows会把每一行当作独立的一维信号。

6. 应用场景列表

场景名场景说明典型用途
场景A:频谱分析查看信号的频率成分音频、振动、图像分析
场景B:卷积加速把时域卷积转成频域乘法快速滤波
场景C:相关计算利用频域乘法求相关模板匹配、信号对齐

7. 函数使用示例

下面的 Console 程序演示Cv2.Dft。为了让结果更容易理解,我们使用一个 1x4 的实数向量,并显式要求输出完整复数频谱。

usingSystem.Globalization;usingSystem.Text;usingOpenCvSharp;internalstaticclassProgram{/// <summary>/// 程序入口。/// </summary>privatestaticvoidMain(){// 控制台要支持中文输出,这样说明文字不会乱码。Console.OutputEncoding=Encoding.UTF8;// 这里选一个很容易手算的 1x4 信号,方便核对 DFT 的输出。varsourceData=newdouble[,]{{1.0,2.0,3.0,4.0},};usingvarsource=CreateMatrix(sourceData);usingvarspectrum=newMat();// 显式要求完整复数输出,这样每个频率点的实部和虚部都能直接观察。Cv2.Dft(source,spectrum,DftFlags.ComplexOutput);varactualSpectrum=ReadComplexMatrix(spectrum);varexpectedSpectrum=newdouble[,]{{10.0,0.0},{-2.0,2.0},{-2.0,0.0},{-2.0,-2.0},};PrintMatrix("源信号 x[n]",sourceData);PrintComplexMatrixComparison("频谱 X[k]",actualSpectrum,expectedSpectrum);}/// <summary>/// 把二维数组包装成单通道矩阵。/// </summary>privatestaticMatCreateMatrix(double[,]values){// OpenCV 的 Dft 需要浮点型输入,所以这里直接创建 CV_64FC1。returnMat.FromPixelData(values.GetLength(0),values.GetLength(1),MatType.CV_64FC1,values);}/// <summary>/// 读取复数矩阵,并把每个复数展开成一行 [实部, 虚部]。/// </summary>privatestaticdouble[,]ReadComplexMatrix(Matsource){// 统一转成双精度双通道,避免不同深度影响读取逻辑。usingvarconverted=newMat();source.ConvertTo(converted,MatType.CV_64FC2);varresult=newdouble[converted.Rows*converted.Cols,2];varindex=0;for(varrow=0;row<converted.Rows;row++){for(varcol=0;col<converted.Cols;col++){// Vec2d 的 Item0 是实部,Item1 是虚部。varvalue=converted.At<Vec2d>(row,col);result[index,0]=value.Item0;result[index,1]=value.Item1;index++;}}returnresult;}/// <summary>/// 打印普通实数矩阵。/// </summary>privatestaticvoidPrintMatrix(stringtitle,double[,]matrix){Console.WriteLine(title);for(varrow=0;row<matrix.GetLength(0);row++){varline=newStringBuilder("[");for(varcol=0;col<matrix.GetLength(1);col++){line.Append(matrix[row,col].ToString("F6",CultureInfo.InvariantCulture));if(col<matrix.GetLength(1)-1){line.Append(", ");}}line.Append(']');Console.WriteLine(line.ToString());}Console.WriteLine();}/// <summary>/// 打印复数矩阵,把每一行当成一个复数来显示。/// </summary>privatestaticvoidPrintComplexMatrix(stringtitle,double[,]matrix){Console.WriteLine(title);for(varrow=0;row<matrix.GetLength(0);row++){varrealText=matrix[row,0].ToString("F6",CultureInfo.InvariantCulture);varimagValue=matrix[row,1];varsign=imagValue>=0?"+":"-";varimagText=Math.Abs(imagValue).ToString("F6",CultureInfo.InvariantCulture);Console.WriteLine($"[{row}]{realText}{sign}{imagText}i");}Console.WriteLine();}/// <summary>/// 打印复数矩阵对照结果,并计算最大绝对误差。/// </summary>privatestaticvoidPrintComplexMatrixComparison(stringtitle,double[,]actual,double[,]expected){PrintComplexMatrix($"{title}- 实际值",actual);PrintComplexMatrix($"{title}- 期望值",expected);Console.WriteLine($"最大绝对误差:{ComputeMaxAbsDiff(actual,expected):F12}");Console.WriteLine();}/// <summary>/// 计算两个矩阵的最大绝对误差。/// </summary>privatestaticdoubleComputeMaxAbsDiff(double[,]left,double[,]right){varmax=0.0;for(varrow=0;row<left.GetLength(0);row++){for(varcol=0;col<left.GetLength(1);col++){vardiff=Math.Abs(left[row,col]-right[row,col]);if(diff>max){max=diff;}}}returnmax;}}

8. 运行结果解读

如果你运行上面的示例,应该能看到下面几个结论:

  1. 频谱的第 0 项是 10,表示直流分量,也就是整体平均水平。
  2. 第 1 项和第 3 项互为共轭对称,说明原信号是实数信号。
  3. 第 2 项的虚部为 0,说明这个频率点的能量是纯实数形式。
  4. 这组结果可以直接拿去做 Idft 往返验证。

9. 常见错误与排查

  1. 忘记给实数输入加DftFlags.ComplexOutput,结果只看到 CCS 压缩格式。
  2. 把频谱当成普通实数矩阵来理解,忽略了它其实是两通道复数数据。
  3. 做逆变换时忘记加DftFlags.Scale,导致还原结果比原信号大一倍或NNN倍。
  4. 输入矩阵不是浮点型,导致函数直接报错。

10. 进阶说明

DftFlags里最值得先记住的几个标志是:

  1. ComplexOutput:把实数输入展开成完整复数频谱。
  2. RealOutput:只用于逆变换,把满足共轭对称的频谱还原成实数输出。
  3. Scale:为变换结果做归一化。
  4. Rows:每一行独立做一维变换。

nonzeroRows在大多数教学示例里不会马上用到,但在卷积、相关和模板匹配里非常有价值,因为它可以让 OpenCV 少处理那些全 0 的行。

11. 总结

Cv2.Dft的核心职责,就是把信号从时域搬到频域。只要你先记住“实数输入 +ComplexOutput= 完整复数频谱”,后面再学IdftMulSpectrums和频域滤波,就会顺很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询