【OpenCV】离散傅里叶变换

news2024/9/24 21:26:35

离散傅里叶变换

  • 傅里叶变换
  • 代码实现
    • 扩展图片
    • 创建储存实部和虚部值的矩阵
    • 进行离散傅里叶变换
    • 将复数转换成振幅
    • 对数转换
    • 裁剪和重排
    • 归一化
  • 离散傅里叶变换在图像处理中的应用
  • 参考

傅里叶变换

在图片处理中,傅里叶变化会将对图片的时域分析转变为频域分析。

傅里叶的基本思路就是,任何函数都可以近似地变成无限个 sin ⁡ \sin sin cos ⁡ \cos cos函数的和。对于具有 N − 1 N-1 N1个采样点的离散信号,可以将其中每个采样点的幅值与频率为 k k k sin ⁡ \sin sin cos ⁡ \cos cos函数中对应点的幅值相乘,就得到了该采样点在频率为 k k k sin ⁡ \sin sin cos ⁡ \cos cos函数上的变换结果。将频率 k k k进行变化,又可以得到新的结果,将所有这些结果相加,就得到了在该采样点上的离散傅里叶变换结果。用数学公式来表示:
F ( k ) = ∑ n = 0 N − 1 f ( n ) e − i ∗ k n ( 2 π N ) F(k) = \sum\limits_{n=0}^{N-1}f(n)e^{-i*kn(\frac{2\pi}{N})} F(k)=n=0N1f(n)eikn(N2π)
根据欧拉公式( e i x = cos ⁡ x + i sin ⁡ x e^{ix}=\cos x +i\sin x eix=cosx+isinx)展开得:
F ( k ) = ∑ n = 0 N − 1 f ( n ) [ cos ⁡ k n ( 2 π N ) − i sin ⁡ k n ( 2 π N ) ] F(k) = \sum\limits_{n=0}^{N-1}f(n)[\cos{kn(\frac{2\pi}{N})} -i\sin{kn(\frac{2\pi}{N})}] F(k)=n=0N1f(n)[coskn(N2π)isinkn(N2π)]

  • k k k sin ⁡ \sin sin cos ⁡ \cos cos函数的角频率
  • F ( k ) F(k) F(k)是频率为 k k k时傅里叶变换的结果
  • n n n为第n个采样点
  • f ( n ) f(n) f(n)为离散信号在第 n n n个采样点上的幅值
  • i i i为复数中的i,即 − 1 \sqrt{-1} 1
  • N N N为采样点的总数+1

对于图片数据来说,像素就是采样点,像素上的值就相当于采样点的幅值。所以在图片上的傅里叶变换,有以下公式:
F ( k , l ) = ∑ m = 0 N − 1 ∑ n = 0 N − 1 f ( m , n ) e − i 2 π ( k m N + l n N ) F(k,l) = \sum\limits_{m=0}^{N-1} \sum\limits_{n=0}^{N-1} f(m,n)e^{-i2\pi(\frac{km}{N}+\frac{ln}{N})} F(k,l)=m=0N1n=0N1f(m,n)ei2π(Nkm+Nln)
根据欧拉公式展开可得:
F ( k , l ) = ∑ m = 0 N − 1 ∑ n = 0 N − 1 f ( m , n ) cos ⁡ 2 π ( k m N + l n N ) − i sin ⁡ 2 π ( k m N + l n N ) F(k,l) = \sum\limits_{m=0}^{N-1} \sum\limits_{n=0}^{N-1} f(m,n)\cos{2\pi(\frac{km}{N}+\frac{ln}{N})} -i \sin {2\pi(\frac{km}{N}+\frac{ln}{N})} F(k,l)=m=0N1n=0N1f(m,n)cos2π(Nkm+Nln)isin2π(Nkm+Nln)

  • 由于图片数据为二维数组, f ( m , n ) f(m,n) f(m,n)代表m行n列的像素值
  • k k k l l l分别代表应用在 k k k行和 l l l列上的频率
  • F(k, l)表示频率分别为 k k k l l l时求得的傅里叶变换结果
  • i i i仍然为 − 1 \sqrt{-1} 1

也可以说 f f f是时域上的像素值,而 F F F是频域上的像素值。傅里叶变换的结果是一个复数。在图片处理算法中,为了方便查看变换结果,一般需要将复数转换成振幅图片(magnitude image)。它虽然只能展示每个像素值的信息,不能展示频率或相位的信息,但这些也不是我们想在图片中看到的。所以,这里还是用振幅图片来展示傅里叶变换。

电子图片是离散信号,其中的像素值都是有特定值域的。比如说一张灰度图片中所有的像素值都在0到255之间。所以对图片进行的傅里叶转换用到的是离散傅里叶变换(DFT)。

将一张灰度图片进行灰度转换需要以下步骤:

  1. 扩展图片
  2. 创建储存实部和虚部的矩阵
  3. 进行离散傅里叶变换
  4. 将复数转换为振幅
  5. 对数转换
  6. 裁剪和重排
  7. 归一化

代码实现

扩展图片

图片的尺寸会影响DFT的运算。当图片尺寸是2、3或5的倍数的时候,DFT的运算速度最快。所以对原始图片进行适当的扩展,将会提高运算速度。getOptimalDFTSize()函数能根据输入的图片尺寸计算最佳尺寸,copyMakeBorder()函数则可以扩展图片(新增的像素全设为0)。

Mat padded;
int m = getOptimalDFTSize(I.rows);
int n = getOptimalDFTSize(I.cols);
copyMakeBorder(I, padded, 0, m-I.rows, o, n-I.cols, BORDER_CONSTANT, Scalaar::all(0));

copyMakeBorder()函数的API如下:

void cv::copyMakeBorder(InputArray src,
						OutputArray dst,
						int top,
						int bottom,
						int left,
						int right,
						int borderType,
						const Scalar& value = Scalar())
  • src 原始图片
  • dst 输出图片,其尺寸为(src.cols+left+right, src.rows+top+bottom) .
  • top 顶部扩展的像素个数
  • bottom 底部扩展的像素个数
  • left 左边……
  • right 右边……
  • borderType 边框类型;扩展的像素像边框一样包围着原始图片
  • value 边框类型为BORDER_CONSTANT时,边框内的像素的值

创建储存实部和虚部值的矩阵

傅里叶变换的结果是复数,所以其中每个像素值都有两部分——实部和虚部。而且,频域的范围要比时域大很多,所以,其数据类型至少得是浮点型。以下代码,将单通道的浮点数矩阵扩展成双通道的矩阵,用来同时储存结果的实部和虚部。

Mat planes[]{ Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };
Mat complexI;
merge(planes, 2, complexI);

merge函数将数组中的矩阵合并为一个多通道的矩阵,其API如下:

void cv::merge(	const Mat * mv,
				size_t count,
				OutputArray dst)
  • mv 矩阵数组;数组中的所有矩阵必须有相同的尺寸。
  • count 数组中矩阵的数量,必须大于0。
  • dst 输出矩阵与矩阵数组中的第一个矩阵的尺寸相同;通道数量与count相同。

傅里叶变换的复数结果矩阵示意图如下:
傅里叶变换复数结果矩阵

进行离散傅里叶变换

OpenCV中已经有现成的DFT函数:

dft(complexI, complexI);

第1个参数为输入矩阵,第2个参数为输出矩阵。这里用同一个矩阵对象来储存计算结果。

将复数转换成振幅

对于复数 z = x + i y z=x+iy z=x+iy,它的模,即 ∣ z ∣ = x 2 + y 2 |z|=\sqrt{x^2+y^2} z=x2+y2 。因此在图片数据中,将DFT算出的复数结果转换成振幅需要以下计算:
M = R e ( D F T ( I ) ) 2 + I M ( D F T ( I ) ) 2 M=\sqrt{Re(DFT(I))^2+IM(DFT(I))^2} M=Re(DFT(I))2+IM(DFT(I))2

  • R E RE RE为复数的实部
  • I M IM IM为复数的虚部
  • D F T ( I ) DFT(I) DFT(I)为矩阵I的DFT结果
  • M M M为振幅结果

在OpenCV中可用以下代码实现:

split(complexI, planes);	//planes[0] = Re(DFT(I)), planes[1] = Img(DFT(I))
magnitude(planes[0], planes[1], planes[0]);
Mat magI = planes[0];

其中,split函数将多通道矩阵变成几个单通道矩阵。如上面的代码中,complexI为需要进行分裂的矩阵,planes为接收分裂结果的矩阵数组中,有2个矩阵,一个储存了DFT结果的实部,一个储存了DFT结果的虚部。

magnitude函数计算振幅结果,planes[0]为实部矩阵,planes[1]为虚部矩阵,后面一个planes[0]是用来储存计算结果的矩阵。

对数转换

由于傅里叶系数的动态值域太宽,无法在屏幕上显示,太大的值会变成白点,太小的值会变成黑点。所以要对计算结果进行对数转换,以缩小其值域:
M 1 = ln ⁡ ( 1 + M ) M_1=\ln(1+M) M1=ln(1+M)

magI += Scalar::all(1);
log(magI, magI);

log函数对第一个参数进行取自然对数的运算,然后将结果储存在第二个参数中。

裁剪和重排

因为一开始我们将原始图片进行了扩展,所以现在要进行相应的裁剪。
而且为了更好地呈现结果,还需要将结果分成4个大小相等的矩形区域,并进行重排,好让原来的原点能够在中心。

//通过按位与运算来确定裁剪后的列数和行数
magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));

//将傅里叶图片的4个1/4进行重新排列,以使原点处于图片中心
int cx = magI.cols/2;
int cy = magI.rows/2;

Mat q0(magI, Rect(0, 0, cx, cy));   // 左上角1/4
Mat q1(magI, Rect(cx, 0, cx, cy));  // 右上角1/4
Mat q2(magI, Rect(0, cy, cx, cy));  // 左下角1/4
Mat q3(magI, Rect(cx, cy, cx, cy)); // 右下角1/4

Mat tmp;                           // 左上角和右下角交换
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);

q1.copyTo(tmp);                    // 右上角和左下角交换
q2.copyTo(q1);
tmp.copyTo(q2);

归一化

傅里叶图片中的数值仍然是超出显示范围的浮点数,所以最后还需要对其进行归一化,使其变成0-1之间的浮点数

normalize(magI, magI, 0, 1, NORM_MINMAX);

这里使用normalize()函数进行归一化操作,其中第一个参数为输入图片,第二个参数为输出图片,第三、四个参数分别为归一化的下限和上限,最后一个参数为归一化类型。

离散傅里叶变换在图像处理中的应用

离散傅里叶变换可以用来呈现图片中的几何方向。例如,检测图片中的文字或其他对象是否是水平的。

  • 当文字是水平的时候,傅里叶变换的结果如下:
    水平文本的傅里叶变换结果
  • 当文字有一定的倾斜时,傅里叶变换的结果如下:
    倾斜文本的傅里叶变换结果
    频域中的主要成分(亮点部分)与图片中的文本对象的倾斜方向是一致的。这样就可以计算倾斜的角度,从而进行相应的对齐操作。

再比如,下面的原图中,山和岸都有点向右倾斜,傅里叶变换后得到的频域图中的主要成分也像右倾斜:

  • 原图:
    原图
  • 傅里叶变换频域图:
    傅里叶变换频域图

参考

  1. Discrete Fourier Transform, The Core Functionality (core module), OpenCV Tutorials
  2. 《深度实践OCR:基于深度学习的文字识别》3.1.1.2 傅里叶特征算子

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2084228.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

模型 麦肯锡七步成诗法

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。问题到解决方案的七步跨越。 1 麦肯锡七步成诗法的应用 1.1零售业客户体验转型实践 随着消费者对购物体验的要求日益提高&#xff0c;一家零售企业面临客户流失和销售增长放缓的问题。企业管理层决定…

分类预测|基于哈里斯鹰优化混合核极限学习机的数据分类预测Matlab程序HHO-HKELM多特征输入多类别输出含基础程序

分类预测|基于哈里斯鹰优化混合核极限学习机的数据分类预测Matlab程序HHO-HKELM多特征输入多类别输出含基础程序 文章目录 前言分类预测|基于哈里斯鹰优化混合核极限学习机的数据分类预测Matlab程序HHO-HKELM多特征输入多类别输出含基础程序 一、HHO-HKELM模型HHO-HKELM 分类预…

告别PDF格式困扰,2024年PDF转换器推荐

PDF现在已经逐渐成为了文件传输的主流格式了&#xff0c;它有保存文件页面版式的优点&#xff0c;但是这个格式编辑对大部分人来说还是不那么方便&#xff0c;日常我们还是习惯将它们转换成我们常见的 文本格式来操作。今天我分享一下可以实现PDF格式转换的pdf转换器有哪些吧。…

CSS3实现购物车动画效果

概述 小程序商城或者 web 端网站时,我们可以只通过 CSS 的 animation 和transform,而不需要借助额外的第三方库轻松实现简单的动画效果,丰富页面的表达效果 效果 如下图所示,点击按钮就会有个商品进入左下角的购物车内 购物车动画示例地址 代码示例 元素 开始只需要写…

Uniapp 调用aar、jar包

废话 坑是真的多&#xff0c;官方文档简陋到可以忽略不计。 大概流程 1. 新建一个Android模块&#xff0c;需要用这个模块打包成aar 2. 用这个模块引用uniapp-v8-release.aar以及你需要用到的aar、jar&#xff0c;用不到则忽略这步 坑一&#xff1a;不要直接放到这个模块的…

window11彻底关闭Microsoft Defender

Microsoft Defender Antivirus 是 Microsoft Windows 11 操作系统的默认防病毒解决方案。默认情况下它处于打开的状态。大多数第三方的杀毒软件都可以识别&#xff0c;并代替它。 但是大多数情况下&#xff0c;我们总是有各种理由需要关闭它&#xff0c;例如 Windows Defender …

轻量级冠军:NVIDIA 发布具有领先准确率的小语言模型

Mistral-NeMo-Minitron 8B 是最近发布的 Mistral NeMo 12B 模型的微型版本&#xff0c;具有高精度和高计算效率&#xff0c;可在 GPU 加速数据中心、云和工作站上运行模型。 生成式 AI 开发者通常需要在模型尺寸和准确性之间做出权衡。然而&#xff0c;NVIDIA 发布的一款新语言…

内存管理篇-14kmalloc机制实现分析

引入这个kmalloc的目的&#xff0c;是因为前面的slab接口太过于复杂&#xff0c;因此需要一个全新的封装kmalloc接口&#xff0c;内存申请编程接口实现。kmalloc底层起始也是基于slab缓存实现的。 1.kmalloc 调用流程 参数解析: 解析 gfp_mask 参数&#xff0c;确定分配时是否…

数据结构与算法学习day18-层序遍历

层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。需要借用一个辅助数据结构即队列来实现&#xff0c;队列先进先出&#xff0c;符合一层一层遍历的逻辑&#xff0c;而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。…

硬盘崩溃数据无踪?Windows数据恢复TOP4揭秘,2024年助你找回宝贵资料

现在我们的生活、工作、学习都离不开电脑&#xff0c;电脑里的硬盘就像个装满宝贝的箱子&#xff0c;里面全是我们宝贵的照片、文档、视频和美好回忆。但要是硬盘突然坏了&#xff0c;东西一下子全没了&#xff0c;那感觉真是太糟糕了。别担心&#xff0c;今天我们就给你介绍几…

2024年中国运筹学会运筹竞赛(数据驱动赛道)报名通知

竞赛组织 主办单位&#xff1a;中国运筹学会&#xff08;国家一级学会&#xff09; 承办单位&#xff1a;中国科学技术大学 支持单位&#xff1a;杉数科技、海康威视、中国科学技术大学管理学院、《运筹学学报》杂志 竞赛内容 本次竞赛&#xff08;本科生组&#xff09;由竞…

不平衡数据集的单类分类算法

不平衡数据集的单类分类算法 异常值或异常是与其他数据不符的罕见例子。 识别数据中的离群值称为离群值或异常检测&#xff0c;机器学习中专注于此问题的子领域称为单类分类。这些是无监督学习算法&#xff0c;旨在对“正常”示例进行建模&#xff0c;以便将新示例分类为正常…

记录一次给iOS 工程添加.gitignore文件

新建了一个iOS工程&#xff0c;修改过代码之后&#xff0c;提交发现有一些自己不想要提交的内容 如下图&#xff0c;里面有.DS_Store文件&#xff0c;还有xcsuserstate文件&#xff0c; 这个时候需要添加忽略文件 首先在工程文件夹中执行 touch .gitignore 创建忽略文件&#…

Unity2D游戏开发-Pak木鱼

在接下来文章里我会以Unity为主一起制作游戏 在unity 里如何制作一个简单的敲木鱼游戏&#xff1f; 创建一个2D场景&#xff08;本人使用Unity2023&#xff09; (每个一段时间要申请一个个人许可证) 点击下方蓝色按钮创建 将以下素材拖动到Assets文件夹中 这张图随意命名我…

Swift concurrency 4 — Task和.task的理解与使用

Task Swift中的Task是一种异步操作&#xff0c;它提供了一种替代DispatchQueue.main.async{}等传统方法的方法。通过使用Task&#xff0c;我们可以简化代码并更好地控制异步操作。此外&#xff0c;Task还提供了其他选项&#xff0c;可以进一步增强任务执行。 先看一个Task的基…

net core中byte数组如何高效转换为16进制字符串

在 .NET Core 中&#xff0c;如何把 byte[] 转换为 16 进制字符串&#xff1f;你能想到哪些方法&#xff1f;什么方式性能最好&#xff1f;今天和大家分享几种转换方式。 往往在处理字符串性能问题时&#xff0c;首先应该想到的是怎么想办法减少内存分配&#xff0c;怎么优化字…

22.优化器

优化器 当使用损失函数时&#xff0c;可以调用损失函数的 backward&#xff0c;得到反向传播&#xff0c;反向传播可以求出每个需要调节的参数对应的梯度&#xff0c;有了梯度就可以利用优化器&#xff0c;优化器根据梯度对参数进行调整&#xff0c;以达到整体误差降低的目的。…

Cryptomator:开源云存储加密

采用最新技术标准&#xff0c;提供最佳保护 如果有人查看您云中的文件夹&#xff0c;他们无法对您的数据得出任何结论。 Cryptomator 提供开源的客户端云文件加密。 它适用于 Windows、Linux、macOS 和 iOS。 Cryptomator 可与 Dropbox、Google Drive、OneDrive、MEGA、pClo…

【QT | 开发环境搭建】Linux系统(Ubuntu 18.04) 安装 QT 5.12.12 开发环境

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a; 2024-08-29 …

C# 委托详解(Delegate)

引言 在 C# 编程当中&#xff0c;委托&#xff08;Delegate&#xff09;是一种特殊的类型&#xff0c;它允许将方法作为参数传递给其他方法&#xff0c;或者将方法作为返回值返回&#xff0c;这种特性使得委托成为实现回调函数、事件处理等&#xff0c;所有的委托都派生自Syst…