Opencv笔记(12)傅里叶变换
在之前了解的OpenCV为我们实现的图像变换,这些本质上是从图像到输出图像的映射,即输入仍是一幅图像。本章的傅里叶变换,输出数组的值在含义上和原图像的强度值大不相同,是输入图像的频域表示。
cv::dft()离散傅里叶变换
dft(InputArray src, // 输入图像,可以是实数或虚数
OutputArray dst, // 输出图像,其大小和类型取决于第三个参数flags
int flags = 0, // 转换的标识符,有默认值0
int nonzeroRows = 0);// 当这个参数不为0,函数会假设只有输入数组(没有设置DFT_INVERSE)的第一行或第一个输出数组(设置了DFT_INVERSE)包含非零值
flag参数
- DFT_INVERSE: 用一维或二维逆变换取代默认的正向变换
- DFT_SCALE: 缩放比例标识符,根据数据元素个数平均求出其缩放结果,如有N个元素,则输出结果以1/N缩放输出,常与DFT_INVERSE搭配使用。
- DFT_ROWS: 对输入矩阵的每一行执行正变换或逆变换;此标志允许您同时变换多个向量,并可用于减少执行3D和更高维度变换等的开销(有时比处理本身大几倍)。
- DFT_COMPLEX_OUTPUT: 对一维或二维的实数数组进行正向变换,这样的结果虽然是复数阵列,但拥有复数的共轭对称性(CCS),可以以一个和原数组尺寸大小相同的实数数组进行填充,这是最快的选择也是函数默认的方法。你可能想要得到一个全尺寸的复数数组(像简单光谱分析等等),通过设置标志位可以使函数生成一个全尺寸的复数输出数组。
- DFT_REAL_OUTPUT: 对一维二维复数数组进行逆向变换,这样的结果通常是一个尺寸相同的复数矩阵,但是如果输入矩阵有复数的共轭对称性(比如是一个带有DFT_COMPLEX_OUTPUT标识符的正变换结果),便会输出实数矩阵。
示例:
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main() {
clock_t start, end;
start = clock();
int count = 0; Mat src = imread("F:/wallpaper/1.jpg", 0);
//1. 放大到合适尺寸加速运算
Mat padded;
int opWidth = getOptimalDFTSize(src.cols);
int opHeight = getOptimalDFTSize(src.rows);
copyMakeBorder(src, padded, 0, opHeight - src.rows, 0, opWidth - src.cols, BORDER_REFLECT); //2. dft 当输入是双通道时,两个通道分别代表实部和虚部
Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
Mat comImg;
merge(planes, 2, comImg);
dft(comImg, comImg, DFT_COMPLEX_OUTPUT); //3. 分离通道,显示幅值
split(comImg, planes);
Mat magMat;
magnitude(planes[0], planes[1], magMat);
magMat += Scalar::all(1);
log(magMat, magMat);
magMat = magMat(Rect(0, 0, magMat.cols & -2, magMat.rows & -2));
normalize(magMat, magMat, 0, 1, NORM_MINMAX); //4. 把零频移到中心
Mat magImg(magMat.size(), CV_8UC1);
magMat.convertTo(magImg, CV_8UC1, 255, 0);
magMat = magMat(Rect(0, 0, magMat.cols & -2, magMat.rows & -2));
int cx = magImg.cols / 2;
int cy = magImg.rows / 2;
Mat q1 = magImg({ 0, 0, cx, cy });
Mat q2 = magImg({ 0, cy, cx, cy });
Mat q3 = magImg({ cx, 0, cx, cy });
Mat q4 = magImg({ cx, cy, cx, cy });
Mat temp;
q1.copyTo(temp);
q4.copyTo(q1);
temp.copyTo(q4);
q2.copyTo(temp);
q3.copyTo(q2);
temp.copyTo(q3); end = clock();
cout << end - start << endl;
return 0;
}
低频信号对应图像内变化缓慢的灰度分量。高频信号对应图像内变化越来越快的灰度分量,是由灰度的尖锐过渡造成的,例如边界。从频谱图上可以看出,当将频谱移频到原点以后,图像中心比较亮。在频谱图中,一个点的亮暗主要与这个频率中点的数目和点的灰度值有关,也就是说在空间域中包含这种频率的点越多。而经过频移后,频率为0的部分,也就是傅里叶变换所得到的常量分量在图像中心,往外扩散,点所代表的频率越来越高。也说明了图像中的“能量”主要集中在低频部分。
cv::idft()离散傅里叶逆变换
dft()不仅可以实现离散傅里叶变换,也可以实现逆变换,dft(src, dst, flags | #DFT_INVERSE) 和idft(src, dst, flags)的效果相等。
void idft(InputArray src, OutputArray dst, int flags = 0, int nonzeroRows = 0);
但是为了代码的可读性,应当使用专门的函数进行逆变换。将flag设置为DFT_REAL_OUTPUT,可以直接输入之前傅里叶变换得到的结果,得到实数矩阵的输出。
cv::mulSpectrums()频谱乘法
void mulSpectrums(InputArray a, //输入图像(ccs or complex)
InputArray b, //和a一样为ccs格式单通道频谱or双通道复数频谱
OutputArray c, //目标数组,大小类型与输入一样
int flags, //只支持DFT_ROWS
bool conjB = false);//可选标志,用于决定在乘法操作前是否对第二个输入频谱取共轭。true表示取共轭;false表示不取
对于将零频移动到中心的频谱图,舍去中心的低频部分,只保留高频,增强图像细节,即用下图a中的mask与频谱图进行频谱乘法。如果没有移动频谱,即用下图b的mask相乘。
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main() {
clock_t start, end;
start = clock();
int count = 0; Mat src = imread("F:/wallpaper/1.jpg", 0);
//1. 放大到合适尺寸加速运算
Mat padded;
int opWidth = getOptimalDFTSize(src.cols);
int opHeight = getOptimalDFTSize(src.rows);
copyMakeBorder(src, padded, 0, opHeight - src.rows, 0, opWidth - src.cols, BORDER_REFLECT); //2. dft 当输入是双通道时,两个通道分别代表实部和虚部
Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
Mat comImg;
merge(planes, 2, comImg);
dft(comImg, comImg, DFT_COMPLEX_OUTPUT); //3. 保留高频
int d = 100;
Mat mask(opHeight, opWidth, CV_8UC1, Scalar(1));
Rect r1(0, 0, d, d);
Rect r2(0, opHeight - d, d, d);
Rect r3(opWidth - d, 0, d, d);
Rect r4(opWidth-d, opHeight-d, d, d);
mask(r1).setTo(0);
mask(r2).setTo(0);
mask(r3).setTo(0);
mask(r4).setTo(0);
mask.convertTo(mask, CV_32F);
Mat maskImg;
Mat masks[] = { mask,mask };
merge(masks, 2, maskImg);
mulSpectrums(comImg, maskImg, comImg, DFT_ROWS); //4. 傅里叶反变换
Mat dst;
idft(comImg, comImg, DFT_COMPLEX_OUTPUT);
split(comImg, planes);
magnitude(planes[0], planes[1], dst);
normalize(dst, dst, 0, 1, NORM_MINMAX);
dst.convertTo(dst, CV_8U, 255); end = clock();
cout << end - start << endl;
return 0;
}
结果:
参考文献:
Opencv笔记(12)傅里叶变换的更多相关文章
- 常用数据结构[OpenCV 笔记12]
Point 二维坐标系下的整数点, 定义如下 typedef Point_<int> Point2i; typedef Point2i Point; typedef Point_<f ...
- Opencv笔记(二十一)——傅里叶变换
参考 Numpy 中的傅里叶变换 首先我们看看如何使用 Numpy 进行傅里叶变换.Numpy 中的 FFT 包可以帮助我们实现快速傅里叶变换.函数 np.fft.fft2() 可以对信号进行频率转换 ...
- OpenCV笔记大集锦(转载)
整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的.如果有好的资源,也欢迎介绍和分享. 1:OpenCV学习笔记 作者:CSDN数量:55篇博文网址: ...
- opencv笔记5:频域和空域的一点理解
time:2015年10月06日 星期二 12时14分51秒 # opencv笔记5:频域和空域的一点理解 空间域和频率域 傅立叶变换是f(t)乘以正弦项的展开,正弦项的频率由u(其实是miu)的值决 ...
- opencv笔记2:图像ROI
time:2015年 10月 03日 星期六 12:03:45 CST # opencv笔记2:图像ROI ROI ROI意思是Region Of Interests,感兴趣区域,是一个图中的一个子区 ...
- 学习笔记 - 快速傅里叶变换 / 大数A * B的另一种解法
转: 学习笔记 - 快速傅里叶变换 / 大数A * B的另一种解法 文章目录 前言 ~~Fast Fast TLE~~ 一.FFT是什么? 二.FFT可以干什么? 1.多项式乘法 2.大数乘法 三.F ...
- 机器学习实战 - 读书笔记(12) - 使用FP-growth算法来高效发现频繁项集
前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第12章 - 使用FP-growth算法来高效发现频繁项集. 基本概念 FP-growt ...
- opencv笔记6:角点检测
time:2015年10月09日 星期五 23时11分58秒 # opencv笔记6:角点检测 update:从角点检测,学习图像的特征,这是后续图像跟踪.图像匹配的基础. 角点检测是什么鬼?前面一篇 ...
- opencv笔记4:模板运算和常见滤波操作
time:2015年10月04日 星期日 00时00分27秒 # opencv笔记4:模板运算和常见滤波操作 这一篇主要是学习模板运算,了解各种模板运算的运算过程和分类,理论方面主要参考<图像工 ...
- opencv笔记3:trackbar简单使用
time:2015年 10月 03日 星期六 13:54:17 CST # opencv笔记3:trackbar简单使用 当需要测试某变量的一系列取值取值会产生什么结果时,适合用trackbar.看起 ...
随机推荐
- Spring开发:动态代理的艺术与实践
本文分享自华为云社区<Spring高手之路17--动态代理的艺术与实践>,作者: 砖业洋__. 1. 背景 动态代理是一种强大的设计模式,它允许开发者在运行时创建代理对象,用于拦截对真实对 ...
- css 中的BFC
一.是什么 我们在页面布局的时候,经常出现以下情况: 这个元素高度怎么没了? 这两栏布局怎么没法自适应? 这两个元素的间距怎么有点奇怪的样子? ...... 归根究底是元素之间相互的影响,导致了意料之 ...
- 初接触:从创建工程到导出gerber(学习Altium Designer)
学习Altium Designer Altium Designer的工程文件后缀为.PrjPcb,主要包含Source Documents和Libraries.Source Documents里面有S ...
- 基于 eBPF 的 Kubernetes 可观测实践
简介: 阿里云可观测团队构建了 kubernetes 统一监控,无侵入式地提供多语言.应用性能黄金指标,支持多种协议,结合 Kubernetes 管控层与网络系统层监控,提供全栈一体式的可观测体验.通 ...
- 360 政企安全集团基于 Flink 的 PB 级数据即席查询实践
简介: Threat Hunting 平台的架构与设计,及以降低 IO 为目标的优化与探索.为什么以及如何使用块索引. 本文整理自 360 政企安全集团的大数据工程师苏军以及刘佳在 Flink For ...
- 延迟绑定与retdlresolve
延迟绑定与retdlresolve 我们以前在ret2libc的时候,我们泄露的libc地址是通过延迟绑定实现的,我们知道,在调用libc里面的函数时候,它会先通过plt表和gor表绑定到,函数真实地 ...
- [Blockchain] Cosmos Starport 安装的三种方式
官方二进制包方式: # 下载 starport 二进制到 /usr/local/bin $ curl https://get.starport.network/starport! | bash # ...
- WPF 设置 IncludePackageReferencesDuringMarkupCompilation 属性导致分析器不工作
本文记录在 WPF 项目里面设置 IncludePackageReferencesDuringMarkupCompilation 属性为 False 导致了项目所安装的分析器不能符合预期工作 设置 I ...
- dotnet C# 只创建对象不调用构造函数方法
有时我期望只是创建出对象,但是不要调用对象的构造方法,可以通过使用 FormatterServices 的 GetUninitializedObject 函数来实现只创建对象不调用构造函数方法 这个 ...
- Python 潮流周刊#48:Python 3.14 的发布计划
本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...