diff --git a/backend/src/main/java/pdl/backend/ImageController.java b/backend/src/main/java/pdl/backend/ImageController.java index 406ca3d..ff67f84 100644 --- a/backend/src/main/java/pdl/backend/ImageController.java +++ b/backend/src/main/java/pdl/backend/ImageController.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import boofcv.io.image.ConvertBufferedImage; +import boofcv.struct.border.BorderType; import boofcv.struct.image.GrayU8; import boofcv.struct.image.Planar; @@ -135,6 +136,23 @@ public class ImageController { return nodes; } + public BorderType getBorderType(String borderType) { + switch (borderType) { + case "EXTENDED": + return BorderType.EXTENDED; + case "REFLECT": + return BorderType.REFLECT; + case "REFLECT_101": + return BorderType.NORMALIZED; + case "WRAP": + return BorderType.WRAP; + case "ZERO": + return BorderType.ZERO; + default: + return null; + } + } + /** * récupère une image et la modifie si un algorithme est renseigné * @@ -210,8 +228,9 @@ public class ImageController { } break; case "meanFilter": - if (!parameters.containsKey("size") || parameters.size() != 2) - return JSONError("Algorithm 'meanFilter' requires one parameter 'size'(positive odd integer)", + if (!parameters.containsKey("size") || !parameters.containsKey("borderType") || parameters.size() != 3) + return JSONError( + "Algorithm 'meanFilter' requires two parameters: 'size'(positive odd integer) and 'borderType'('SKIP', 'EXTENDED', 'WRAP', 'REFLECT', 'ZERO' or 'NORMALIZED')", HttpStatus.BAD_REQUEST); try { int i = Integer.parseInt(parameters.get("size")); @@ -219,24 +238,40 @@ public class ImageController { return JSONError("Parameter 'size' should be a positive odd integer", HttpStatus.BAD_REQUEST); Planar clone = input.clone(); - ImageProcessing.meanFilter(clone, input, i); + BorderType borderType = getBorderType(parameters.get("borderType")); + if (borderType == null) { + return JSONError( + "Parameter 'borderType' should be 'SKIP', 'EXTENDED', 'WRAP', 'REFLECT', 'ZERO' or 'NORMALIZED'", + HttpStatus.BAD_REQUEST); + } + ImageProcessing.meanFilter(clone, input, i, borderType); } catch (NumberFormatException e) { return JSONError("Parameter 'size' should be a positive odd integer", HttpStatus.BAD_REQUEST); } break; case "gaussienFilter": - if (/* !parameters.containsKey("size") || */ parameters.size() != 1) - return JSONError("Algorithm 'gaussienFilter' requires no parameters", HttpStatus.BAD_REQUEST); + if (!parameters.containsKey("size") || !parameters.containsKey("sigma") + || !parameters.containsKey("borderType") || parameters.size() != 5) + return JSONError( + "Algorithm 'gaussienFilter' requires 3 parameters: 'size'(positive odd integer), 'sigma'(positive number) and 'borderType'('SKIP', 'EXTENDED', 'WRAP', 'REFLECT', 'ZERO' or 'NORMALIZED')", + HttpStatus.BAD_REQUEST); try { - // int i = Integer.parseInt(parameters.get("size")); - // if (i % 2 == 0) - // return JSONError("Parameter 'size' should be a positive odd integer", - // HttpStatus.BAD_REQUEST); + int i = Integer.parseInt(parameters.get("size")); + if (i % 2 == 0 || i < 0) + return JSONError("Parameter 'size' should be a positive odd integer", + HttpStatus.BAD_REQUEST); + double sigma = Double.parseDouble(parameters.get("sigma")); Planar clone = input.clone(); - ImageProcessing.gaussienFilter(clone, input, 5); + BorderType borderType = getBorderType(parameters.get("borderType")); + if (borderType == null) { + return JSONError( + "Parameter 'borderType' should be 'SKIP', 'EXTENDED', 'WRAP', 'REFLECT', 'ZERO' or 'NORMALIZED'", + HttpStatus.BAD_REQUEST); + } + ImageProcessing.gaussienFilter(clone, input, i, sigma, borderType); } catch (NumberFormatException e) { - return JSONError("Parameter 'size' should be a positive odd integer", + return JSONError("Parameters 'size' and 'sigma' should be a positive numbers", HttpStatus.BAD_REQUEST); } break; diff --git a/backend/src/main/java/pdl/backend/ImageProcessing.java b/backend/src/main/java/pdl/backend/ImageProcessing.java index 3ca405b..611d9bd 100644 --- a/backend/src/main/java/pdl/backend/ImageProcessing.java +++ b/backend/src/main/java/pdl/backend/ImageProcessing.java @@ -1,5 +1,6 @@ package pdl.backend; +import boofcv.struct.border.BorderType; import boofcv.struct.image.GrayU8; import boofcv.struct.image.Planar; @@ -279,74 +280,77 @@ public class ImageProcessing { /** * Applique un filtre moyenneur de taille indiquée à l'image - * passée en paramètre (le bord de l'image n'est pas modifié) + * passée en paramètre et stocke le résultat dans l'image de sortie. * - * @param input image d'entrée - * @param output image de sortie - * @param size taille du filtre à appliquer (doit être impaire) + * @param input image d'entrée + * @param output image de sortie + * @param size taille du filtre à appliquer (doit être impaire) + * @param borderType type de bord à utiliser pour la convolution */ - public static void meanFilter(Planar input, Planar output, int size) { + public static void meanFilter(Planar input, Planar output, int size, BorderType borderType) { // Vérification que la taille est impaire - if (size % 2 == 0) { - throw new IllegalArgumentException("La taille doit être impaire"); + if (size % 2 == 0 || size < 0) { + throw new IllegalArgumentException( + "La taille doit être impaire et positive"); } - // Boucle pour parcourir tous les pixels de l'image d'entrée - BoofConcurrency.loopFor(0, input.height, y -> { - for (int x = 0; x < input.width; x++) { - if (y < size / 2 || x < size / 2 || y >= input.height - size / 2 - || x >= input.width - size / 2) { - setRGBValue(output, x, y, getRGBValue(input, x, y)); - continue; - } - int totalValue[] = new int[3]; - // Boucle pour parcourir le carré size x size autour du pixel actuel - for (int j = y - size / 2; j < y + size / 2 + 1; j++) { - for (int i = x - size / 2; i < x + size / 2 + 1; i++) { - for (int k = 0; k < totalValue.length; k++) { - totalValue[k] += input.getBand(k).get(i, j); - } - } - } - // Calcule la moyenne des pixels du carré et l'applique au pixel actuel dans - // l'image de - // sortie - for (int k = 0; k < totalValue.length; k++) { - totalValue[k] /= size * size; - } - setRGBValue(output, x, y, totalValue); + int[][] kernel = new int[size][size]; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + kernel[i][j] = 1; } - }); - + } + convolution(input, output, kernel, borderType); } /** - * TODO - * Calcule le noyau gaussien de taille demandée * - * @param size taille du noyau gaussien - * @return noyau gaussien + * Applique un filtre gaussien de taille et de valeur données sur une image et + * stocke le résultat dans l'image de sortie. + * + * @param input L'image d'entrée + * @param output L'image de sortie + * @param size La taille du filtre à appliquer (doit être impaire) + * @param sigma La valeur du sigma à utiliser pour le filtre gaussien. + * @param borderType Le type de bord à utiliser pour la convolution */ - public static int[][] createGaussienKernel(int size) { - // Vérification que la taille est impaire - if (size % 2 == 0 || size < 0) { - throw new IllegalArgumentException("La taille doit être impaire et positive"); + public static void gaussienFilter(Planar input, Planar output, + int size, double sigma, BorderType borderType) { + if (size % 2 == 0 || size < 0 || sigma <= 0) { + throw new IllegalArgumentException( + "La taille doit être impaire et positive et sigma doit être strictement positif"); } double[][] kernel = new double[size][size]; - double sigma = 4 / 3; - double sum = 0.0; // For accumulating the kernel values - for (int x = 0; x < size; ++x) - for (int y = 0; y < size; ++y) { - kernel[x][y] = 1 / (2 * Math.PI * sigma * sigma) * Math.exp(-(x * x + y * y) / (2 * sigma * sigma)); - + int half = size / 2; + double sum = 0; + for (int x = -half; x <= half; ++x) { + for (int y = -half; y <= half; ++y) { + double gaussianValue = 1 / (2 * Math.PI * sigma * sigma) + * Math.exp(-(x * x + y * y) / (2 * sigma * sigma)); + kernel[x + half][y + half] = gaussianValue; + sum += gaussianValue; } + } + // On récupère le pourcentage de chaque valeur par rapport à la somme + for (int x = -half; x <= half; ++x) { + for (int y = -half; y <= half; ++y) { + kernel[x + half][y + half] = kernel[x + half][y + half] / sum; + } + } - // Normalize the kernel - int[][] kernelk = new int[size][size]; - for (int x = 0; x < size; ++x) - for (int y = 0; y < size; ++y) - kernelk[x][y] = (int) (kernel[x][y] / sum); - - return kernelk; + int[][] kernelInt = new int[size][size]; + double factor = 1 / kernel[0][0]; + // Si le facteur est trop grand (la valeur du coin est très très petite par + // rapport au reste), on le fixe à 1 million + if (factor > 1000000) { + factor = 1000000; + } + // normalisation + for (int x = 0; x < size; ++x) { + for (int y = 0; y < size; ++y) { + kernelInt[x][y] = (int) (kernel[x][y] * factor); + } + } + convolution(input, output, kernelInt, borderType); } /** @@ -354,15 +358,15 @@ public class ImageProcessing { * Effectue une convolution sur une image avec le noyau * donné et stocke le résultat dans l'image de sortie. * - * @param input L'image d'entrée - * @param output L'image de sortie - * @param kernel Le noyau de convolution à utiliser. + * @param input L'image d'entrée + * @param output L'image de sortie + * @param kernel Le noyau de convolution à utiliser. + * @param borderType Le type de bord à utiliser pour la convolution */ - public static void gaussienFilter(Planar input, Planar output, - int size) { - int[][] kernel = { { 1, 2, 3, 2, 1 }, { 2, 6, 8, 6, 2 }, { 3, 8, 10, 8, 3 }, { 2, 6, 8, 6, 2 }, - { 1, 2, 3, 2, 1 } }; - int half = size / 2; + public static void convolution(Planar input, Planar output, + int[][] kernel, BorderType borderType) { + int size = kernel.length; + int half = kernel.length / 2; int coefs = 0; // Calcul du nombre total de coefficients dans le noyau for (int i = 0; i < kernel.length; i++) { @@ -374,10 +378,54 @@ public class ImageProcessing { // Parcours de l'image d'entrée et application de la convolution BoofConcurrency.loopFor(0, input.height, y -> { for (int x = 0; x < input.width; x++) { - // Vérification si le carré sera à l'intérieur de l'image + // On traite d'abord les bords if (y < half || x < half || y >= input.height - half || x >= input.width - half) { - setRGBValue(output, x, y, getRGBValue(input, x, y)); + if (borderType == BorderType.SKIP) { + setRGBValue(output, x, y, getRGBValue(input, x, y)); + } else if (borderType == BorderType.ZERO) { + setRGBValue(output, x, y, new int[] { 0, 0, 0 }); + } else { + int totalValue[] = new int[3]; + int coefsUsed = 0; + for (int j = y - half; j < y + half + 1; j++) { + int tmpj = j; + if (j < 0 || j >= input.height) { + if (borderType == BorderType.EXTENDED) { + tmpj = j < 0 ? 0 : input.height - 1; + } else if (borderType == BorderType.NORMALIZED) { + continue; + } else if (borderType == BorderType.REFLECT) { + tmpj = j < 0 ? -j : (input.height - 1) - (j - (input.height - 1)); + } else if (borderType == BorderType.WRAP) { + tmpj = j < 0 ? j + (input.height - 1) : (j - (input.height - 1)); + } + } + for (int i = x - size / 2; i < x + size / 2 + 1; i++) { + int tmpi = i; + if (i < 0 || i >= input.width) { + if (borderType == BorderType.EXTENDED) { + tmpi = i < 0 ? 0 : input.width - 1; + } else if (borderType == BorderType.NORMALIZED) { + continue; + } else if (borderType == BorderType.REFLECT) { + tmpi = i < 0 ? -i : (input.width - 1) - (i - (input.width - 1)); + } else if (borderType == BorderType.WRAP) { + tmpi = i < 0 ? i + (input.width - 1) : (i - (input.width - 1)); + } + } + for (int k = 0; k < totalValue.length; k++) { + totalValue[k] += input.getBand(k).get(tmpi, tmpj) + * kernel[tmpi][tmpj]; + } + coefsUsed += kernel[tmpi][tmpj]; + } + } + for (int k = 0; k < totalValue.length; k++) { + totalValue[k] /= coefsUsed; + } + setRGBValue(input, x, y, totalValue); + } continue; } int currentJ = 0;