只是用OpenCvSharp将官网源码跑一遍,关于傅立叶变换的理论,还是自己百度吧,反正,本人是还没搞明白!
相关函数
CopyMakeBorder边界外推
原文中使用了CopyMakeBorder函数,就顺便了解下BorderType参数各值时的效果,具体见OpenCvSharp函数:CopyMakeBorder边框外推_图南堂的博客-CSDN博客
GetOptimalDFTSize返回适合DFT的最佳大小
返回一个大于等于原值,且满足N=2^p * 3^q * 5^r(p,q,r为大于等于0的整数)的最小整数。
Dft离散傅立叶变换
对一张图像使用傅立叶变换就是将它分解成正弦和余弦两部分。也就是将图像从空间域(spatial domain)转换到频域(frequency domain)。 这一转换的理论基础来自于以下事实:任一函数都可以表示成无数个正弦和余弦函数的和的形式
Merge图像合并
将多个矩阵合并为一个矩阵
Normalize归一化
将图像归一化,便于显示
图像示例
应用示例,用于判断文本的方向
源码示例
public void Run(ParamBase paramBase) {
//var imgFile = ImagePath.Lena;
//var imgFile = ImagePath.ImageTextN;
var imgFile = ImagePath.ImageTextR;
using var mat = Cv2.ImRead(imgFile);
if (mat.Empty()) throw new Exception($"图像打开有误:{imgFile}");
//用CopyMakeBorder演示BorderTypes各值的不同效果
TestBorderTypes(mat);
using var I = mat.CvtColor(ColorConversionCodes.BGR2GRAY);
if (I.Empty()) throw new Exception($"图像打开有误:{imgFile}");
//扩展为大小最适合做离散傅立叶转换的矩阵
using var padded = new Mat();
//大于等下原值,且满足N = 2^p * 3^q * 5^r(p,q,r为大于等于0的整数)
int rows = Cv2.GetOptimalDFTSize(I.Rows);
int cols = Cv2.GetOptimalDFTSize(I.Cols);
//用0扩展图像边框
Cv2.CopyMakeBorder(I, padded, 0, rows - I.Rows, 0, cols - I.Cols, BorderTypes.Constant, Scalar.All(0));
//结果需要用浮点型存储
padded.ConvertTo(padded, MatType.CV_32F);
var planes = new Mat[] { padded, Mat.Zeros(padded.Size(), MatType.CV_32F) };
using var complexSrc = new Mat();
Cv2.Merge(planes, complexSrc);
//离散傅立叶变换
Cv2.Dft(complexSrc, complexSrc);
//分割实数与复数部分 planes[0]为实数部分,planes[1]为复数部分
Cv2.Split(complexSrc,out planes);
//将结果转为幅度
//dst(I) = Math.Sqrt(x(I)^2 + y(I)^2)
Cv2.Magnitude(planes[0], planes[1], planes[0]);
var magSrc = planes[0];
magSrc += Scalar.All(1);
//由于幅度结果太大,无法显示,使用对数尺度替换线性尺度
Cv2.Log(magSrc, magSrc);
//行、列都为偶数
magSrc= magSrc[new Rect(0,0, magSrc.Cols & -2, magSrc.Rows & -2)];
int cx = magSrc.Cols / 2;
int cy = magSrc.Rows / 2;
Mat q0 = new Mat(magSrc, new Rect(0, 0, cx, cy));
Mat q1 = new Mat(magSrc, new Rect(cx, 0, cx, cy));
Mat q2 = new Mat(magSrc, new Rect(0, cy, cx, cy));
Mat q3 = new Mat(magSrc, new Rect(cx, cy, cx, cy));
//左上角与右下角对换
Mat tmp=new Mat();
q0.CopyTo(tmp);
q3.CopyTo(q0);
tmp.CopyTo(q3);
//右上角与左下角对换
q1.CopyTo(tmp);
q2.CopyTo(q1);
tmp.CopyTo(q2);
//原幅度值仍更不便于显示,归一化后方便可视
Cv2.Normalize(magSrc, magSrc, 0, 1, NormTypes.MinMax);
Cv2.ImShow("Input Image", I);
Cv2.ImShow("spectrum magnitude", magSrc);
Cv2.WaitKey();
Cv2.DestroyAllWindows();
}
/// <summary>
/// 测试BorderTypes
/// </summary>
/// <param name="src"></param>
private void TestBorderTypes(Mat src) {
var bBorderTypes = Enum.GetValues(typeof(BorderTypes));
foreach(BorderTypes b in bBorderTypes) {
try {
//外推宽度
var extend = 100;
using var dst = new Mat();
Cv2.CopyMakeBorder(src, dst, extend, extend, extend, extend, b, Scalar.All(127));
Cv2.Rectangle(dst, new Rect(extend, extend, src.Width, src.Height), Scalar.Red);
Cv2.ImShow($"{b.ToString()}", dst);
}
catch (Exception ex) {
//不支持 Transparent
}
}
Cv2.WaitKey();
Cv2.DestroyAllWindows();
}
参考