该代码源自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)
傅里叶分析之掐死教程(完整版)
图像傅里叶变换
二维傅里叶变换深度研究-图像与其频域关系
傅里叶变换在工程中的一些应用
形象理解二维傅里叶变换