一、代码结构
二、代码实现
Denoise.java:
package com.xj.ppm.toimg;
import java.awt.FlowLayout;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
/**
* Image noise reduction program via image stacking.
*
* <p>
* Uncomment one pair of dir/prefix lines to denoise different series of images.
* The images are located in the img folder of the project.
*
*/
public class Denoise {
/**
* Entry point of the program.
*
* @param args not used
*/
public static void main(String[] args) {
String dir1 = "main/resources/img/";
// cone nebula
//String dir2 = "1";
//String prefix = "cone_nebula";
// N44F
//String dir2 = "2";
//String prefix = "n44f";
// Orion nebula
//String dir2 = "3";
//String prefix = "orion";
// Carina nebula
//String dir2 = "4";
//String prefix = "wfc3_uvis";
// wallpaper
String dir2 = "wallpaper";
String prefix = "wallpaper";
//分成10个ppm文件
int n = 10;
PpmStacker stacker = new PpmStacker(dir1 + dir2, prefix, n);
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new FlowLayout());
//将stacker.avg转成BufferedImage
BufferedImage buf = stacker.getAverage().asBufferedImage();
//向frame中添加图像
frame.getContentPane().add(new JLabel(new ImageIcon(buf)));
//根据窗口里面的布局及组件的preferredSize来确定frame的最佳大小
frame.pack();
//显示frame
frame.setVisible(true);
/**
* 点击窗口右上角关闭,四种关闭方式:
* 1.DO_NOTHING_ON_CLOSE,(在你点击关闭按钮的时候,不会被关闭,)不执行任何操作。
* 2.HIDE_ON_CLOSE,(当你点击关闭按钮的时候,不会释放内存,只是隐藏该界面,没有真正的关闭,还占有资源)只隐藏界面,setVisible(false)。
* 3.DISPOSE_ON_CLOSE,点击关闭按钮的时候,隐藏并释放窗体,dispose(),当最后一个窗口被释放后,则程序也随之运行结束。
* 4.EXIT_ON_CLOSE,直接关闭应用程序,System.exit(0)。一个main函数对应一整个程序。
*/
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
PpmAsciiReader.java:
package com.xj.ppm.toimg;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
/**
* A reader of PPM ASCII images. This reader can handle blank lines and full
* line comments, but cannot handle comments that appear on the same line as
* image data. This reader has other limitations as described below.
*
* <p>
* The image header must be equal to {@code P3}, otherwise an exception is
* thrown.
*
* <p>
* The image width and height must appear on the same line, otherwise an
* exception is thrown.
*
* <p>
* The maximum value for each color must appear on a separate line, otherwise a
* read error is likely to occur when reading in the pixel color data.
*
* <p>
* Each line of the image pixel data must contain a multiple of 3 unsigned
* integer values.
*
*/
public class PpmAsciiReader {
//图像的magicNumber
private String magicNumber;
//图像的高
private int height;
//图像的宽
private int width;
//图像的maxValue
private int maxValue;
private SimpleImage img;
/**
* Initializes a reader object that reads the PPM file located in the specified
* directory having the specified filename. If the specified file is readable
* then this constructor attempts to read the file as though it were a PPM
* plaintext color image file.
*
* @param dir the directory containing the PPM file
* @param filename the filename of the PPM file
* @throws PpmException if the PPM file is not readable, or if the file is
* readable but contains invalid or unexpected data
*/
public PpmAsciiReader(String dir, String filename) {
//根据src、dir和filename拼接出文件路径
Path path = FileSystems.getDefault().getPath("src", dir, filename);
List<String> lines = null;
try {
//读取指定文件的所有行
lines = Files.readAllLines(path);
} catch (IOException x) {
x.printStackTrace();
throw new PpmException(x.getMessage());
}
//读取magic number
List<String> next = readMagicNumber(lines);
//跳过空格和注释
next = skipBlanksAndComments(next);
//获取图片的width和height
next = readSize(next);
//跳过空格和注释
next = skipBlanksAndComments(next);
//获取max value,即最大颜色值
next = readMaxValue(next);
//初始化一个SimpleImage
this.img = new SimpleImage(this.width, this.height);
//跳过空格和注释
next = skipBlanksAndComments(next);
//读取数据填充到pixels中
readPixels(next);
}
/**
* Attempts to read the magic number from the PPM file returning the remaining
* lines of the file.
*
* @param lines the currently unread lines of text of the PPM file
* @return the remaining lines of text after reading the magic number
* @throws PpmException if the magic number is missing or is not equal to "P3"
*/
private List<String> readMagicNumber(List<String> lines) {
//如果文件为空,则报异常
if (lines.isEmpty()) {
throw new PpmException("missing magic number");
}
//获取magic number,默认都是在第一行 trim()删除字符串的首尾空格
this.magicNumber = lines.get(0).trim();
//如果不是P3则不为正常文件
if (!this.magicNumber.equals("P3")) {
throw new PpmException("unexpected magic number: " + this.magicNumber);
}
//跳过magic number的那一行
return lines.subList(1, lines.size());
}
/**
* Skips over a sequence of blank lines and lines of comments until some image
* information is found returning the remaining lines of the file.
*
* @param lines the currently unread lines of text of the PPM file
* @return the remaining lines of text after reading a sequence of blank lines
* and lines of comments
* @throws PpmException if lines is empty
*/
private List<String> skipBlanksAndComments(List<String> lines) {
if (lines.isEmpty()) {
throw new PpmException("unexpected end of file");
}
//检查每一行,直到找到非空格和非注释的行
for (int i = 0; i < lines.size(); i++) {
//删除第i行的首尾空格
String s = lines.get(i).trim();
//s不能是空格或者以#开头的字符串(以#号开头的字符串为注释)
if (!(s.isEmpty() || s.startsWith("#"))) {
// this line is not blank, return the sublist starting at this line
return lines.subList(i, lines.size());
}
}
//未找到复合条件的行,则返回空list
return lines.subList(lines.size(), lines.size());
}
/**
* Attempts to read the width and height of the image from the file returning
* the remaining lines of the file.
*
* @param lines the currently unread lines of text of the PPM file
* @return the remaining lines of text after reading the image width and height
* @throws PpmException if the width or height is missing
* @throws NumberFormatException if a non-numeric width or height is found
*/
private List<String> readSize(List<String> lines) {
if (lines.isEmpty()) {
throw new PpmException("unexpected end of file");
}
// width and height should be on the first line of the sublist
//删除第一行数据的首尾空格
String s = lines.get(0).trim();
//将第一行数据以空格分割成字符串数组
String[] parts = s.split("\\s+");
//parts数组只会有两个元素,即width和height。多于两个即为异常
if (parts.length != 2) {
throw new PpmException("missing size : " + s);
}
//获取图片的宽
this.width = Integer.parseInt(parts[0]);
//获取图片的高
this.height = Integer.parseInt(parts[1]);
//跳过第一行,将剩余行返回
return lines.subList(1, lines.size());
}
/**
* Attempts to read the maximum color value from the file returning the
* remaining lines of the file.
*
* @param lines the currently unread lines of text of the PPM file
* @return the remaining lines of text after reading the maximum color value
* @throws PpmException if the maximum color value is missing
* @throws NumberFormatException if a non-numeric maximum color value is found
*/
private List<String> readMaxValue(List<String> lines) {
if (lines.isEmpty()) {
throw new PpmException("unexpected end of file");
}
// max color value should be on the first line of the sublist
//删除第一行数据的为首空格
String s = lines.get(0).trim();
//将第一行数据以空格分割成字符串数组
String[] parts = s.split("\\s+");
//parts数组只有一个元素,即max value,否则即为异常
if (parts.length != 1) {
throw new PpmException("missing max value : " + s);
}
//获取max value,即最大颜色值
this.maxValue = Integer.parseInt(parts[0]);
//跳过第一行,将剩余行返回
return lines.subList(1, lines.size());
}
/**
* Tests that the specified value is between 0 and the maximum color value.
*
* @param val a color value to test
* @throws PpmException if the specified value is not between 0 and the maximum
* color value
*/
private void testColorValue(int val) {
if (val < 0) {
throw new PpmException("color value less than zero");
}
if (val > this.maxValue) {
throw new PpmException("color value greater than " + this.maxValue);
}
}
/**
* Attempts to read the pixel color data from the remaining lines of the file.
* This method assumes that there are a multiple of 3 integer values on each
* line (i.e., RGB triplets are not split across multiple lines).
*
* <p>
* The pixels are stored in the SimpleImage object this.img
*
* @param lines the currently unread lines of text of the PPM file
* @throws PpmException if a color value less than 0 or greater than
* the maximum color values is found
* @throws NumberFormatException if a non-numeric color value is found
*/
private void readPixels(List<String> lines) {
// IMPLEMENT THIS METHOD
String[] allPiexls = new String[this.img.width()*this.img.height()*3];
int count = 0;
for(String s : lines) {
//将第一行数据以空格分割成字符串数组
String[] sArr = s.split("\\s+");
for(int i = 0; i < sArr.length; i++) {
//空格跳过
if(sArr[i].trim().isEmpty()) {
continue;
}
//将组成像素的点存入到allPiexls中
allPiexls[count++] = sArr[i];
}
}
//填充piexels
int index = 0;
for(int h = 0; h < this.img.height(); h++) {
for(int w = 0; w < this.img.width(); w++) {
//每三个点表示一个像素,每个点以“$”分隔
String p = allPiexls[index] + "$" + allPiexls[index + 1] + "$" + allPiexls[index + 2];
index+=3;
// p 类似 0$36$82
this.img.piexels[h][w] = p;
}
}
}
/*
* EVERYTHING ELSE IS ALREADY DONE
*/
/**
* Returns the width of the image read by this reader.
*
* @return the width of the image read by this reader
*/
public int width() {
return this.width;
}
/**
* Returns the height of the image read by this reader.
*
* @return the height of the image read by this reader
*/
public int height() {
return this.height;
}
/**
* Returns the magic number of the image read by this reader.
*
* @return the magic number of the image read by this reader
*/
public String magicNumber() {
return this.magicNumber;
}
/**
* Returns the maximum color value of the image read by this reader.
*
* @return the maximum color value of the image read by this reader
*/
public int maxValue() {
return this.maxValue;
}
/**
* Returns the image read by this reader.
*
* @return the image read by this reader
*/
public SimpleImage image() {
return this.img;
}
/**
* Reads a small PPM image of the Queen's tricolor flag and displays the image.
*
* @param args not used
*/
public static void main(String[] args) {
//String filename = "tricolor.ppm";
//String dir = "/main/resources/img";
//文件名
String filename = "grant_hall.ppm";
//文件路径
String dir = "/main/resources/img";
PpmAsciiReader r = new PpmAsciiReader(dir, filename);
//输出文件中的信息
System.out.println("header : " + r.magicNumber());
System.out.println("width : " + r.width());
System.out.println("height : " + r.height());
System.out.println("max : " + r.maxValue());
//设置frame标题
JFrame frame = new JFrame("PpmAsciiReader");
//设置frame窗口最小大小
frame.setMinimumSize(new Dimension(500, 100));
//设置frame布局
frame.getContentPane().setLayout(new FlowLayout());
//获得BufferedImage
BufferedImage buf = r.image().asBufferedImage();
//向frame中添加图像
frame.getContentPane().add(new JLabel(new ImageIcon(buf)));
//根据窗口里面的布局及组件的preferredSize来确定frame的最佳大小
frame.pack();
//显示frame
frame.setVisible(true);
/**
* 点击窗口右上角关闭,四种关闭方式:
* 1.DO_NOTHING_ON_CLOSE,(在你点击关闭按钮的时候,不会被关闭,)不执行任何操作。
* 2.HIDE_ON_CLOSE,(当你点击关闭按钮的时候,不会释放内存,只是隐藏该界面,没有真正的关闭,还占有资源)只隐藏界面,setVisible(false)。
* 3.DISPOSE_ON_CLOSE,点击关闭按钮的时候,隐藏并释放窗体,dispose(),当最后一个窗口被释放后,则程序也随之运行结束。
* 4.EXIT_ON_CLOSE,直接关闭应用程序,System.exit(0)。一个main函数对应一整个程序。
*/
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
PpmException.java:
package com.xj.ppm.toimg;
/**
* Exception class for Assignment 3.
*
*/
public class PpmException extends RuntimeException {
/**
* Constant needed by the Serializable interface.
*/
private static final long serialVersionUID = 1L;
/**
* Initialize this exception with the specified message.
*
* @param msg the exception message
*/
public PpmException(String msg) {
super(msg);
}
}
PpmStacker.java:
package com.xj.ppm.toimg;
import java.util.ArrayList;
import java.util.List;
/**
* A class that uses image stacking to perform noise reduction on images. On
* initialization, a stacker object reads a series of PPM text-based image
* files. A noise reduced image is produced by computing the average color of
* each pixel over all of the images.
*/
public class PpmStacker {
/**
* The noise reduced average image.
*/
private SimpleImage avg;
/**
* The stack of images.
*/
private List<SimpleImage> stack;
/**
* Initializes an image stacker object by reading a series of PPM text-based
* image files and computing a noise reduced image by averaging the pixel colors
* over all of the images. The image files are assumed to be all in the same
* directory and the names of the image files should follow the pattern
* PREFIX_000.ppm, PREFIX_001.ppm, PREFIX_002.ppm, etc., where PREFIX is a
* common filename prefix.
*
* @param dir a directory that the image files are contained in
* @param filePrefix the common filename prefix
* @param n the number of images in the stack
* @throws PpmException if a PPM file in the stack is readable but contains
* invalid or unexpected data, or if images in the stack
* have different sizes
*/
public PpmStacker(String dir, String filePrefix, int n) {
if (n < 0) {
throw new IllegalArgumentException("number of images less than zero");
}
//读取dir路径下的n个filePrefix图像文件存储到stack中
this.stack = readImageStack(dir, filePrefix, n);
//将stack中所有图像文件的像素平均值合成一个SimpleImage,即avg
this.avg = average();
}
/**
* Reads a series of PPM text-based images files and returns the list of read
* images.
*
* @param dir a directory that the image files are contained in
* @param filePrefix the common filename prefix
* @param n the number of images in the stack
* @return a list of images
* @throws PpmException if a PPM file in the stack is readable but contains
* invalid or unexpected data, or if images in the stack
* have different sizes
*/
private static List<SimpleImage> readImageStack(String dir, String filePrefix, int n) {
List<SimpleImage> simpleImages = new ArrayList<>();
PpmAsciiReader r = null;
for(int i = 0; i < n; i++) {
//filename = one_nebula + _00 + i + .ppm
String filename = filePrefix + "_00" + i + ".ppm";
//读取dir路径下的filename文件
r = new PpmAsciiReader(dir, filename);
//添加到list集合中
simpleImages.add(r.image());
}
return simpleImages;
}
/**
* Computes the average image over all of the images in the image stack
* this.stack without modifying any images in this.stack
*
* @return the average image
*/
private SimpleImage average() {
//获得图像的高
int height = this.getImage(0).height();
//获得图像的宽
int width = this.getImage(0).width();
//定义一个SimpleImage图像
SimpleImage s = new SimpleImage(width,height);
//将10个图像的像素平均值填充到SimpleImage
for(int h =0; h < height; h++) {
for(int w = 0; w < width; w++) {
//RGB是red,green, blue的简写,也就是红绿蓝三种颜色。他们是三原色,通过不同的比例相加,以产生多种多样的色光
int r = 0;
int g = 0;
int b = 0;
//因为10个图像,循环10次
for(int i = 0; i < 10; i++) {
// p 类似 0$36$82
String p = this.getImage(i).getPiexel()[h][w];
String[] px = p.split("\\$");
r+= Integer.parseInt(px[0]);
g+= Integer.parseInt(px[1]);
b+= Integer.parseInt(px[2]);
}
//重新组装成 0$36$82 格式
s.piexels[h][w] = r/10 + "$" + g/10 + "$" + b/10;
}
}
return s;
}
/*
* EVERYTHING ELSE IS ALREADY DONE
*/
/**
* Returns the noise reduced averaged image.
*
* @return the noise reduced averaged image
*/
public SimpleImage getAverage() {
return this.avg;
}
/**
* Returns the specified image in the image stack. The first image in the stack
* is image 0.
*
* @param i the index of the image in the image stack to return
* @return the specified image in the image stack
*/
public SimpleImage getImage(int i) {
return this.stack.get(i);
}
}
SimpleImage.java:
package com.xj.ppm.toimg;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
/**
* A simple representation of a color 2D image that allows the user to query the
* dimensions of the image, retrieve an image pixel, and set an image pixel.
*
* <p>
* An image has a size defined by a positive integer width (in pixels) and positive
* integer height (in pixels).
*
* <p>
* Pixels are retrieved and set using 0-based row and column indexes. The upper-left
* corner is the origin of the image (row = 0, column = 0).
*
*/
public class SimpleImage {
private int width;
private int heigth;
//存储像素点的二维数组
public String[][] piexels = null;
/**
* Initialize an image to the specified width and height. The pixels of the
* image are set to {@code Color.BLACK}.
*
* @param width the width of the image
* @param height the height of the image
* @throws IllegalArgumentException if the width or height of the image is less
* than 1
*/
public SimpleImage(int width, int height) {
this.width = width;
this.heigth = height;
this.piexels = new String[height][width];
}
/**
* Initialize an image by copying the specified image. The image has the
* same width and height of the copied image. The image has its own grid
* of pixels, and each pixel is equal to the corresponding pixel in the
* copied image.
*
* @param img an image to copy
*/
public SimpleImage(SimpleImage img) {
// IMPLEMENT THIS CONSTRUCTOR
this.width = img.width;
this.heigth = img.heigth;
for(int h = 0; h < img.heigth ; h++) {
for(int w = 0; w < img.width; w++) {
this.piexels[h][w] = img.getPiexel()[h][w];
}
}
}
public int height() {
return this.heigth;
}
public int width() {
return this.width;
}
public String[][] getPiexel(){
return this.piexels;
}
public Color get(int height, int width) {
String[][] tempP = this.getPiexel();
String p = tempP[height][width];
if(p == null || "".equals(p)) {
throw new PpmException("is empty...");
}
// p 类似 0$36$82
String[] px = p.split("\\$");
return new Color(Integer.parseInt(px[0]),Integer.parseInt(px[1]),Integer.parseInt(px[2]));
}
/**
* IMPLEMENTED FOR YOU.
*
* <p>
* Returns a {@code BufferedImage} having equal pixels as this image. This
* method can be used to provide a suitable image for the Java Standard
* Library classes that are part of the Abstract Window Toolkit.
*
* @return a BufferedImage having equal pixels as this image
*/
public BufferedImage asBufferedImage() {
//定义一个BufferedImage
BufferedImage b = new BufferedImage(this.width(), this.height(), BufferedImage.TYPE_INT_ARGB);
//向BufferedImage中填充数据
for (int i = 0; i < this.height(); i++) {
for (int j = 0; j < this.width(); j++) {
b.setRGB(j, i, this.get(i, j).getRGB());
}
}
return b;
}
/**
* Creates and displays a SimpleImage.
*
* @param args not used
*/
public static void main(String[] args) {
SimpleImage img = new SimpleImage(100, 50);
//文件名
String filename = "grant_hall.ppm";
//文件路径
String dir = "/main/resources/img";
//读取文件
PpmAsciiReader r = new PpmAsciiReader(dir, filename);
img = r.image();
//设置frame标题
JFrame frame = new JFrame("SimpleImage");
//设置frame窗口最小大小
frame.setMinimumSize(new Dimension(500, 100));
//设置frame布局
frame.getContentPane().setLayout(new FlowLayout());
//获得BufferedImage
BufferedImage buf = img.asBufferedImage();
//向frame中添加图像
frame.getContentPane().add(new JLabel(new ImageIcon(buf)));
//根据窗口里面的布局及组件的preferredSize来确定frame的最佳大小
frame.pack();
//显示frame
frame.setVisible(true);
/**
* 点击窗口右上角关闭,四种关闭方式:
* 1.DO_NOTHING_ON_CLOSE,(在你点击关闭按钮的时候,不会被关闭,)不执行任何操作。
* 2.HIDE_ON_CLOSE,(当你点击关闭按钮的时候,不会释放内存,只是隐藏该界面,没有真正的关闭,还占有资源)只隐藏界面,setVisible(false)。
* 3.DISPOSE_ON_CLOSE,点击关闭按钮的时候,隐藏并释放窗体,dispose(),当最后一个窗口被释放后,则程序也随之运行结束。
* 4.EXIT_ON_CLOSE,直接关闭应用程序,System.exit(0)。一个main函数对应一整个程序。
*/
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
NoiseProcessor.java:
package com.xj.ppm.toppm;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.ImageIO;
/**
* @Author : xjfu
* @Date : 2023/2/22 20:36
* @Description : 图像降噪处理程序
*/
public class NoiseProcessor {
private File source;
private File destDir;
public NoiseProcessor(File source, File destDir) {
this.source = source;
this.destDir = destDir;
}
public void process(int numNoiseyImages) throws IOException {
//获取带后缀的文件名
String filename = source.getName();
//将文件名中的后缀截掉(例如".jpb"、".gif"等)
filename = filename.substring(0, filename.length() - 4);
BufferedImage image = ImageIO.read(source);
//获取文件的高
int height = image.getHeight();
//获取文件的宽
int width = image.getWidth();
//定义一个长度为10的字符类型的输出流
PrintWriter[] printers = new PrintWriter[numNoiseyImages];
//将文件头信息写入到printers[i]中
for (int i = 0; i < numNoiseyImages; i++) {
/**
* %s:输出字符串
* %3d:输出三位的数字
* %03d:输出三位的数字,不足用0补全
*/
//拼出ppm类型文件的文件名
String numFilename = String.format("%s_%03d.ppm", filename, i);
//在destDir路径下创建名为numFilename的文件
File destFile = new File(destDir, numFilename);
printers[i] = new PrintWriter(destFile);
//第一行写入“P3”
printers[i].println("P3");
//第二行写入“width height”,即图像的宽和高
printers[i].println(width + " " + height);
//第三行写入“255”,即maxValue
printers[i].println("255");
}
//将经过处理的像素信息写入到printers[i]中
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
//获取图像image坐标为(x,y)的像素
Color pixel = new Color(image.getRGB(x, y));
//将像素pixel根据特定算法分成numNoiseyImages份
Color[] pixels = getNoise(pixel, numNoiseyImages);
//写入到printers[i]文件中
for (int i = 0; i < numNoiseyImages; i++) {
printers[i].println(
pixels[i].getRed() + " " + pixels[i].getGreen()
+ " " + pixels[i].getBlue());
}
}
}
//关闭流
for (PrintWriter p : printers) {
p.close();
}
}
//将像素pixel根据特定算法分成num份
private Color[] getNoise(Color pixel, int num) {
//像素中的R值部分
List<Integer> red = getIntNoise(pixel.getRed(), num);
//像素中的G值部分
List<Integer> green = getIntNoise(pixel.getGreen(), num);
//像素中的B值部分
List<Integer> blue = getIntNoise(pixel.getBlue(), num);
Color[] colors = new Color[num];
for (int i = 0; i < num; i++) {
//通过R、G、B重新组合成一个像素,即RGB
colors[i] = new Color(red.get(i), green.get(i), blue.get(i));
}
return colors;
}
//将val根据特定算法分成num份
private List<Integer> getIntNoise(int val, int num) {
List<Integer> values = new ArrayList<Integer>();
//获取255-val的绝对值
int upDiff = Math.abs(255-val);
int downDiff = val;
int diff;
if (downDiff < upDiff) {
diff = downDiff;
} else {
diff = upDiff;
}
for (int i = 0; i < num; i++) {
if (i%2 == 0) {
//偶数减法
values.add(val - diff);
} else {
//奇数加法
values.add(val + diff);
}
}
//将values中的元素进行随机排列
Collections.shuffle(values);
return values;
}
}
StackCreator.java:
package com.xj.ppm.toppm;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
* @Author : xjfu
* @Date : 2023/2/22 20:36
* @Description : 图像降噪处理程序窗口
*/
public class StackCreator {
public static final int NUM_NOISEY_IMAGES = 10;
private File source;
private File destRoot;
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
StackCreator converter = new StackCreator();
Chooser chooser = converter.new Chooser();
chooser.setVisible(true);
}
private void processFiles() throws IOException {
//图片文件数组
File[] originalImages = source.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
//图片的名字全部转成小写
String tmp = name.toLowerCase();
//判断图片的后缀
return tmp.endsWith("jpg") || tmp.endsWith("gif")
|| tmp.endsWith("bmp") || tmp.endsWith("png");
}
});
for (File original : originalImages) {
//获取带后缀的文件名
String filename = original.getName();
//将文件名中的后缀截掉(例如".jpb"、".gif"等)
filename = filename.substring(0, filename.length() - 4);
File destDir = new File(destRoot, filename);
//判断destDir是否存在,否则创建
if (!destDir.exists()) {
//创建filename名字的文件夹
destDir.mkdir();
}
System.out.println("Processing " + destDir.getCanonicalPath());
try {
NoiseProcessor creator = new NoiseProcessor(original, destDir);
//开始将source文件分成NUM_NOISEY_IMAGES份写入到指定目录下
creator.process(NUM_NOISEY_IMAGES);
} catch (IOException e) {
System.err.println("Error processing " + original);
e.printStackTrace(System.err);
}
}
JOptionPane.showMessageDialog(null, "Done.");
}
class Chooser extends JFrame implements ActionListener {
private static final long serialVersionUID = -8389660714995462075L;
private JTextField sourceDirTF;
private JTextField destDirTF;
private JButton sourceButton;
private JButton destButton;
public Chooser() throws IOException {
//定义一个面板,要求是网格布局
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
//布局为水平排列
constraints.fill = GridBagConstraints.VERTICAL;
constraints.gridx = 0;
constraints.gridy = 0;
//在(0,0)的坐标位置上添加名为Source的Label
panel.add(new JLabel("Source: "), constraints);
source = new File(".");
//声明了一个JTextField
sourceDirTF = new JTextField();
//将source的抽象路径设置为JTextFileld中
sourceDirTF.setText(source.getCanonicalPath());
constraints.gridx++;
//在(1,0)的坐标位置上添加了sourceDirTF
panel.add(sourceDirTF, constraints);
//声明了一个名为Browse的按钮
sourceButton = new JButton("Browse");
//添加一个事件监听器
sourceButton.addActionListener(this);
constraints.gridx++;
//在(2,0)的坐标位置添加一个按钮
panel.add(sourceButton, constraints);
// destination
constraints.gridx = 0;
constraints.gridy++;
//在(0,1)的位置添加一个名为Destination的label
panel.add(new JLabel("Destination: "), constraints);
destRoot = new File(".");
//声明一个JTextField
destDirTF = new JTextField();
//将destRoot的抽象路径设置为JTextFileld中
destDirTF.setText(destRoot.getCanonicalPath());
constraints.gridx++;
//在(1,1)的位置添加一个destDirTF
panel.add(destDirTF, constraints);
//声明了一个名为Browse的按钮
destButton = new JButton("Browse");
//添加监听事件
destButton.addActionListener(this);
constraints.gridx++;
//(2,1)的坐标位置上添加了一个按钮
panel.add(destButton, constraints);
//声明一个名为Process的按钮
JButton processButton = new JButton("Process");
//以匿名内部类的方式添加一个监听事件
processButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
try {
//处理文件的方法
processFiles();
} catch (IOException e) {
e.printStackTrace();
}
}
});
constraints.gridx = 0;
constraints.gridy++;
//(0,2)的坐标位置添加一个按钮
panel.add(processButton, constraints);
//将panel添加到JFrame中
add(panel);
//设置标题
setTitle("图像转ppm文件");
//JFrame窗口大小随组件进行自适应
pack();
//用户单击窗口的关闭按钮时执行此句
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//当创建多个窗口且位置相同时可以重叠
setLocationByPlatform(true);
}
@Override
public void actionPerformed(ActionEvent e) {
File current;
String title;
//判断那个按钮被点击
if (e.getSource() == sourceButton) {
current = source;
title = "Choose source folder";
} else {
current = destRoot;
title = "Choose destination folder";
}
//定义一个文件导航窗口
JFileChooser chooser = new JFileChooser();
//设置导航窗口的标题
chooser.setDialogTitle(title);
//将current设置为默认路径
chooser.setCurrentDirectory(current);
//只能选择文件夹
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
chooser.setAcceptAllFileFilterUsed(false);
//如果点击“打开”,则返回0;如果点击“取消”则返回1
int result = chooser.showOpenDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {
File dir = chooser.getSelectedFile();
try {
//判断那个按钮被点击
if (e.getSource() == sourceButton) {
source = dir;
//将选择的路径赋值给sourceDirTF
sourceDirTF.setText(source.getCanonicalPath());
} else {
destRoot = dir;
//将选择的路径赋值给destDirTF
destDirTF.setText(destRoot.getCanonicalPath());
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
grant_hall.ppm:
tricolor.ppm:
P3
# width height
9 6
# max color value
255
# pixel colors, each triplet is a pixel color red green blue
0 36 82 0 36 82 0 36 82
250 189 15 250 189 15 250 189 15
185 14 49 185 14 49 185 14 49
0 36 82 0 36 82 0 36 82
250 189 15 250 189 15 250 189 15
185 14 49 185 14 49 185 14 49
0 36 82 0 36 82 0 36 82
250 189 15 250 189 15 250 189 15
185 14 49 185 14 49 185 14 49
0 36 82 0 36 82 0 36 82
250 189 15 250 189 15 250 189 15
185 14 49 185 14 49 185 14 49
0 36 82 0 36 82 0 36 82
250 189 15 250 189 15 250 189 15
185 14 49 185 14 49 185 14 49
0 36 82 0 36 82 0 36 82
250 189 15 250 189 15 250 189 15
185 14 49 185 14 49 185 14 49
wallpaper.jpg:
三、运行结果
1.运行PpmAsciiReader中的main方法,将tricolor.ppm文件转成图片
2.运行PpmAsciiReader中的main方法,将grant_hall.ppm文件转成图片
3.运行StackCreator中的main方法,将wallpaper.jpg文件转成10个ppm文件
4.运行Denoise中的main方法,将wallpaper文件夹下的10个ppm文件还原成一个jpg图片
说明:Denoise类就负责图片降噪,即通过特定算法提高图片的清晰度。
四、参考
1.PPM文件格式详解_kinghzkingkkk的博客-CSDN博客_ppm支持多少位
2.PPM文件格式_gengshenghong的博客-CSDN博客_ppm文件
3.PPM文件的正确打开方式_Iking123的博客-CSDN博客_ppm文件怎么打开
4.IntelliJ IDEA使用第三方应用程序打开项目中的某类型文件_棒棒不是糖_的博客-CSDN博客_idea 打开.ppm文件