OpencvSharp 算子学习教案之 - Cv2.Idft
大家好,Opencv在很多工程项目中都会用到,而OpencvSharp则是以C#开发与实现的Opencv操作库,对.NET开发人员友好,但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳,因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案,供大家参考学习。
Cv2.Idft
- 教案版本:V1.0
- 面向对象:OpenCvSharp 初学者
- 所属模块:core
- 源码位置:OpenCvSharp/Cv2/Cv2_core.cs:2916
摘要:Idft 会把频域中的复数频谱还原成时域信号。本文用一个已经共轭对称的完整频谱演示 RealOutput 和 Scale 的配合方式,帮助初学者把“前向 DFT”和“逆向 Idft”连成完整闭环。
1. 函数名称(带参数签名)
publicstaticvoidIdft(InputArraysrc,OutputArraydst,DftFlagsflags=DftFlags.None,intnonzeroRows=0)2. 函数用途
Cv2.Idft的作用,是把频域中的频谱还原回时域信号。
它最常见的用途有:
- 把频域滤波后的结果变回图像或音频信号。
- 和
Dft配合,实现频域卷积或相关。 - 从完整复数频谱恢复原始实数序列。
对初学者来说,最重要的知识点是:Idft默认不会自动帮你缩放。如果你想让Dft和Idft真正互为逆运算,通常要显式开启DftFlags.Scale。
3. 函数公式
逆离散傅里叶变换的基本公式是:
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
其中:
- XkX_kXk是频域频谱;
- xnx_nxn是还原后的时域信号;
- NNN是采样点数;
- jjj表示虚数单位。
本文示例使用的频谱是:
X=[10, −2+2j, −2, −2−2j] X = [10,\,-2+2j,\,-2,\,-2-2j]X=[10,−2+2j,−2,−2−2j]
还原出来的时域信号是:
x=[1,2,3,4] x = [1, 2, 3, 4]x=[1,2,3,4]
4. 函数原理说明
Cv2.Idft可以理解成下面几步:
- 检查输入是否为浮点型实数或复数矩阵。
- 判断
flags是否要求实数输出。 - 对频谱做逆傅里叶变换。
- 把结果写入
dst。 - 如果传入
DftFlags.Scale,则把结果按元素数量归一化。
这里最容易混淆的是RealOutput。它只适合逆变换,因为它的前提是输入频谱已经满足共轭对称。换句话说,如果你把前向Dft的输出拿来做逆变换,RealOutput才是最直观的选择。
5. 参数含义解析
| 参数名 | 类型 | 必填 | 含义 |
|---|---|---|---|
| src | InputArray | 是 | 输入频谱,可以是实数或复数矩阵 |
| dst | OutputArray | 是 | 输出时域信号,类型受 flags 影响 |
| flags | DftFlags | 否 | 变换标志,默认None |
| nonzeroRows | int | 否 | 只把前几行视为非零时的性能提示 |
补充说明:
DftFlags.RealOutput会让逆变换直接返回实数矩阵。DftFlags.Scale让逆变换补上标准化因子。DftFlags.Rows适合把每一行看成独立频谱的场景。- 逆变换时如果输入不是共轭对称频谱,实数输出就没有物理意义。
6. 应用场景列表
| 场景名 | 场景说明 | 典型用途 |
|---|---|---|
| 场景A:频域还原 | 把处理后的频谱变回时域 | 频域滤波 |
| 场景B:卷积链路 | 和 Dft、MulSpectrums 串联 | 快速卷积 |
| 场景C:相关链路 | 和共轭乘法串联 | 模板匹配 |
7. 函数使用示例
下面的 Console 程序演示Cv2.Idft。为了方便理解,我们直接拿前向Dft的输出频谱作为输入,再把结果还原成原始实数序列。
usingSystem.Globalization;usingSystem.Text;usingOpenCvSharp;internalstaticclassProgram{/// <summary>/// 程序入口。/// </summary>privatestaticvoidMain(){// 控制台要支持中文输出,这样说明文字不会乱码。Console.OutputEncoding=Encoding.UTF8;// 这个频谱正好对应实数信号 [1, 2, 3, 4],因此很适合做往返验证。varspectrumData=newdouble[,]{{10.0,0.0},{-2.0,2.0},{-2.0,0.0},{-2.0,-2.0},};varexpectedSignal=newdouble[,]{{1.0,2.0,3.0,4.0},};usingvarspectrum=CreateComplexRowVector(spectrumData);usingvarreconstructed=newMat();// RealOutput 表示把逆变换结果写回实数矩阵,Scale 则补上 1/N 归一化。Cv2.Idft(spectrum,reconstructed,DftFlags.RealOutput|DftFlags.Scale);varactualSignal=ReadDoubleMatrix(reconstructed);PrintComplexMatrix("输入频谱 X[k]",spectrumData);PrintMatrixComparison("逆变换结果 x[n]",actualSignal,expectedSignal);}/// <summary>/// 把二维复数数组包装成 1 行 N 列的复数矩阵。/// </summary>privatestaticMatCreateComplexRowVector(double[,]complexPairs){// 每一行只存一个复数,因此这里只需要一行 N 列。varvector=newMat(1,complexPairs.GetLength(0),MatType.CV_64FC2);for(varindex=0;index<complexPairs.GetLength(0);index++){// Vec2d 的 Item0 是实部,Item1 是虚部。vector.At<Vec2d>(0,index)=newVec2d(complexPairs[index,0],complexPairs[index,1]);}returnvector;}/// <summary>/// 从 Mat 读取单通道实数矩阵。/// </summary>privatestaticdouble[,]ReadDoubleMatrix(Matsource){usingvarconverted=newMat();source.ConvertTo(converted,MatType.CV_64FC1);varresult=newdouble[converted.Rows,converted.Cols];for(varrow=0;row<converted.Rows;row++){for(varcol=0;col<converted.Cols;col++){result[row,col]=converted.At<double>(row,col);}}returnresult;}/// <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>privatestaticvoidPrintMatrixComparison(stringtitle,double[,]actual,double[,]expected){PrintMatrix($"{title}- 实际值",actual);PrintMatrix($"{title}- 期望值",expected);Console.WriteLine($"最大绝对误差:{ComputeMaxAbsDiff(actual,expected):F12}");Console.WriteLine();}/// <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>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, 2, 3, 4]。 - 只有同时开启
RealOutput和Scale,往返验证才会和前向Dft对齐。 - 这说明前向和逆向变换是互相配套的,而不是两个完全独立的函数。
9. 常见错误与排查
- 忘记加
DftFlags.Scale,结果比原始信号大。 - 把共轭对称频谱当成普通实数矩阵来处理。
- 在逆变换里误用了
DftFlags.ComplexOutput,这没有意义。 - 输入频谱的格式不对,导致函数报错。
10. 进阶说明
Idft的进阶使用方式,通常离不开以下几点:
RealOutput用于把共轭对称频谱还原成实数矩阵。Scale用于让前向和逆向结果互为逆运算。Rows可以让每一行独立逆变换,适合批量频谱处理。nonzeroRows可以在卷积、相关等场景里减少不必要的计算。
如果你已经理解了Dft,那么Idft其实就是把“频域结果再搬回时域”这一步走完。
11. 总结
Cv2.Idft的核心职责,就是把频谱还原成时域信号。把RealOutput、Scale、ComplexOutput这几个标志的方向搞清楚之后,后面的频域卷积、相关和滤波就会清楚很多。