Home  >  Article  >  Backend Development  >  Gaussian blur implementation produces strange output

Gaussian blur implementation produces strange output

WBOY
WBOYforward
2024-02-11 09:20:081187browse

Gaussian blur implementation produces strange output

php Xiaobian Yuzai pointed out that Gaussian blur is a common image processing technology that can blur images and is often used to beautify photos or achieve special effects. However, if the Gaussian blur algorithm is not implemented correctly, it can produce strange output. This may include issues such as image distortion, blurred edges, or color shifts. Therefore, when using Gaussian blur technology, attention must be paid to the correct implementation of the algorithm to ensure the expected output effect.

Question content

I am trying to implement Gaussian blur on golang image.image object. For the following images:

The generated output image is:

As one can see, the output image contains some unhandled boundaries that correspond to the current implementation decision not to handle edges, which makes me think that I may have messed up the calculation in some way (I mean Yes, this part of the implementation works so I can discard the off-by-one error when iterating over image pixels). I've checked this code multiple times but I can't find my error. I'd really appreciate some help and considerations on implementation that could help me figure it out. The code is included below. If any edits or clarifications are needed, please let me know!

package main

import (
    "image"
    "image/color"
    "image/draw"
    "image/jpeg"
    "math"
    "os"
)

func main() {
    f, err := os.Open("dog.jpeg")
    if err != nil {
        panic(err)
    }

    img, err := jpeg.Decode(f)
    if err != nil {
        panic(err)
    }

    newImg := gaussianBlur(img, 3)

    out, err := os.Create("dog-blurred.jpeg")
    if err != nil {
        panic(err)
    }

    err = jpeg.Encode(out, newImg, nil)
    if err != nil {
        panic(err)
    }
}

func applyGaussianFunction(x, y, stdDev float64) float64 {
    // eFactor := 1 / (2 * math.Pi * stdDev*stdDev);
    ePowNominator := -(x*x + y*y);
    ePowDenominator := 2 * stdDev*stdDev;

    return math.Pow(math.E, (ePowNominator/ePowDenominator));
}

func generateKernel(radius int) [][]float64 {
    size := 1 + (radius * 2);
    kernel := make([][]float64, size);
    stdDev := math.Max(float64(radius / 2), 1);

    sum := float64(0);

    for i := 0; i < size; i++ {
        kernel[i] = make([]float64, size);
    }

    for i := -radius; i < radius + 1; i++ {
        for j := -radius; j < radius + 1; j++ {
            val := applyGaussianFunction(float64(j), float64(i), stdDev);
            kernel[i + radius][j + radius] = val;
            sum += val;
        }
    }

    for i := 0; i < size; i++ {
        for j := 0; j < size; j++ {
            kernel[i][j] /= sum;
        }
    }

    return kernel;
}

func makeImageRGBA(src image.Image) *image.RGBA {
    b := src.Bounds().Size();
    rgba := image.NewRGBA(image.Rect(0, 0, b.X, b.Y));
    draw.Draw(rgba, rgba.Bounds(), src, image.Pt(0, 0), draw.Src);

    return rgba;
}

func gaussianBlur(img image.Image, radius int) image.Image {
    size := img.Bounds().Size();
    rgbaImg := image.NewRGBA(image.Rect(0, 0, size.X, size.Y));

    kernel := generateKernel(radius);

    for y := radius; y < size.Y - radius; y++ {
        for x := radius; x < size.X - radius; x++ {
            var nr, ng, nb, na float64 = 0, 0, 0, 0;

            for i := -radius; i < radius + 1; i++ {
                for j := -radius; j < radius + 1; j++ {
                    // NEW: Get pixels from original Image
                    pr, pg, pb, pa := img.At(x - j, y - i).RGBA();

                    nr += float64(pr) * kernel[i + radius][j + radius];
                    ng += float64(pg) * kernel[i + radius][j + radius];
                    nb += float64(pb) * kernel[i + radius][j + radius];
                    na += float64(pa) * kernel[i + radius][j + radius];
                }
            }

            // Handle overflow by using 64-bit alphapremultiplied values
            rgbaImg.Set(x, y, color.RGBA64{uint16(nr), uint16(ng), uint16(nb), uint16(na)});
        }
    }

    return rgbaImg;
}

edit

  • I modified the code so that it reads the pixels from the original image instead of rgbaimg
  • I also annotated the efactor from the applygaussianfunction function since I already used the sum variable to normalize the kernel
  • Modified .set method to use 64-bit rgba structure

This is the newly generated image

Those black borders are easy to fix, I'm already working on them. This is no longer part of the problem.

Workaround

You are reading from the same image that you are writing to. You should read from the original image:

pr, pg, pb, pa := img.at(x+j, y+i).rgba()

edit: Additionally, image.at returns color.rgba, while func (color.rgba) rgba returns the range from 0 to 0xffff. However the color.rgba constructor expects them to be in the range 0 to 255. You may want to use color.rgba64 when writing the results:

rgbaImg.Set(x, y, color.RGBA64{uint16(nr), uint16(ng), uint16(nb), uint16(na)});

The above is the detailed content of Gaussian blur implementation produces strange output. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete