目标:
我们将寻找下面这些问题的答案:
什么是傅里叶变换以及为什么我们使用它;
在OpenCV中怎么做傅里叶变换;
copyMakeBorder(),merge(),dft(),getOptimalDFTSize(),log()以及normalize()等函数的使用;
源代码:
-
-
-
-
-
-
- #include<cv.h>
- #include<highgui.h>
- #include<imgproc\imgproc_c.h>
- #include <iostream>
-
-
- using namespace cv;
- using namespace std;
-
- static void help(char* progName)
- {
- cout << endl
- << "This program demonstrated the use of the discrete Fourier transform (DFT). " << endl
- << "The dft of an image is taken and it's power spectrum is displayed." << endl
- << "Usage:" << endl
- << progName << " [image_name -- default lena.jpg] " << endl << endl;
- }
-
- int main(int argc, char ** argv)
- {
- help(argv[0]);
-
- const char * filename = "F:/Photo/OpenCV_Photo/lena.jpg";
- Mat I = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
- if (I.empty())
- return -1;
-
-
-
-
- Mat padded;
- int m = getOptimalDFTSize(I.rows);
- int n = getOptimalDFTSize(I.cols);
- 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);
-
- dft(complexI, complexI);
-
-
-
-
-
- split(complexI, planes);
- magnitude(planes[0], planes[1], planes[0]);
-
- Mat magI = planes[0];
-
- magI += Scalar::all(1);
-
- log(magI, magI);
-
-
-
-
-
-
-
-
-
-
-
-
-
- //主要目的是保证裁切的时候方便,能均切成4块
- 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(q2);
-
- normalize(magI, magI, 0, 1, CV_MINMAX);
-
-
-
- imshow("Input Image", I);
- imshow("spectrum magnitude", magI);
- waitKey();
-
- return 0;
- }
解释:
傅里叶变换将把一幅图像分解成它的正弦和余弦部分。换句话说,它将把一幅图像从其空间域转换到其频域。这个思想就是任何函数都可以由无数个正弦和余弦函数来近似。傅里叶变换就是一种做这样的工作的方法。从数学上来讲,一个二维图像的傅里叶变换是:

在这里f是图像在其空间域的值,F是其的频域值。转换的结果是复数。我们可以通过一幅实数的图像以及一幅复数的图像或者通过幅度图以及相图来显示它。然而,纵观图像处理算法,只有幅度图是比较适合的,因为它包含了我们对一幅图像几何形状的所有信息。因此,如果你像要以这些方式对一幅图像做一些修改,你就需要将其再变回到原型,并且你需要把他们都保存起来。
在这个例子中主要展现如何去计算以及显示一个傅里叶变换的幅度图像(傅里叶谱)。从数字的角度看图像是离散的。这就意味着他们可以从一个给定的阈值中获得一个值。例如,在基本的灰度图像中,值通常都在0到255之间。因此傅里叶变换也需要是离散值,这就是离散傅里叶变换(DFT)。在任何时候当你需要从一个几何的角度去确定一幅图像的结构的时候,你将想要使用这种傅里叶变换。下面是你要遵循的步骤(以输入图像是灰度图像为例):
1、 将图像延伸到最优的尺寸。DFT的性能取决于图像的尺寸。当图像的尺寸是数字2,3或5的倍数时,DFT趋近于最快。因此,为了达到这种最优的性能,通过填充图像的边界值来得到这种特征的尺寸通常是一种很好的方法。函数getOptimalDFTSize()返回这种最优的尺寸,并且我们能够使用函数copyMakeBorder()去扩展图像的边界:
- Mat padded;
- int m = getOptimalDFTSize( I.rows );
- int n = getOptimalDFTSize( I.cols );
- copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
附加的像素值都被初始化为0.
2、 为复数和实数创造空间。傅里叶变换的结果是复数。这就意味着,对于每一个图像值,其结果是两幅图像的值(每一个组成部分是一幅图像)。甚至,频域范围比其空间域更大。因此,我们通常至少使用float形式来储存这些值。因此,我们将我们的输入图像转换成这种类型,并且以另外一种通道来扩展它来保存复数值:
- Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
- Mat complexI;
- merge(planes, 2, complexI);
3、 做离散傅里叶变换。使用in-place计算(输入和输出一样):
4、 将实数和复数值转换为量值。一个复数值有实数部分(Re)和复数部分(Im)。DFT的结果是复数值。
转换为opencv代码就是:
- split(complexI, planes);
- magnitude(planes[0], planes[1], planes[0]);
- Mat magI = planes[0];
5、 转换成对数的形式。结果表明傅里叶系数的动态范围对于在显示器上显示来说是非常大的。我们有一些小的以及一些高的值是我们观察不到的。因此高的值都将转变为白点,而小的值都想是黑点。去使用这种灰度值来进行可视化我们可以将我们的线性量转换为对数形式:
转换为OpenCV代码:
- magI += Scalar::all(1);
- log(magI, magI);
6、 截断以及重新调整。还记得我们在第一步中将我们的图像扩展了吗?是的,是时候把这些新产生的值给丢弃了。为了可视化的目的,我们也可以重新调整结果的象限,这样的话原点就对应着图像的中心。
- 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(q2);
7、 规范化。这样做还是为了可视化的目的。我们现在有了量值,然而这样还是在我们的0到的现实范围之外。我们可以使用normalize()函数来将我们的值归一化到这个范围之内。
- normalize(magI, magI, 0, 1, CV_MINMAX);
-
最终的效果图:

