opencv学习(十五)之图像傅里叶变换dft

opencv学习(⼗五)之图像傅⾥叶变换dft
在学习信号与系统或通信原理等课程⾥⾯可能对傅⾥叶变换有了⼀定的了解。我们知道傅⾥叶变换是把⼀个信号从时域变换到其对应的频域进⾏分析。如果有⼩伙伴还对傅⾥叶变换处于很迷糊的状态,请戳,⾮常通俗易懂。⽽在图像处理中也有傅⾥叶分析的概念,我这⾥给出在其官⽅指导⽂件opencv_tutorials中给出的解释。
傅⾥叶变换可以将⼀幅图⽚分解为正弦和余弦两个分量,换⽽⾔之,他可以将⼀幅图像从其空间域(spatial domain)转换为频域(frequency domain)。这种变换的思想是任何函数可以很精确的接近⽆穷个sin()函数和cos()函数的和。傅⾥叶变换提供了这种⽅法来达到这种效果。对于⼆位图像其傅⾥叶变换公式如下:
式中f(i, j)是图像空间域的值⽽F是频域的值。傅⾥叶转换的结果是复数,这也显⽰出了傅⾥叶变换是⼀副实数图像(real image)和虚数图像(complex image)叠加或者是幅度图像(magitude image)和相位图像(phase image)叠加的结果。在实际的图像处理算法中仅有幅度图像(magnitude image)图像能
够⽤到,因为幅度图像包含了我们所需要的所有图像⼏何结构的信息。但是,如果想通过修改幅度图像或者相位图像来间接修改原空间图像,需要保留幅度图像和相位图像来进⾏傅⾥叶逆变换,从⽽得到修改后图像。
1.dft()
⾸先看⼀下opencv提供的傅⾥叶变换函数dft(),其定义如下:
C++: void dft(InputArray src, OutputArray dst, int flags=0, int nonzeroRows=0);
参数解释:
. InputArray src: 输⼊图像,可以是实数或虚数
. OutputArray dst: 输出图像,其⼤⼩和类型取决于第三个参数flags
. int flags = 0: 转换的标识符,有默认值0.其可取的值如下所⽰:
。DFT_INVERSE: ⽤⼀维或⼆维逆变换取代默认的正向变换
。DFT_SCALE: 缩放⽐例标识符,根据数据元素个数平均求出其缩放结果,如有N个元素,则输出结
果以1/N缩放输出,常与
DFT_INVERSE搭配使⽤。
。DFT_ROWS: 对输⼊矩阵的每⾏进⾏正向或反向的傅⾥叶变换;此标识符可在处理多种适量的的时候⽤于减⼩资源的开销,这些处理常常是三维或⾼维变换等复杂操作。
。DFT_COMPLEX_OUTPUT: 对⼀维或⼆维的实数数组进⾏正向变换,这样的结果虽然是复数阵列,但拥有复数的共轭对称性(CCS),可以以⼀个和原数组尺⼨⼤⼩相同的实数数组进⾏填充,这是最快的选择也是函数默认的⽅法。你可能想要得到⼀个全尺⼨的复数数组(像简单光谱分析等等),通过设置标志位可以使函数⽣成⼀个全尺⼨的复数输出数组。
。DFT_REAL_OUTPUT: 对⼀维⼆维复数数组进⾏逆向变换,这样的结果通常是⼀个尺⼨相同的复数矩阵,但是如果输⼊矩阵有复数的共轭对称性(⽐如是⼀个带有DFT_COMPLEX_OUTPUT标识符的正变换结果),便会输出实数矩阵。
. int nonzeroRows = 0: 当这个参数不为0,函数会假设只有输⼊数组(没有设置DFT_INVERSE)的第⼀⾏或第⼀个输出数组(设置了DFT_INVERSE)包含⾮零值。这样的话函数就可以对其他的⾏进⾏更⾼效的处理节省⼀些时间,这项技术尤其是在采⽤DFT计算矩阵卷积时⾮常有效。
2. getOptimalDFTSize()
返回给定向量尺⼨经过DFT变换后结果的最优尺⼨⼤⼩。其函数定义如下:
C++: int getOptimalDFTSize(int vecsize);
参数解释:
int vecsize: 输⼊向量尺⼨⼤⼩(vector size)
DFT变换在⼀个向量尺⼨上不是⼀个单调函数,当计算两个数组卷积或对⼀个数组进⾏光学分析,它常常会⽤0扩充⼀些数组来得到稍微⼤点的数组以达到⽐原来数组计算更快的⽬的。⼀个尺⼨是2阶指数(2,4,8,16,32…)的数组计算速度最快,⼀个数组尺⼨是2、3、5的倍数(例如:300 = 5*5*3*2*2)同样有很⾼的处理效率。
getOptimalDFTSize()函数返回⼤于或等于vecsize的最⼩数值N,这样尺⼨为N的向量进⾏DFT变换能得到更⾼的处理效率。在当前N通过p,q,r等⼀些整数得出N = 2^p*3^q*5^r.
这个函数不能直接⽤于DCT(离散余弦变换)最优尺⼨的估计,可以通过getOptimalDFTSize((vecsize+1)/2)*2得到。
3.magnitude()
计算⼆维⽮量的幅值,其定义如下:
C++: void magnitude(InputArray x, InputArray y, OutputArray magnitude);
参数解释:
dst指数
. InputArray x: 浮点型数组的x坐标⽮量,也就是实部
. InputArray y: 浮点型数组的y坐标⽮量,必须和x尺⼨相同
. OutputArray magnitude: 与x类型和尺⼨相同的输出数组
其计算公式如下:
4. copyMakeBorder()
扩充图像边界,其函数定义如下:
C++: void copyMakeBorder(InputArray src, OutputArray dst, int top, int bottom, int left, int right, int borderType, const Scalar& value=Scalar() );
参数解释:
. InputArray src: 输⼊图像
. OutputArray dst: 输出图像,与src图像有相同的类型,其尺⼨应为ls+left+right, ws+top+bottom)
. int类型的top、bottom、left、right: 在图像的四个⽅向上扩充像素的值
. int borderType: 边界类型,由来定义,常见的取值为BORDER_CONSTANT
. const Scalar& value = Scalar(): 如果边界类型为BORDER_CONSTANT则表⽰为边界值
5. normalize()
归⼀化就是把要处理的数据经过某种算法的处理限制在所需要的范围内。⾸先归⼀化是为了后⾯数据处理的⽅便,其次归⼀化能够保证程序
运⾏时收敛加快。归⼀化的具体作⽤是归纳同意样本的统计分布性,归⼀化在0-1之间是统计的概率分布,归⼀化在某个区间上是统计的坐
标分布,在机器学习算法的数据预处理阶段,归⼀化也是⾮常重要的步骤。其定义如下:
C++: void normalize(InputArray src, OutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray() )
参数解释:
. InputArray src: 输⼊图像
. OutputArray dst: 输出图像,尺⼨⼤⼩和src相同
. double alpha = 1: range normalization模式的最⼩值
. double beta = 0: range normalization模式的最⼤值,不⽤于norm normalization(范数归⼀化)模式
. int norm_type = NORM_L2: 归⼀化的类型,主要有
。NORM_INF: 归⼀化数组的C-范数(绝对值的最⼤值)
。NORM_L1: 归⼀化数组的L1-范数(绝对值的和)
。NORM_L2: 归⼀化数组的L2-范数(欧⼏⾥得)
。NORM_MINMAX: 数组的数值被平移或缩放到⼀个指定的范围,线性归⼀化,⼀般较常⽤。
. int dtype = -1: 当该参数为负数时,输出数组的类型与输⼊数组的类型相同,否则输出数组与输⼊数组只是通道数相同,⽽depth =
CV_MAT_DEPTH(dtype)
. InputArray mask = noArray(): 操作掩膜版,⽤于指⽰函数是否仅仅对指定的元素进⾏操作。
⽰例程序:
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;
int main()
int main()
{
Mat I = imread("lena.jpg", IMREAD_GRAYSCALE);      //读⼊图像灰度图
//判断图像是否加载成功
if (I.empty())
{
cout << "图像加载失败!" << endl;
return -1;
}
else
cout << "图像加载成功!" << endl << endl;
Mat padded;                //以0填充输⼊图像矩阵
int m = ws);
int n = ls);
//填充输⼊图像I,输⼊矩阵为padded,上⽅和左⽅不做填充处理
copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
Mat complexI;
merge(planes, 2, complexI);    //将planes融合合并成⼀个多通道数组complexI
dft(complexI, complexI);        //进⾏傅⾥叶变换
//计算幅值,转换到对数尺度(logarithmic scale)
//=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
split(complexI, planes);        //planes[0] = Re(DFT(I),planes[1] = Im(DFT(I))
//即planes[0]为实部,planes[1]为虚部
magnitude(planes[0], planes[1], planes[0]);    //planes[0] = magnitude
Mat magI = planes[0];
magI += Scalar::all(1);
log(magI, magI);                //转换到对数尺度(logarithmic scale)
//如果有奇数⾏或列,则对频谱进⾏裁剪
magI = magI(Rect(0, 0, ls&-2, ws&-2));
/
/重新排列傅⾥叶图像中的象限,使得原点位于图像中⼼
int cx = ls / 2;
int cy = ws / 2;
Mat q0(magI, Rect(0, 0, cx, cy));      //左上⾓图像划定ROI区域
Mat q1(magI, Rect(cx, 0, cx, cy));      //右上⾓图像
Mat q2(magI, Rect(0, cy, cx, cy));      //左下⾓图像
Mat q3(magI, Rect(cx, cy, cx, cy));    //右下⾓图像
//变换左上⾓和右下⾓象限
Mat tmp;
//变换右上⾓和左下⾓象限
//归⼀化处理,⽤0-1之间的浮点数将矩阵变换为可视的图像格式
normalize(magI, magI, 0, 1, CV_MINMAX);
imshow("输⼊图像", I);
imshow("频谱图", magI);
waitKey(0);
return0;
}
程序分析:
1.图像填充:
Mat padded;
int m = ws);
int n = ls);
copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
根据前⾯的理论介绍可以知道当图像尺⼨为2、3、5的倍数时可以得到最快的处理速度,所以通过getOptimalDFTSize()函数获取最佳DFT 变换尺⼨,之后再结合copyMakeBorder()函数对图像进⾏扩充。
2.为实部和虚部分配存储空间
Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
Mat complexI;
merge(planes, 2, complexI);
傅⾥叶变换的结果是复数,这就意味着经过傅⾥叶变换每个图像值都会变成两个值,此外其频域(frequency domains)范围⽐空间域(spatial counterpart)范围⼤很多。我们通常以浮点型数据格式对结果进⾏存储。因此我们将输⼊图像转换为这种类型,通过另外的通道扩充图像。
3.傅⾥叶变换
dft(complexI, complexI);
傅⾥叶变换函数,对图像进⾏傅⾥叶变换。
4.将实数和复数的值转换为幅度值
split(complexI, planes);
magnitude(planes[0], planes[1], planes[0]);
Mat magI = planes[0];
复数包含实部和虚部两个部分,傅⾥叶变换的结果是⼀个复数,傅⾥叶变换的幅度计算公式是:
5.转换为对数尺度(Switch to a logarithmic scale)
magI += Scalar::all(1);
log(magI, magI);
之所以要进⾏对数转换是因为傅⾥叶变换后的结果对于在显⽰器显⽰来讲范围⽐较⼤,这样的话对于⼀些⼩的变化或者是⾼的变换值不能进⾏观察。因此⾼的变化值将会转变成⽩点,⽽较⼩的变化值则会变成⿊点。为了能够获得可视化的效果,可以利⽤灰度值将我们的线性尺度(linear scale)转变为对数尺度(logarithmic scale),其计算公式如下:
6.剪切和象限变换
在进⾏傅⾥叶变换时,为了取得更快的计算效果,对图像进⾏了扩充,现在就需要对新增加的⾏列进⾏裁剪了。为了可视化的需要,我们同
样需要对显⽰的结果图像像素进⾏调整,如果不进⾏调整,最后显⽰的结果是这样的:
可以看到四周的⾓上时⾼频分量,现在我们通过重新调整象限将⾼频分量调整到图像正中间。
7.归⼀化
对结果进⾏归⼀化处理同样是处于可视化的⽬的。现在我们得到了幅度值,但是这仍然超出了0-1的显⽰范围。这就需要利⽤normalize()函数对数据进⾏归⼀化处理。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));
Mat q1(magI, Rect(cx, 0, cx, cy));
Mat q2(magI, Rect(0, cy, cx, cy));
Mat q3(magI, Rect(cx, cy, cx, cy));
//变换左上⾓和右下⾓象限
Mat tmp ;
tmp .copyTo (q3);
//变换右上⾓和左下⾓象限
tmp .copyTo (q2);
normalize(magI, magI, 0, 1, CV_MINMAX);

本文发布于:2024-09-21 20:53:00,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/3/380311.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:图像   变换   数组   函数   复数
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议