使用Java代码实现傅里叶变换_java傅立叶变黑白-程序员宅基地

技术标签: 算法  图像处理  土味  

说明

  • 该代码源自java使用傅里叶变换,对其进行了部分优化,可以实现将灰度图像转换为频率域图像,以及从频率域恢复为原图像。

  • 初次接触傅里叶算法,有很多新概念,理解起来比较困难,需要多看几遍,参考链接都在文章最后。

  • 这边的代码逻辑其实很简单,就是输入一组复数数组,进行处理后,返回相同长度的复数数组,处理的算法和下面的公式有关,然后和三角函数没有太大关联,但想理清整个傅里叶变换,三角函数还是绕不过去的。

  • 通过去除图像中的低频率来提取边缘,或者通过去除高频率来模糊图像已经实现了,在recover()方法里面,去除高频率需要手动去除注释。
    在这里插入图片描述

代码

主类

package com.example.springboot01.util;

import org.junit.Test;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

/**
 * 代码参考:https://blog.csdn.net/wangjichen_1/article/details/51120194
 */
public class FourierTransformer {
    

	@Test
	public void test() throws IOException {
    
		String fromPic = "C:\\Users\\Dell\\Pictures\\aaa.jpg";
		BufferedImage bufferedImage = ImageIO.read(new File(fromPic));

		// 转换为频率域图像
		BufferedImage destImg = convert(bufferedImage);
		// 从频率域恢复为原图片,未实现去除高频率功能
		// BufferedImage destImg = recover(bufferedImage);
		File newFile = new File("d:\\test10.jpg");
		ImageIO.write(destImg, "jpg", newFile);
	}

	/**
	 * 生成频率域图像
	 * @param srcImage
	 * @return
	 */
	public BufferedImage convert(BufferedImage srcImage) {
    
		int iw = srcImage.getWidth();
		int ih = srcImage.getHeight();
		int[] pixels = new int[iw * ih];
		int[] newPixels;
		srcImage.getRGB(0, 0, iw, ih, pixels, 0, iw);

		// 赋初值
		int w = 1;
		int h = 1;
		// 计算进行付立叶变换的宽度和高度(2的整数次方)
		while (w * 2 <= iw) {
    
			w *= 2;
		}
		while (h * 2 <= ih) {
    
			h *= 2;
		}
		// 分配内存
		// 从左往右,从上往下排序
		Complex[] src = new Complex[h * w];
		// 从左往右,从上往下排序
		Complex[] dest = new Complex[h * w];
		newPixels = new int[h * w];

		for (int i = 0; i < h; i++) {
    
			for (int j = 0; j < w; j++) {
    
				// 初始化src,dest
				dest[i * w + j] = new Complex();
				// 获取蓝色通道分量,也就是像素点的灰度值
				src[i * w + j] = new Complex(pixels[i * iw + j] & 0xff, 0);
			}
		}

		// 在y方向上进行快速傅立叶变换
		//
		// 一行一行的处理
		// 处理完后第一列都是各自行里面的最大值
		//
		for (int i = 0; i < h; i++) {
    
			Complex[] temp = new Complex[w];
			for (int k = 0; k < w; k++) {
    
				temp[k] = src[i * w + k];
			}
			temp = FFT.fft(temp);

			for (int k = 0; k < w; k++) {
    
				dest[i * w + k] = temp[k];
			}
		}

		// 对x方向进行傅立叶变换
		// 一列一列的处理,从左往右
		// 处理完后第一行都是各自列里面的最大值
		for (int i = 0; i < w; i++) {
    
			Complex[] temp = new Complex[h];
			for (int k = 0; k < h; k++) {
    
				temp[k] = dest[k * w + i];
			}
			temp = FFT.fft(temp);

			for (int k = 0; k < h; k++) {
    
				dest[k * w + i] = temp[k];
			}
		}

		// 打印傅里叶频率域图像
		for (int i = 0; i < h; i++) {
    
			for (int j = 0; j < w; j++) {
    
				// 从左到右,从上到下遍历
				double re = dest[i * w + j].re;
				double im = dest[i * w + j].im;

				int ii = 0, jj = 0;
				// 缩小数值,方便展示
				int temp = (int) (Math.sqrt(re * re + im * im) / 100);

				// 用十字将图像切割为4等分,然后每个部分旋转180度,再拼接起来,方便观察
				if (i < h / 2) {
    
					ii = i + h / 2;
				} else {
    
					ii = i - h / 2;
				}
				if (j < w / 2) {
    
					jj = j + w / 2;
				} else {
    
					jj = j - w / 2;
				}
				newPixels[ii * w + jj] = (clamp(temp) << 16) | (clamp(temp) << 8) | clamp(temp);;
			}
		}

		BufferedImage destImg = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY);
		destImg.setRGB(0, 0, w, h, newPixels, 0, w);

		return destImg;
	}

	/**
	 * 从频率还原为图像
	 * 已经实现了通过去除低频率提取边缘和通过去除高频率模糊图像两个功能
	 * @param srcImage
	 * @return
	 */
	public BufferedImage recover(BufferedImage srcImage) {
    
		int iw = srcImage.getWidth();
		int ih = srcImage.getHeight();
		int[] pixels = new int[iw * ih];
		int[] newPixels;
		srcImage.getRGB(0, 0, iw, ih, pixels, 0, iw);

		// 赋初值
		int w = 1;
		int h = 1;
		// 计算进行付立叶变换的宽度和高度(2的整数次方)
		while (w * 2 <= iw) {
    
			w *= 2;
		}
		while (h * 2 <= ih) {
    
			h *= 2;
		}
		// 分配内存
		// 从左往右,从上往下排序
		Complex[] src = new Complex[h * w];
		// 从左往右,从上往下排序
		Complex[] dest = new Complex[h * w];
		newPixels = new int[h * w];

		for (int i = 0; i < h; i++) {
    
			for (int j = 0; j < w; j++) {
    
				// 初始化src,dest
				dest[i * w + j] = new Complex();
				// 获取蓝色通道分量,也就是像素点的灰度值
				src[i * w + j] = new Complex(pixels[i * iw + j] & 0xff, 0);
			}
		}

		// 在y方向上进行快速傅立叶变换
		//
		// 先一行一行的处理
		// 处理完后第一列都是各自行里面的最大值
		//
		for (int i = 0; i < h; i++) {
    
			Complex[] temp = new Complex[w];
			for (int k = 0; k < w; k++) {
    
				temp[k] = src[i * w + k];
			}
			temp = FFT.fft(temp);

			for (int k = 0; k < w; k++) {
    
				dest[i * w + k] = temp[k];
			}
		}

		// 对x方向进行傅立叶变换
		// 再一列一列的处理,从左往右
		// 处理完后第一行都是各自列里面的最大值
		for (int i = 0; i < w; i++) {
    
			Complex[] temp = new Complex[h];
			for (int k = 0; k < h; k++) {
    
				temp[k] = dest[k * w + i];
			}
			temp = FFT.fft(temp);

			for (int k = 0; k < h; k++) {
    
				dest[k * w + i] = temp[k];
			}
		}
		
		// 去除指定频率部分,然后还原为图像
		int halfWidth = w/2;
		int halfHeight = h/2;
		// 比例越小,边界越突出
		double res = 0.24;

		// 比例越大,图片越模糊
		double del = 0.93;
		// 先对列进行逆傅里叶变换,并去除指定频率。和傅里叶变换顺序相反
		for (int i = 0; i < w; i++) {
    
			Complex[] temp = new Complex[h];
			for (int k = 0; k < h; k++) {
    
				// 去除低频率,保留边缘,因为频率图尚未做十字切割并分别旋转180度,所以此时图像dest[] 的四个角落是低频率,而中心部分是高频率
				// 第一个点(0,0)不能删除,删掉后整张图片就变黑了
				if (i == 0 && k == 0) {
    
					temp[k] = dest[k * w + i];
				} else if ((i <res*halfWidth && k < res*halfHeight) || (i <res*halfWidth && k > (1-res)*halfHeight)
						|| (i > (1-res)*halfWidth && k <res*halfHeight) || (i > (1-res)*halfWidth && k > (1-res)*halfHeight)) {
    
					temp[k] = new Complex(0, 0);
				} else {
    
					temp[k] = dest[k * w + i];
				}

				// 去除高频率,模糊图像。和去除低频率代码冲突,需要先屏蔽上面去除低频率的代码才能执行
				/*if ((i > ((1-del)*halfWidth) && k > ((1-del)*halfHeight)) && (i <((1+del)*halfWidth) && k < ((1+del)*halfHeight))) {
					temp[k] = new Complex(0, 0);
				} else {
					temp[k] = dest[k * w + i];
				}*/
			}
			temp = FFT.ifft(temp);
			for (int k = 0; k < h; k++) {
    
				// 这边需要除以N,也就是temp[]的长度
				dest[k * w + i].im = temp[k].im / h;
				dest[k * w + i].re = temp[k].re / h;
			}
		}

		// 再对行进行逆傅里叶变换
        for (int i = 0; i < h; i++) {
    
            Complex[] temp = new Complex[w];
            for (int k = 0; k < w; k++) {
    
            	temp[k] = dest[i * w + k];
            }
            temp = FFT.ifft(temp);
            for (int k = 0; k < w; k++) {
    
            	// 这边需要除以N,也就是temp[]的长度
				dest[i * w + k].im = temp[k].im / w;
                dest[i * w + k].re = temp[k].re / w;
            }
        }
        
        for (int i = 0; i < h; i++) {
    
            for (int j = 0; j < w; j++) {
    
                // 从左到右,从上到下遍历
                double re = dest[i * w + j].re;
                int temp = (int) re;
                newPixels[i * w + j] = (clamp(temp) << 16) | (clamp(temp) << 8) | clamp(temp);;
            }
        }

        BufferedImage destImg = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY);
        destImg.setRGB(0, 0, w, h, newPixels, 0, w);

        return destImg;
	}

	/**
	 * 如果像素点的值超过了0-255的范围,予以调整
	 *
	 * @param value 输入值
	 * @return 输出值
	 */
	private int clamp(int value) {
    
		return value > 255 ? 255 : (Math.max(value, 0));
	}
}

傅里叶变换类

package com.example.springboot01.util;

/**
 * 快速傅里叶变换
 * 傅里叶介绍参考:https://www.ruanx.net/fft/
 *	https://blog.csdn.net/YY_Tina/article/details/88361459
 */
public class FFT {
    

	/**
	 * 快速傅里叶变换
	 * @param x
	 * @return
	 */
	// compute the FFT of x[], assuming its length is a power of 2
	public static Complex[] fft(Complex[] x) {
    
		int N = x.length;

		// base case
		if (N == 1)
			return new Complex[] {
     x[0] };

		// radix 2 Cooley-Tukey FFT
		if (N % 2 != 0) {
    
			throw new RuntimeException("N is not a power of 2");
		}

		// fft of even terms
		Complex[] even = new Complex[N / 2];
		for (int k = 0; k < N / 2; k++) {
    
			even[k] = x[2 * k];
		}
		Complex[] q = fft(even);

		// fft of odd terms
		Complex[] odd = even; // reuse the array
		for (int k = 0; k < N / 2; k++) {
    
			odd[k] = x[2 * k + 1];
		}
		Complex[] r = fft(odd);

		// combine
		Complex[] y = new Complex[N];
		for (int k = 0; k < N / 2; k++) {
    
			double kth = 2 * k * Math.PI / N;
			Complex wk = new Complex(Math.cos(kth), Math.sin(kth));
			y[k] = q[k].plus(wk.times(r[k]));
			y[k + N / 2] = q[k].minus(wk.times(r[k]));
		}
		return y;
	}


	/**
	 * 逆快速傅里叶变换
	 * 与fft()相比,只修改了kth的值
	 * @param x
	 */
	public static Complex[] ifft(Complex[] x) {
    
		int N = x.length;

		// base case
		if (N == 1)
			return new Complex[] {
     x[0] };

		// radix 2 Cooley-Tukey FFT
		if (N % 2 != 0) {
    
			throw new RuntimeException("N is not a power of 2");
		}

		// fft of even terms
		Complex[] even = new Complex[N / 2];
		for (int k = 0; k < N / 2; k++) {
    
			even[k] = x[2 * k];
		}
		Complex[] q = ifft(even);

		// fft of odd terms
		Complex[] odd = even; // reuse the array
		for (int k = 0; k < N / 2; k++) {
    
			odd[k] = x[2 * k + 1];
		}
		Complex[] r = ifft(odd);

		// combine
		Complex[] y = new Complex[N];
		for (int k = 0; k < N / 2; k++) {
    
			// 这边有区别
			double kth = 2 * (N - k) * Math.PI / N;
			Complex wk = new Complex(Math.cos(kth), Math.sin(kth));
			y[k] = q[k].plus(wk.times(r[k]));
			y[k + N / 2] = q[k].minus(wk.times(r[k]));
		}
		return y;
	}


}

复数类

package com.example.springboot01.util;

/**
 * 复数类
 */
public class Complex {
    
    public  double re;   // the real part
    public  double im;   // the imaginary part
 
    public Complex() {
    
        re = 0;
        im = 0;
    }
    
    // create a new object with the given real and imaginary parts
    public Complex(double real, double imag) {
    
        re = real;
        im = imag;
    }
 
    // return a string representation of the invoking Complex object
    public String toString() {
    
        if (im == 0) return re + "";
        if (re == 0) return im + "i";
        if (im <  0) return re + " - " + (-im) + "i";
        return re + " + " + im + "i";
    }
 
    // return abs/modulus/magnitude and angle/phase/argument
    public double abs()   {
     return Math.hypot(re, im); }  // Math.sqrt(re*re + im*im)
    public double phase() {
     return Math.atan2(im, re); }  // between -pi and pi
 
    // return a new Complex object whose value is (this + b)
    public Complex plus(Complex b) {
    
        Complex a = this;             // invoking object
        double real = a.re + b.re;
        double imag = a.im + b.im;
        return new Complex(real, imag);
    }
 
    // return a new Complex object whose value is (this - b)
    public Complex minus(Complex b) {
    
        Complex a = this;
        double real = a.re - b.re;
        double imag = a.im - b.im;
        return new Complex(real, imag);
    }
 
    // return a new Complex object whose value is (this * b)
    public Complex times(Complex b) {
    
        Complex a = this;
        double real = a.re * b.re - a.im * b.im;
        double imag = a.re * b.im + a.im * b.re;
        return new Complex(real, imag);
    }
 
    // scalar multiplication
    // return a new object whose value is (this * alpha)
    public Complex times(double alpha) {
    
        return new Complex(alpha * re, alpha * im);
    }
 
    // return a new Complex object whose value is the conjugate of this
    public Complex conjugate() {
      return new Complex(re, -im); }
 
    // return a new Complex object whose value is the reciprocal of this
    public Complex reciprocal() {
    
        double scale = re*re + im*im;
        return new Complex(re / scale, -im / scale);
    }
 
    // return the real or imaginary part
    public double re() {
     return re; }
    public double im() {
     return im; }
 
    // return a / b
    public Complex divides(Complex b) {
    
        Complex a = this;
        return a.times(b.reciprocal());
    }
 
    // return a new Complex object whose value is the complex exponential of this
    public Complex exp() {
    
        return new Complex(Math.exp(re) * Math.cos(im), Math.exp(re) * Math.sin(im));
    }
 
    // return a new Complex object whose value is the complex sine of this
    public Complex sin() {
    
        return new Complex(Math.sin(re) * Math.cosh(im), Math.cos(re) * Math.sinh(im));
    }
 
    // return a new Complex object whose value is the complex cosine of this
    public Complex cos() {
    
        return new Complex(Math.cos(re) * Math.cosh(im), -Math.sin(re) * Math.sinh(im));
    }
 
    // return a new Complex object whose value is the complex tangent of this
    public Complex tan() {
    
        return sin().divides(cos());
    }
    
 
 
    // a static version of plus
    public static Complex plus(Complex a, Complex b) {
    
        double real = a.re + b.re;
        double imag = a.im + b.im;
        Complex sum = new Complex(real, imag);
        return sum;
    }
}

效果图

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

参考

java使用傅里叶变换,得到变换之后的傅里叶频谱图像。
快速傅里叶变换
快速傅里叶变换(FFT)和逆快速傅里叶变换(IFFT)
傅里叶分析之掐死教程(完整版)
图像傅里叶变换
二维傅里叶变换深度研究-图像与其频域关系
傅里叶变换在工程中的一些应用
形象理解二维傅里叶变换

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_34067387/article/details/118364994

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法