最近在学习OpenCV3编程入门(毛星云编著)这本书,书中的示例都是用C++写的,而自己用的是C#,配合EMGU 3.4.1版本,网上的资料非常非常少,很多时候遇到问题,都找不到解决方法。为了记录自己的学习过程,也为了后来的学习者能有资料参考,在此分享自己转化后的C# EMGU3.4.1版本的代码。
下面是原书中5.5.8章节的一个示例程序:离散傅里叶变换,我用C#配合EMGU 3.4.1.2976版本,进行了重新书写,过程中遇到很多问题,可能很简单,但苦于网上没有参考资料,费了大量时间去调试。这里分享程序代码供大家参考(注意:我用的是Winform,而不是控制台),一些基本的引用我就不说明了,相信大家书看到这里,肯定都是知道的。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.Util;
namespace FourierTransform
{
public partial class Form1 : Form
{
//以灰度模式读取原始图像
Mat srcImage = new Mat("meal.jpg", 0);
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
if (srcImage.IsEmpty)
MessageBox.Show("读取图片错误,请确认目录下是否存在imread函数指定的图片!");
//显示原始图像
srcImageBox.Image = srcImage;
}
private void DFT_Btn_Click(object sender, EventArgs e)
{
//将输入图像扩展到最佳的尺寸,边界用0补充
int m = CvInvoke.GetOptimalDFTSize(srcImage.Rows);
int n = CvInvoke.GetOptimalDFTSize(srcImage.Cols);
Mat padded = new Mat();
CvInvoke.CopyMakeBorder(srcImage, padded, 0, m - srcImage.Rows, 0, n - srcImage.Cols, BorderType.Constant);
padded.ConvertTo(padded, DepthType.Cv32F); //将padded转换为Cv32F类型,此为傅里叶变换的实部
//创建元素值都为0的zeroMat,此为傅里叶变换的虚部
Mat zeroMat = Mat.Zeros(padded.Rows, padded.Cols, DepthType.Cv32F, 1);
//Mat[] planes = { padded, zeroMat };
//VectorOfMat matVector = new VectorOfMat(planes);
VectorOfMat matVector = new VectorOfMat(); //创建mat型向量
matVector.Push(padded); //将padded压入matVector中
matVector.Push(zeroMat); //将zeroMat压入matVector中
//创建channel数为2的mat,用于存储傅里叶变换的实部和虚部
Mat complexI = new Mat(padded.Size, DepthType.Cv32F, 2);
CvInvoke.Merge(matVector, complexI); //将matVector中存储的2个mat,merge到complexI中
//创建mat,用于保存傅里叶变换的结果
Mat fourier = new Mat(complexI.Size, DepthType.Cv32F, 2);
//调用傅里叶变换函数Dft,进行傅里叶变换
CvInvoke.Dft(complexI, fourier, DxtType.Forward, complexI.Rows);
//将复数转换为幅值,即=>log(1+sqrt(Re(DFT(I)^2+Im(DFT(I)^2))
Mat magnitudeImage = Magnitude(fourier);
//若有奇数行或奇数列,则进行频谱裁剪
magnitudeImage = new Mat(magnitudeImage, new Rectangle(0, 0, magnitudeImage.Cols & -2, magnitudeImage.Rows & -2));
//重新排列傅里叶图像中的象限,使得原点位于图像中心
SwitchQuadrants(ref magnitudeImage);
//归一化,用0到1之间的浮点值将矩阵变换为可视的图像格式
//CvInvoke.Normalize(magnitudeImage, magnitudeImage, 1.0, 0.0, NormType.MinMax, DepthType.Cv32F);
//CvInvoke.Imshow("Fourier Transform", magnitudeImage);
//若要在imageBox控件上显示傅里叶变换后的图像,需要按如下方式进行数据规整
//DepthType的类型一定要是Cv8U,否则图片在imageBox控件中显示为纯黑色图片
CvInvoke.Normalize(magnitudeImage, magnitudeImage, 0, 255, NormType.MinMax, DepthType.Cv8U);
dftImageBox.Image = magnitudeImage;
}
//将复数转换为幅值,并进行对数尺度缩放
private Mat Magnitude(Mat fftData)
{
//傅里叶变换的实部
Mat Real = new Mat(fftData.Size, DepthType.Cv32F, 1);
//傅里叶变换的虚部
Mat Imaginary = new Mat(fftData.Size, DepthType.Cv32F, 1);
VectorOfMat channels = new VectorOfMat();
CvInvoke.Split(fftData, channels); //将多通道mat分离成几个单通道mat
Real = channels.GetOutputArray().GetMat(0);
Imaginary = channels.GetOutputArray().GetMat(1);
CvInvoke.Pow(Real, 2.0, Real);
CvInvoke.Pow(Imaginary, 2.0, Imaginary);
CvInvoke.Add(Real, Imaginary, Real);
CvInvoke.Pow(Real, 0.5, Real);
Mat onesMat = Mat.Ones(Real.Rows, Real.Cols, DepthType.Cv32F, 1);
CvInvoke.Add(Real, onesMat, Real);
CvInvoke.Log(Real,Real); //求自然对数
return Real;
}
//重新排列傅里叶图像中的象限,使得原点位于图像中心
private void SwitchQuadrants(ref Mat mat)
{
int cx = mat.Cols / 2;
int cy = mat.Rows / 2;
Mat q0 = new Mat(mat, new Rectangle(0, 0, cx, cy)); //ROI左上区域
Mat q1 = new Mat(mat, new Rectangle(cx, 0, cx, cy)); //ROI右上区域
Mat q2 = new Mat(mat, new Rectangle(0, cy, cx, cy)); //ROI左下区域
Mat q3 = new Mat(mat, new Rectangle(cx, cy, cx, cy)); //ROI右下区域
Mat temp = new Mat(q0.Size, DepthType.Cv32F, 1);
//交换象限(左上和右下进行交换)
q0.CopyTo(temp);
q3.CopyTo(q0);
temp.CopyTo(q3);
//交换象限(右上和左下进行交换)
q1.CopyTo(temp);
q2.CopyTo(q1);
temp.CopyTo(q2);
}
}
}