0 图像灰度化、直方图均衡、灰度线性拉伸

  • 原理太简单,略~

1 图像的离散傅里叶变换与逆变换

1.1 简介

  • 离散傅里叶变换 (DFT),是傅里叶变换在时域和频域上都呈现离散的形式,将时域信号的采样变换为在离散时间傅里叶变换 (DTFT) 频域的采样。在形式上,变换两端 (时域和频域上) 的序列是有限长的,而实际上这两组序列都应当被认为是离散周期信号的主值序列。即使对有限长的离散信号作 DFT,也应当将其看作经过周期延拓成为周期信号再作变换。在实际应用中通常采用快速傅里叶变换 (FFT) 以高效计算 DFT。FFT的逆变换可通过共轭->FFT->共轭->缩放实现。

1.2 公式

1.3 基2 FFT算法原理

1.4 效果

原图

频谱

逆变换结果

原图

频谱

逆变换结果

2 图像的离散余弦变换与逆变换

2.1 简介

  • 离散余弦变换 (DCT for Discrete Cosine Transform) 是与傅里叶变换相关的一种变换,它类似于离散傅里叶变换 (DFT for Discrete Fourier Transform) , 但是只使用实数。离散余弦变换相当于一个长度大概是它两倍的离散傅里叶变换,这个离散傅里叶变换是对一个实偶函数进行的 (因为一个实偶函数的傅里叶变换仍然是一个实偶函数) ,在有些变形里面需要将输入或者输出的位置移动半个单位。DCT有8种标准类型,其中4种是常见的。

2.2 公式

2.3 基于FFT的快速离散余弦变换算法原理

  • 借助傅里叶变换计算余弦变换的步骤:
    • 把 $f(x)$ 延拓成 $f_e(x)$ ,长度为 $2N$
    • 求 $f_e(x)$ 的 $2N$ 点的 FFT
    • 对 $u$ 各项乘上对应的因子 $\sqrt{2}exp{-j\frac{\pi u}{2N}}$
    • 取实部,并乘上因子 $\sqrt{\frac{1}{N}}$
    • 取 $F(u)$ 的前 $N$ 项,即为 $f(x)$ 的余弦变换
  • 同理,IDCT 可由 $F_e(u)e^{j\frac{\pi u}{2N}}$ 的 $2N$ 点的 IFFT 实现。

2.4 效果

原图

频谱

逆变换结果

原图

频谱

逆变换结果

3 基于JPEG压缩算法的图像压缩

3.1 步骤与原理

JPEG压缩算法

  • DCT:由于 DCT 主要应用在数据和图像的压缩,因此希望原信号的能量在变换后能尽量集中在少数系数上,且这些大能量的系数能处在相对集中的位置,这将有利于进一步的量化和编码。但是如果对整段的数据或整幅图像来做 DCT,那就很难保证大能量的系数能处在相对集中的位置。因此,在实际应用中,一般都是将数据分成一段一段来做,一般分成 8x8。
    • 首先将颜色空间转换为YUV,转换公式为:
    • 然后将图像的像素值减去 128,将像素值控制在 -128~127 之间进行 DCT 变换。
    • 由于是 8x8 矩阵的 DCT 变换,故采用基于 FFT 的算法没有优势,更由于 FFT 引入复数运算,使计算过程变得复杂。因此此处采用基于变换矩阵的算法,即:

  • 程序中使用了矩阵运算的第三方 Java 库 jama ,结果表明使用变换矩阵算法的速度远远高于 FFT 和原生 DCT 算法。
    • 量化:经过DCT变换后的数据,极大限度的去除了相关性,并且实现了能量的集中,那么会出现数据特别大而多数数据特别小的情况。量化的过程就是对这些数据做新的映射处理,目的是减少非“0”系数的幅度以及增加“0”值系数的数目。

  • Zigzag 扫描:DCT 将一个 8x8 的数组变换成另一个 8x8 的数组. 但是内存里所有数据都是线形存放的, 如果我们一行行的存放这 64 个数字, 每行的结尾的点和下行开始的点就没有什么关系, 所以 JPEG 规定按如下顺序依次保存和读取 64 个 DCT 的系数值:从 8*8 矩阵的左上角开始,按照英文字母 Z 的形状进行扫描的,一般将其称之为 Zigzag 扫描排序。如下图所示:

  • Huffman 编码:对出现概率大的字符分配字符长度较短的二进制编码,对出现概率小的字符分配字符长度较长的二进制编码,从而使得字符的平均编码长度最短。Huffman 编码时 DC 系数与 AC 系数分别采用不同的 Huffman 编码表,对于亮度和色度也采用不同的 Huffman 编码表。因此,需要 4 张 Huffman 编码表才能完成熵编码的工作。具体的 Huffman 编码采用查表的方式来高效地完成。然而,在 JPEG 标准中没有定义缺省的 Huffman 表,用户可以根据实际应用自由选择,也可以使用 JPEG 标准推荐的 Huffman 表。或者预先定义一个通用的 Huffman 表,也可以针对一副特定的图像,在压缩编码前通过搜集其统计特征来计算 Huffman 表的值。

3.2 效果

  • 标题栏注明了量化参数和压缩后文件大小,单位 KB

4 基于DCT的图像数字水印嵌入与提取

4.1 步骤与原理

  • 首先参照图像压缩时的步骤,将原图像按照8x8的大小分块并进行DCT变换。然后对照原图像的分块将水印图像进行适当扩展并分成同样数量的块。对于嵌入位置的选择,由于嵌入低频区域对原图像影响较大,而嵌入高频区域很容易被过滤,因此选择将水印图像嵌入8x8块的中频部分。
  • 显然,嵌入位置的选择限制了水印图像的大小,使用过大的图片会导致嵌入失败。
  • 提取水印时只需将携带水印的图像分块进行DCT变换,然后从每一块的中频区域提取水印图像的信息。

4.2 效果

原图像

水印图像

水印嵌入结果

水印提取结果

  • 由于DCT变换涉及浮点数与整数的转换,因此图像会有一定损失,但是我不太清楚是不是因为代码写错了,使得图像有如此规律的黑色竖纹。诡异的是,对于下面这幅图像就没有黑色竖纹。时间原因,没能解决这一问题。

水印图像

水印嵌入结果

水印提取结果

5 边缘检测

5.1 原理

  • 边缘 (edge) 是指图像局部强度变化最显著的部分。主要存在于目标与目标、目标与背景、区域与区域(包括不同色彩)之间,是图像分割、纹理特征和形状特征等图像分析的重要基础。
  • 图像强度的显著变化可分为:
    • 阶跃变化函数,即图像强度在不连续处的两边的像素灰度值有着显著的差异;
    • 线条 (屋顶) 变化函数,即图像强度突然从一个值变化到另一个值,保持一较小行程后又回到原来的值。
  • 图像的边缘有方向和幅度两个属性,沿边缘方向像素变化平缓,垂直于边缘方向像素变化剧烈.边缘上的这种变化可以用微分算子检测出来,通常用一阶或二阶导数来检测边缘。

5.2 常用算子

5.2.1 Sobel算子

  • 主要用于边缘检测,在技术上它是以离散型的差分算子,用来运算图像亮度函数的梯度的近似值,Sobel 算子是典型的基于一阶导数的边缘检测算子,由于该算子中引入了类似局部平均的运算,因此对噪声具有平滑作用,能很好的消除噪声的影响。Sobel 算子对于像素位置的影响做了加权,与 Prewitt 算子、Roberts 算子相比效果更好。
  • Sobel 算子包含两组3x3的矩阵,分别为横向及纵向模板,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。实际使用中,常用如下两个模板来检测图像边缘:
    • 检测水平边沿横向模板:
    • 检测垂直平边沿纵向模板:
  • 图像的每一个像素的横向及纵向梯度近似值可用以下的公式结合,来计算梯度的大小:
  • 缺点是Sobel算子并没有将图像的主题与背景严格地区分开来,换言之就是Sobel算子并没有基于图像灰度进行处理,由于Sobel算子并没有严格地模拟人的视觉生理特征,所以提取的图像轮廓有时并不能令人满意。

5.2.2 Prewitt算子

  • Prewitt 算子是一种一阶微分算子的边缘检测,利用像素点上下、左右邻点的灰度差,在边缘处达到极值检测边缘,去掉部分伪边缘,对噪声具有平滑作用 。其原理是在图像空间利用两个方向模板与图像进行邻域卷积来完成的,这两个方向模板一个检测水平边缘,一个检测垂直边缘。
  • 经典 Prewitt 算子认为:凡灰度新值大于或等于阈值的像素点都是边缘点。即选择适当的阈值 $T$ ,若 $P(i,j)\ge T$ ,则 $(i,j)$ 为边缘点,$P(i,j)$ 为边缘图像。这种判定是欠合理的,会造成边缘点的误判,因为许多噪声点的灰度值也很大,而且对于幅值较小的边缘点,其边缘反而丢失了。
  • Prewitt 算子对噪声有抑制作用,抑制噪声的原理是通过像素平均,但是像素平均相当于对图像的低通滤波,所以 Prewitt 算子对边缘的定位不如 Roberts 算子。
  • 因为平均能减少或消除噪声,Prewitt 梯度算子法就是先求平均,再求差分来求梯度。水平和垂直梯度模板分别为:
  • 该算子与 Sobel 算子类似,只是权值有所变化,但两者实现起来功能还是有差距的,据经验得知 Sobel 要比 Prewitt 更能准确检测图像边缘。

5.2.3 Laplace算子

  • Laplace 算子是一种各向同性算子,二阶微分算子,在只关心边缘的位置而不考虑其周围的象素灰度差值时比较合适。Laplace 算子对孤立象素的响应要比对边缘或线的响应要更强烈,因此只适用于无噪声图象。存在噪声情况下,使用 Laplacian 算子检测边缘之前需要先进行低通滤波。所以,通常的分割算法都是把 Laplacian 算子和平滑算子结合起来生成一个新的模板。
  • Laplace 算子也是最简单的各向同性微分算子,具有旋转不变性。一个二维图像函数的拉普拉斯变换是各向同性的二阶导数,定义:

  • Laplace 算子一般不以其原始形式用于边缘检测,因为其作为一个二阶导数,Laplace 算子对噪声具有无法接受的敏感性,同时其幅值产生算边缘,这是复杂的分割不希望有的结果,最后 Laplace 算子不能检测边缘的方向,所以 Laplace 在分割中所起的作用包括:
    • 利用它的零交叉性质进行边缘定位
    • 确定一个像素是在一条边缘暗的一面还是亮的一面
  • 一般使用的是高斯型拉普拉斯算子 (Laplace of a Gaussian,LoG) ,由于二阶导数是线性运算,利用 LoG 卷积一幅图像与首先使用高斯型平滑函数卷积改图像,然后计算所得结果的拉普拉斯是一样的。所以在 LoG 公式中使用高斯函数的目的就是对图像进行平滑处理,使用 Laplace 算子的目的是提供一幅用零交叉确定边缘位置的图像;图像的平滑处理减少了噪声的影响并且它的主要作用还是抵消由 Laplace 算子的二阶导数引起的逐渐增加的噪声影响。

5.3 效果

6 源码

MainWindow.java (应用主窗口)

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;
import javax.imageio.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.image.PixelGrabber;
import java.io.*;
import java.text.DecimalFormat;

public class MainWindow extends JFrame {
public int currPixArray[] = null;
public int sourcePixArray[] = null;
public JLabel imageLabel = null;
public JScrollPane pane=null;
public int height;
public int width;
public BufferedImage image=null;

public cosineListener cosine=new cosineListener(this);

private MainWindow(String title) {
super(title);
JMenuBar bar = new JMenuBar();
JMenu fileMenu = new JMenu("文件 ");
bar.add(fileMenu);
JMenuItem openImage = new JMenuItem("打开图片");
fileMenu.add(openImage);
openImage.addActionListener(new OpenListener(this));
JMenuItem exitItem = new JMenuItem("退出");
fileMenu.add(exitItem);
exitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});

JMenu processMenu = new JMenu("p1 ");
bar.add(processMenu);
JMenuItem revoke = new JMenuItem("还原");
processMenu.add(revoke);
revoke.addActionListener(new RevokeListener(this));
JMenuItem toGray = new JMenuItem("灰度化");
processMenu.add(toGray);
toGray.addActionListener(new GrayListener(this));
JMenuItem toBalance = new JMenuItem("直方图均衡");
processMenu.add(toBalance);
toBalance.addActionListener(new HistogramListener(this));
JMenuItem grayStretch = new JMenuItem("线性拉伸");
processMenu.add(grayStretch);
grayStretch.addActionListener(new GrayStretchListener(this));

JMenu processMenu2 = new JMenu("p2 ");
bar.add(processMenu2);
JMenuItem fourier = new JMenuItem("DFT / IDFT");
processMenu2.add(fourier);
fourier.addActionListener(new fourierListener(this));
JMenuItem cosin = new JMenuItem("DCT / IDCT");
processMenu2.add(cosin);
cosin.addActionListener(cosine);
JMenuItem compress = new JMenuItem("DCT图像压缩");
processMenu2.add(compress);
compress.addActionListener(new compressListener(this));
JMenuItem watermark = new JMenuItem("DCT数字水印");
processMenu2.add(watermark);
watermark.addActionListener(new watermarkListener(this));
JMenuItem waterrecover = new JMenuItem("数字水印提取");
processMenu2.add(waterrecover);
waterrecover.addActionListener(new waterrecoverListener(this));

JMenu processMenu3 = new JMenu("p3 ");
bar.add(processMenu3);
JMenuItem sobel = new JMenuItem("sobel边缘检测");
processMenu3.add(sobel);
sobel.addActionListener(new edgeListener(this,"sobel"));
JMenuItem prewitt = new JMenuItem("prewitt边缘检测");
processMenu3.add(prewitt);
prewitt.addActionListener(new edgeListener(this,"prewitt"));
JMenuItem laplace = new JMenuItem("laplace边缘检测");
processMenu3.add(laplace);
laplace.addActionListener(new edgeListener(this,"laplace"));

this.setJMenuBar(bar);

imageLabel = new JLabel("");
pane = new JScrollPane(imageLabel);
this.add(pane, BorderLayout.CENTER);
}


public void showImage(int[] array) {
Image pic = this.createImage(new MemoryImageSource(width, height,array, 0, width));
ImageIcon icon = new ImageIcon(pic);
imageLabel.setIcon(icon);
imageLabel.repaint();
}

public static void main(String args[]) {
MainWindow process = new MainWindow("IM");
process.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
process.setLocationRelativeTo(null);
process.setMinimumSize(new Dimension(300,300));
process.setSize(300, 300);
process.setVisible(true);
}
}

Complex.java (复数运算)

public class Complex {
private final double re;
private final double im;

public Complex(double real, double imag) {
re = real;
im = imag;
}

public double abs() {
return Math.hypot(re, im);
}

public Complex plus(Complex b) {
Complex a = this;
double real = a.re + b.re;
double imag = a.im + b.im;
return new Complex(real, imag);
}

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);
}

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);
}

public Complex scale(double alpha) {
return new Complex(alpha * re, alpha * im);
}

public Complex conjugate() {
return new Complex(re, -im);
}

public double re(){return re;}

public double im(){return im;}

}

OpenListener.java (打开图片)

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.PixelGrabber;
import java.io.File;
import java.io.IOException;

public class OpenListener implements ActionListener {
private MainWindow mainWindow;

public OpenListener(MainWindow mainWindow){
this.mainWindow = mainWindow;
}

public void actionPerformed(ActionEvent e) {
JFileChooser file = new JFileChooser();
String filePath;
int feedback = file.showOpenDialog(null);
if (feedback == JFileChooser.APPROVE_OPTION) {
File target = file.getSelectedFile();
if (target != null) {
filePath = target.getAbsolutePath();
try {
mainWindow.image = ImageIO.read(new File(filePath));
mainWindow.width = mainWindow.image.getWidth();
mainWindow.height = mainWindow.image.getHeight();

//////////////////dangerous/////////////
mainWindow.cosine.setWidth(mainWindow.image.getWidth());
mainWindow.cosine.setHeight(mainWindow.image.getHeight());
//////////////////////////////////////

PixelGrabber p;
int[] array = new int[mainWindow.width * mainWindow.height];
try {
p = new PixelGrabber(mainWindow.image, 0, 0, mainWindow.width, mainWindow.height, array, 0, mainWindow.width);
if (!p.grabPixels())
try {
throw new Exception();
} catch (Exception e1) {}
} catch (Exception e2){}
mainWindow.currPixArray = array;
mainWindow.sourcePixArray = array;
mainWindow.imageLabel.setIcon(new ImageIcon(mainWindow.image));
} catch (IOException e3) {}
}
}
mainWindow.repaint();
mainWindow.setSize(mainWindow.width+22,mainWindow.height+76);
mainWindow.pane.setSize(mainWindow.width,mainWindow.height);
}
}

GrayListener.java (图像灰度化)

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.ColorModel;

public class GrayListener implements ActionListener {
private MainWindow mainWindow;

public GrayListener(MainWindow mainWindow){
this.mainWindow = mainWindow;
}

public void actionPerformed(ActionEvent e) {
int[] array = new int[mainWindow.height * mainWindow.width];
ColorModel colorModel = ColorModel.getRGBdefault();
int i, j, k, r, g, b;
for (i = 0; i < mainWindow.height; i++) {
for (j = 0; j < mainWindow.width; j++) {
k = i * mainWindow.width + j;
r = colorModel.getRed(mainWindow.currPixArray[k]);
g = colorModel.getGreen(mainWindow.currPixArray[k]);
b = colorModel.getBlue(mainWindow.currPixArray[k]);
r = g = b = (int) (r * 0.3 + g * 0.59 + b * 0.11);
array[k] = (255 << 24) | (r << 16) | (g << 8) | b;
}
}
mainWindow.currPixArray = array;
mainWindow.showImage(array);
}
}

HistogramListener.java (图像直方图均衡)

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class HistogramListener implements ActionListener {
private MainWindow mainWindow;

public HistogramListener(MainWindow mainWindow){
this.mainWindow = mainWindow;
}

public void actionPerformed(ActionEvent e) {
int grayLevel; // 每个像素的灰度级
int[] grayArray = new int[256]; // 记录每个灰度级出现的像素数量
int[] resPixArray = new int[mainWindow.width * mainWindow.height]; // 均衡化之后的新像素矩阵
for (int i = 0; i < mainWindow.height; i++) {
for (int j = 0; j < mainWindow.width; j++) {
grayLevel = mainWindow.currPixArray[i * mainWindow.width + j] & 0xff; // 后8位为该像素的灰度级
grayArray[grayLevel]++;
}
}
double[] p = new double[256]; // 记录每个灰度级的出现的概率
for (int i = 0; i < 256; i++) {
p[i] = (double) grayArray[i] / (mainWindow.width * mainWindow.height);
}
grayArray[0] = (int) (p[0] * 255 + 0.5);
for (int i = 0; i < 255; i++) {
p[i + 1] += p[i];
grayArray[i + 1] = (int) (p[i + 1] * 255 + 0.5);
}
int oldGray, newGray;
for (int i = 0; i < mainWindow.height; i++) {
for (int j = 0; j < mainWindow.width; j++) {
oldGray = mainWindow.currPixArray[i * mainWindow.width + j] & 0x0000ff;
newGray = grayArray[oldGray];
resPixArray[i * mainWindow.width + j] = 255 << 24 | newGray << 16 | newGray << 8 | newGray;
}
}
mainWindow.currPixArray = resPixArray;
mainWindow.showImage(resPixArray);
}
}

GrayStretchListener.java (图像灰度线性拉伸)

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class GrayStretchListener extends JFrame implements ActionListener {
private MainWindow mainWindow;
private JSlider slider1,slider2,slider3,slider4;
private JLabel label1,label2,label3,label4;
private JButton ok,cancle;
private int a, b, c, d;
public GrayStretchListener(MainWindow mainWindow) {
this.mainWindow = mainWindow;
JPanel panel = new JPanel();
panel.setLayout(null);
slider1 = new JSlider(0, 255);
slider2 = new JSlider(0, 255);
slider3 = new JSlider(0, 255);
slider4 = new JSlider(0, 255);
label1 = new JLabel("127");
label2 = new JLabel("127");
label3 = new JLabel("127");
label4 = new JLabel("127");
panel.add(slider1);
slider1.setBounds(20, 20, 270, 20);
panel.add(label1);
label1.setBounds(290, 20, 30, 20);
panel.add(slider2);
slider2.setBounds(20, 70, 270, 20);
panel.add(label2);
label2.setBounds(290, 70, 30, 20);
panel.add(slider3);
slider3.setBounds(20, 120, 270, 20);
panel.add(label3);
label3.setBounds(290, 120, 30, 20);
panel.add(slider4);
slider4.setBounds(20, 170, 270, 20);
panel.add(label4);
label4.setBounds(290, 170, 30, 20);

slider1.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
label1.setText(String.valueOf(slider1.getValue()));
}
});

slider2.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
label2.setText(String.valueOf(slider2.getValue()));
}
});

slider3.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
label3.setText(String.valueOf(slider3.getValue()));
}
});

slider4.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
label4.setText(String.valueOf(slider4.getValue()));
}
});
ok = new JButton("确定");
panel.add(ok);
ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
a = slider1.getValue();
b = slider2.getValue();
c = slider3.getValue();
d = slider4.getValue();
int[] resultArray = new int[mainWindow.width * mainWindow.height];
int oldGray, newGray;
for (int i = 0; i < mainWindow.height; i++) {
for (int j = 0; j < mainWindow.width; j++) {
oldGray = mainWindow.currPixArray[i * mainWindow.width + j] & 0x0000ff;
if (oldGray < a)
newGray = c;
else if (oldGray < b)
newGray = (int) (1.0 * (d - c) / (b - a) * (oldGray - a) + c);
else
newGray = d;
resultArray[i * mainWindow.width + j] = 255 << 24 | newGray << 16 | newGray << 8 | newGray;
}
}
mainWindow.currPixArray = resultArray;
mainWindow.showImage(resultArray);
dispose();
}
});
ok.setBounds(60, 220, 80, 30);
cancle = new JButton("取消");
panel.add(cancle);
cancle.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
}
});
cancle.setBounds(200, 220, 80, 30);

Container c = getContentPane();
c.setLayout(new BoxLayout(c, BoxLayout.Y_AXIS));
c.add(panel);
this.setVisible(false);
this.setLocationRelativeTo(null);
this.setSize(380, 300);
this.setResizable(false);
this.setTitle("区间设置[a,b]->[c,d]");
this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}

public void actionPerformed(ActionEvent e) {
this.setVisible(true);
}
}

FFT.java (快速傅里叶变换与逆变换算法)

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

public class FFT {
private BufferedImage im;
private Complex [][][] F;
private double [][][] T;

public FFT(BufferedImage image){
this.im = image;
}

// convert RGB int matrix into image
private static BufferedImage RGBtoImage (int[][][]m){
int w = m[0].length;
int h = m[0][0].length;
BufferedImage output_img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
for (int j = 0; j < h; j++){
for (int i = 0; i < w; i++){
int gray=(int)(m[0][i][j]*0.3+m[1][i][j]*0.59+m[2][i][j]*0.11);
Color color = new Color(m[0][i][j],m[1][i][j],m[2][i][j]);
output_img.setRGB(i,j, color.getRGB());
}
}
return output_img;
}

public static Complex[] fft1(Complex[] array) {
int n = array.length;
if (n == 1) return new Complex[] { array[0] };
if (n % 2 != 0) { throw new RuntimeException("n is not a power of 2"); }
Complex[] even = new Complex[n/2];
for (int k = 0; k < n/2; k++) {
even[k] = array[2*k];
}
Complex[] q = fft1(even);
Complex[] odd = even;
for (int k = 0; k < n/2; k++) {
odd[k] = array[2*k + 1];
}
Complex[] r = fft1(odd);
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;
}

public static Complex[][][] fft2(Complex[][][] m){
int w = m[0].length;
int h = m[0][0].length;
Complex[][][] output = new Complex[3][w][h];
Complex [] row = new Complex [w];
Complex [] col = new Complex [h];
for (int k = 0; k <3; k++){
for (int j = 0; j < h; j++){
for (int i = 0; i < w; i++){
row[i] = m[k][i][j];
}
row = fft1(row);
for (int i = 0; i < w; i++)
output[k][i][j] = row[i];
}
for (int i = 0; i < w; i++){
for (int j = 0; j < h; j++){
col[j] = output[k][i][j];
}
col = fft1(col);
for (int j = 0; j < h; j++)
output[k][i][j] = col[j];
}
}
return output;
}

private static Complex[] ifft1(Complex[] x) {
int n = x.length;
Complex[] y = new Complex[n];
for (int i = 0; i < n; i++) {
y[i] = x[i].conjugate();
}
y = fft1(y);
for (int i = 0; i < n; i++) {
y[i] = y[i].conjugate();
}
for (int i = 0; i < n; i++) {
y[i] = y[i].scale(1.0 / n);
}
return y;
}

public static Complex[][][] ifft2 (Complex[][][] m){
int w = m[0].length;
int h = m[0][0].length;
Complex[][][] output = new Complex[3][w][h];
Complex [] row = new Complex [w];
Complex [] col = new Complex [h];
for (int k = 0; k <3; k++){
for (int j = 0; j < h; j++){
for (int i = 0; i < w; i++){
row[i] = m[k][i][j];
}
row = ifft1(row);
for (int i = 0; i < w; i++)
output[k][i][j] = row[i];
}
for (int i = 0; i < w; i++){
for (int j = 0; j < h; j++){
col[j] = output[k][i][j];
}
col = ifft1(col);
for (int j = 0; j < h; j++) {
output[k][i][j] = col[j];
}
}
}
return output;
}

public static int[][][] imgToMatrix(BufferedImage img, int w, int h,boolean expend,boolean RGB){
int w1,h1;
int[][][] output;
if (((w & (w - 1)) != 0 || (h & (h - 1)) != 0) && expend ){ // check if it is w or h is power of 2 int
if ((w & (w - 1)) != 0 ) {w1 = (int)Math.pow(2,Math.ceil(Math.log(w)/Math.log(2)));}
else{w1 = w;}
if ((h & (h - 1)) != 0){h1 = (int)Math.pow(2,Math.ceil(Math.log(h)/Math.log(2)));}
else{h1 = h;}
output = new int [3][w1][h1];
for (int k = 0; k <3; k++){
for (int j = 0; j < h1; j++){
for (int i = 0; i < w1; i++){
output[k][i][j]=0;
}
}
}
}
else{
output = new int[3][w][h];
}
for (int j = 0; j < h; j++){
for (int i = 0; i < w; i++){
int pixel = img.getRGB(i,j);
Color color = new Color(pixel);
int R = color.getRed();
int G = color.getGreen();
int B = color.getBlue();
if(RGB){
output[0][i][j] = R;
output[1][i][j] = G;
output[2][i][j] = B;
}else{
output[0][i][j] = (int)(0.299*R+0.587*G+0.114*B);
output[1][i][j] = (int)(-0.1687*R-0.3313*G+0.5*B+128);
output[2][i][j] = (int)(0.5*R-0.418*G-0.0813*B+128);
}
}
}
return output;
}

//The high frequency values will be shifted to the center in spectrum after applying "shift_to_center"
private static int[][][] shift_to_center(int[][][] matrix){
int w = matrix[0].length;
int h = matrix[0][0].length;
for (int k = 0; k <3; k++){
for (int j = 0; j < h; j++){
for (int i = 0; i < w; i++){
if ((i+j)%2 != 0){
matrix[k][i][j] = ~matrix[k][i][j];
}
}
}
}
return matrix;
}

//convert from int matrix to complex number matrix
private static Complex[][][] getComplexMatrix(int [][][] matrix){
int w = matrix[0].length;
int h = matrix[0][0].length;
Complex [][][] output = new Complex [3][w][h];
for (int k = 0; k <3; k++){
for (int j = 0; j < h; j++){
for (int i = 0; i < w; i++){
output[k][i][j] = new Complex((double)(matrix[k][i][j]), (double)(0));
}
}
}
return output;
}

private static int max(int a, int b){
if (a>=b)
return a;
else
return b;
}

private static int scale(int a){ //keep image values within 0 to 255
if (a<0)
return 0;
else if (a>255)
return 255;
else
return a;
}

public static BufferedImage complexToImg (Complex [][][] matrix, Boolean spectrum){
int w = matrix[0].length;
int h = matrix[0][0].length;
BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
int [][][] m = new int [3][w][h];
int max = 0;
for (int k = 0; k <3; k++){
for (int j = 0; j<h; j++){
for (int i = 0; i<w; i++){
if (spectrum){
m[k][i][j] = (int)Math.log(matrix[k][i][j].abs()+1);
}
else{
m[k][i][j] = (int)matrix[k][i][j].abs();
m[k][i][j] = scale(m[k][i][j]);
}
max = max(max, m[k][i][j]);
}
}
}
if (spectrum){
int C = 255/max; //cofficient C used to scale the image
for (int k = 0; k <3; k++){
for (int j = 0; j<h; j++){
for (int i = 0; i<w; i++){
m[k][i][j] = (int)(m[k][i][j]*C);
}
}
}
}
output = RGBtoImage(m);
return output;
}

private static Complex [][][] crop(Complex [][][] F, int w, int h){
Complex [][][] output = new Complex [3][w][h];
for (int k = 0; k <3; k++){
for (int j = 0; j < h; j++){
for (int i = 0; i < w; i++){
output[k][i][j] = F[k][i][j];
}
}
}
return output;
}

public BufferedImage fft_image(){
int w0 = im.getWidth();
int h0 = im.getHeight();
int [][][] colors = shift_to_center(imgToMatrix(im, w0, h0,true,true));
Complex [][][] f = getComplexMatrix(colors);
F = fft2(f);
return complexToImg(F, true);
}

public BufferedImage ifft_image(){
int w0 = im.getWidth();
int h0 = im.getHeight();
int w = F[0].length;
int h = F[0][0].length;
Complex [][][] filted = ifft2(F);
if (w != w0 || h != h0) filted = crop(filted, w0, h0);
return complexToImg(filted,false);
}

private static double[] fct1(double[] array) {
int n = array.length;
Complex[] fd = new Complex[2*n];
for (int k = 0; k < n; k++) {
fd[k] = new Complex(array[k],0);
}
for (int k = n; k < 2*n; k++) {
fd[k] = new Complex(0,0);
}
Complex[] q = fft1(fd);
double[] des = new double[n];
double dtmp=1/Math.sqrt(n);
des[0]=q[0].re()*dtmp;
dtmp*=Math.sqrt(2);
for(int i=1;i<n;i++)
des[i]=(q[i].re()*Math.cos(i*Math.PI /(n*2))+
q[i].im()*Math.sin(i*Math.PI /(n*2)))*dtmp;
return des;
}

private static double[][][] fct2(int[][][] m,int q){
int w = m[0].length;
int h = m[0][0].length;
double[][][] output = new double[3][w][h];
double [] row = new double [w];
double [] col = new double [h];
for (int k = 0; k <3; k++){
for (int j = 0; j < h; j++){
for (int i = 0; i < w; i++){
row[i] = m[k][i][j];
}
row = fct1(row);
for (int i = 0; i < w; i++)
output[k][i][j] = row[i];
}
for (int i = 0; i < w; i++){
for (int j = 0; j < h; j++){
col[j] = output[k][i][j];
}
col = fct1(col);
for (int j = 0; j < h; j++) {
output[k][i][j] = col[j];
}
}
}

return output;
}

private static double[] ifct1(double[] array){
int n = array.length;
Complex[] fd = new Complex[2*n];
for (int k = 0; k < n; k++) {
fd[k] = (new Complex(array[k]*Math.cos(k*Math.PI /(n*2)),
array[k]*Math.sin(k*Math.PI /(n*2))));
}
for (int k = n; k < 2*n; k++) {
fd[k] = new Complex(0,0);
}
Complex[] q = ifft1(fd);
double[] des = new double[n];
for(int i=0;i<n;i++)
des[i]=(1.0/Math.sqrt(n)-
Math.sqrt(2.0/n))*array[0]+Math.sqrt(8.0*n)*q[i].re();
return des;
}

private static double[][][] ifct2(double[][][] m){
int w = m[0].length;
int h = m[0][0].length;
double[][][] output = new double[3][w][h];
double [] row = new double [w];
double [] col = new double [h];
for (int k = 0; k <3; k++){
for (int j = 0; j < h; j++){
for (int i = 0; i < w; i++){
row[i] = m[k][i][j];
}
row = ifct1(row);
for (int i = 0; i < w; i++)
output[k][i][j] = row[i];
}
for (int i = 0; i < w; i++){
for (int j = 0; j < h; j++){
col[j] = output[k][i][j];
}
col = ifct1(col);
for (int j = 0; j < h; j++)
output[k][i][j] = col[j];
}
}
int[][][] rr= new int[3][w][h];
for (int k = 0; k <3; k++)
for (int j = 0; j < h; j++)
for (int i = 0; i < w; i++){
rr[k][i][j] = (int)m[k][i][j];
}
return output;
}

private BufferedImage ifct_image(){
int w0 = im.getWidth();
int h0 = im.getHeight();
int w = T[0].length;
int h = T[0][0].length;
double [][][] filted = ifct2(T);
int[][][] TT = new int[3][w0][h0];
for (int k = 0; k <3; k++)
for (int j = 0; j < h0; j++)
for (int i = 0; i < w0; i++){
TT[k][i][j] = (int)filted[k][i][j];
}
Complex[][][] d = getComplexMatrix(TT);

Complex[][][] ff = null;
if (w != w0 || h != h0)
ff = crop(d, w0, h0);
else
ff=d;

return complexToImg(ff,false);
}

public BufferedImage[] fct_image(int a,int b,int c,int d){
int w0 = im.getWidth();
int h0 = im.getHeight();
BufferedImage cache = im;
int [][][] colors = imgToMatrix(im, w0, h0,true,true);
T = fct2(colors,1);
int count=0;
for (int j = 0; j < h0; j++)
for (int i = 0; i < w0; i++){
if(i+j>299) {
count++;
T[0][i][j] = 0;
T[1][i][j] = 0;
T[2][i][j] = 0;
}
}
System.out.print(w0+" "+h0+" "+count);
int [][][] TT = new int[3][w0][h0];
for (int k = 0; k <3; k++)
for (int j = 0; j < h0; j++)
for (int i = 0; i < w0; i++){
TT[k][i][j] = (int)T[k][i][j];
}


// BufferedImage jjy=null;
// try{
// jjy=ImageIO.read(new File("E://jjy.jpg"));
// } catch(Exception eee){}
// int[][][] jjjy=imgToMatrix(jjy,jjy.getWidth(),jjy.getHeight(),false,true);
// for (int k = 0; k <3; k++)
// for (int j = 0; j < jjy.getHeight(); j++)
// for (int i = 0; i < jjy.getWidth(); i++){
// T[k][w0-1-i][h0-1-j] += jjjy[k][i][j]/30;
// }

int w1=0,w2=w0,h1=0,h2=h0;
if(b>a && d>c){
w1=a;w2=b;h1=c;h2=d;
}
for (int k = 0; k <3; k++)
for (int j = 0; j < h0; j++)
for (int i = 0; i < w0; i++){
if(i<w1||i>w2||j<h1||j>h2)
T[k][i][j] = 0.0;
}
BufferedImage[] res = new BufferedImage[2];
res[0] = ifct_image();
res[1]=complexToImg(getComplexMatrix(TT),true);
return res;
}

}

fourierListener.java (快速傅里叶变换应用)

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

public class fourierListener implements ActionListener {
private MainWindow mainWindow;
private JFrame frame1 = new JFrame("频谱");
private JFrame frame2 = new JFrame("傅里叶逆变换");
private JLabel label1=new JLabel();
private JLabel label2=new JLabel();

public fourierListener(MainWindow mainWindow){
this.mainWindow = mainWindow;
}

public void actionPerformed(ActionEvent e) {
if(mainWindow.image!=null) {
FFT trans = new FFT(mainWindow.image);
BufferedImage spec = trans.fft_image();
label1.setIcon(new ImageIcon(spec));
Container c1 = frame1.getContentPane();
c1.add(label1);
frame1.pack();
frame1.setVisible(true);
BufferedImage ifft = trans.ifft_image();
label2.setIcon(new ImageIcon(ifft));
Container c2 = frame2.getContentPane();
c2.add(label2);
frame2.pack();
frame2.setVisible(true);
}
}
}

cosineListener.java (基于快速傅里叶变换的快速离散余弦变换)

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

public class cosineListener extends JFrame implements ActionListener {
private MainWindow mainWindow;
private JFrame frame1 = new JFrame("频谱");
private JFrame frame2 = new JFrame("余弦逆变换图像");
private JLabel label5=new JLabel();
private JLabel label6=new JLabel();
private JSlider slider1,slider2,slider3,slider4;
private JLabel label1,label2,label3,label4;
private JButton ok,cancle;
private int a, b, c, d;

public void setWidth(int width){
slider1.setMaximum(width);
slider2.setMaximum(width);
}

public void setHeight(int height){
slider3.setMaximum(height);
slider4.setMaximum(height);
}

public cosineListener(MainWindow mainWindow) {
this.mainWindow = mainWindow;
JPanel panel = new JPanel();
panel.setLayout(null);
slider1 = new JSlider(0,0);
slider2 = new JSlider(0,0);
slider3 = new JSlider(0,0);
slider4 = new JSlider(0,0);
label1 = new JLabel("0");
label2 = new JLabel("0");
label3 = new JLabel("0");
label4 = new JLabel("0");
panel.add(slider1);
slider1.setBounds(20, 20, 270, 20);
panel.add(label1);
label1.setBounds(290, 20, 30, 20);
panel.add(slider2);
slider2.setBounds(20, 70, 270, 20);
panel.add(label2);
label2.setBounds(290, 70, 30, 20);
panel.add(slider3);
slider3.setBounds(20, 120, 270, 20);
panel.add(label3);
label3.setBounds(290, 120, 30, 20);
panel.add(slider4);
slider4.setBounds(20, 170, 270, 20);
panel.add(label4);
label4.setBounds(290, 170, 30, 20);

slider1.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
label1.setText(String.valueOf(slider1.getValue()));
}
});

slider2.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
label2.setText(String.valueOf(slider2.getValue()));
}
});

slider3.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
label3.setText(String.valueOf(slider3.getValue()));
}
});

slider4.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
label4.setText(String.valueOf(slider4.getValue()));
}
});
ok = new JButton("确定");
panel.add(ok);
ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(mainWindow.image!=null) {
a = slider1.getValue();
b = slider2.getValue();
c = slider3.getValue();
d = slider4.getValue();
FFT trans = new FFT(mainWindow.image);
BufferedImage[] spec = trans.fct_image(a,b,c,d);
label5.setIcon(new ImageIcon(spec[1]));
Container c1 = frame1.getContentPane();
c1.add(label5);
frame1.pack();
frame1.setVisible(true);
label6.setIcon(new ImageIcon(spec[0]));
Container c2 = frame2.getContentPane();
c2.add(label6);
frame2.pack();
frame2.setVisible(true);
dispose();
}
}
});
ok.setBounds(60, 220, 80, 30);
cancle = new JButton("取消");
panel.add(cancle);
cancle.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
}
});
cancle.setBounds(200, 220, 80, 30);

Container c = getContentPane();
c.setLayout(new BoxLayout(c, BoxLayout.Y_AXIS));
c.add(panel);
this.setVisible(false);
this.setLocationRelativeTo(null);
this.setSize(380, 300);
this.setResizable(false);
this.setTitle("像素采样[x1,x2]->[y1,y2]");
this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}

public void actionPerformed(ActionEvent e) {
this.setVisible(true);
}

}

RevokeListener.java (撤销操作)

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class RevokeListener implements ActionListener {
private MainWindow mainWindow;

public RevokeListener(MainWindow mainWindow){
this.mainWindow = mainWindow;
}

public void actionPerformed(ActionEvent e) {
mainWindow.currPixArray=mainWindow.sourcePixArray;
mainWindow.showImage(mainWindow.currPixArray);
}
}

blockDCT.java (基于矩阵运算的离散余弦变换)

import Jama.Matrix;

public class blockDCT {
private int QUALITY;
private int N = 8;
private Matrix C = null;
private int[][] Y={
{16,11,10,16,24,40,51,61},
{12,12,14,19,26,58,60,55},
{14,13,16,24,40,57,69,56},
{14,17,22,29,51,87,80,62},
{18,22,37,56,68,109,103,77},
{24,35,55,64,81,104,113,92},
{49,64,78,87,103,121,120,101},
{72,92,95,98,112,100,103,99}
};
private int[][] UV={
{17,18,24,47,99,99,99,99},
{18,21,26,66,99,99,99,99},
{24,26,56,99,99,99,99,99},
{47,66,99,99,99,99,99,99},
{99,99,99,99,99,99,99,99},
{99,99,99,99,99,99,99,99},
{99,99,99,99,99,99,99,99},
{99,99,99,99,99,99,99,99}
};

public blockDCT(int QUALITY){
this.QUALITY = QUALITY;
}

public void initMatrix(){
double tmp[][] = new double[N][N];
for(int j=0;j<N;j++)
tmp[0][j] = Math.sqrt(1.0/N)*Math.cos((Math.PI*0*(j+0.5))/N);
for(int i=1;i<N;i++)
for(int j=0;j<N;j++)
tmp[i][j] = Math.sqrt(2.0/N)*Math.cos((Math.PI*i*(j+0.5))/N);
C = new Matrix(tmp);
}

public int[][][] quantization(double inputData[][][]) {
int outputData[][][] = new int[3][N][N];
for (int i=0; i<N; i++)
for (int j=0; j<N; j++) {
outputData[0][i][j] = (int)Math.round (inputData[0][i][j] / (Y[i][j] * QUALITY/3));
outputData[1][i][j] = (int) Math.round(inputData[1][i][j] / (UV[i][j] * QUALITY/3));
outputData[2][i][j] = (int) Math.round(inputData[2][i][j] / (UV[i][j] * QUALITY/3));
}
return outputData;
}

public int[][][] deQuantization(int[][][] inputData) {
int outputData[][][] = new int[3][N][N];
for (int i=0; i<N; i++)
for (int j=0; j<N; j++) {
outputData[0][i][j] = inputData[0][i][j] * (Y[i][j] * QUALITY/3);
outputData[1][i][j] = inputData[1][i][j] * (UV[i][j] * QUALITY/3);
outputData[2][i][j] = inputData[2][i][j] * (UV[i][j] * QUALITY/3);
}
return outputData;
}

public double[][][] dct(double input[][][]) {
double output[][][] = new double[3][N][N];
for(int i=0;i<3;i++){
Matrix f = new Matrix(input[i]);
Matrix r = C.times(f).times(C.transpose());
output[i]=r.getArray();
}

return output;
}

public double[][][] idct(int input[][][]) {
double output[][][] = new double[3][N][N];
double[][][] dinput = new double[3][N][N];
for(int i=0;i<3;i++)
for(int j=0;j<N;j++)
for(int k=0;k<N;k++)
dinput[i][j][k]=(double) input[i][j][k];
for(int i=0;i<3;i++){
Matrix f = new Matrix(dinput[i]);
Matrix r = C.transpose().times(f).times(C);
output[i]=r.getArray();
}
return output;
}

public int getN(){
return this.N;
}

}

Compress.java (基于离散余弦变换的图像压缩算法)

import Jama.Matrix;

import java.awt.image.BufferedImage;
import static java.awt.image.BufferedImage.TYPE_INT_RGB;

public class Compress {
private blockDCT dct;
private BufferedImage im;

public Compress(BufferedImage image,int QUALITY) {
im=image;
this.dct=new blockDCT(QUALITY);
}

public BufferedImage compress(){
dct.initMatrix();
int rows = im.getWidth();
int columns =im.getHeight();
int row2 = (int)Math.ceil(rows/(dct.getN()*1.0))*dct.getN();
int column2 = (int)Math.ceil(columns/(dct.getN()*1.0))*dct.getN();
int[][][] pixels;
int[][][] pixel2 = new int[3][row2][column2];
double[][][] databuffer = new double[3][dct.getN()][dct.getN()];
double[][][] temp;
int[][][] output= new int[3][row2][column2];
int[][][] quantizedOutput;
int[][][] dequantized;
double[][][] recovered;
BufferedImage imageOutput = new BufferedImage(rows, columns, TYPE_INT_RGB);
pixels = FFT.imgToMatrix(im,rows,columns,false,false);
for(int a=0;a<3;a++)
for (int i = 0; i < rows; i++)
for (int j = 0; j < columns; j++)
pixel2[a][i][j]=pixels[a][i][j]-128;

for(int a=0;a<3;a++)
for (int i = 0; i < row2; i=(i+dct.getN())) {
for (int j = 0; j < column2; j=(j+dct.getN())) {
for (int k = 0; k < dct.getN(); k++) {
for (int l = 0; l < dct.getN(); l++) {
databuffer[a][k][l] = (double) pixel2[a][(k+i)][(j+l)];
}
}
temp = dct.dct(databuffer);
quantizedOutput = dct.quantization(temp);
dequantized = dct.deQuantization(quantizedOutput);
recovered = dct.idct(dequantized);
for (int k = 0; k < dct.getN(); k++) {
for (int l = 0; l < dct.getN(); l++) {
output[a][(k+i)][(j+l)] = (int)recovered[a][(k)][(l)]+128;
if(output[a][(k+i)][(j+l)]>255)output[a][(k+i)][(j+l)]=255;
if(output[a][(k+i)][(j+l)]<0)output[a][(k+i)][(j+l)]=0;
}
}
}
}
for (int i = 0; i < rows; i++)
for (int j = 0; j < columns; j++) {
int R = (int)(output[0][i][j]+1.402*(output[2][i][j]-128));
int G = (int)(output[0][i][j]-0.34414*(output[1][i][j]-128)-0.71414*(output[2][i][j]-128));
int B = (int)(output[0][i][j]+1.772*(output[1][i][j]-128));
if(R>255)R=255;if(R<0)R=0;
if(G>255)G=255;if(G<0)G=0;
if(B>255)B=255;if(B<0)B=0;
int rgb=Math.round((R * 256 + G) * 256 + B);
imageOutput.setRGB(i, j,rgb);
}
return imageOutput;
}
}

compressListener.java (基于离散余弦变换的图像压缩应用)

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.text.DecimalFormat;

public class compressListener extends JFrame implements ActionListener {
private MainWindow mainWindow;
private JSlider slider1;
private JLabel label1;
private JButton ok= new JButton("确定");
private JButton cancle=new JButton("取消");
private JRadioButton PNG = new JRadioButton("png");
private JLabel png = new JLabel("PNG");
private JRadioButton JPG = new JRadioButton("jpg");
private JLabel jpg = new JLabel("JPG");
private JRadioButton BMP = new JRadioButton("bmp");
private JLabel bmp = new JLabel("BMP");
private JRadioButton GIF = new JRadioButton("gif");
private JLabel gif = new JLabel("GIF");
private int a;
private String type;

public compressListener(MainWindow mainWindow) {
this.mainWindow = mainWindow;
JPanel panel = new JPanel();
panel.setLayout(null);
slider1 = new JSlider(1, 10,1);
label1 = new JLabel("1");
panel.add(slider1);
slider1.setBounds(30, 30, 270, 40);
panel.add(label1);
label1.setBounds(310, 30, 40, 40);

slider1.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
label1.setText(String.valueOf(slider1.getValue()));
}
});

panel.add(PNG);
PNG.setBounds(30, 60, 20, 40);
panel.add(png);
png.setBounds(50, 60, 50, 40);
panel.add(BMP);
BMP.setBounds(90, 60, 20, 40);
panel.add(bmp);
bmp.setBounds(110, 60, 50, 40);
panel.add(JPG);
JPG.setBounds(150, 60, 20, 40);
panel.add(jpg);
jpg.setBounds(170, 60, 50, 40);
panel.add(GIF);
GIF.setBounds(210, 60, 20, 40);
panel.add(gif);
gif.setBounds(230, 60, 50, 40);
ButtonGroup group = new ButtonGroup();// 创建单选按钮组
group.add(PNG);
group.add(BMP);
group.add(JPG);
group.add(GIF);
PNG.setSelected(true);

panel.add(ok);
ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (mainWindow.image != null) {
a = slider1.getValue();
if(PNG.isSelected())
type="png";
else if(BMP.isSelected())
type="bmp";
else if(GIF.isSelected())
type="gif";
else
type="jpg";

JFrame frame2 = new JFrame();
JLabel label2 = new JLabel();
Compress trans = new Compress(mainWindow.image,a);
BufferedImage spec = trans.compress();
label2.setIcon(new ImageIcon(spec));
File outputfile = new File("cache."+type);
try {
ImageIO.write(spec, type, outputfile);
}catch (Exception ee){}
File picture = new File("cache."+type);
DecimalFormat df = new DecimalFormat("#.00");
String size = df.format(picture.length()/1024.0);
Container c2 = frame2.getContentPane();
c2.add(label2);
frame2.pack();
frame2.setTitle("压缩后图像 QUALITY="+String.valueOf(a)+" SIZE="+size);
// picture.delete();
frame2.setVisible(true);
a = slider1.getValue();
dispose();
}
}
});
ok.setBounds(60, 120, 80, 30);
panel.add(cancle);
cancle.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
}
});
cancle.setBounds(200, 120, 80, 30);

Container c = getContentPane();
c.setLayout(new BoxLayout(c, BoxLayout.Y_AXIS));
c.add(panel);
this.setVisible(false);
this.setLocationRelativeTo(null);
this.setSize(380, 200);
this.setResizable(false);
this.setTitle("量化参数Q");
this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}

public void actionPerformed(ActionEvent e) {
if (mainWindow.image != null)
ok.setEnabled(true);
else
ok.setEnabled(false);
this.setVisible(true);
}
}

waterMark.java (水印嵌入与提取算法)

import Jama.Matrix;

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

import static java.awt.image.BufferedImage.TYPE_INT_RGB;

public class waterMark {
private blockDCT dct = new blockDCT(8);
private BufferedImage watermark;
private BufferedImage carrier;
private int nwidth,nheight;
private double alpha;
private int[][][] waterRecover;

public waterMark(BufferedImage carrier,double alpha,BufferedImage water){
this.alpha = alpha;
this.carrier = carrier;
this.watermark = water;
}

public waterMark(BufferedImage carrier,double alpha,int nwidth,int nheight){
this.carrier = carrier;
this.alpha = alpha;
this.nwidth = nwidth;
this.nheight = nheight;
}

public double[] feeddback(){
double[] res = new double[3];
res[0]=alpha;
res[1]=nwidth;
res[2]=nheight;
return res;
}

private int[][][] getExpandCarry(int w1,int h1){
int[][][] carryPix = FFT.imgToMatrix(carrier,carrier.getWidth(),carrier.getHeight(),false,true);
int[][][] carryArr = new int[3][w1][h1];
for(int a=0;a<3;a++)
for (int i = 0; i < carrier.getWidth(); i++)
for (int j = 0; j < carrier.getHeight(); j++)
carryArr[a][i][j]=carryPix[a][i][j]-128;
return carryArr;
}

private int[][][] getExpandwater(int w1,int h1){
int[][][] waterPix = FFT.imgToMatrix(watermark,watermark.getWidth(),watermark.getHeight(),false,true);
int w = (int)Math.ceil((watermark.getWidth()+0.0)/(w1/dct.getN()))*(w1/dct.getN());
int h = (int)Math.ceil((watermark.getHeight()+0.0)/(h1/dct.getN()))*(h1/dct.getN());
int[][][] waterArr = new int[3][w][h];
for(int a=0;a<3;a++)
for (int i = 0; i < watermark.getWidth(); i++)
for (int j = 0; j < watermark.getHeight(); j++)
waterArr[a][i][j]=waterPix[a][i][j];
this.nwidth = (w/(w1/dct.getN()));
this.nheight = (h/(h1/dct.getN()));
return waterArr;
}

private int[][][] doubleToInt(double[][][] a){
int[][][] res = new int[3][a[0].length][a[0][0].length];
for(int i=0;i<3;i++)
for(int j=0;j<a[0].length;j++)
for(int k=0;k<a[0][0].length;k++)
res[i][j][k] = (int)a[i][j][k];
return res;
}

private double[][][] addWater(double[][][] carry,int[][][] water,int count){
double[][][] res = carry.clone();
int wnum = water[0].length/nwidth;
int h = ((count-1)/wnum)*nheight;
int w = ((count-h/nheight*wnum)-((count-h/nheight*wnum)/(wnum+1))*wnum-1)*nwidth;
int[] buffer = new int[nwidth*nheight];
for(int a=0;a<3;a++) {
int c = 0;
for (int i = w; i < w + nwidth; i++)
for (int j = h; j < h + nheight; j++) {
buffer[c] = water[a][i][j];
c++;
}
c=0;
loop:for(int i=0;i<dct.getN();i++)
for(int j=0;j<dct.getN();j++){
if(i+j>=3 && i+j<=5) {
res[a][i][j] = alpha*(buffer[c]-128);
c++;
if(c==buffer.length)break loop;
}
}
}
return res;
}

private void minusWater(double[][][] carry,int count,int a){
double[][][] res = carry.clone();
int wnum = waterRecover[0].length/nwidth;
int h = ((count-1)/wnum)*nheight;
int w = ((count-h/nheight*wnum)-((count-h/nheight*wnum)/(wnum+1))*wnum-1)*nwidth;
double[] buffer = new double[nwidth*nheight];
int c = 0;
loop:for(int i=0;i<dct.getN();i++)
for(int j=0;j<dct.getN();j++){
if(i+j>=3 && i+j<=5) {
buffer[c] = carry[a][i][j]/alpha+128;
c++;
if(c==buffer.length)break loop;
}
}
c=0;
for (int i = w; i < w + nwidth; i++)
for (int j = h; j < h + nheight; j++) {
waterRecover[a][i][j] = (int)buffer[c];
c++;
}

}

public BufferedImage insert(){
int rows = carrier.getWidth();
int columns =carrier.getHeight();
int row2 = (int)Math.ceil(rows/(dct.getN()*1.0))*dct.getN();
int column2 = (int)Math.ceil(columns/(dct.getN()*1.0))*dct.getN();
double[][][] databuffer = new double[3][dct.getN()][dct.getN()];
double[][][] temp;
double[][][] temp2;
int[][][] temp3;
int[][][] output= new int[3][row2][column2];
double[][][] recovered;
dct.initMatrix();
BufferedImage imageOutput = new BufferedImage(rows, columns, TYPE_INT_RGB);
int[][][] carryArr = getExpandCarry(row2,column2);
int[][][] waterArr = getExpandwater(row2,column2);
int count=1;
for(int a=0;a<3;a++) {
for (int i = 0; i < row2; i = (i + dct.getN())) {
for (int j = 0; j < column2; j = (j + dct.getN())) {
for (int k = 0; k < dct.getN(); k++) {
for (int l = 0; l < dct.getN(); l++) {
databuffer[a][k][l] = (double) carryArr[a][(k + i)][(j + l)];
}
}
temp = dct.dct(databuffer);
temp2 = addWater(temp, waterArr, count);
count++;
temp3 = doubleToInt(temp2);
recovered = dct.idct(temp3);
for (int k = 0; k < dct.getN(); k++) {
for (int l = 0; l < dct.getN(); l++) {
output[a][(k + i)][(j + l)] = (int) recovered[a][(k)][(l)] + 128;
if (output[a][(k + i)][(j + l)] > 255) output[a][(k + i)][(j + l)] = 255;
if (output[a][(k + i)][(j + l)] < 0) output[a][(k + i)][(j + l)] = 0;
}
}
}
}
count=1;
}
for (int i = 0; i < rows; i++)
for (int j = 0; j < columns; j++) {
int R = (int)output[0][i][j];
int G = (int)output[1][i][j];
int B = (int)output[2][i][j];
if(R>255)R=255;if(R<0)R=0;
if(G>255)G=255;if(G<0)G=0;
if(B>255)B=255;if(B<0)B=0;
int rgb=Math.round((R * 256 + G) * 256 + B);
imageOutput.setRGB(i, j,rgb);
}
return imageOutput;
}

public BufferedImage extract(){
int rows = carrier.getWidth();
int columns =carrier.getHeight();
int row2 = (int)Math.ceil(rows/(dct.getN()*1.0))*dct.getN();
int column2 = (int)Math.ceil(columns/(dct.getN()*1.0))*dct.getN();
int waterW = row2/dct.getN()*nwidth;
int waterH = column2/dct.getN()*nheight;
waterRecover = new int[3][waterW][waterH];
double[][][] databuffer = new double[3][dct.getN()][dct.getN()];
double[][][] temp;
dct.initMatrix();
BufferedImage imageOutput = new BufferedImage(waterW, waterH, TYPE_INT_RGB);
int[][][] carryArr = getExpandCarry(row2,column2);
int count=1;
for(int a=0;a<3;a++) {
for (int i = 0; i < row2; i = (i + dct.getN())) {
for (int j = 0; j < column2; j = (j + dct.getN())) {
for (int k = 0; k < dct.getN(); k++) {
for (int l = 0; l < dct.getN(); l++) {
databuffer[a][k][l] = (double) carryArr[a][(k + i)][(j + l)];
}
}
temp = dct.dct(databuffer);
minusWater(temp, count, a);
count++;
}
}
count=1;
}

for (int i = 0; i < waterW; i++)
for (int j = 0; j < waterH; j++) {
int R = waterRecover[0][i][j];
int G = waterRecover[1][i][j];
int B = waterRecover[2][i][j];
if(R>255)R=255;if(R<0)R=0;
if(G>255)G=255;if(G<0)G=0;
if(B>255)B=255;if(B<0)B=0;
int rgb=Math.round((R * 256 + G) * 256 + B);
imageOutput.setRGB(i, j,rgb);
}
return imageOutput;
}
}

watermarkListener.java (水印嵌入应用)

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.PixelGrabber;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;

public class watermarkListener extends JFrame implements ActionListener {
private MainWindow mainWindow;
private JSlider slider1;
private JLabel label1;
private JButton ok= new JButton("确定");
private JButton cancle=new JButton("取消");
private JRadioButton PNG = new JRadioButton("png");
private JLabel png = new JLabel("PNG");
private JRadioButton JPG = new JRadioButton("jpg");
private JLabel jpg = new JLabel("JPG");
private JRadioButton BMP = new JRadioButton("bmp");
private JLabel bmp = new JLabel("BMP");
private JRadioButton GIF = new JRadioButton("gif");
private JLabel gif = new JLabel("GIF");
private String type;
private BufferedImage water;

public watermarkListener(MainWindow mainWindow) {
this.mainWindow = mainWindow;


JPanel panel = new JPanel();
panel.setLayout(null);
slider1 = new JSlider(1, 100,1);
label1 = new JLabel("0.01");
panel.add(slider1);
slider1.setBounds(30, 30, 270, 40);
panel.add(label1);
label1.setBounds(310, 30, 40, 40);

slider1.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
label1.setText(String.valueOf(slider1.getValue()/100.0));
}
});

panel.add(PNG);
PNG.setBounds(30, 60, 20, 40);
panel.add(png);
png.setBounds(50, 60, 50, 40);
panel.add(BMP);
BMP.setBounds(90, 60, 20, 40);
panel.add(bmp);
bmp.setBounds(110, 60, 50, 40);
panel.add(JPG);
JPG.setBounds(150, 60, 20, 40);
panel.add(jpg);
jpg.setBounds(170, 60, 50, 40);
panel.add(GIF);
GIF.setBounds(210, 60, 20, 40);
panel.add(gif);
gif.setBounds(230, 60, 50, 40);
ButtonGroup group = new ButtonGroup();// 创建单选按钮组
group.add(PNG);
group.add(BMP);
group.add(JPG);
group.add(GIF);
PNG.setSelected(true);

panel.add(ok);
ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (mainWindow.image != null) {
if(PNG.isSelected())
type="png";
else if(BMP.isSelected())
type="bmp";
else if(GIF.isSelected())
type="gif";
else
type="jpg";

JFrame frame2 = new JFrame();
JLabel label2 = new JLabel();
waterMark trans = new waterMark(mainWindow.image,slider1.getValue()/100.0,water);
BufferedImage spec = trans.insert();
label2.setIcon(new ImageIcon(spec));
File outputfile = new File("water."+type);
try {
ImageIO.write(spec, type, outputfile);
}catch (Exception ee){}
Container c2 = frame2.getContentPane();
c2.add(label2);
frame2.pack();
double[] back = trans.feeddback();
frame2.setTitle("水印嵌入完成 alpha="+back[0]+" W="+back[1]+" H="+back[2]);
// picture.delete();
frame2.setVisible(true);
dispose();
}
}
});
ok.setBounds(60, 120, 80, 30);
panel.add(cancle);
cancle.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
}
});
cancle.setBounds(200, 120, 80, 30);

Container c = getContentPane();
c.setLayout(new BoxLayout(c, BoxLayout.Y_AXIS));
c.add(panel);
this.setVisible(false);
this.setLocationRelativeTo(null);
this.setSize(380, 200);
this.setResizable(false);
this.setTitle("水印嵌入参数 alpha");
this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}

public void actionPerformed(ActionEvent e) {
if (mainWindow.image != null)
ok.setEnabled(true);
else
ok.setEnabled(false);
JFileChooser file = new JFileChooser();
String filePath;
int feedback = file.showOpenDialog(null);
if (feedback == JFileChooser.APPROVE_OPTION) {
File target = file.getSelectedFile();
if (target != null) {
filePath = target.getAbsolutePath();
try {
water = ImageIO.read(new File(filePath));
PixelGrabber p;
int[] array = new int[water.getWidth() * water.getHeight()];
try {
p = new PixelGrabber(water, 0, 0, water.getWidth(), water.getHeight(), array, 0, water.getWidth());
if (!p.grabPixels())
try {
throw new Exception();
} catch (Exception e1) {}
} catch (Exception e2){}
} catch (IOException e3) {}
JFrame frame3 = new JFrame();
JLabel label3 = new JLabel();
label3.setIcon(new ImageIcon(water));
Container c3 = frame3.getContentPane();
c3.add(label3);
frame3.pack();
frame3.setTitle("水印图像");
frame3.setVisible(true);
this.setVisible(true);
}
}
}
}

waterrecoverListener.java (水印提取应用)

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

public class waterrecoverListener extends JFrame implements ActionListener {
private MainWindow mainWindow;
private JFrame frame1 = new JFrame("水印图像");
private JLabel label5=new JLabel();
private JLabel label6=new JLabel();
private JSlider slider1,slider2,slider3;
private JLabel label1,label2,label3;
private JButton ok,cancle;
private double a;
private int b, c;

public waterrecoverListener(MainWindow mainWindow) {
this.mainWindow = mainWindow;
JPanel panel = new JPanel();
panel.setLayout(null);
slider1 = new JSlider(1,100,1);
slider2 = new JSlider(1,8,1);
slider3 = new JSlider(1,8,1);
label1 = new JLabel("0.01");
label2 = new JLabel("1");
label3 = new JLabel("1");
panel.add(slider1);
slider1.setBounds(20, 20, 270, 20);
panel.add(label1);
label1.setBounds(290, 20, 30, 20);
panel.add(slider2);
slider2.setBounds(20, 70, 270, 20);
panel.add(label2);
label2.setBounds(290, 70, 30, 20);
panel.add(slider3);
slider3.setBounds(20, 120, 270, 20);
panel.add(label3);
label3.setBounds(290, 120, 30, 20);

slider1.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
label1.setText(String.valueOf(slider1.getValue()/100.0));
}
});

slider2.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
label2.setText(String.valueOf(slider2.getValue()));
}
});

slider3.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
label3.setText(String.valueOf(slider3.getValue()));
}
});

ok = new JButton("确定");
panel.add(ok);
ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(mainWindow.image!=null) {
a = slider1.getValue()/100.0;
b = slider2.getValue();
c = slider3.getValue();
waterMark trans = new waterMark(mainWindow.image,a,b,c);
BufferedImage spec = trans.extract();
label5.setIcon(new ImageIcon(spec));
Container c1 = frame1.getContentPane();
c1.add(label5);
frame1.pack();
frame1.setVisible(true);
dispose();
}
}
});
ok.setBounds(60, 170, 80, 30);
cancle = new JButton("取消");
panel.add(cancle);
cancle.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
}
});
cancle.setBounds(200, 170, 80, 30);

Container c = getContentPane();
c.setLayout(new BoxLayout(c, BoxLayout.Y_AXIS));
c.add(panel);
this.setVisible(false);
this.setLocationRelativeTo(null);
this.setSize(380, 250);
this.setResizable(false);
this.setTitle("参数设置 alpha-W-H");
this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}

public void actionPerformed(ActionEvent e) {
this.setVisible(true);
}

}

edgeListener.java (边缘检测)

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import static java.awt.image.BufferedImage.TYPE_INT_RGB;

public class edgeListener implements ActionListener {
private MainWindow mainWindow;
private String mod;

public edgeListener(MainWindow mainWindow,String mod){
this.mainWindow = mainWindow;
this.mod = mod;
}

public void actionPerformed(ActionEvent e) {
if(mainWindow.image==null)return;
int[][] result = new int[mainWindow.width][mainWindow.height];
int[][] gray = new int[mainWindow.width][mainWindow.height];
for(int j=0;j<mainWindow.width;j++)
for(int k=0;k<mainWindow.height;k++) {
Color pixel = new Color(mainWindow.image.getRGB(j, k));
gray[j][k]=(int)(pixel.getRed()*0.3+pixel.getGreen()*0.59+pixel.getBlue()*0.11);
if(gray[j][k]>255)gray[j][k]=255;
if(gray[j][k]<0)gray[j][k]=0;
}
for(int j=0;j<mainWindow.width;j++)
for(int k=0;k<mainWindow.height;k++){
if(j==0||k==0||j==mainWindow.width-1||k==mainWindow.height-1) {
result[j][k] = 0;
}
else{
if(this.mod.equals("sobel")) {
int gradx = gray[j - 1][k - 1] + 2 * gray[j - 1][k] + gray[j - 1][k + 1]
- gray[j + 1][k - 1] - 2 * gray[j + 1][k] - gray[j + 1][k + 1];
int grady = gray[j - 1][k - 1] + 2 * gray[j][k - 1] + gray[j + 1][k - 1]
- gray[j - 1][k + 1] - 2 * gray[j][k + 1] - gray[j + 1][k + 1];
result[j][k] = (int) Math.sqrt(gradx * gradx + grady * grady);
}else if(this.mod.equals("prewitt")){
int gradx = gray[j - 1][k - 1] + gray[j - 1][k] + gray[j - 1][k + 1]
- gray[j + 1][k - 1] - gray[j + 1][k] - gray[j + 1][k + 1];
int grady = gray[j - 1][k - 1] + gray[j][k - 1] + gray[j + 1][k - 1]
- gray[j - 1][k + 1] - gray[j][k + 1] - gray[j + 1][k + 1];
result[j][k] = (int) Math.sqrt(gradx * gradx + grady * grady);
}else if(this.mod.equals("laplace")){
result[j][k] = Math.abs(gray[j - 1][k]+gray[j + 1][k]+gray[j][k + 1]+gray[j][k - 1]-4*gray[j][k]);
}
if(result[j][k]>255)
result[j][k]=255;
if(result[j][k]<0)
result[j][k]=0;

}
}
BufferedImage imageOutput=new BufferedImage(mainWindow.width, mainWindow.height, TYPE_INT_RGB);
for (int i = 0; i < mainWindow.width; i++)
for (int j = 0; j < mainWindow.height; j++) {
imageOutput.setRGB(i,j,new Color(result[i][j],result[i][j],result[i][j]).getRGB());
}
JFrame frame3 = new JFrame();
JLabel label3 = new JLabel();
label3.setIcon(new ImageIcon(imageOutput));
Container c3 = frame3.getContentPane();
c3.add(label3);
frame3.pack();
frame3.setTitle(this.mod+"边缘检测");
frame3.setVisible(true);
}
}

7 Demo 程序