在上一篇文章《C++实现一维离散傅里叶变换》中,我们介绍了一维信号傅立叶变换的公式和C++实现,并阐述了频域幅值的意义。
一维傅立叶变换只适用于一维信号,例如音频数据、心脑电图等。
在图像处理中,图像信号具有高度和宽度两个属性,属于二维空间信号。将图像信号从空间域转换到频域时,需使用二维离散傅立叶变换。因此,需要将傅立叶变换从一维推广至二维。
二维连续傅立叶变换公式如下:
经过离散化后,上述公式变为:
,其中u=0,1,2,…,M-1,v=0,1,2,…,N-1。 (式1-1)
相应地,其逆变换为:
对式1-1稍作变换,可得:
其中,刚好是一维傅立叶变换的离散形式。故二维傅立叶变换可理解为先对图像的每一行进行一维傅立叶变换,变换的结果构成一个新的矩阵,再对新矩阵的每一列进行一维傅立叶变换,这样经过两次变换的结果就是二维傅立叶变换的最终结果。
也就是说,二维傅里叶变换相当于先按行变换,再按列变换(也可以先按列变换,再按行变换)。最终生成的频域数据,就是由两个方向的频率强度信息叠加而成。
我们可以用伪代码来实现上述逻辑。假设一维傅里叶变换函数为dft1(vec),二维信号高度为h,宽度为w,那么伪代码可写成如下形式:for (int i=0; i dft1(row[i]); for (int i=0; i 在本文中,我们暂不采用上述伪代码思路。我们直接按式1-1来进行编程实现: 其中,e-i2π(ux/M+vy/N)可转化为三角函数cos(-2π(ux/M+vy/N))+isin(-2π(ux/M+vy/N)),i为虚数单位。转化成三角函数形式,有利于计算机编程实现。只需要采用两个for循环,就可求出单个F(u,v)值,外面再嵌套两个for循环,即可求出所有F(u,v)值。 由于涉及复数运算,需要先定义一个复数类,并实现复数运算。 在上一篇文章《C++实现一维离散傅里叶变换》中,我们已经实现了一个复数类ComplexNumber。直接引用它即可。 (注:编译环境是VS2013,使用MFC对话框模板) 二维离散傅里叶变换的头文件如下: 实现文件如下: Dft2.cpp 在CDft2实现文件中,实现二维傅里叶变换的函数dft2()主要代码如下(基本是按照式1-1来实现): 求模函数generate_spectrum()的实现逻辑:对于复数x = a+bi,其模为m = mod(x) = sqrt(a2+b2),其中sqrt代表根号。 归一化函数normalize_spectrum()的实现逻辑:先遍历矩阵,找到最大值max,然后对于矩阵中的每一个元素matrix[i],执行matrix[i] = 255*(matrix[i]/max),从而归一化到[0,255]。 现在,我们编写并运行一个测试线程,对一个包含三行四列数据的二维信号进行傅里叶变换,以验证上述代码。 在MFC对话框资源中添加一个test按钮,在按钮事件响应函数中添加: ::CreateThread(NULL,0, test, 0, 0, NULL); 可以看到,逆变换的结果和原始信号完全一致。 使用Matlab的fft2()函数对原始信号进行变换,得到的结果也和上述变换结果一致。 因此我们的实现代码是有效的,输出了正确的变换结果。 当原始信号超过512*512时,本文给出的实现代码执行一次变换大约需要几十秒,这令人难以忍受。 后续我们将介绍基于蝶形分解的快速傅里叶变换,其完成一次512*512原始信号变换只需要几十毫秒。
由于二维傅里叶变换后得到的矩阵元素数值很大,并且包含实数和虚数。为便于观察分析,需要将变换后的结果进行求模,然后归一化到[0,255],以便保存为频谱图。归一化后,大部分像素灰度较低,在频谱图上接近黑色,肉眼不容易察觉,因此还需要使用log函数对低灰度区域进行增强。另外,由于傅里叶变换本身具有对称性,最终生成的频谱图的四个角也具有对称性,因此,一般在完成归一化后,还有一个把频谱原点平移到频谱图中心的操作。在类CDft2的声明中,对应的求模、归一化和中心平移函数分别为generate_spectrum()、normalize_spectrum()和shift_spectrum_to_center()。
#pragma once
#include "ComplexNumber.h"
#define MAX_MATRIX_SIZE 4194304 // 2048 * 2048
#define PI 3.141592653
class CDft2
{
public:
CDft2(void);
~CDft2(void);
public:
bool dft2(double IN const matrix[], int IN const width, int IN const height); // 二维离散傅里叶变换
bool idft2(LPVOID OUT *pMatrix, int OUT *width, int OUT *height); // 二维离散傅里叶逆变换
void generate_spectrum(); // 对变换结果求模,生成频谱/幅度谱
void normalize_spectrum(); // 对频谱进行归一化操作
bool has_dft2_matrix(); // 是否已存有变换结果
bool is_shifted_to_center(); // 是否已将频谱原点平移到图像中心
void clear_dft2_matrix(); // 清除已有的变换结果
void print_matrix(); // 打印变换结果
void print_spectrum(); // 打印频谱
void shift_spectrum_to_center(); // 将频谱原点平移到图像中心
public:
CComplexNumber *m_dft2_matrix;
double *m_spectrum_data;
protected:
bool m_has_dft_matrix;
bool m_is_normalized;
bool m_is_spectrum_shifted;
int m_dft_matrix_height;
int m_dft_matrix_width;
};
#include "StdAfx.h"
#include "Dft2.h"
CDft2::CDft2(void)
{
m_dft2_matrix = NULL;
m_spectrum_data = NULL;
m_has_dft_matrix = false;
m_is_normalized = false;
m_is_spectrum_shifted = false;
m_dft_matrix_height = 0;
m_dft_matrix_width = 0;
}
CDft2::~CDft2(void)
{
if (m_has_dft_matrix && (NULL != m_dft2_matrix) && ((m_dft_matrix_height*m_dft_matrix_width)>0))
delete[] m_dft2_matrix;
if (NULL != m_spectrum_data)
delete[] m_spectrum_data;
}
bool CDft2::has_dft2_matrix()
{
return m_has_dft_matrix;
}
bool CDft2::is_shifted_to_center()
{
return m_is_spectrum_shifted;
}
void CDft2::clear_dft2_matrix()
{
if (m_has_dft_matrix && (NULL != m_dft2_matrix) && ((m_dft_matrix_height*m_dft_matrix_width)>0)) {
delete[] m_dft2_matrix;
m_has_dft_matrix = false;
m_dft_matrix_height = 0;
m_dft_matrix_width = 0;
}
}
void CDft2::print_matrix()
{
char msg[2560] = "11111 ";
if ((!m_has_dft_matrix) || (NULL == m_dft2_matrix) || (m_dft_matrix_height <= 0) || (m_dft_matrix_width <= 0))
return;
for (int u = 0; u
逆变换函数idft2()也主要是按照前面给出的逆变换公式定义来实现。
m_dft2_matrix = new CComplexNumber[width*height];
CComplexNumber cplTemp(0, 0);
double fixed_factor_for_axisX = (-2 * PI) / height; // evaluate -i2π/N of -i2πux/N, and store the value for computing efficiency
double fixed_factor_for_axisY = (-2 * PI) / width; // evaluate -i2π/N of -i2πux/N, and store the value for computing efficiency
for (int u = 0; u
DWORD WINAPI test(LPVOID lParam)
{
char msg[256] = "11111 ";
int width = 4;
int height = 3;
double mat[] = { 1, 1, 3, 2, 3, 4, 123, 154, 55, 2, 22, 233 };
// Fourier transform
CDft2 dft2;
dft2.dft2(mat, width, height);
dft2.print_matrix();
dft2.generate_spectrum();
dft2.normalize_spectrum();
dft2.print_spectrum();
// inverse Fourier transform
LPVOID pMat = NULL;
int iHeight = 0;
int iWidth = 0;
dft2.idft2(&pMat, &iWidth, &iHeight);
double *iMat = (double *)pMat;
if (((iWidth*iHeight)>0) && (NULL != iMat)) {
for (int x = 0; x