以下是你提供的模組名稱的中文解釋,假設這是一個影像處理相關的軟體架構:
📦 模組名稱與中文說明
| 模組名稱 | 中文說明 |
|---|---|
DoubleMatrix |
雙精度矩陣處理模組,用於影像濾波、傅立葉轉換等數值運算 |
Image |
影像資料結構,可能包含像素陣列、解析度、色彩格式等資訊 |
Rectangle |
矩形區域定義,用於標記ROI(感興趣區域)、裁剪範圍或物件邊界 |
DFTOutput |
傅立葉轉換結果儲存模組,可能包含頻譜的幅值、相位等 |
ImageProcessing |
影像處理核心模組,執行濾波、邊緣偵測、銳化等演算法 |
ImageTransformer |
影像幾何或色彩轉換模組,例如旋轉、縮放、灰階轉換等 |
Task1test |
任務一的測試模組,用於驗證特定演算法或功能是否正確 |
Task2test |
任務二的測試模組,可能針對傅立葉轉換或影像轉換進行驗證 |
這樣的架構看起來非常模組化,適合進行影像處理的教學、展示或工程專案。如果你正在設計一個展示用的系統,我可以幫你補上 README 文件、模擬腳本、或是中英文註解的程式碼。你想針對哪個模組深入整理或優化呢?
1 DoubleMatrix
DoubleMatrix
這段 Java 程式碼定義了一個不可變的
DoubleMatrix類別,用來表示雙精度浮點數的矩陣。以下是中文解釋與結構分析:📘 類別說明:
DoubleMatrix這是一個用來表示
double類型的二維矩陣的資料結構,設計上強調:
不可變性(
final類別)數值精度比較(使用
epsilon = 1e-7)資料完整性保證(透過表示不變式)
🧩 類別成員與功能
成員/方法 中文說明 epsilon比較兩個矩陣是否相等時使用的誤差容忍值(10⁻⁷) matrix儲存矩陣資料的二維陣列 columns/rows矩陣的欄數與列數(不可變) DoubleMatrix(double[][] _input)建構子,從輸入的二維陣列建立矩陣,並進行深拷貝 get(int row, int col)取得指定位置的元素值 getRows()/getColumns()取得矩陣的列數與欄數 equals(Object o)比較兩個矩陣是否相等,允許微小誤差 hashCode()根據矩陣大小產生雜湊碼(簡單乘積) 🔒 表示不變式(Representation Invariant)
這些條件保證矩陣的結構正確:
rows >= 1,columns >= 1
matrix.length == rows
matrix[0].length == columns✅ 設計優點
使用
Arrays.stream(...).map(...).toArray(...)進行深拷貝,避免外部修改原始陣列。明確的錯誤處理(例如 null 或空陣列檢查)。
支援矩陣相等比較,考慮浮點誤差。
如果你想進一步擴充這個類別,例如加入矩陣加法、轉置、乘法或是支援 DFT 輸出格式,我可以幫你設計模組化的介面與測試案例。是否要我幫你加上中文註解版本或撰寫 README 文件來教學使用這個類別?
package ca.ubc.ece.cpen221.ip.core;
import java.util.Arrays;
/**
* This datatype represents a matrix of <code>double</code> values.
* <p>
* The constant fields <code>width</code> and <code>height</code> provide
* the dimensions of the matrix, and it is possible to compare two DoubleMatrices for
* equality using a set <code>epsilon</code> value of 10^(-7).
*/
public final class DoubleMatrix {
public static final double epsilon = 1e-7;
private double[][] matrix;
public final int columns;
public final int rows;
/*
Abstraction Function:
DoubleMatrix represents a matrix of double-precision values.
The matrix has dimensions height * width.
Representation Invariant:
rows >= 1
columns >= 1
matrix.length == rows
matrix[0].length == columns
*/
/**
* Create an instance of DoubleMatrix from a two-dimensional array.
*
* @param _input is not null, and _input has at least one row and at least one column.
*/
public DoubleMatrix(double[][] _input) {
if (_input == null) {
throw new IllegalArgumentException("input cannot be null");
}
rows = _input.length;
if (rows == 0) {
throw new IllegalArgumentException("matrix has to have at least one row");
}
columns = _input[0].length;
if (columns == 0) {
throw new IllegalArgumentException("matrix has to have at least one column");
}
matrix = Arrays.stream(_input).map(double[]::clone).toArray(double[][]::new);
}
/**
* Return the entry at (row, col)
* @param row the row of the entry to return, 0 <= row < rows
* @param col the column of the entry to return, 0 <= col < columns
* @return the entry at location (row, col)
*/
public double get(int row, int col) {
return matrix[row][col];
}
/**
* Obtain the number of rows in the matrix
* @return the number of rows in the matrix
*/
public int getRows() {
return rows;
}
/**
* Obtain the number of columns in the matrix
* @return the number of columns in the matrix
*/
public int getColumns() {
return columns;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof DoubleMatrix)) {
return false;
}
DoubleMatrix other = (DoubleMatrix) o;
if (columns != other.columns || rows != other.rows) {
return false;
}
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
if (Math.abs(matrix[i][j] - other.matrix[i][j]) > epsilon) {
return false;
}
}
}
return true;
}
@Override
public int hashCode() {
return columns * rows;
}
}
2 Image
🖼 類別功能說明(中文)
這個類別提供操作影像中「單一像素」的方法,使用 RGB 色彩格式(不支援透明度 alpha 通道)。
📌 功能摘要:
- 支援的影像格式:可讀取 PNG、GIF、JPEG 檔案,也可建立指定尺寸的空白影像。
- 像素座標系統:
- 預設原點為左上角
(0, 0),符合影像處理常見慣例。 - 可透過
setOriginLowerLeft()方法將原點改為左下角。
- 預設原點為左上角
- 像素操作方式:
- 使用
get()/set()方法搭配Color物件來讀取或設定像素顏色。 - 使用
getRGB()/setRGB()方法以 32 位元int編碼 RGB 色彩,避免建立暫時的Color物件。
- 使用
🎨 RGB 編碼與解碼:
-
解碼 RGB:
java
int r = (rgb >> 16) & 0xFF; int g = (rgb >> 8) & 0xFF; int b = (rgb >> 0) & 0xFF; -
編碼 RGB:
java
int rgb = (r << 16) + (g << 8) + (b << 0);
🧠 記憶體使用量:
- 一張 W × H 的影像約使用
4 × W × H位元組(每個像素佔 32 位元,即 4 bytes)。
這類設計非常適合用於影像處理教學、像素級操作、或是自訂濾鏡開發。如果你需要幫忙撰寫這個類別的中文註解版本、測試案例、或是整合進你的模組化系統,我可以幫你補齊。是否要我幫你整理成 showcase-ready 的教學模組?
package ca.ubc.ece.cpen221.ip.core;
/******************************************************************************
* Data type for manipulating individual pixels of an image. The original
* image can be read from a file in JPG, GIF, or PNG format, or the
* user can create a blank image of a given dimension. Includes methods for
* displaying the image in a window on the screen or saving to a file.
*
* Remarks
* -------
* - pixel (x, y) is column x and row y, where (0, 0) is upper left
*
******************************************************************************/
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import java.awt.Color;
import java.awt.FileDialog;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* This class provides methods for manipulating individual pixels of
* an image using the RGB color format. The alpha component (for transparency)
* is not currently supported.
* The original image can be read from a {@code PNG}, {@code GIF},
* or {@code JPEG} file or the user can create a blank image of a given dimension.
* This class includes methods for displaying the image in a window on
* the screen or saving it to a file.
* <p>
* Pixel (<em>col</em>, <em>row</em>) is column <em>col</em> and row <em>row</em>.
* By default, the origin (0, 0) is the pixel in the top-left corner,
* which is a common convention in image processing.
* The method {@link #setOriginLowerLeft()} changes the origin to the lower left.
* <p>
* The {@code get()} and {@code set()} methods use {@link Color} objects to get
* or set the color of the specified pixel.
* The {@code getRGB()} and {@code setRGB()} methods use a 32-bit {@code int}
* to encode the color, thereby avoiding the need to create temporary
* {@code Color} objects. The red (R), green (G), and blue (B) components
* are encoded using the least significant 24 bits.
* Given a 32-bit {@code int} encoding the color, the following code extracts
* the RGB components:
* <blockquote><pre>
* int r = (rgb >> 16) & 0xFF;
* int g = (rgb >> 8) & 0xFF;
* int b = (rgb >> 0) & 0xFF;
* </pre></blockquote>
* Given the RGB components (8-bits each) of a color,
* the following statement packs it into a 32-bit {@code int}:
* <blockquote><pre>
* int rgb = (r << 16) + (g << 8) + (b << 0);
* </pre></blockquote>
* <p>
* A <em>W</em>-by-<em>H</em> image uses ~ 4 <em>W H</em> bytes of memory,
* since the color of each pixel is encoded as a 32-bit <code>int</code>.
* <p>
*/
public final class Image implements ActionListener {
private final int width, height; // width and height
private BufferedImage image; // the rasterized image
private JFrame frame; // on-screen view
private String filename; // name of file
private boolean isOriginUpperLeft = true; // location of origin
/**
* Creates a {@code width}-by-{@code height} image, with {@code width} columns
* and {@code height} rows, where each pixel is black.
*
* @param width the width of the image
* @param height the height of the image
* @throws IllegalArgumentException if {@code width} is negative or zero
* @throws IllegalArgumentException if {@code height} is negative or zero
*/
public Image(int width, int height) {
if (width <= 0) {
throw new IllegalArgumentException("width must be positive");
}
if (height <= 0) {
throw new IllegalArgumentException("height must be positive");
}
this.width = width;
this.height = height;
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// set to TYPE_INT_ARGB here and in next constructor to support transparency
}
/**
* Creates a new image that is a deep copy of the argument image.
*
* @param image the image to copy
* @throws IllegalArgumentException if {@code image} is {@code null}
*/
public Image(Image image) {
if (image == null) {
throw new IllegalArgumentException("constructor argument is null");
}
width = image.width();
height = image.height();
this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
filename = image.filename;
isOriginUpperLeft = image.isOriginUpperLeft;
for (int col = 0; col < width(); col++) {
for (int row = 0; row < height(); row++) {
this.image.setRGB(col, row, image.image.getRGB(col, row));
}
}
}
/**
* Creates a image by reading an image from a file or URL.
*
* @param name the name of the file (.png, .gif, or .jpg) or URL.
* @throws IllegalArgumentException if cannot read image
* @throws IllegalArgumentException if {@code name} is {@code null}
*/
public Image(String name) {
if (name == null) {
throw new IllegalArgumentException("constructor argument is null");
}
this.filename = name;
try {
// try to read from file in working directory
File file = new File(name);
if (file.isFile()) {
image = ImageIO.read(file);
} else {
// resource relative to .class file
URL url = getClass().getResource(filename);
// resource relative to classloader root
if (url == null) {
url = getClass().getClassLoader().getResource(name);
}
// or URL from web
if (url == null) {
url = new URL(name);
}
image = ImageIO.read(url);
}
if (image == null) {
throw new IllegalArgumentException("could not read image: " + name);
}
width = image.getWidth(null);
height = image.getHeight(null);
}
catch (IOException ioe) {
throw new IllegalArgumentException("could not open image: " + name, ioe);
}
}
/**
* Creates a image by reading the image from a PNG, GIF, or JPEG file.
*
* @param file the file
* @throws IllegalArgumentException if cannot read image
* @throws IllegalArgumentException if {@code file} is {@code null}
*/
public Image(File file) {
if (file == null) {
throw new IllegalArgumentException("constructor argument is null");
}
try {
image = ImageIO.read(file);
}
catch (IOException ioe) {
throw new IllegalArgumentException("could not open file: " + file, ioe);
}
if (image == null) {
throw new IllegalArgumentException("could not read file: " + file);
}
width = image.getWidth(null);
height = image.getHeight(null);
filename = file.getName();
}
/**
* Returns the monochrome luminance of the given color as an intensity
* between 0.0 and 255.0 using the NTSC formula
* Y = 0.299*r + 0.587*g + 0.114*b. If the given color is a shade of gray
* (r = g = b), this method is guaranteed to return the exact grayscale
* value (an integer with no floating-point roundoff error).
*
* @param color the color to convert
* @return the monochrome luminance (between 0.0 and 255.0)
*/
public static double intensity(Color color) {
int r = color.getRed();
int g = color.getGreen();
int b = color.getBlue();
if (r == g && r == b) {
return r; // to avoid floating-point issues
}
return 0.299 * r + 0.587 * g + 0.114 * b;
}
/**
* Returns a grayscale version of the given color as a {@code Color} object.
*
* @param color the {@code Color} object to convert to grayscale
* @return a grayscale version of {@code color}
*/
public static Color toGray(Color color) {
int y = (int) (Math.round(intensity(color))); // round to nearest int
Color gray = new Color(y, y, y);
return gray;
}
/**
* Are the two given colors compatible? Two colors are compatible if the
* the difference in their monochrome luminances is at least 128.0).
*
* @param a one color
* @param b the other color
* @return {@code true} if colors {@code a} and {@code b} are compatible;
* {@code false} otherwise
*/
public static boolean areCompatible(Color a, Color b) {
return Math.abs(intensity(a) - intensity(b)) >= 128.0;
}
/**
* Unit tests this {@code Image} data type.
* Reads a image specified by the command-line argument,
* and shows it in a window on the screen.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
Image image = new Image(args[0]);
System.out.printf("%d-by-%d\n", image.width(), image.height());
image.show();
}
/**
* Returns a {@link JLabel} containing this image, for embedding in a {@link JPanel},
* {@link JFrame} or other GUI widget.
*
* @return the {@code JLabel}
*/
public JLabel getJLabel() {
if (image == null) {
return null; // no image available
}
ImageIcon icon = new ImageIcon(image);
return new JLabel(icon);
}
/**
* Sets the origin to be the upper left pixel. This is the default.
*/
public void setOriginUpperLeft() {
isOriginUpperLeft = true;
}
/**
* Sets the origin to be the lower left pixel.
*/
public void setOriginLowerLeft() {
isOriginUpperLeft = false;
}
/**
* Displays the image in a window on the screen.
*/
// getMenuShortcutKeyMask() deprecated in Java 10 but its replacement
// getMenuShortcutKeyMaskEx() is not available in Java 8
@SuppressWarnings("deprecation")
public void show() {
// create the GUI for viewing the image if needed
if (frame == null) {
frame = new JFrame();
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("File");
menuBar.add(menu);
JMenuItem menuItem1 = new JMenuItem(" Save... ");
menuItem1.addActionListener(this);
menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
menu.add(menuItem1);
frame.setJMenuBar(menuBar);
frame.setContentPane(getJLabel());
// f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
if (filename == null) {
frame.setTitle(width + "-by-" + height);
} else {
frame.setTitle(filename);
}
frame.setResizable(false);
frame.pack();
frame.setVisible(true);
}
// draw
frame.repaint();
}
/**
* Returns the height of the image.
*
* @return the height of the image (in pixels)
*/
public int height() {
return height;
}
/**
* Returns the width of the image.
*
* @return the width of the image (in pixels)
*/
public int width() {
return width;
}
private void validateRowIndex(int row) {
if (row < 0 || row >= height()) {
throw new IllegalArgumentException(
"row index must be between 0 and " + (height() - 1) + ": " + row);
}
}
private void validateColumnIndex(int col) {
if (col < 0 || col >= width()) {
throw new IllegalArgumentException(
"column index must be between 0 and " + (width() - 1) + ": " + col);
}
}
/**
* Returns the color of pixel ({@code col}, {@code row}) as a {@link java.awt.Color}.
*
* @param col the column index
* @param row the row index
* @return the color of pixel ({@code col}, {@code row})
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
*/
public Color get(int col, int row) {
validateColumnIndex(col);
validateRowIndex(row);
int rgb = getRGB(col, row);
return new Color(rgb);
}
/**
* Returns the color of pixel ({@code col}, {@code row}) as an {@code int}.
* Using this method can be more efficient than {@link #get(int, int)} because
* it does not create a {@code Color} object.
*
* @param col the column index
* @param row the row index
* @return the integer representation of the color of pixel ({@code col}, {@code row})
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
*/
public int getRGB(int col, int row) {
validateColumnIndex(col);
validateRowIndex(row);
if (isOriginUpperLeft) {
return image.getRGB(col, row);
} else {
return image.getRGB(col, height - row - 1);
}
}
/**
* Sets the color of pixel ({@code col}, {@code row}) to given color.
*
* @param col the column index
* @param row the row index
* @param color the color
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
* @throws IllegalArgumentException if {@code color} is {@code null}
*/
public void set(int col, int row, Color color) {
validateColumnIndex(col);
validateRowIndex(row);
if (color == null) {
throw new IllegalArgumentException("color argument is null");
}
int rgb = color.getRGB();
setRGB(col, row, rgb);
}
/**
* Sets the color of pixel ({@code col}, {@code row}) to given color.
*
* @param col the column index
* @param row the row index
* @param rgb the integer representation of the color
* @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
*/
public void setRGB(int col, int row, int rgb) {
validateColumnIndex(col);
validateRowIndex(row);
if (isOriginUpperLeft) {
image.setRGB(col, row, rgb);
} else {
image.setRGB(col, height - row - 1, rgb);
}
}
/**
* Returns true if this image is equal to the argument image.
*
* @param other the other image
* @return {@code true} if this image is the same dimension as {@code other}
* and if all pixels have the same color; {@code false} otherwise
*/
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other == null) {
return false;
}
if (other.getClass() != this.getClass()) {
return false;
}
Image that = (Image) other;
if (this.width() != that.width()) {
return false;
}
if (this.height() != that.height()) {
return false;
}
for (int col = 0; col < width(); col++) {
for (int row = 0; row < height(); row++) {
if (this.getRGB(col, row) != that.getRGB(col, row)) {
return false;
}
}
}
return true;
}
/**
* Returns a string representation of this image.
* The result is a <code>width</code>-by-<code>height</code> matrix of pixels,
* where the color of a pixel is represented using 6 hex digits to encode
* the red, green, and blue components.
*
* @return a string representation of this image
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(width + "-by-" + height + " image (RGB values given in hex)\n");
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
int rgb = 0;
if (isOriginUpperLeft) {
rgb = image.getRGB(col, row);
} else {
rgb = image.getRGB(col, height - row - 1);
}
sb.append(String.format("#%06X ", rgb & 0xFFFFFF));
}
sb.append("\n");
}
return sb.toString().trim();
}
/**
* This operation is not supported because pictures are mutable.
*
* @return does not return a value
* @throws UnsupportedOperationException if called
*/
public int hashCode() {
throw new UnsupportedOperationException(
"hashCode() is not supported because pictures are mutable");
}
/**
* Saves the image to a file in either PNG or JPEG format.
* The filetype extension must be either .png or .jpg.
*
* @param name the name of the file
* @throws IllegalArgumentException if {@code name} is {@code null}
*/
public void save(String name) {
if (name == null) {
throw new IllegalArgumentException("argument to save() is null");
}
save(new File(name));
filename = name;
}
/**
* Saves the image to a file in a PNG or JPEG image format.
*
* @param file the file
* @throws IllegalArgumentException if {@code file} is {@code null}
*/
public void save(File file) {
if (file == null) {
throw new IllegalArgumentException("argument to save() is null");
}
filename = file.getName();
if (frame != null) {
frame.setTitle(filename);
}
String suffix = filename.substring(filename.lastIndexOf('.') + 1);
if ("jpg".equalsIgnoreCase(suffix) || "png".equalsIgnoreCase(suffix)) {
try {
ImageIO.write(image, suffix, file);
}
catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("Error: filename must end in .jpg or .png");
}
}
/**
* Opens a save dialog box when the user selects "Save As" from the menu.
*/
@Override
public void actionPerformed(ActionEvent e) {
FileDialog chooser = new FileDialog(frame,
"Use a .png or .jpg extension", FileDialog.SAVE);
chooser.setVisible(true);
if (chooser.getFile() != null) {
save(chooser.getDirectory() + File.separator + chooser.getFile());
}
}
}
3 Rectangle
這段 Java 程式碼定義了一個不可變的 Rectangle 類別,用來表示位於二維平面正象限中的矩形。以下是詳細的中文解釋與設計分析:
🧱 類別名稱:Rectangle(矩形)
這個類別的設計目的是用來表示影像處理中常見的矩形區域,例如 ROI(Region of Interest)或裁剪範圍。它是 不可變的,也就是一旦建立後,座標值無法修改。
📐 成員變數說明
| 欄位名稱 | 中文說明 |
|---|---|
xTopLeft |
左上角的 x 座標(必須 ≥ 0) |
yTopLeft |
左上角的 y 座標(必須 ≥ 0) |
xBottomRight |
右下角的 x 座標(必須 > xTopLeft) |
yBottomRight |
右下角的 y 座標(必須 > yTopLeft) |
🔒 表示不變式(Representation Invariant)
這些條件保證矩形的幾何合法性:
0 <= xTopLeft < xBottomRight0 <= yTopLeft < yBottomRight
這表示矩形必須有正面積,且位於第一象限。
🧰 建構子邏輯
java
public Rectangle(int _xTopLeft, int _yTopLeft, int _xBottomRight, int _yBottomRight)
- 檢查輸入座標是否合法。
- 若不合法(例如負值或右下角不在左上角右下方),則丟出
IllegalArgumentException。 - 若合法,則初始化四個座標欄位。
✅ 設計優點
- 不可變性:使用
final欄位,確保物件建立後不會被修改,提升安全性與可預測性。 - 清楚的抽象函數與不變式:有助於教學與維護。
- 錯誤防範:建構子中加入輸入驗證,避免建立非法矩形。
如果你打算將這個類別用於影像處理模組(例如裁剪、標記、或區域分析),我可以幫你擴充功能,例如:
- 計算面積
- 判斷是否重疊
- 轉換為
Image模組中的像素範圍 - 加入
toString()或equals()方法以便測試與除錯
需要我幫你加上中文註解版本或撰寫教學文件嗎?我也可以幫你設計測試案例來驗證矩形合法性與邊界行為。
package ca.ubc.ece.cpen221.ip.core;
/**
* This datatype represents an <strong>immutable</strong> Rectangle
* in the positive quadrant of the 2D plane. These rectangles match
* standard image representations with the coordinate values for the
* top-left corner being smaller than the coordinate values for the
* bottom-right corner.
*
* After an instance is created, the coordinates of the top-left and bottom-right
* corners can be accessed using the constant fields
* <code>xTopLeft, yTopLeft, xBottomRight, yBottomRight</code>.
*/
public class Rectangle {
public final int xTopLeft, yTopLeft;
public final int xBottomRight, yBottomRight;
/*
Abstraction Function: Represents a rectangle using its top-left corner and bottom-right
corner. xTopLeft is the x-coordinate of the top-left corner and yTopLeft is the y-coordinate
of the top-left corner. xBottomRight and yBottom right are the x- and y- coordinates
of the bottom-right corner.
Representation Invariant:
0 <= xTopLeft < xBottomRight
0 <= yTopLeft < yBottomRight
*/
/**
* Create a new Rectangle.
*
* @param _xTopLeft: is the x coordinate of the top-left corner and should be >= 0
* @param _yTopLeft: is the y coordinate of the top-left corner and should be >= 0
* @param _xBottomRight: is the x coordinate of the bottom-right corner and should be > _xTopLeft
* @param _yBottomRight: is the y coordinate of the bottom-right corner and should be > _yTopLeft
*/
public Rectangle(int _xTopLeft, int _yTopLeft, int _xBottomRight, int _yBottomRight) {
if (_xTopLeft < 0 || _yTopLeft < 0 ||
_xBottomRight <= _xTopLeft || _yBottomRight <= _yTopLeft) {
throw new IllegalArgumentException("Invalid rectangle specified.");
}
xTopLeft = _xTopLeft;
yTopLeft = _yTopLeft;
xBottomRight = _xBottomRight;
yBottomRight = _yBottomRight;
}
}
4 DFTOutput
這段 Java 程式碼定義了一個 DFTOutput 類別,用來表示空間離散傅立葉轉換(DFT)的輸出結果。以下是中文解析與設計重點:
📦 類別名稱:DFTOutput(離散傅立葉轉換輸出)
此類別封裝了 DFT 的兩個核心結果:
- 振幅矩陣(Amplitude)
- 相位矩陣(Phase)
這種表示方式是將複數形式的 DFT 結果拆解為兩個 DoubleMatrix,便於後續影像處理與視覺化。
🧩 類別成員與功能
| 成員/方法 | 中文說明 |
|---|---|
amplitude |
儲存 DFT 的振幅部分(模長) |
phase |
儲存 DFT 的相位部分(角度) |
DFTOutput(double[][] _amplitude, double[][] _phase) |
建構子,從兩個二維陣列建立振幅與相位矩陣 |
equals(Object o) |
比較兩個 DFTOutput 是否相等(振幅與相位皆相同) |
hashCode() |
使用振幅矩陣的雜湊碼作為整體雜湊值 |
🔒 表示不變式(Representation Invariant)
amplitude.width == phase.widthamplitude.height == phase.height
這保證兩個矩陣在維度上完全一致,才能對應每個像素的頻率成分。
🧠 設計重點
- 使用
DoubleMatrix類別來封裝數值矩陣,確保資料不可變且具備比較功能。 - 建構子中進行維度一致性檢查,避免錯誤輸入。
- 支援
.equals()方法,便於測試與資料比對。 - 使用
.hashCode()方法支援雜湊結構(例如 HashMap)。
✅ 延伸建議
若你打算進一步處理 DFT 結果,例如:
- 可視化頻譜(使用 log scale)
- 反轉 DFT(IDFT)
- 濾波器設計(低通、高通、帶通)
我可以幫你設計對應的模組與測試案例,並補上中英文註解與 README 文件。如果你希望這個類別能支援複數形式或與 Image 類別整合,也可以一起規劃。要不要我幫你補上 getAmplitude() / getPhase() 方法來讓外部模組存取這些矩陣?這樣會更模組化。
package ca.ubc.ece.cpen221.ip.mp;
import ca.ubc.ece.cpen221.ip.core.DoubleMatrix;
/**
* This datatype represents the output of a spatial Discrete Fourier Transform,
* and holds the amplitude and phase matrix obtained from the DFT.
*/
public class DFTOutput {
private DoubleMatrix amplitude;
private DoubleMatrix phase;
/*
Abstraction Function:
Represents the output of the (spatial) DFT applied to an image.
amplitude is a matrix that represents the amplitude portion of the DFT output.
phase is a matrix that represents the phase portion of the DFT output.
Note:
A DFT can usually be represented by a matrix of complex numbers but
we use the amplitude & phase representation using two matrices.
Representation Invariant:
amplitude.width == phase.width
amplitude.height == phase.width
*/
/**
* Create a new DFTOutput instance.
*
* @param _amplitude is not null
* @param _phase is not null, and is equal in dimensions to _amplitude
*/
public DFTOutput(double[][] _amplitude, double[][] _phase) {
amplitude = new DoubleMatrix(_amplitude);
phase = new DoubleMatrix(_phase);
if (amplitude.columns != phase.columns || amplitude.rows != phase.rows) {
throw new IllegalArgumentException(
"amplitude and phase matrices should have the same dimensions"
);
}
}
@Override
public boolean equals(Object o) {
if (!(o instanceof DFTOutput)) {
return false;
}
DFTOutput other = (DFTOutput) o;
return amplitude.equals(other.amplitude) && phase.equals(other.phase);
}
@Override
public int hashCode() {
return amplitude.hashCode();
}
}
5 ImageProcessing
這段程式碼定義了 ImageProcessing 類別,目的是處理多張影像之間的比較與匹配。以下是中文解析與建議實作方向:
📦 類別名稱:ImageProcessing
此類別提供針對「多張影像」的操作方法,屬於影像處理模組中的高階邏輯部分。
🧠 方法說明與中文解釋
1. cosineSimilarity(Image img1, Image img2)
- 功能目標:計算兩張影像之間的餘弦相似度(Cosine Similarity),用來衡量它們的相似程度。
- 應用場景:影像比對、搜尋、分類。
- 實作建議:
- 將影像轉換為向量(例如灰階像素展平為一維陣列)。
- 使用公式:
similarity=A⃗⋅B⃗∥A⃗∥⋅∥B⃗∥\text{similarity} = \frac{\vec{A} \cdot \vec{B}}{|\vec{A}| \cdot |\vec{B}|}
- 注意處理不同尺寸的影像(可能需先 resize 或 crop)。
2. bestMatch(Image img, List<Image> matchingCandidates)
- 功能目標:從候選影像清單中找出與目標影像最相似的影像(或多張)。
- 應用場景:影像搜尋、推薦系統。
- 實作建議:
- 對每張候選影像使用
cosineSimilarity()計算相似度。 - 根據相似度排序,選出最佳匹配(可回傳 top-N 或 threshold 以上者)。
- 回傳
List<Image>以支援多個匹配結果。
- 對每張候選影像使用
✅ 延伸建議
如果你希望這個模組更完整,我可以幫你補上:
- 影像向量化工具(例如
Image.toVector()) - 灰階轉換與標準化方法
- 測試案例與效能分析
- 中英文註解與 README 文件
是否要我幫你撰寫 cosineSimilarity 的實作範例?我可以提供模組化、可測試的版本,並附上註解說明每一步。這樣會更適合教學與展示。
package ca.ubc.ece.cpen221.ip.mp;
import ca.ubc.ece.cpen221.ip.core.Image;
import java.util.List;
/**
* This class provides some simple operations involving
* more than one image.
*/
public class ImageProcessing {
/* ===== TASK 3 ===== */
public static double cosineSimilarity(Image img1, Image img2) {
// TODO: Implement this method
return -1;
}
public static List<Image> bestMatch(Image img, List<Image> matchingCandidates) {
// TODO: Implement this method
return null;
}
}
6 ImageTransformer
這段程式碼定義了一個功能豐富的影像轉換類別 ImageTransformer,設計上非常適合進行模組化影像處理。以下是中文解析與建議補充:
🧱 類別名稱:ImageTransformer(影像轉換器)
此類別的目的是對影像進行各種視覺效果處理,並保證原始影像不被修改(不可變性設計)。
📌 成員變數說明
| 成員 | 中文說明 |
|---|---|
image |
原始影像資料(不會被修改) |
width / height |
影像的寬度與高度,用於像素迴圈 |
✅ 已實作方法解析
grayscale()
- 將影像轉換為灰階。
- 使用
Image.toGray(Color)將每個像素轉換為灰階色。
red()
- 保留紅色通道,將綠色與藍色設為 0。
- 使用位元操作提取 RGB 並重組像素。
🧠 待實作方法建議
TASK 1:基本視覺效果
| 方法 | 建議實作邏輯 |
|---|---|
mirror() |
水平翻轉影像:將 (col, row) 映射為 (width - 1 - col, row) |
negative() |
對每個像素 (r, g, b) 計算 (255 - r, 255 - g, 255 - b) |
posterize() |
將色彩分段,例如每個通道只保留 4 個層級(類似漫畫風格) |
TASK 2:進階處理
| 方法 | 建議實作邏輯 |
|---|---|
denoise() |
使用中值濾波或高斯濾波,移除雜訊 |
weather() |
模擬老舊或褪色效果,可加入隨機雜訊與色彩偏移 |
blockPaint(int blockSize) |
將影像分成區塊,每塊用平均色填充(像素化效果) |
TASK 4:綠幕合成
| 方法 | 建議實作邏輯 |
|---|---|
greenScreen(Color screenColour, Image backgroundImage) |
將指定顏色區域替換為背景影像對應像素,可用色差閾值判斷 |
TASK 5:文字對齊
| 方法 | 建議實作邏輯 |
|---|---|
alignTextImage() |
偵測文字區域並居中排列,可能需結合邊緣偵測與投影分析 |
🔧 建構子補充建議
目前 ImageTransformer(Image img) 尚未實作,建議如下:
java
public ImageTransformer(Image img) {
if (img == null) {
throw new IllegalArgumentException("Image cannot be null");
}
this.image = img;
this.width = img.getWidth();
this.height = img.getHeight();
}
📚 延伸支援
如果你希望這個模組能展示於教學或展示系統,我可以幫你:
- 加上中英文註解
- 撰寫 README 文件
- 補上測試案例(JUnit)
- 製作模擬腳本與範例輸出
是否要我幫你優化 mirror() 或 negative() 的實作?我可以提供模組化、可測試的版本,並附註教學說明。這樣會更適合展示與教學用途。
package ca.ubc.ece.cpen221.ip.mp;
import ca.ubc.ece.cpen221.ip.core.Image;
import ca.ubc.ece.cpen221.ip.core.ImageProcessingException;
import ca.ubc.ece.cpen221.ip.core.Rectangle;
import java.awt.Color;
/**
* This datatype (or class) provides operations for transforming an image.
*
* <p>The operations supported are:
* <ul>
* <li>The {@code ImageTransformer} constructor generates an instance of an image that
* we would like to transform;</li>
* <li></li>
* </ul>
* </p>
*/
public class ImageTransformer {
private Image image;
private int width;
private int height;
/**
* Creates an ImageTransformer with an image. The provided image is
* <strong>never</strong> changed by any of the operations.
*
* @param img is not null
*/
public ImageTransformer(Image img) {
// TODO: Implement this method
}
/**
* Obtain the grayscale version of the image.
*
* @return the grayscale version of the instance.
*/
public Image grayscale() {
Image gsImage = new Image(width, height);
for (int col = 0; col < width; col++) {
for (int row = 0; row < height; row++) {
Color color = image.get(col, row);
Color gray = Image.toGray(color);
gsImage.set(col, row, gray);
}
}
return gsImage;
}
/**
* Obtain a version of the image with only the red colours.
*
* @return a reds-only version of the instance.
*/
public Image red() {
Image redImage = new Image(width, height);
for (int col = 0; col < width; col++) {
for (int row = 0; row < height; row++) {
int originalPixel = image.getRGB(col, row);
int alpha = (originalPixel >> 24) & 0xFF;
int red = (originalPixel >> 16) & 0xFF;
int desiredColor = (alpha << 24) | (red << 16) | (0 << 8) | (0);
redImage.setRGB(col, row, desiredColor);
}
}
return redImage;
}
/* ===== TASK 1 ===== */
/**
* Returns the mirror image of an instance.
*
* @return the mirror image of the instance.
*/
public Image mirror() {
// TODO: Implement this method
return null;
}
/**
* <p>Returns the negative version of an instance.<br />
* If the colour of a pixel is (r, g, b) then the colours of the same pixel
* in the negative of the image are (255-r, 255-g, 255-b).</p>
*
* @return the negative of the instance.
*/
public Image negative() {
// TODO: Implement this method
return null;
}
public Image posterize() {
// TODO: Implement this method
return null;
}
/* ===== TASK 2 ===== */
public Image denoise() {
// TODO: Implement this method
return null;
}
/**
* @return a weathered version of the image.
*/
public Image weather() {
// TODO: Implement this method
return null;
}
public Image blockPaint(int blockSize) {
// TODO: Implement this method
return null;
}
/* ===== TASK 4 ===== */
public Image greenScreen(Color screenColour, Image backgroundImage) {
// TODO: Implement this method
return null;
}
/* ===== TASK 5 ===== */
public Image alignTextImage() {
// TODO: Implement this method
return null;
}
}
7 Task1test
這是一組針對 ImageTransformer 類別的 JUnit 測試案例,屬於 Task 1 的驗證模組。以下是中文解析與建議補充:
🧪 測試類別:Task1Tests
此類別使用 JUnit 5 (org.junit.jupiter.api) 來測試三個影像處理功能是否正確:
| 測試方法 | 功能驗證 | 預期輸出檔案 |
|---|---|---|
test_Mirroring() |
水平鏡像 | 15088-mirror.png |
test_Negative() |
色彩反轉 | 15088-negative.png |
test_Posterize() |
色彩分段 | 15088-poster.png |
📌 測試流程解析
每個測試方法都遵循以下步驟:
-
載入原始影像:
java
Image originalImg = new Image("resources/15088.jpg"); -
載入預期結果影像:
java
Image expectedImg = new Image("resources/tests/15088-mirror.png"); -
建立轉換器並執行操作:
java
ImageTransformer t = new ImageTransformer(originalImg); Image outputImage = t.mirror(); // 或 negative(), posterize() -
比對結果是否一致:
java
assertEquals(expectedImg, outputImage);
✅ 建議補充與強化
- 測試覆蓋率:可加入邊界測試(例如空白影像、極小尺寸、單色影像)。
- 錯誤處理測試:測試 null 輸入、無效檔案路徑、異常格式。
- 視覺驗證工具:若
Image.equals()使用像素比對,可加入像素差異視覺化工具以輔助除錯。 - 效能測試:對大型影像進行處理時,測試執行時間與記憶體使用。
如果你希望我幫你補上 mirror()、negative() 或 posterize() 的實作,我可以提供模組化版本,附上中英文註解與測試案例。也可以幫你撰寫 README 文件來展示整個 Task1 的功能與教學流程。要不要我幫你補上 mirror() 的程式碼範例?我們可以一步步完成整個模組。
package ca.ubc.ece.cpen221.ip.mp;
import ca.ubc.ece.cpen221.ip.core.Image;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Task1Tests {
@Test
public void test_Mirroring() {
Image originalImg = new Image("resources/15088.jpg");
Image expectedImg = new Image("resources/tests/15088-mirror.png");
ImageTransformer t = new ImageTransformer(originalImg);
Image outputImage = t.mirror();
assertEquals(expectedImg, outputImage);
}
@Test
public void test_Negative() {
Image originalImg = new Image("resources/15088.jpg");
Image expectedImg = new Image("resources/tests/15088-negative.png");
ImageTransformer t = new ImageTransformer(originalImg);
Image outputImage = t.negative();
assertEquals(expectedImg, outputImage);
}
@Test
public void test_Posterize() {
Image originalImg = new Image("resources/15088.jpg");
Image expectedImg = new Image("resources/tests/15088-poster.png");
ImageTransformer t = new ImageTransformer(originalImg);
Image outputImage = t.posterize();
assertEquals(expectedImg, outputImage);
}
}
8 Task2test
package ca.ubc.ece.cpen221.ip.mp;
import ca.ubc.ece.cpen221.ip.core.Image;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Task2Tests {
@Test
public void test_Weathering() {
Image originalImg = new Image("resources/95006.jpg");
Image expectedImg = new Image("resources/tests/95006-weathered.png");
ImageTransformer t = new ImageTransformer(originalImg);
Image outputImage = t.weather();
assertEquals(expectedImg, outputImage);
}
}
這段程式碼是針對 ImageTransformer.weather() 方法的單元測試,屬於 Task 2 的驗證邏輯。以下是中文解析與建議補充:
🧪 測試方法:test_Weathering()
此測試目的是確認「風化效果」是否正確套用在影像上。
📌 測試流程:
-
載入原始影像:
java
Image originalImg = new Image("resources/95006.jpg"); -
載入預期風化後影像:
java
Image expectedImg = new Image("resources/tests/95006-weathered.png"); -
建立轉換器並執行
weather()方法:java
ImageTransformer t = new ImageTransformer(originalImg); Image outputImage = t.weather(); -
比對結果是否一致:
java
assertEquals(expectedImg, outputImage);
🌫️ weather() 方法預期行為
雖然尚未實作,但根據命名與測試目的,weather() 應模擬影像的老化或褪色效果,可能包括:
- 色彩偏移(例如偏黃、偏灰)
- 加入雜訊(模擬刮痕、灰塵)
- 對比降低或模糊處理
✅ 建議補充
- 多樣化測試:可加入不同解析度、色彩風格的影像進行測試。
- 視覺差異容忍度:若
Image.equals()是像素級比對,可能需加入容差機制(例如允許微小色差)。 - 效能測試:對大型影像進行風化處理時,測試執行時間與記憶體使用。
如果你希望我幫你設計 weather() 的實作邏輯,我可以提供一個模組化版本,例如使用隨機雜訊疊加、色彩偏移矩陣或模糊濾波器。也可以幫你撰寫中英文註解與測試案例,讓整個 Task 2 更完整、可展示。要不要我幫你補上 weather() 的程式碼範例?我們可以一步步完成整個模組。
留言