4

I implemented Gaussian Blur in Java, it seems to work on smaller radiuses but not on bigger ones. I'm not sure why on bigger radiuses the image darkens, I followed the same formula and the steps to apply the blur. Generating the blur matrix and applying it on the original image, and setting the pixel value to be the sum of the result of multiplying the image matrix with the blur matrix. I added the code I wrote below:

public class GaussianBlur extends ImageFilter {
    private int radius;
    private double sigma;

    public GaussianBlur(String imageFilename, int radius) {
        super(imageFilename);
        this.radius = radius;
        this.sigma = ((2.0 * radius) + 1.0) / 2.0;
    }

    @Override
    public void applyFilter() throws IOException {
        init();

        Matrix<Double> gaussianMatrix = getGaussianMatrix();

        Matrix<Color> imageMatrix, weightedImageMatrix;
        Color weightedPixelSum;
        for(int i = 0; i < getWidth(); i++) {
            for(int j = 0; j < getHeight(); j++) {
                imageMatrix = getImageMatrix(i, j);
                weightedImageMatrix = multiplyImageMatrixWithWeight(imageMatrix, gaussianMatrix);
                weightedPixelSum = getWeightedGaussianBlurValue(weightedImageMatrix);

                getFilter().setRGB(i, j, weightedPixelSum.getRGB());
            }
        }
    }

    private Matrix<Double> getGaussianMatrix() {
        Matrix<Double> gaussianMatrix = new Matrix<>(Double.class, radius);
        double weightedSum = 0.0;

        int matrixI = 0, matrixJ;
        double gaussianValue;
        for(int i = -radius; i <= radius; i++) {
            matrixJ = 0;

            for(int j = -radius; j <= radius; j++) {
                gaussianValue = getGaussianValue(i, j);
                weightedSum += gaussianValue;
                gaussianMatrix.setValue(matrixI, matrixJ, gaussianValue);
                matrixJ++;
            }

            matrixI++;
        }

        for(int i = 0; i < gaussianMatrix.getMatrix().length; i++) {
            for(int j = 0; j < gaussianMatrix.getMatrix()[i].length; j++) {
                gaussianMatrix.setValue(i, j, gaussianMatrix.getValue(i, j) / weightedSum);
            }
        }

        return gaussianMatrix;
    }

    private double getGaussianValue(int x, int y) {
        return 1.0 / (2.0 * Math.PI * sigma * sigma) * Math.pow(Math.E, -((x * x) + (y * y)) / (2.0 * (sigma * sigma)));
    }

    private Color getWeightedGaussianBlurValue(Matrix<Color> weightedImageMatrix) {
        int r = 0, g = 0, b = 0;

        for(int i = 0; i < weightedImageMatrix.getMatrix().length; i++) {
            for(int j = 0; j < weightedImageMatrix.getMatrix()[i].length; j++) {
                if(weightedImageMatrix.getValue(i, j) != null) {
                    r += weightedImageMatrix.getValue(i, j).getRed();
                    g += weightedImageMatrix.getValue(i, j).getGreen();
                    b += weightedImageMatrix.getValue(i, j).getBlue();
                }
            }
        }

        return new Color(r, g, b);
    }

    /*
     * Multiply each image pixel with its matrix value to get a new matrix with the weighted pixel values.
     */
    private Matrix<Color> multiplyImageMatrixWithWeight(Matrix<Color> imageMatrix, Matrix<Double> gaussianMatrix) {
        Matrix<Color> weightedImageMatrix = new Matrix<>(Color.class, this.radius);

        Color weightedValue;
        for(int i = 0; i < weightedImageMatrix.getMatrix().length; i++) {
            for(int j = 0; j < weightedImageMatrix.getMatrix()[i].length; j++) {
                if(imageMatrix.getValue(i, j) != null) {
                    weightedValue = new Color(
                        (int) ((double) imageMatrix.getValue(i, j).getRed() * gaussianMatrix.getValue(i, j)),
                        (int) ((double) imageMatrix.getValue(i, j).getGreen() * gaussianMatrix.getValue(i, j)),
                        (int) ((double) imageMatrix.getValue(i, j).getBlue() * gaussianMatrix.getValue(i, j))
                    );

                    weightedImageMatrix.setValue(i, j, weightedValue);
                } else {
                    weightedImageMatrix.setValue(i, j, null);
                }
            }
        }

        return weightedImageMatrix;
    }

    /*
     * Given the center points (i, j), construct a matrix from the image to blur.
     */
    private Matrix<Color> getImageMatrix(int i, int j) {
        Matrix<Color> imageMatrix = new Matrix<>(Color.class, radius);

        int matrixI = 0, matrixJ;
        for(int x = i - radius; x <= i + radius; x++) {
            matrixJ = 0;

            for(int y = j - radius; y <= j + radius; y++) {
                if(x > -1 && y > -1 && x < getOriginal().getWidth() && y < getOriginal().getHeight()) {
                    imageMatrix.setValue(matrixI, matrixJ, new Color(getOriginal().getRGB(x, y)));
                } else {
                    imageMatrix.setValue(matrixI, matrixJ, null);
                }

                matrixJ++;
            }

            matrixI++;
        }

        return imageMatrix;
    }

    private class Color {
        private int r, g, b;

        public Color(int r, int g, int b) {
            this.r = r;
            this.g = g;
            this.b = b;
        }

        public Color(int rgb) {
            this((rgb >> 16) & 0xff, (rgb >> 8) & 0xff, rgb & 0xff);
        }

        public int getRed() {
            return r;
        }

        public int getGreen() {
            return g;
        }

        public int getBlue() {
            return b;
        }

        public int getRGB() {
            return (r << 16) | (g << 8) | b;
        }

        @Override
        public String toString() {
            return "(" + r + "," + g + "," + b + ")";
        }
    }

    private class Matrix<T> {
        private T[][] matrix;

        public Matrix(Class<T> clazz, int radius) {
            int length = (2 * radius) + 1;
            matrix = (T[][]) Array.newInstance(clazz, length, length);
        }

        public T getValue(int i, int j) {
            return matrix[i][j];
        }

        public void setValue(int i, int j, T value) {
            matrix[i][j] = value;
        }

        public T[][] getMatrix() {
            return matrix;
        }
    }
}

The class ImageFilter is just an abstract class with two instances of BufferedImage (one for the original image and one for the blurred image), and the displayImage function just displays the image in a message dialog.

The main method using this class is

public static void main(String[] args) throws IOException {
    String filename = "res" + File.separator + "TajMahal.jpeg";

    GaussianBlur gaussianBlur = new GaussianBlur(filename, 2);
    gaussianBlur.applyFilter();
    gaussianBlur.displayImage();
}

And below are the resulting images

The original image: Original Image

Blurred with radius 2: Blur Radius 2

Blurred with radius 7: Blur Radius 7

How come blurring it with radius 7 is darkening the image? Is there something missing in the formula or something that I have missed?

hakuna matata
  • 3,243
  • 13
  • 56
  • 93
  • 1
    "Blurred with radius 2" seems to be a bit darker, too! If so, your algorithm darkens in general. – Seelenvirtuose Jan 06 '20 at 13:20
  • Are you reading the "blackness" from outside the image bounds? – Andy Turner Jan 06 '20 at 13:21
  • @AndyTurner In case I'm at the borders (for example the extremities or the borders), I'm just setting the pixels to null as to not use them when calculating the weights. I'm not sure what you mean by "blackness"? – hakuna matata Jan 06 '20 at 13:23
  • @hakunamatata but are you normalizing for the "lack" of their contribution? As in, if the kernel is fully inside the image, the sum of the weight over all pixels is 1; but if you place it at the edge, if will be less than 1. – Andy Turner Jan 06 '20 at 13:25
  • 1
    @hakunamatata look at the black border around the radius 2 image: that "blackness" is coming from somewhere. – Andy Turner Jan 06 '20 at 13:25
  • 1
    looks like the elements of `getGaussianMatrix` don't add up to 1, so the image will be darker overall. A bigger problem is that you accumulate a lot of downward rounding errors in `getWeightedGaussianBlurValue` – Matt Timmermans Jan 06 '20 at 13:46
  • @AndyTurner Will setting the out of bound pixels in an image matrix to a constant color pixel (for example white) reduce the blackness? Or maybe I should take the closest existing pixel? – hakuna matata Jan 06 '20 at 13:47
  • Maybe I can skip the pixels that will have out of bounds pixels when getting the image matrix? – hakuna matata Jan 06 '20 at 14:07
  • @MattTimmermans I added up the weights of the final matrix and it ranges between 0.99999... and 1.000000... with a very small margin of error, I'm not sure if this is what you mean. I tried it on radiuses 1, 2, 5, 7, 20 and 50. – hakuna matata Jan 06 '20 at 15:30
  • Ah, ok. that's close enough. You want to fix those rounding errors, though. a 40x40 matrix means 1600 round-downs with average error 0.5... except it's less because a lot of those elements are close to 0. Anyway it subtracts a lot from your pixel values – Matt Timmermans Jan 06 '20 at 15:43
  • I was able to get rid of the black borders by taking the closest edge pixel when dealing with extremities or edges, however, the darkening is still occurring. – hakuna matata Jan 06 '20 at 16:51
  • Make sure your indexing is correct. If the image is darkening then some pixels are not contributing, assuming the kernel is normalized of course. – Entalpi Jan 13 '20 at 12:28

0 Answers0