OpenCV 【十七】离散傅立叶变换
目录
1 key
2 原理
3 实例
3代码
4运行结果
5应用举例
1 key
什么是傅立叶变换及其应用?
如何使用OpenCV提供的傅立叶变换?
相关函数的使用,如: copyMakeBorder(), merge(), dft(), getOptimalDFTSize(), log() 和 normalize() .
简单点说就是:所有的波都可以用很多个正弦波叠加表示。
然而这些波又可以通过频率、幅值和相位来表示。这样你就可以从左边那张图中时域的视角转化为咱们高大上的频域视角啦。
图引用!!!
2 原理
对一张图像使用傅立叶变换就是将它分解成正弦和余弦两部分。也就是将图像从空间域(spatial domain)转换到频域(frequency domain)。
这一转换的理论基础来自于以下事实:任一函数都可以表示成无数个正弦和余弦函数的和的形式。傅立叶变换就是一个用来将函数分解的工具。 2维图像的傅立叶变换可以用以下数学公式表达:
式中 f 是空间域(spatial domain)值, F 则是频域(frequency domain)值。 转换之后的频域值是复数, 因此,显示傅立叶变换之后的结果需要使用实数图像(real image) 加虚数图像(complex image),
或者幅度图像(magitude image)加相位图像(phase image)。
在实际的图像处理过程中,仅仅使用了幅度图像,因为幅度图像包含了原图像的几乎所有我们需要的几何信息。
然而,如果你想通过修改幅度图像或者相位图像的方法来间接修改原空间图像,你需要使用逆傅立叶变换得到修改后的空间图像,这样你就必须同时保留幅度图像和相位图像了。
3 实例
在此展示如何计算以及显示傅立叶变换后的幅度图像。由于数字图像的离散性,像素值的取值范围也是有限的。比如在一张灰度图像中,像素灰度值一般在0到255之间。 因此,我们这里讨论的也仅仅是离散傅立叶变换(DFT)。 如果你需要得到图像中的几何结构信息,那你就要用到它了。请参考以下步骤(假设输入图像为单通道的灰度图像 I):
将图像延扩到最佳尺寸. 离散傅立叶变换的运行速度与图片的尺寸息息相关。当图像的尺寸是2, 3,5的整数倍时,计算速度最快。 因此,为了达到快速计算的目的,经常通过添凑新的边缘像素的方法获取最佳图像尺寸。函数 getOptimalDFTSize() 返回最佳尺寸,而函数 copyMakeBorder() 填充边缘像素 ,添加的像素初始化为0.
Mat padded; //将输入图像延扩到最佳的尺寸 int m = getOptimalDFTSize( I.rows ); int n = getOptimalDFTSize( I.cols ); // 在边缘添加0 copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
为傅立叶变换的结果(实部和虚部)分配存储空间. 傅立叶变换的结果是复数,这就是说对于每个原图像值,结果是两个图像值。 此外,频域值范围远远超过空间值范围, 因此至少要将频域储存在 float 格式中。 结果我们将输入图像转换成浮点类型,并多加一个额外通道来储存复数部分:
Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)}; Mat complexI; merge(planes, 2, complexI); // 为延扩后的图像增添一个初始化为0的通道
进行离散傅立叶变换. 支持图像原地计算 (输入输出为同一图像):
dft(complexI, complexI); // 变换结果很好的保存在原始矩阵中
将复数转换为幅度*.复数包含实数部分(Re*)和复数部分 (imaginary - Im)。 离散傅立叶变换的结果是复数,对应的幅度可以表示为:
split(complexI, planes); // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I)) magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude Mat magI = planes[0]; 计算二维矢量的幅值:magnitude()函数 该函数用来计算二维矢量的幅值 void magnitude(InputArray x,InputArray y,OutputArray magnitude) 第一个参数:InputArray类型的x,表示矢量的浮点型X坐标值,也就是实部 第二个参数:InputArray类型的y,表示矢量的浮点型Y坐标值,也就是虚部 第三个参数:OutputArray类型的magnitude,输出的幅值,它和第一个参数X有着同样的尺寸和类型
对数尺度(logarithmic scale)缩放. 傅立叶变换的幅度值范围大到不适合在屏幕上显示。高值在屏幕上显示为白点,而低值为黑点,高低值的变化无法有效分辨。为了在屏幕上凸显出高低变化的连续性,我们可以用对数尺度来替换线性尺度:
转化为OpenCV代码:
magI += Scalar::all(1); // 转换到对数尺度 log(magI, magI);
剪切和重分布幅度图象限. 还记得我们在第一步时延扩了图像吗? 那现在是时候将新添加的像素剔除了。为了方便显示,我们也可以重新分布幅度图象限位置(注:将第五步得到的幅度图从中间划开得到四张1/4子图像,将每张子图像看成幅度图的一个象限,重新分布即将四个角点重叠到图片中心)。 这样的话原点(0,0)就位移到图像中心。
magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2)); int cx = magI.cols/2; int cy = magI.rows/2; Mat q0(magI, Rect(0, 0, cx, cy)); // Top-Left - 为每一个象限创建ROI Mat q1(magI, Rect(cx, 0, cx, cy)); // Top-Right Mat q2(magI, Rect(0, cy, cx, cy)); // Bottom-Left Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right Mat tmp; // 交换象限 (Top-Left with Bottom-Right) q0.copyTo(tmp); q3.copyTo(q0); tmp.copyTo(q3); q1.copyTo(tmp); // 交换象限 (Top-Right with Bottom-Left) q2.copyTo(q1); tmp.copyTo(q2);
归一化. 这一步的目的仍然是为了显示。 现在我们有了重分布后的幅度图,但是幅度值仍然超过可显示范围[0,1] 。我们使用 normalize() 函数将幅度归一化到可显示范围。
normalize(magI, magI, 0, 1, CV_MINMAX); // 将float类型的矩阵转换到可显示图像范围// (float [0, 1]).
3代码
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
using namespace std;
int main(int argc, char ** argv)
{Mat I = imread("C:\\Users\\guoqi\\Desktop\\\ch7\\4.jpg", IMREAD_GRAYSCALE);if (I.empty()) {cout << "Error opening image" << endl;return EXIT_FAILURE;}
//! [expand]Mat padded; //expand input image to optimal sizeint m = getOptimalDFTSize(I.rows);int n = getOptimalDFTSize(I.cols); // on the border add zero valuescopyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));//! [expand]
//! [complex_and_real]Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };Mat complexI;merge(planes, 2, complexI); // Add to the expanded another plane with zeros//! [complex_and_real]
//! [dft]dft(complexI, complexI); // this way the result may fit in the source matrix//! [dft]
// compute the magnitude and switch to logarithmic scale// => log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))//! [magnitude]split(complexI, planes); // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitudeMat magI = planes[0];//! [magnitude]
//! [log]magI += Scalar::all(1); // switch to logarithmic scalelog(magI, magI);//! [log]
//! [crop_rearrange]// crop the spectrum, if it has an odd number of rows or columnsmagI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));
// rearrange the quadrants of Fourier image so that the origin is at the image center//重新排列傅里叶图像的象限,使原点位于图像中心int cx = magI.cols / 2;int cy = magI.rows / 2;
Mat q0(magI, Rect(0, 0, cx, cy)); // Top-Left - Create a ROI per quadrantMat q1(magI, Rect(cx, 0, cx, cy)); // Top-RightMat q2(magI, Rect(0, cy, cx, cy)); // Bottom-LeftMat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right
Mat tmp; // swap quadrants (Top-Left with Bottom-Right)q0.copyTo(tmp);q3.copyTo(q0);tmp.copyTo(q3);
q1.copyTo(tmp); // swap quadrant (Top-Right with Bottom-Left)q2.copyTo(q1);tmp.copyTo(q2);//! [crop_rearrange]
//! [normalize]normalize(magI, magI, 0, 1, NORM_MINMAX); // Transform the matrix with float values into a// viewable image form (float between values 0 and 1).//! [normalize]
imshow("Input Image", I); // Show the resultimshow("spectrum magnitude", magI);waitKey();
return EXIT_SUCCESS;
}
4运行结果
5应用
离散傅立叶变换的一个应用是决定图片中物体的几何方向.比如,在文字识别中首先要搞清楚文字是不是水平排列的? 看一些文字,你就会注意到文本行一般是水平的而字母则有些垂直分布。文本段的这两个主要方向也是可以从傅立叶变换之后的图像看出来。我们使用这个 水平文本图像
以及 旋转文本图像
来展示离散傅立叶变换的结果 。
水平文本图像:
水平文本图像对应的DFT变换:
旋转文本图像:
旋转文本图像对应的DFT变换:
观察这两张幅度图你会发现频域的主要内容(幅度图中的亮点)是和空间图像中物体的几何方向相关的。 通过这点我们可以计算旋转角度并修正偏差。
6 应用拓展机器含义
这就得出了一个结论:傅里叶变换后的白色部分(即幅度较大的低频部分),表示的是图像中慢变化的特性,或者说是灰度变化缓慢的特性(低频部分)。
傅里叶变换后的黑色部分(即幅度低的高频部分),表示图像中快变化的特性,或者说是灰度变化快的特性(高频部分)。
参考
OpenCV 【十七】离散傅立叶变换相关推荐
- opencv dft离散傅立叶变换
文章目录 概念 一.算法步骤 1.将图像扩展到最佳大小 2.为复数和实值开辟空间 3.进行离散傅里叶变换 4.将实部和复部转换为振幅 5.转换到对数尺度 6.裁剪和重新排列 7.归一化 运行效果 完整 ...
- OpenCV之core 模块. 核心功能(2)基本绘图 随机数发生器绘制文字 离散傅立叶变换 输入输出XML和YAML文件 与 OpenCV 1 同时使用
基本绘图 目的 本节你将学到: 如何用 Point 在图像中定义 2D 点 如何以及为何使用 Scalar 用OpenCV的函数 line 绘 直线 用OpenCV的函数 ellipse 绘 椭圆 用 ...
- OpenCV离散傅立叶变换DFT的实例(附完整代码)
OpenCV离散傅立叶变换DFT的实例 OpenCV离散傅立叶变换DFT的实例 OpenCV离散傅立叶变换DFT的实例 #include "opencv2/core.hpp" #i ...
- OpenCV离散傅立叶变换
OpenCV离散傅立叶变换 离散傅立叶变换 目标 源代码 解释 将图像放大到最佳尺寸 兼顾复杂和真实的值 进行离散傅立叶变换 将真实和复杂的值转换为幅度 裁剪并重新排列 归一化 结果 离散傅立叶变换 ...
- 离散傅立叶变换与逆变换
一.怎样为一副图像增加一个通道 Mat A=(Mat_<double>(3,3)<<1,2,3,4,5,6,7,8,9);Mat B=Mat::zeros(A.size(),A ...
- 理解离散傅立叶变换(一)
理解离散傅立叶变换(一) ------傅立叶变换的由来 关于傅立叶变换,无论是书本还是在网上可以很容易找到关于傅立叶变换的描述,但是大都是些故弄玄虚的 文章,太过抽象,尽是一些让人看了就望而生畏的公式 ...
- UA OPTI512R 傅立叶光学导论17 离散傅立叶变换简介
UA OPTI512R 傅立叶光学导论17 离散傅立叶变换简介 DFT及其矩阵形式 DFT的性质 上一讲提到对连续波形f(x)f(x)f(x)做周期性采样时可以用采样函数来表示采样结果: fS(x)= ...
- 理解离散傅立叶变换(三.复数)
理解离散傅立叶变换(三) ------复数形式离散傅立叶变换 复数形式的离散傅立叶变换非常巧妙地运用了复数的方法,使得傅立叶变换变换更加自然和简洁,它并不是只是简单地运用替换的方法来运用复数,而是完全 ...
- JavaScript实现离散傅立叶变换DFT算法(附完整源码)
JavaScript实现离散傅立叶变换DFT算法(附完整源码) radianToDegree.js完整源代码 ComplexNumber.js完整源代码 discreteFourierTransfor ...
最新文章
- Linux远程拷贝下载文件
- CG CTF WEB SQL注入1
- 银盒子扫码下单在线订单开启商品售卖时段使用说明
- 汇编语言(八)之删除数组中为零的元素
- MySQL管理工具HeidiSQL
- 关于Arduino下载并添加DHT11库文件的详细教程
- 鼠标右键没有新建选项怎么办
- 我写了款依赖检查的插件
- 怎么快速无损的将FLV格式转换MP4格式
- 用C#编写HMI界面,用USB转485和PLC通讯,PLC用的是台达的DVP-12SE系列
- 开源并兼容Windows NT的操作系统ReactOS简单介绍
- 做为一名大数据新手,如何成为大数据工程师?附上学习路线
- 一杯茶,一包烟,一个环境配一天
- 电机的反电动势了解及步进电机的基本特性
- 关于 数学 线性代数
- IDMPhotoBrowser 图片管理器
- python_习题四
- (找规律)45,65,75,12,11,9,2,4,? 问号处填什么?
- Redisson的使用与可重入锁
- 论文阅读 [TPAMI-2022] U2Fusion: A Unified Unsupervised Image Fusion Network
热门文章
- centos 7 mysql界面管理器_centos7安装mysql5.7.24,并使用system管理mysql
- graphpad做折线图坐标轴数字_pandas做数据可视化具体操作,快来看看吧
- kali linux安装wine32,永恒之蓝msf下 ms17_010 (64位kali下安装wine32)
- 华为机考HJ8合并表记录
- html 3d立体阴影效果图,CSS3打造带阴影的旋转3D图像
- 线性代数--矩阵、向量
- 自然语言处理:网购商品评论情感判定
- 连通域最小外接矩形算法原理_基于分割的文本检测算法之PSENet/PAN/DBNet
- 兰州大学C语言程序设计课程作业,【兰州大学|兰州大学C语言程序设计课程作业( 五 )】语言程序设计|课程|作业-傻大方...
- 基于点云描述子的立体视觉里程计快速鲁棒的位置识别方法