高斯模糊实现生成了奇怪的输出。

huangapple go评论86阅读模式
英文:

Gaussian Blur implementation generates weird output

问题

我正在尝试在Go语言的image.Image对象上实现高斯模糊。对于以下图片:

高斯模糊实现生成了奇怪的输出。

生成的输出图片是:
高斯模糊实现生成了奇怪的输出。

可以看到,输出图片包含一些未处理的边界,这是因为当前实现决定不处理边缘,这让我觉得可能在计算上出了问题(我的意思是,这部分实现是正确的,所以我可以排除在遍历图像像素时的偏移错误)。我已经多次检查了这段代码,但找不到错误。我非常感谢对实现的帮助和考虑,这可以帮助我解决问题。以下是代码。如果需要任何编辑或澄清,请告诉我!

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;
}

编辑

  • 我修改了代码,使像素从原始图像中读取,而不是从rgbaImg中读取
  • 我还注释掉了applyGaussianFunction函数中的eFactor,因为我已经使用sum变量对核进行了归一化
  • 修改了.Set方法,使用了64位的RGBA结构体

这是新生成的图片
高斯模糊实现生成了奇怪的输出。

这些黑色边框很容易解决,我已经在处理它们了。这不再是问题的一部分。

英文:

I'm trying to implement a Gaussian Blur on golang image.Image objects. For the following image:

高斯模糊实现生成了奇怪的输出。

The output image generated is:
高斯模糊实现生成了奇怪的输出。

As one can see, the output image contains some unprocessed borders that corresponds to the current implementation decision to not process the edges, which leads me to think that I might have messed up on calculations somehow (what I mean is, this part of the implementation works, so I can discard off-by-one errors while iterating through image pixels). I've reviewed this code many times, but I can't find my mistake. I would really appreciate some help and considerations on the implementation, that could help me solve the problem. The code is contained below. If any edits or clarifications are necessary, please let me know!

package main

import (
	&quot;image&quot;
	&quot;image/color&quot;
	&quot;image/draw&quot;
	&quot;image/jpeg&quot;
	&quot;math&quot;
	&quot;os&quot;
)

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

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

	newImg := gaussianBlur(img, 3)

	out, err := os.Create(&quot;dog-blurred.jpeg&quot;)
	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 &lt; size; i++ {
        kernel[i] = make([]float64, size);
    }

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

    for i := 0; i &lt; size; i++ {
        for j := 0; j &lt; 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 &lt; size.Y - radius; y++ {
        for x := radius; x &lt; size.X - radius; x++ {
            var nr, ng, nb, na float64 = 0, 0, 0, 0;

            for i := -radius; i &lt; radius + 1; i++ {
                for j := -radius; j &lt; 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;
}

EDITS

  • I modified the code so that pixels are read from the original image, not from rgbaImg
  • I've also commented eFactor from the applyGaussianFunction function, since I'm already normalizing the kernel with the sum variable
  • Modified .Set method to use 64-bit RGBA struct

This is the newly generated image
高斯模糊实现生成了奇怪的输出。

Those black borders are easy to solve, I'm already working them out. This is not a part of the problem anymore.

答案1

得分: 2

你正在从你正在写入的相同图像中读取。你应该从原始图像中读取:

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

编辑:
另外,Image.At 返回的是 color.RGBA,而 func (color.RGBA) RGBA 返回的颜色范围是0到0xFFFF。然而,color.RGBA 构造函数希望它们在0到255的范围内。在写入结果时,你可能希望使用 color.RGBA64

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

You're reading from the same image that you're writing to. You shall read from the original image instead:

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

EDIT:
Additionally, Image.At returns color.RGBA, and func (color.RGBA) RGBA returns colors in the 0 to 0xFFFF range. However color.RGBA constructor expects them to be in 0 to 255 range. You may want to use color.RGBA64 when writing the result:

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

huangapple
  • 本文由 发表于 2023年1月9日 22:10:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/75058496.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定