天天看点

图像的二维傅里叶变换的频谱图代码实现

实现图像的二维傅里叶变换的频谱图

图像的频谱分析有助于我们理解图像的二维傅里叶变换(离散非周期),并且以直观的方式来展现图像的低通或高通滤波,然而如何获得图像的频谱呢?在matlab中只要短短的几行代码,就可以利用库中的函数轻松地做到。

可是,在此过程中都发生了哪些数学变换,以及得到的频谱图的含义,以及二维傅里叶变换的执行过程…都被蒙在了暗盒里。此篇文章将以应用的角度来加深对二维傅里叶变换执行过程的理解。不过,谈不上实际应用,因为普通的二维傅里叶变换实在是…太!慢!了!

由于我只熟悉java,所以用java来写。如果你熟悉的语言不是java并不会影响你对本文的理解,我会一步步解释,保证你用C/C++也能实现它。

1. 图像矩阵f(x,y)装入二维数组

这个简单,在双重循环里获取图像(i,j)坐标处的像素值(这里为了减少运算量,只获取了一个颜色通道),存到二维数组中待下一步操作

for (int i = 0;i<width;i++) {
    for(int j=0;j<height;j++){
       pixel[i][j]=image.getRGB(i,j)<<24>>24&0xff;
    }
}
           

2.编写二维傅里叶变换类

F ( k , l ) = ∑ x = 0 w i d t h − 1 ∑ y = 0 h e i g h t − 1 f ( x , y ) e − i 2 π ( k x w i d t h + l y h e i g h t ) F(k,l)=\sum_{x=0}^{width-1}\sum_{y=0}^{height-1} f(x,y) e^{-i2\pi(\frac{kx}{width}+\frac{ly}{height})} F(k,l)=x=0∑width−1​y=0∑height−1​f(x,y)e−i2π(widthkx​+heightly​)

由欧拉公式:

e − i x = c o s ( x ) − i s i n ( x ) e^{-ix}=cos(x)-isin(x) e−ix=cos(x)−isin(x)

得:

F ( k , l ) = ∑ x = 0 w i d t h − 1 ∑ y = 0 h e i g h t − 1 f ( x , y ) [ c o s ( 2 π ( k x w i d t h + l y h e i g h t ) ) − i s i n ( 2 π ( k x w i d t h + l y h e i g h t ) ) ] F(k,l)=\sum_{x=0}^{width-1}\sum_{y=0}^{height-1} f(x,y)[cos(2\pi(\frac{kx}{width}+\frac{ly}{height}))-isin(2\pi(\frac{kx}{width}+\frac{ly}{height}))] F(k,l)=x=0∑width−1​y=0∑height−1​f(x,y)[cos(2π(widthkx​+heightly​))−isin(2π(widthkx​+heightly​))]

即最终变换出的结果应该是复数的形式(re指实部,im指虚部)

F ( k , l ) = r e + i ∗ i m F(k,l)=re+i*im F(k,l)=re+i∗im

所以在类中定义一个求实部re的方法和求虚部im的方法

static double doDFTre(int pixel[][],int width,int height,int u,int v){
        double sum=0;
        for(int i=0;i<width;i++){
            for(int j=0;j<height;j++){
                sum=sum+(pixel[i][j]*pow(-1,i+j)*cos(2*PI*u*i/width+2*PI*v*j/height));
            }
        }
        return sum;
    }//Real part
    static double doDFTim(int pixel[][],int width,int height,int u,int v){
        double sum=0;
        for(int i=0;i<width;i++){
            for(int j=0;j<height;j++){
                sum=sum+(pixel[i][j]*pow(-1,i+j)*sin(2*PI*u*i/width+2*PI*v*j/height));
            }
        }
        return sum;
    }//Imaginary part
           

图像的频谱图中每个像素点的值表示的是以幅值的对数来刻画的(可类比地震级、亮度级)

求幅值Am

A m = r e 2 + i m 2 Am=\sqrt{re^2+im^2} Am=re2+im2

进行对数变换

方 法 的 返 回 值 是 l g ( 1 + A m ) 2 方法的返回值是lg(1+Am)^2 方法的返回值是lg(1+Am)2

static int doDFTam(int pixel[][],int width,int height,int i,int j){
        Thread thread1=new Thread(()->{
            re=doDFTre(pixel,width,height,i,j);
        });
        Thread thread2=new Thread(()->{
            im=doDFTim(pixel,width,height,i,j);
        });
        thread1.start();
        thread2.start();
        Am=sqrt(pow(re,2)+pow(im,2));
        return (int)pow(log(1+Am),2.3)
           

在多核处理器上,为了计算更快,可以分多个线程并发计算

这里设了两个线程,一个求实部,一个求虚部

3.将低频部分移动到频谱图中心

以上得出的频谱图低频部分分散在四周,高频部分在中央,为了集中低频于中央,可在步骤1中对原图f(i,j)作如下变换得到u(i,j)再对u(i,j)作二维傅里叶变换(这部分我也不理解,只是套公式)

u ( i , j ) = f ( i , j ) ( − 1 ) i + j u(i,j)=f(i,j)(-1)^{i+j} u(i,j)=f(i,j)(−1)i+j

4.运行

可以再写一个守护线程用来计时

5.完整的代码

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import static java.lang.Math.*;

public class Fourier2D {
    static int logam=0;
    public static void main(String[] args) {
        Thread timethread=new Thread(()->{
            int i=0;
            while(true){
                System.out.println("Time Consumption  "+i+"  sec");
                i=i+1;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });//计时器
        timethread.setDaemon(true);
        timethread.setPriority(1);
        timethread.start();
        
        try {
            BufferedImage image= ImageIO.read(new File("C:\\Users\\Unicode718A\\Desktop\\学习笔记\\IO\\in.png"));
            int width=image.getWidth();
            int height=image.getHeight();

            BufferedImage outimage=new BufferedImage(width,height,2);
            int pixel[][]=new int[width][height];


            for (int i = 0;i<width;i++) {
                for(int j=0;j<height;j++){
                    pixel[i][j]=image.getRGB(i,j)<<24>>24&0xff;
                }
            }
            for(int i=0;i<width;i++){
                for(int j=0;j<width;j++){
                    logam=DFT.doDFTam(pixel,width,height,i,j);
                    if(logam>255){
                        logam=255;
                    }//8-bit png图片亮度等级最大只有255
                    if(logam<0){
                        logam=0;
                    }
                    outimage.setRGB(i,j,255<<24|logam<<16|logam<<8|logam);
                }
            }
            ImageIO.write(outimage,"png",new File("C:\\Users\\Unicode718A\\Desktop\\学习笔记\\IO\\out.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class DFT{
    static double re;
    static double im;
    static double Am;

    static double doDFTre(int pixel[][],int width,int height,int u,int v){
        double sum=0;
        for(int i=0;i<width;i++){
            for(int j=0;j<height;j++){
                sum=sum+(pixel[i][j]*pow(-1,i+j)*cos(2*PI*u*i/width+2*PI*v*j/height));
            }
        }
        return sum;
    }//Real part
    static double doDFTim(int pixel[][],int width,int height,int u,int v){
        double sum=0;
        for(int i=0;i<width;i++){
            for(int j=0;j<height;j++){
                sum=sum+(pixel[i][j]*pow(-1,i+j)*sin(2*PI*u*i/width+2*PI*v*j/height));
            }
        }
        return sum;
    }//Imaginary part
    static int doDFTam(int pixel[][],int width,int height,int i,int j){
        Thread thread1=new Thread(()->{
            re=doDFTre(pixel,width,height,i,j);
        });//线程1
        Thread thread2=new Thread(()->{
            im=doDFTim(pixel,width,height,i,j);
        });//线程2
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Am=sqrt(pow(re,2)+pow(im,2));
        return (int)pow(log(1+Am),2.3);//Log Transformation of Amplitude
    }
}
           

6.结果展示

100 x 100像素原图

图像的二维傅里叶变换的频谱图代码实现

对应频谱图

图像的二维傅里叶变换的频谱图代码实现

256 x 256像素的原图

图像的二维傅里叶变换的频谱图代码实现

对应频谱图

图像的二维傅里叶变换的频谱图代码实现

7.普通二维傅里叶变换运算量巨大

图像的二维傅里叶变换的频谱图代码实现

256 x 256 像素转换的运算时间竟然达到了10分钟,而且还是双线程并发的

而100 x 100像素转换的时间”只要”19秒

为什么会相差…这么巨大?

算算处理器执行循环的总次数就大略明白了:

100 x 100的情况:共2x100 x100 x100 x100=2 x 10^8次

256 x 256的情况:共2x256 x256 x256 x256=8.6 x10^9此

工作量增加了43倍,运算时间也会增加相应的倍数

要是图像再大点,耗时更是无法想象,所以实际应用中的傅里叶变换依赖更高效的算法

以上仅为我个人的理解,如有错误之处,还望大佬们指正。

参考:

1. http://fourier.eng.hmc.edu/e101/lectures/Image_processing/node6.html

2. https://plus.maths.org/content/fourier-transforms-images

3. 数字图像处理 冈萨雷斯 4th Edition