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的作用,是把时域中的信号变成频域中的频谱。
它最常见的用途有:
- 做频谱分析,观察信号里有哪些频率成分。
- 做快速卷积和相关。
- 在图像处理中进入频域后再做滤波。
对初学者来说,最重要的知识点是:实数输入并不一定直接返回一张普通的实数矩阵。默认情况下,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=0∑N−1xne−j2π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=0∑N−1Xkej2π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,−2−2j]
4. 函数原理说明
Cv2.Dft可以理解成下面几步:
- 检查输入是否为浮点型实数或复数矩阵。
- 根据
flags决定输出是完整复数、实数,还是压缩的 CCS 格式。 - 对输入做离散傅里叶变换。
- 把结果写入
dst。 - 如果传入
nonzeroRows,OpenCV 会把它当成性能优化提示,用在卷积、相关等场景里。
这里最容易混淆的是输出格式。对初学者来说,建议先把DftFlags.ComplexOutput记住:它会把实数输入直接变成完整的复数频谱,这样每个频率点的实部和虚部都能清楚看到。
5. 参数含义解析
| 参数名 | 类型 | 必填 | 含义 |
|---|---|---|---|
| src | InputArray | 是 | 输入信号,可以是实数或复数矩阵 |
| dst | OutputArray | 是 | 输出频谱,形状和类型受 flags 影响 |
| flags | DftFlags | 否 | 变换标志,默认None |
| nonzeroRows | int | 否 | 只把前几行视为非零时的性能提示 |
补充说明:
DftFlags.ComplexOutput适合做教学演示和频谱可视化。DftFlags.RealOutput只适合逆变换。DftFlags.Scale常和逆变换一起使用,避免结果比原信号大一倍或NNN倍。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. 运行结果解读
如果你运行上面的示例,应该能看到下面几个结论:
- 频谱的第 0 项是 10,表示直流分量,也就是整体平均水平。
- 第 1 项和第 3 项互为共轭对称,说明原信号是实数信号。
- 第 2 项的虚部为 0,说明这个频率点的能量是纯实数形式。
- 这组结果可以直接拿去做 Idft 往返验证。
9. 常见错误与排查
- 忘记给实数输入加
DftFlags.ComplexOutput,结果只看到 CCS 压缩格式。 - 把频谱当成普通实数矩阵来理解,忽略了它其实是两通道复数数据。
- 做逆变换时忘记加
DftFlags.Scale,导致还原结果比原信号大一倍或NNN倍。 - 输入矩阵不是浮点型,导致函数直接报错。
10. 进阶说明
DftFlags里最值得先记住的几个标志是:
ComplexOutput:把实数输入展开成完整复数频谱。RealOutput:只用于逆变换,把满足共轭对称的频谱还原成实数输出。Scale:为变换结果做归一化。Rows:每一行独立做一维变换。
nonzeroRows在大多数教学示例里不会马上用到,但在卷积、相关和模板匹配里非常有价值,因为它可以让 OpenCV 少处理那些全 0 的行。
11. 总结
Cv2.Dft的核心职责,就是把信号从时域搬到频域。只要你先记住“实数输入 +ComplexOutput= 完整复数频谱”,后面再学Idft、MulSpectrums和频域滤波,就会顺很多。