1.目的
(1)什么是傅立叶变换
(2)使用openCV进行傅立叶变换
(3)相关函数的使用,如: 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)。 在实际的图像处理过程中,仅仅使用了幅度图像,因为幅度图像包含了原图像的几乎所有我们需要的几何信息。 然而,如果你想通过修改幅度图像或者相位图像的方法来间接修改原空间图像,你需要使用逆傅立叶变换得到修改后的空间图像,这样你就必须同时保留幅度图像和 相位图像了。
在此示例中,我将展示如何计算以及显示傅立叶变换后的幅度图像。由于数字图像的离散性,像素值的取值范围也是有限的。比如在一张灰度图像中,像素灰度值一般在0到255之间。 因此,我们这里讨论的
也仅仅是离散傅立叶变换(DFT)。
3.部分代码解释
(1)将图像拓展到合适的尺寸
离散傅立叶变换的运行速度与图片的尺寸息息相关。当图像的尺寸是2, 3,5的整数倍时,计算速度最快。 因此,为了达到快速计算的目的,经常通过添凑新的边缘像素的方法获取最佳图像尺寸。函数 getOptimalDFTSize() 返回最佳尺寸,而函数 copyMakeBorder() 填充边缘像素:
//获取合适尺寸
int row = getOptimalDFTSize(I.rows);
int col = getOptimalDFTSize(I.cols);
Mat padd;
//对拓展的矩阵进行0填充
copyMakeBorder(I, padd, 0, row-I.rows, 0, col-I.cols, BORDER_CONSTANT, Scalar::all(0));
(2)为傅立叶变换的结果分配空间(实部和虚部)
傅立叶变换的结果是复数,这就是说对于每个原图像值,结果是两个图像值。 此外,频域值范围远远超过空间值范围, 因此至少要将频域储存在 float 格式中。 结果我们将输入图像转换成浮点类型,并多加一个额外通道来储存复数部分:
//增加存储空间,存储实部和虚部
Mat plane[] = {Mat_<float>(padd),Mat::zeros(padd.size(), CV_32F)};
Mat complexI;
merge(plane,2, complexI);
(3)进行离散傅立叶变换
//进行离散傅立叶变换,傅立叶变换结果是一个复数形式
dft(complexI, complexI);
(4)计算幅度值
复数包含实数部分(Re)和复数部分 (imaginary - Im)。 离散傅立叶变换的结果是复数,对应的幅度可以表示为:
//将两通道的复数图像分割成多个单通道图像
split(complexI, plane);
//计算机幅度值
magnitude(plane[0], plane[1], plane[0]);
(5)对尺度进行缩放
傅立叶变换的幅度值范围大到不适合在屏幕上显示。高值在屏幕上显示为白点,而低值为黑点,高低值的变化无法有效分辨。为了在屏幕上凸显出高低变化的连续性,我们可以用对数尺度来替换线性尺度:
Mat magI = plane[0];
magI = magI + Scalar::all(1);
//对幅度值+1并进行log对数变换,因为幅度值可能远远超过峰值
log(magI, magI);
(6)剪切和重新分布幅度图像
为了方便显示,我们也可以重新分布幅度图象限位置(注:将第五步得到的幅度图从中间划开得到四张1/4子图像,将每张子图像看成幅度图的一个象限,重新分布即将四个角点重叠到图片中心。
//将magI变换成偶数尺寸
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;
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
//二三互换
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q1);
(7)归一化
这一步的目的仍然是为了显示。 现在我们有了重分布后的幅度图,但是幅度值仍然超过可显示范围[0,1] 。我们使用 normalize() 函数将幅度归一化到可显示范围。
normalize(magI, magI, 0,1,CV_MINMAX);
4.完整代码
CommonInclude.h
#ifndef COMMON_INCLUDE
#define COMMON_INCLUDE
#include<iostream>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
#endif
DFT.cpp
#include "CommonInclude.h"
int main(int argc, char** argv){
if(argc < 2){
cout << "more parameters are required!!!" << endl;
return(-1);
}
Mat I = imread(argv[1]);
Mat gray;
cvtColor(I, gray, CV_BGR2GRAY);
if(!I.data){
cout << "error to read image!!!" << endl;
return(-1);
}
I = gray;
//获取适合傅立叶变换的矩阵尺度大小
int row = getOptimalDFTSize(I.rows);
int col = getOptimalDFTSize(I.cols);
cout << I.rows << " " << I.cols << endl;
cout << row << " " << col << endl;
Mat padd;
//对拓展的矩阵进行0填充
copyMakeBorder(I, padd, 0, row-I.rows, 0, col-I.cols, BORDER_CONSTANT, Scalar::all(0));
//增加面板
Mat plane[] = {Mat_<float>(padd),Mat::zeros(padd.size(), CV_32F)};
Mat complexI;
merge(plane,2, complexI);
//进行离散傅立叶变换,傅立叶变换结果是一个复数形式
dft(complexI, complexI);
//将两通道的复数图像分割成多个单通道图像
split(complexI, plane);
//计算机幅度值
magnitude(plane[0], plane[1], plane[0]);
Mat magI = plane[0];
magI = magI + Scalar::all(1);
//对幅度值+1并进行log对数变换,因为幅度值可能远远超过峰值
log(magI, magI);
//变换幅度矩阵:copyMakeBorder的逆变换
//将矩阵尺寸修改为偶数
//其中-2的二进制表示为:11111110
magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));
cout << magI.cols << " " << magI.rows << endl;
int cx = magI.cols/2;
int cy = magI.rows/2;
cout << "(cx,cy)" << cx << " " << cy << endl;
//第一象限
Mat q0(magI, Rect(0, 0, cx, cy)); // Top-Left - Create a ROI per quadrant
//第二象限
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;
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
//二三互换
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q1);
//对结果进行归一化
normalize(magI, magI, 0,1,CV_MINMAX);
imshow("original", I);
imshow("DFT", magI);
waitKey(0);
return(0);
}