在图像中像素点为一个个离散的点,因此可以利用离散信号x(nT)来计算。又因为对图像的二维快速傅立叶变化相当于对图像的像素矩阵进行横和竖两次一维的快速傅立叶变化,我们这里原理按照一维快速傅立叶变换说明。
首先,有限长离散信号x(n),n=0,1,…,N-1的DFT定义为:
将x(n)分解为偶数与奇数的两个序列之和,即
x1(n)和x2(n)的长度都是N/2,x1(n)是偶数序列,x2(n)是奇数序列,则
由于
则原式等于
其中X1(k)和X2(k)分别为x1(n)和x2(n)的N/2点DFT。由于X1(k)和X2(k)均以N/2为周期,且WN k+N/2=-WN k,所以X(k)又可表示为:
上式的运算称之为蝶形运算,依此类推,经过m-1次分解,最后将N点DFT分解为N/2个两点DFT。下图为8点FFT的分解流程:
从原理中可以看出,我们在对图像进行快速傅立叶变换时需要图像的长和宽都为2的幂次方,所以首先我们需要对图像的长和宽进行拓展,矩阵拓展的部分补0。
为方便的找到长和宽需要拓展的最小2幂值,将寻找最小2次幂值函数写成一个函数的形式,输入为int的值,输出也为int的值。
寻找最小2次幂值具体函数如下:
public int find2n(int num)
{
double b = Math.Log(num, 2);
int c = (int)(Math.Log(num, 2));
if (b - c != 0)
{
num = (int)Math.Pow(2, (int)c + 1);
}
return num;
}
计算出需要拓展的长和宽后,我们需要对原图进行拓展补0。将拓展写成一个函数的形式,输入为byte[]格式的图像、图像的长和宽和需要拓展的长和宽,输出也为byte[]格式的图像。这里将图像写成一维的形式还是二维形式一样,对应关系为:[i, j]=[i * imageWidth + j]。
拓展具体函数如下:
//imageWidth和imageHeight为原图的宽度和长度;Width和Height为需拓展的宽度和长度
public byte[] expand(byte[] image, int imageWidth, int imageHeight, int Width, int Height)
{
byte[,] matrix = new byte[Height, Width];
for (int i = 0; i < imageHeight; i++)
{
for (int j = 0; j < imageWidth; j++)
{
matrix[i, j] = image[i * imageWidth + j];
}
}
for (int i = imageHeight; i < Height; i++)
{
for (int j = imageWidth; j < Width; j++)
{
matrix[i, j] = 0;
}
}
byte[] result = new byte[Width * Height];
for (int i = 0; i < Height; i++)
{
for (int j = 0; j < Width; j++)
{
result[i * Width1 + j] = matrix[i, j];
}
}
return result;
}
又由于在计算中会涉及到频域计算,需要复数的计算,而C#中没有将复数设计为一种内部数据类型,所以先要自己设计并处理复数运算的C#类Complex。
在解决方案资源管理器中右键点击添加新建项,在“名称“内输入类的名称,我们命名为Complex.cs,然后单击“添加“完成Complex类的添加。
具体Complex类定义如下:
class Complex
{
//复数的实部
private double real = 0.0;
//复数的虚部
private double imaginary = 0.0;
//实部的属性
public double Real
{
get
{
return real;
}
set
{
real = value;
}
}
//虚部的属性
public double Imaginary
{
get
{
return imaginary;
}
set
{
imaginary = value;
}
}
//基本构造函数
public Complex()
{
}
//指定值得构造函数
public Complex(double dbreal, double dbimag)
{
real = dbreal;
imaginary = dbimag;
}
//复制构造函数
public Complex(Complex other)
{
real = other.real;
imaginary = other.imaginary;
}
//重载+运算符
public static Complex operator +(Complex comp1, Complex comp2)
{
return comp1.Add(comp2);
}
//重载-运算符
public static Complex operator -(Complex compl, Complex comp2)
{
return compl.Subtract(comp2);
}
//重载*运算符
public static Complex operator *(Complex compl, Complex comp2)
{
return compl.Multiply(comp2);
}
//实现复数加法
public Complex Add(Complex comp)
{
double x = real + comp.real;
double y = imaginary + comp.imaginary;
return new Complex(x, y);
}
//实现复数减法
public Complex Subtract(Complex comp)
{
double x = real - comp.real;
double y = imaginary - comp.imaginary;
return new Complex(x, y);
}
//实现复数乘法
public Complex Multiply(Complex comp)
{
double x = real * comp.real - imaginary * comp.imaginary;
double y = real * comp.imaginary + imaginary * comp.real;
return new Complex(x, y);
}
//求幅度
public double Abs()
{
//取得实部的绝对值
double x = Math.Abs(real);
//取得虚部的绝对值
double y = Math.Abs(imaginary);
//实部为0
if (real == 0)
{
return y;
}
//虚部为0
if (imaginary == 0)
{
return x;
}
//计算模
if (x > y)
{
return (x * Math.Sqrt(1 + (y / x) * (y / x)));
}
else
{
return (y * Math.Sqrt(1 + (y / x) * (y / x)));
}
}
//求相位角
public double Angle()
{
//实数和虚数都为0
if (real == 0 && imaginary == 0)
return 0;
if (real == 0)
{
//实部位0
if (imaginary > 0)
return Math.PI / 2;
else
return -Math.PI / 2;
}
else
{
if (real > 0)
{
//实部大于0
return Math.Atan2(imaginary, real);
}
else
{
//实部小于0
if (imaginary >= 0)
return Math.Atan2(imaginary, real) + Math.PI;
else
return Math.Atan2(imaginary, real) - Math.PI;
}
}
}
//共轭复数
public Complex conjugate()
{
return new Complex(this.real, -this.imaginary);
}
}
我们将图像快速傅立叶变换也写成一个函数的形式,输入为byte[]格式的图像和图像的长和宽,输出为Complex[]格式的频域信息。
图像快速傅立叶变换具体函数如下:
//图像傅里叶变换
private Complex[] FFT(byte[] imageData, int imageWidth, int imageHeight)
{
int bytes = imageWidth * imageHeight;
byte[] bmpValues = new byte[bytes];
Complex[] tempCom1 = new Complex[bytes];
bmpValues = (byte[])imageData.Clone();
//赋值:把实数变为复数,即虚部为0
for (int i = 0; i < bytes; i++)
{
tempCom1[i] = new Complex(bmpValues[i], 0);
}
//水平方向快速傅里叶变换
Complex[] tempCom2 = new Complex[imageWidth];
Complex[] tempCom3 = new Complex[imageWidth];
for (int i = 0; i < imageHeight; i++)
{
for (int j = 0; j < imageWidth; j++)
{
tempCom2[j] = tempCom1[i * imageWidth + j];
}
//调用一维傅里叶变换
tempCom3 = fft(tempCom2, imageWidth);
Complex[] tempCom9 = new Complex[imageWidth];
for (int j = 0; j < imageWidth; j++)
{
tempCom9[j] = tempCom3[j];
}
//将结果赋值回去
for (int j = 0; j < imageWidth; j++)
{
tempCom1[i * imageWidth + j] = tempCom3[j];
}
}
//垂直方向傅里叶变换
Complex[] tempCom4 = new Complex[imageHeight];
Complex[] tempCom5 = new Complex[imageHeight];
for (int i = 0; i < imageWidth; i++)
{
for (int j = 0; j < imageHeight; j++)
{
tempCom4[j] = tempCom1[j * imageWidth + i];
}
//调用一维傅里叶变换
tempCom5 = fft(tempCom4, imageHeight);
//把结果赋值回去
for (int j = 0; j < imageHeight; j++)
{
tempCom1[j * imageWidth + i] = tempCom5[j];
}
}
return tempCom1;
}
在图像傅立叶变换时中调用的一维快速傅立叶变换的函数,其输入为Complex[]格式的频域信息,输出也为Complex[]格式的频域信息。
一维快速傅立叶变换具体函数如下:
//一维傅里叶变换
private Complex[] fft(Complex[] sourceData, int countN)
{
//fft的级数
int r = Convert.ToInt32(Math.Log(countN, 2));
Complex[] w = new Complex[countN / 2];
Complex[] interVar1 = new Complex[countN];
Complex[] interVar2 = new Complex[countN];
interVar1 = (Complex[])sourceData.Clone();
//求加权系数w
for (int i = 0; i < countN / 2; i++)
{
double angle = -i * Math.PI * 2 / countN;
w[i] = new Complex(Math.Cos(angle), Math.Sin(angle));
}
//蝶形运算
for (int i = 0; i < r; i++)
{
int interval = 1 << i;
int halfN = 1 << (r - i);
//对每级的每一组点循环
for (int j = 0; j < interval; j++)
{
int gap = j * halfN;
//对每级的每一点循环
for (uint k = 0; k < halfN / 2; k++)
{
//运行蝶形算法
interVar2[k + gap] = interVar1[k + gap] + interVar1[k + gap + halfN / 2];
interVar2[k + gap + halfN / 2] = (interVar1[k + gap] - interVar1[k + gap + halfN / 2]) * w[k * interval];
}
}
interVar1 = (Complex[])interVar2.Clone();
}
//按位取反
for (uint j = 0; j < countN; j++)
{
uint rev = 0;
uint num = j;
//重新排序
for (uint i = 0; i < r; i++)
{
rev <<= 1;
rev |= num & 1;
num >>= 1;
}
interVar2[rev] = interVar1[j];
}
return interVar2;
}
相同的,我们将图像快速傅立叶反变换也写成一个函数的形式,输入为Complex[]格式的频域信息和图像的长和宽,输出为double[]格式的图像。这里输出double[]而不是byte[]是因为有时候后续图像处理操作有较高的精度要求。
图像快速傅立叶反变换具体函数如下:
//傅里叶反变换
private double[] IFFT(Complex[] freData, int imageWidth, int imageHeight)
{
int bytes = imageWidth * imageHeight;
byte[] bmpValues = new byte[bytes];
Complex[] tempCom1 = new Complex[bytes];
tempCom1 = (Complex[])freData.Clone();
//水平方向傅里叶逆变化
Complex[] tempCom2 = new Complex[imageWidth];
Complex[] tempCom3 = new Complex[imageWidth];
for (int i = 0; i < imageHeight; i++)
{
//得到水平方向复数序列
{
for (int j = 0; j < imageWidth; j++)
{
tempCom2[j] = tempCom1[i * imageWidth + j];
}
//调用一维傅里叶变换
tempCom3 = ifft(tempCom2, imageWidth);
//把结果赋值回去
for (int j = 0; j < imageWidth; j++)
{
tempCom1[i * imageWidth + j] = tempCom3[j];
}
}
}
//垂直方向傅里叶逆变换
Complex[] tempCom4 = new Complex[imageHeight];
Complex[] tempCom5 = new Complex[imageHeight];
for (int i = 0; i < imageWidth; i++)
{
//得到垂直方向复数序列
for (int j = 0; j < imageHeight; j++)
{
tempCom4[j] = tempCom1[j * imageWidth + i];
}
//调用一维傅里叶变换
tempCom5 = ifft(tempCom4, imageHeight);
//把结果赋值回去
for (int j = 0; j < imageHeight; j++)
{
tempCom1[j * imageWidth + i] = tempCom5[j];
}
}
//赋值:把复数转换为实数,只保留复数的实数部分
double[] tempDouble = new double[bytes];
for (int i = 0; i < bytes; i++)
{
tempDouble[i] = tempCom1[i].Real;
}
return tempDouble;
}
在图像傅立叶反变换时中调用的一维快速傅立叶反变换的函数,其输入为Complex[]格式的频域信息,输出也为Complex[]格式的频域信息。
一维快速傅立叶反变换具体函数如下:
//一维傅里叶反变换
private Complex[] ifft(Complex[] sourceData, int countN)
{
//共轭变换
for (uint i = 0; i < countN; i++)
{
sourceData[i] = sourceData[i].conjugate();
}
Complex[] interVar = new Complex[countN];
//调用快速傅里叶变换
interVar = fft(sourceData, countN);
//共轭变换,并除以长度
for (uint i = 0; i < countN; i++)
{
interVar[i] = new Complex(interVar[i].Real / countN, -interVar[i].Imaginary / countN);
}
return interVar;
}