在之前了解的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;
}

结果:

    

参考文献:

1. 图像的二维傅里叶变换频谱图特点研究

2. OpenCV学习笔记(十)——傅里叶变换

Opencv笔记(12)傅里叶变换的更多相关文章

  1. 常用数据结构[OpenCV 笔记12]

    Point 二维坐标系下的整数点, 定义如下 typedef Point_<int> Point2i; typedef Point2i Point; typedef Point_<f ...

  2. Opencv笔记(二十一)——傅里叶变换

    参考 Numpy 中的傅里叶变换 首先我们看看如何使用 Numpy 进行傅里叶变换.Numpy 中的 FFT 包可以帮助我们实现快速傅里叶变换.函数 np.fft.fft2() 可以对信号进行频率转换 ...

  3. OpenCV笔记大集锦(转载)

    整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的.如果有好的资源,也欢迎介绍和分享. 1:OpenCV学习笔记 作者:CSDN数量:55篇博文网址: ...

  4. opencv笔记5:频域和空域的一点理解

    time:2015年10月06日 星期二 12时14分51秒 # opencv笔记5:频域和空域的一点理解 空间域和频率域 傅立叶变换是f(t)乘以正弦项的展开,正弦项的频率由u(其实是miu)的值决 ...

  5. opencv笔记2:图像ROI

    time:2015年 10月 03日 星期六 12:03:45 CST # opencv笔记2:图像ROI ROI ROI意思是Region Of Interests,感兴趣区域,是一个图中的一个子区 ...

  6. 学习笔记 - 快速傅里叶变换 / 大数A * B的另一种解法

    转: 学习笔记 - 快速傅里叶变换 / 大数A * B的另一种解法 文章目录 前言 ~~Fast Fast TLE~~ 一.FFT是什么? 二.FFT可以干什么? 1.多项式乘法 2.大数乘法 三.F ...

  7. 机器学习实战 - 读书笔记(12) - 使用FP-growth算法来高效发现频繁项集

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第12章 - 使用FP-growth算法来高效发现频繁项集. 基本概念 FP-growt ...

  8. opencv笔记6:角点检测

    time:2015年10月09日 星期五 23时11分58秒 # opencv笔记6:角点检测 update:从角点检测,学习图像的特征,这是后续图像跟踪.图像匹配的基础. 角点检测是什么鬼?前面一篇 ...

  9. opencv笔记4:模板运算和常见滤波操作

    time:2015年10月04日 星期日 00时00分27秒 # opencv笔记4:模板运算和常见滤波操作 这一篇主要是学习模板运算,了解各种模板运算的运算过程和分类,理论方面主要参考<图像工 ...

  10. opencv笔记3:trackbar简单使用

    time:2015年 10月 03日 星期六 13:54:17 CST # opencv笔记3:trackbar简单使用 当需要测试某变量的一系列取值取值会产生什么结果时,适合用trackbar.看起 ...

随机推荐

  1. Detectron2环境配置+Ubantu+CUDA10.1+pytorch1.7.0

    Detectron2环境配置 1.创建detectron2 conda环境 conda create -n detectron2 python=3.7 2.激活detectron2 conda环境 c ...

  2. 深度解读《深度探索C++对象模型》之默认构造函数

    接下来我将持续更新"深度解读<深度探索C++对象模型>"系列,敬请期待,欢迎关注!也可以关注公众号:iShare爱分享,主动获得推文. 提到默认构造函数,很多文章和书籍 ...

  3. Chat2table,简易表格分析助手

    一 写在前面 之前用智谱AI的Chatglm3-6b模型写过一个简单的论文阅读助手,可用来辅助论文阅读等.而像表格,如Excel.CSV文件等内容的分析,也是不可忽略的需要,因此本文同样使用Chatg ...

  4. javascript现代编程系列教程之六——数字型数据类型转换

    一.整数转换 在 JavaScript 中,parseInt() 函数会将其参数转换为字符串,然后解析该字符串,并返回一个整数或 NaN.如果 parseInt() 函数的参数是一个非常大的浮点数(如 ...

  5. ddddocr基本使用和介绍

    ddddocr基本使用和介绍 摘要:在使用爬虫登录网站的时候,经常输入用户名和密码后会遇到验证码,这时候就需要用到今天给大家介绍的python第三方库ddddocr,ddddocr是一款强大的通用开源 ...

  6. 力扣1069(MySQL)-产品分析Ⅱ(简单)

    题目: 编写一个 SQL 查询,按产品 id product_id 来统计每个产品的销售总量. 查询结果格式如下面例子所示:  解题思路: 没有用到product表,直接在sales表中使用聚合函数: ...

  7. EasyNLP玩转文本摘要(新闻标题)生成

    简介: 本⽂将提供关于PEGASUS的技术解读,以及如何在EasyNLP框架中使⽤与PEGASUS相关的文本摘要(新闻标题)生成模型. 作者:王明.黄俊 导读 文本生成是自然语言处理领域的一个重要研究 ...

  8. Spring Cloud Gateway 突发高危漏洞,下一代云原生网关恰逢其时?

    ​简介:Log4j2 的漏洞刚告一段落,Spring 官方在 2022 年 3 月 1 日发布了 Spring Cloud Gateway 的两个 CVE 漏洞:分别为 CVE-2022-22946( ...

  9. 网易游戏基于 Flink 的流式 ETL 建设

    简介: 网易游戏流式 ETL 建设实践及调优经验分享- 网易游戏资深开发工程师林小铂为大家带来网易游戏基于 Flink 的流式 ETL 建设的介绍.内容包括: 专用 ETL EntryX 通用 ETL ...

  10. HTML中元素分类与对应的CSS样式特点

    元素就是标签,布局中常用的有三种标签,块元素.内联元素.内联块元素,了解这三种元素的特性,才能熟练的进行页面布局. 块元素 块元素,也可以称为行元素,布局中常用的标签如:div.p.ul.li.h1~ ...