废话不多说,直接上代码,代码中我都注明了注释,有些讲不清楚的,会在代码的后面专门拿出来讲。
下面这个cpp文件不是我自己写的程序,是opencv提供的关于dft变换的例程,文件一般会包含在你的opencv路径下opencv\sources\samples\cpp下面,可以自行查找。
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
using namespace cv;
using namespace std;
static void help()
{
printf("\nThis program demonstrated the use of the discrete Fourier transform (dft)\n"
"The dft of an image is taken and it's power spectrum is displayed.\n"
"Usage:\n"
"./dft [image_name -- default lena.jpg]\n");
}
const char* keys =
{
"{1| |lena.jpg|input image file}"
};
int main(int argc, const char ** argv)
{
help();
CommandLineParser parser(argc, argv, keys);
string filename = parser.get<string>("1");
Mat img = imread(filename.c_str(), CV_LOAD_IMAGE_GRAYSCALE);
if( img.empty() )
{
help();
printf("Cannot read image file: %s\n", filename.c_str());
return -1;
}
//这里获取符合快速傅里叶变换(FFT)的大小,m和n都可以分解为2、3、5相乘的组合,参见 注1
int M = getOptimalDFTSize( img.rows );
int N = getOptimalDFTSize( img.cols );
Mat padded;
//将原图像的大小变为m*n的大小,补充的位置填0,
copyMakeBorder(img, padded, 0, M - img.rows, 0, N - img.cols, BORDER_CONSTANT, Scalar::all(0));
//这里是获取了两个mat,一个用于存放dft变换的实部,一个用于存放虚部,初始的时候,实部就是图像本身,虚部全为0
Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
Mat complexImg;
//将几个单通道的mat融合成一个多通道的mat,这里融合的complexImg即有实部,又有虚部
merge(planes, 2, complexImg);
//dft变换,因为complexImg本身就是两个通道的mat,所以dft变换的结果也可以保存在其中
dft(complexImg, complexImg);
//将complexImg重新拆分成两个mat,一个是实部,一个是虚部
split(complexImg, planes);
// compute log(1 + sqrt(Re(DFT(img))**2 + Im(DFT(img))**2))
//这一部分是为了计算dft变换后的幅值,以便于显示幅值的计算公式如上
magnitude(planes[0], planes[1], planes[0]);//将两个mat对应位置相乘
Mat mag = planes[0];
mag += Scalar::all(1);
log(mag, mag);
// crop the spectrum, if it has an odd number of rows or columns
//修剪频谱,如果图像的行或者列是奇数的话,那其频谱是不对称的,因此要修剪
//这里为什么要用 &-2这个操作,我会在代码后面的 注2 说明
mag = mag(Rect(0, 0, mag.cols & -2, mag.rows & -2));
Mat _magI = magI.clone();
normalize(_magI, _magI, 0, 1, CV_MINMAX);
imshow("before rearrange ",_magI)
int cx = mag.cols/2;
int cy = mag.rows/2;
// rearrange the quadrants of Fourier image
// so that the origin is at the image center
Mat tmp;
Mat q0(mag, Rect(0, 0, cx, cy));
Mat q1(mag, Rect(cx, 0, cx, cy));
Mat q2(mag, Rect(0, cy, cx, cy));
Mat q3(mag, Rect(cx, cy, cx, cy));
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
normalize(mag, mag, 0, 1, CV_MINMAX);
imshow("spectrum magnitude", mag);
waitKey();
return 0;
}
opencv官方文档是这样说的:
the function supports arrays of arbitrary size. But only those arrays are
prime numbers (2, 3, and 5 in the current implementation). Such an efficient
DFT size can be calculated using the getOptimalDFTSize() method.
就是说dft这个函数虽然对于输入mat的尺寸不做要求,但是如果其行数和列数可以分解为2、3、5的乘积,那么对于dft运算的速度会加快很多。
关于getOptimalDFTSize()这个函数:
DFT performance is not a monotonic function of a vector size. Therefore,
when you calculate convolution of two arrays or perform the spectral
analysis of an array, it usually makes sense to pad the input data with
zeros to get a bit larger array that can be transformed much faster than the
original one. Arrays whose size is a power-of-two (2, 4, 8, 16, 32, ...) are
the fastest to process. Though, the arrays whose size is a product of 2’s,
3’s, and 5’s (for example, 300= 5*5*3*2*2) are also processed quite
efficiently.
The function getOptimalDFTSize returns the minimum number N that is greater
than or equal to vecsize so that the
DFT of a vector of size N can be processed efficiently. In the current
implementation N = 2 p * 3 q * 5 r for some integerp, q, r.
上面这一段话是说,数组的大小是2的整数次幂的情况,dft变换的速度是最快的。当然,大部分情况下,数组的大小不会是2的次幂,但是如果其大小可以分解为2、3、5的累成也是能够提高dft的效率的。
DFT算法的原理对于输入信号的长度不做要求,但是为了可以使用快速傅里叶变换算法(FFT算法)进行加速。所以程序中使用copyMakeBorder填充0使横纵长度变为可以由2、3、5累乘的数,这里行和列的长度不一定是样的,只要都满足条件即可。
我们知道x&-2代表x与-2按位相与,而-2的二进制形式是2的二进制取反加一的结果(这是补码的问题)。2 的二进制结果是(假设用8位表示,实际整型是32位,但是描述方式是一样的,为便于描述,用8位表示)0000 0010,则-2的二进制形式为:1111 1110,在x与-2按位相与后,不管x是奇数还是偶数,最后x都会变成一个偶数。
我相信很多人,包括我自己都会很疑惑,为什么要对傅里叶变换的结果进行坐标转换,才能看到那种中间亮,周围暗的频率图。我也研究了很久,后来发现,这是因为对于正常的傅里叶变换,其变换的结果都是在0~2*pi之间的,如果将其转换为-pi~pi之间,实质上是没有变化的,但是为了方便人观察,才将结果转换到-pi~pi中。实际上如果拿四张一模一样的没有经过坐标变换的图拼在一起,会发现拼接处的图形模样与坐标变换后是一样的。