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 (
"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;
}
EDITS
- I modified the code so that pixels are read from the original image, not from
rgbaImg
- I've also commented
eFactor
from theapplyGaussianFunction
function, since I'm already normalizing the kernel with thesum
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.
CodePudding user response:
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)});