diff --git a/Collector.java b/Collector.java new file mode 100644 index 0000000..768d71a --- /dev/null +++ b/Collector.java @@ -0,0 +1,104 @@ +package main; + +import java.util.ArrayList; + +public class Collector { + + /** + * Find the row, column coordinates of the best element (biggest or smallest) for the given matrix + * + * @param matrix : an 2D array of doubles + * @param smallestFirst : a boolean, indicates if the smallest element is the best or not (biggest is then the best) + * @return an array of two integer coordinates, row first and then column + */ + public static int[] findBest(double[][] matrix, boolean smallestFirst) { + int tempi = 0; + int tempj = 0; + if (smallestFirst) { + double temp = 255; + for (int i = 0; i < matrix.length; i++) { + for (int j = 0; j < matrix[0].length; j++) { + if (matrix[i][j] < temp) { + temp = matrix[i][j]; + tempi = i; + tempj = j; + } + } + } + } else { + double temp = 0; + for (int i = 0; i < matrix.length; i++) { + for (int j = 0; j < matrix[0].length; j++) { + if (matrix[i][j] > temp) { + temp = matrix[i][j]; + tempi = i; + tempj = j; + } + } + } + } + int best[] = new int[]{tempi, tempj}; + return best; + } + + + /** + * Find the row, column coordinate-pairs of the n best (biggest or smallest) elements of the given matrix + * + * @param n : an integer, the number of best elements we want to find + * @param matrix : an 2D array of doubles + * @param smallestFirst : a boolean, indicates if the smallest element is the best or not (biggest is the best) + * @return an array of size n containing row, column-coordinate pairs + */ + public static int[][] findNBest(int n, double[][] matrix, boolean smallestFirst) { + double temp[][] = matrix; + int array[][] = new int[n][2]; + for (int i = 0; i < n; i++) { + int[] best = findBest(temp, smallestFirst); + for (int j = 0; j < 2; j++) + array[i][j] = best[j]; + if (smallestFirst) { + temp[best[0]][best[1]] = Double.POSITIVE_INFINITY; + } else { + temp[best[0]][best[1]] = Double.NEGATIVE_INFINITY; + } + } + return array; + } + + + /** + * BONUS + * Notice : Bonus points are underpriced ! + *

+ * Sorts all the row, column coordinates based on their pixel value + * Hint : Use recursion ! + * + * @param matrix : an 2D array of doubles + * @return A list of points, each point is an array of length 2. + */ + public static ArrayList quicksortPixelCoordinates(double[][] matrix) { + + // TODO implement me correctly for "underpriced" bonus! + return new ArrayList(); + } + + + /** + * BONUS + * Notice : Bonus points are underpriced ! + *

+ * Use a quick sort to find the row, column coordinate-pairs of the n best (biggest or smallest) elements of the given matrix + * Hint : return the n first or n last elements of a sorted ArrayList + * + * @param n : an integer, the number of best elements we want to find + * @param matrix : an 2D array of doubles + * @param smallestFirst : a boolean, indicate if the smallest element is the best or not (biggest is the best) + * @return an array of size n containing row, column-coordinate pairs + */ + public static int[][] findNBestQuickSort(int n, double[][] matrix, boolean smallestFirst) { + + // TODO implement me correctly for underpriced bonus! + return new int[][]{}; + } +} diff --git a/DistanceBasedSearch.java b/DistanceBasedSearch.java new file mode 100644 index 0000000..075ac43 --- /dev/null +++ b/DistanceBasedSearch.java @@ -0,0 +1,71 @@ +package main; + +public class DistanceBasedSearch { + + /** + * Computes the mean absolute error between two RGB pixels, channel by channel. + * + * @param patternPixel : a integer, the second RGB pixel. + * @param imagePixel : a integer, the first RGB pixel. + * @return a double, the value of the error for the RGB pixel pair. (an integer in [0, 255]) + */ + public static double pixelAbsoluteError(int patternPixel, int imagePixel) { + double sum = Math.abs(ImageProcessing.getRed(patternPixel) - ImageProcessing.getRed(imagePixel)) + + Math.abs(ImageProcessing.getGreen(patternPixel) - ImageProcessing.getGreen(imagePixel)) + + Math.abs(ImageProcessing.getBlue(patternPixel) - ImageProcessing.getBlue(imagePixel)); + return sum / 3.0; + } + + /** + * Computes the mean absolute error loss of a RGB pattern if positioned + * at the provided row, column-coordinates in a RGB image + * + * @param row : a integer, the row-coordinate of the upper left corner of the pattern in the image. + * @param col : a integer, the column-coordinate of the upper left corner of the pattern in the image. + * @param pattern : an 2D array of integers, the RGB pattern to find + * @param image : an 2D array of integers, the RGB image where to look for the pattern + * @return a double, mean absolute error value at position (row, col) between the pattern and the part of + * the base image that is covered by the pattern, if the pattern is shifted by x and y. + * should return -1 if the denominator is -1 + */ + public static double meanAbsoluteError(int row, int col, int[][] pattern, int[][] image) { + assert pattern.length != 0 : "pattern contains no pixel"; + assert image.length != 0 : "image contains no pixel"; + assert row < image.length - pattern.length : "Motif non contenu entierement"; + assert col < image[0].length - pattern[0].length : "Motif non contenu entierement"; + double sum = 0; + for (int i = 0; i < pattern.length; i++) { + for (int j = 0; j < pattern[0].length; j++) { + sum += pixelAbsoluteError(pattern[i][j], image[row + i][col + j]); + } + } + double size = pattern.length * pattern[0].length; + double eam = sum / size; + return eam; + } + + /** + * Compute the distanceMatrix between a RGB image and a RGB pattern + * + * @param pattern : an 2D array of integers, the RGB pattern to find + * @param image : an 2D array of integers, the RGB image where to look for the pattern + * @return a 2D array of doubles, containing for each pixel of a original RGB image, + * the distance (meanAbsoluteError) between the image's window and the pattern + * placed over this pixel (upper-left corner) + */ + public static double[][] distanceMatrix(int[][] pattern, int[][] image) { + assert pattern.length != 0 : "pattern contains no pixel"; + assert image.length != 0 : "image contains no pixel"; + int W = image[0].length; + int w = pattern[0].length; + int H = image.length; + int h = pattern.length; + double[][] matrix = new double[H - h][W - w]; + for (int i = 0; i < H - h; i++) { + for (int j = 0; j < W - w; j++) { + matrix[i][j] = meanAbsoluteError(i, j, pattern, image); + } + } + return matrix; + } +} diff --git a/Helper.java b/Helper.java new file mode 100644 index 0000000..a33f7f7 --- /dev/null +++ b/Helper.java @@ -0,0 +1,180 @@ +package main; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +/** + * Provide simple tools to read, write and show pictures. + */ +public final class Helper { + + /** + * Draws a rectangle over a RGB image + * + * @param r : an integer, the vertical coordinate (col) of the upper left corner. + * @param c : an integer, the horizontal coordinate (row) of the upper left corner. + * @param w : an integer, the width of the rectangle. + * @param h : an integer, the height of the rectangle. + * @param dst : a 2D integer array, the RGB image on which to draw the rectangle. + * @param color: an integer representing the RBG value of the line color of the rectangle + * @param strokeWidth: width of pencil stroke + */ + public static void drawBox(int r, int c, int w, int h, int[][] dst, int strokeWidth, int color) { + if (strokeWidth < 1) strokeWidth = 1; + for (int row = r; row < r + h && row < dst.length; ++row) { + for (int col = c; col < c + w && col < dst[0].length; ++col) { + if (row < r + strokeWidth || row >= r + h - strokeWidth || + col < c + strokeWidth || col >= c + w - strokeWidth) { + dst[row][col] = color; + } + } + } + } + + /** + * Draws a red rectangle over a RGB image + * + * @param r : an integer, the vertical coordinate (col) of the upper left corner. + * @param c : an integer, the horizontal coordinate (row) of the upper left corner. + * @param w : an integer, the width of the rectangle. + * @param h : an integer, the height of the rectangle. + * @param dst : a 2D integer array, the RGB image on which to draw the rectangle. + */ + public static void drawBox(int r, int c, int w, int h, int[][] dst) { + drawBox(r, c, w, h, dst, w / 15, 255 << 16); + } + + // Convert specified BufferedImage into an array + private static int[][] fromBufferedImage(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + int[][] array = new int[height][width]; + for (int row = 0; row < height; ++row) { + for (int col = 0; col < width; ++col) { + array[row][col] = image.getRGB(col, row) & 0xffffffff; + } + } + return array; + } + + // Convert specified array into a BufferedImage + private static BufferedImage toBufferedImage(int[][] array) { + int width = array[0].length; + int height = array.length; + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + for (int row = 0; row < height; ++row) { + for (int col = 0; col < width; ++col) { + image.setRGB(col, row, array[row][col] | 0xff000000); + } + } + return image; + } + + /** + * Reads specified image from disk. + * + * @param path : a String, the Input file path + * @return HxW integer array of packed RGB colors, or null on failure + * @see #write + */ + public static int[][] read(String path) { + try { + BufferedImage image = ImageIO.read(new File(path)); + return fromBufferedImage(image); + } catch (IOException e) { + System.out.println(e); + System.out.println("Path: " + path); + System.exit(1); + return null; + } + } + + /** + * Writes specified image to disk. + * + * @param path : a String, the Output file path + * @param array HxW array of packed RGB colors + * @return {@code true} if write operation was successful, {@code false} otherwise + * @see #read + */ + public static boolean write(String path, int[][] array) { + + // Convert array to Java image + BufferedImage image = toBufferedImage(array); + + // Get desired file format + int index = path.lastIndexOf('.'); + if (index < 0) + return false; + String extension = path.substring(index + 1); + + // Export image + try { + return ImageIO.write(image, extension, new File(path)); + } catch (IOException e) { + return false; + } + + } + + /** + * Shows specified image in a window. + * + * @param array : a HxW integer array of packed RGB colors + * @param title : a String, the title to be displayed + */ + public static void show(int[][] array, String title) { + + // Convert array to Java image + final BufferedImage image = toBufferedImage(array); + + // Create a panel to render this image + @SuppressWarnings("serial") + JPanel panel = new JPanel() { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + g.drawImage(image, 0, 0, Math.max(getWidth(), 100), Math.max(getHeight(), 100), null, null); + } + }; + + // Create a frame to hold this panel + final JFrame frame = new JFrame(title); + frame.add(panel); + frame.getContentPane().setPreferredSize(new Dimension(Math.max(image.getWidth(), 300), Math.max(image.getHeight(), 300))); + frame.pack(); + + // Register closing event + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + frame.setVisible(false); + synchronized (frame) { + frame.notifyAll(); + } + } + }); + + // Show this frame + frame.setVisible(true); + + // Wait for close operation + try { + synchronized (frame) { + while (frame.isVisible()) + frame.wait(); + } + } catch (InterruptedException e) { + // Empty on purpose + } + frame.dispose(); + } + +} diff --git a/ImageProcessing.java b/ImageProcessing.java new file mode 100644 index 0000000..747ebff --- /dev/null +++ b/ImageProcessing.java @@ -0,0 +1,155 @@ +package main; + +public final class ImageProcessing { + + /** + * Checks wether or not a RGB component is between 0 and 255 and returns the right value. + * + * @param value : an integer. + * @return an integer, between 0 and 255 + */ + public static int checkInt(int value) { + if (value < 0) + value = 0; + if (value > 255) + value = 255; + return value; + } + + /** + * Returns red component from given packed color. + * + * @param rgb : a 32-bits RGB color + * @return an integer, between 0 and 255 + * @see #getGreen + * @see #getBlue + * @see #getRGB(int, int, int) + */ + public static int getRed(int rgb) { + return (rgb >> 16) & 0xff; + } + + /** + * Returns green component from given packed color. + * + * @param rgb : a 32-bits RGB color + * @return an integer between 0 and 255 + * @see #getRed + * @see #getBlue + * @see #getRGB(int, int, int) + */ + public static int getGreen(int rgb) { + return (rgb >> 8) & 0xff; + } + + /** + * Returns blue component from given packed color. + * + * @param rgb : a 32-bits RGB color + * @return an integer between 0 and 255 + * @see #getRed + * @see #getGreen + * @see #getRGB(int, int, int) + */ + public static int getBlue(int rgb) { + return rgb & 0xff; + } + + + /** + * Returns the average of red, green and blue components from given packed color. + * + * @param rgb : 32-bits RGB color + * @return a double between 0 and 255 + * @see #getRed + * @see #getGreen + * @see #getBlue + * @see #getRGB + */ + public static double getGray(int rgb) { + return ((getRed(rgb) + getBlue(rgb) + getGreen(rgb)) / 3.0); + } + + /** + * Returns packed RGB components from given red, green and blue components. + * + * @param red : an integer + * @param green : an integer + * @param blue : an integer + * @return a 32-bits RGB color + * @see #getRed + * @see #getGreen + * @see #getBlue + */ + public static int getRGB(int red, int green, int blue) { + return checkInt(red) << 16 | checkInt(green) << 8 | checkInt(blue); + } + + /** + * Returns packed RGB components from given gray-scale value. + * + * @param gray : an integer + * @return a 32-bits RGB color + * @see #getGray + */ + public static int getRGB(double gray) { + int g = (int) Math.round(gray); + return checkInt(g) << 16 | checkInt(g) << 8 | checkInt(g); + } + + /** + * Converts packed RGB image to gray-scale image. + * + * @param image : a HxW integer array + * @return a HxW double array + * @see #encode + * @see #getGray + */ + public static double[][] toGray(int[][] image) { + double[][] img = new double[image.length][image[0].length]; + for (int i = 0; i < image.length; i++) { + for (int j = 0; j < image[0].length; j++) { + img[i][j] = getGray(image[i][j]); + } + } + return img; + } + + /** + * Converts gray-scale image to packed RGB image. + * + * @param channels : a HxW double array + * @return a HxW integer array + * @see #decode + * @see #getRGB(double) + */ + public static int[][] toRGB(double[][] gray) { + + int[][] img = new int[gray.length][gray[0].length]; + for (int i = 0; i < gray.length; i++) { + for (int j = 0; j < gray[0].length; j++) { + img[i][j] = getRGB(gray[i][j]); + } + } + return img; + } + + /** + * Convert an arbitrary 2D double matrix into a 2D integer matrix + * which can be used as RGB image + * + * @param matrix : the arbitrary 2D double array to convert into integer + * @param min : a double, the minimum value the matrix could theoretically contains + * @param max : a double, the maximum value the matrix could theoretically contains + * @return an 2D integer array, containing a RGB mapping of the matrix + */ + public static int[][] matrixToRGBImage(double[][] matrix, double min, double max) { + int[][] imageRGB = new int[matrix.length][matrix[0].length]; + for (int i = 0; i < matrix.length; i++) { + for (int j = 0; j < matrix[0].length; j++) { + imageRGB[i][j] = getRGB(255.0 * ((matrix[i][j] - min) / (max - min))); + } + } + return imageRGB; + } +} diff --git a/Main.java b/Main.java new file mode 100644 index 0000000..a6d8841 --- /dev/null +++ b/Main.java @@ -0,0 +1,271 @@ +package main; + +/** + * @author Charles BEAUVILLE and Mike Sinsoillier + *

+ * Where is Charlie Project + */ +public final class Main { + + public static void main(String[] args) { + testGetRed(); + testGrayscale(); + testGetGreen(); + testGetBlue(); + testGetGray(); + testGetRGB(); + testFindBest(); + testFindNBest(); + pixelAbsoluteError(); + testToGray(); + testToRGB(); + testDistanceBasedSearch(); + testSimilarityBasedSearch(); + testNCCPatternEqualImage(); + testSimilarityPatternEqualImage(); + testSimilaritySimple(); + findCharlie(); + } + + /* + * Tests for Class ImageProcessing + */ + public static void testGetRed() { + int color = 0b11110000_00001111_01010101; + int ref = 0b11110000; + int red = ImageProcessing.getRed(color); + if (red == ref) { + System.out.println("Test red passed"); + } else { + System.out.println("Test red failed. Returned value = " + red + " Expected value = " + ref); + } + } + + public static void testGrayscale() { + System.out.println("Test Grayscale"); + int[][] image = Helper.read("images/food.png"); + double[][] gray = ImageProcessing.toGray(image); + Helper.show(ImageProcessing.toRGB(gray), "test bw"); + } + + public static void testGetGreen() { + int color = 0b11110000_00001111_01010101; + int ref = 0b00001111; + int green = ImageProcessing.getGreen(color); + if (green == ref) { + System.out.println("Test green passed"); + } else { + System.out.println("Test green failed. Returned value = " + green + " Expected value = " + ref); + } + } + + public static void testGetBlue() { + int color = 0b11110000_00001111_01010101; + int ref = 0b01010101; + int blue = ImageProcessing.getBlue(color); + if (blue == ref) { + System.out.println("Test blue passed"); + } else { + System.out.println("Test blue failed. Returned value = " + blue + " Expected value = " + ref); + } + } + + public static void testGetGray() { + int color = 0b11110000_00001111_01010101; + int ref = 0b01110001; + double gray = ImageProcessing.getGray(color); + if (Math.round(gray) == ref) { + System.out.println("Test gray passed"); + } else { + System.out.println("Test gray failed. Returned value = " + gray + " Expected value = " + ref); + } + } + + public static void testGetRGB() { + int ref = 0b11110000_00001111_01010101; + int red = 0b11110000; + int green = 0b00001111; + int blue = 0b01010101; + int RGB = ImageProcessing.getRGB(red, green, blue); + if (RGB == ref) { + System.out.println("Test RGB 1 passed"); + } else { + System.out.println("Test RGB 1 failed. Returned value = " + RGB + " Expected value = " + ref); + } + int rgb = ImageProcessing.getRGB(127.0); + int ref2 = 0x7f7f7f; + if (rgb == ref2) { + System.out.println("Test RGB 2 passed"); + } else { + System.out.println("Test RGB 2 failed. Returned value = " + rgb + " Expected value = " + ref2); + } + } + public static void testToGray() { + System.out.println("Test toGray"); + double[][] ref = new double[][] {{0b01110001}, {0b01110001},{0b01110001}}; + int [][] image = new int[][] {{0b01110001_01110001_01110001}, {0b01110001_01110001_01110001},{0b01110001_01110001_01110001}}; + double[][] toGray = ImageProcessing.toGray(image); + for(int i=0 ; i