Blurring an image
Let us first load the image we will be working with as before.
from pathlib import Path
import halide.imageio
import halide as hl
import halide.imageio
from halide_book.paths import Paths
import numpy as np
from halide_book.interactive import Slider, interact
from halide_book.paths import Paths
x = hl.Var("x")
y = hl.Var("y")
c = hl.Var("c")
image_data = halide.imageio.imread(Paths.static / "stuff.png")
input_image = hl.Buffer(image_data)
To blur an image, we update each pixel to become the average of the surrounding pixels. We could write this a simple sum in the case where we only want the nearest neighbors:
blurred[x, y] = image[x, y] + image[x+1, y] + image[x-1, y] + image[x, y+1] + image[x, y - 1]
Blurring mixes the values of a pixel with its surrounding pixels. Pixels on the edge of the image have some non-existent neighbors. To define values to these pixels, we can apply a boundary condition in Halide:
image_bounded = hl.BoundaryConditions.mirror_image(input_image)
Before we continue, we also need to make sure blurring happens without overflow. To ensure this, we convert the input image values from 8-bit uint values to 32-bit floats.
image = hl.Func("image")
image[x, y, c] = hl.f32(image_bounded[x, y, c]) / image_bounded.type().max()
interact(
image,
size=[input_image.width(), input_image.height(), 3],
)
We can now realize an image larger than the original.
interact(
image_bounded,
size=[input_image.width() * 2, input_image.height() * 2, 3],
)
The resulting image is exactly the same as the input image, but we can now use this in our box blur because it has a well-defined exterior.
We can now implement a simple box blur that first averages the surrounding pixels in the x-direction and then in the y-direction:
normalization_factor = hl.f32(1.0 / 3.0)
blurred_x = hl.Func("blurred_x")
blurred_x[x, y, c] = normalization_factor * (image[x - 1, y, c] + image[x, y, c] + image[x + 1, y, c])
blurred_y = hl.Func("blurred_y")
blurred_y[x, y, c] = normalization_factor * (blurred_x[x, y - 1, c] + blurred_x[x, y, c] + blurred_x[x, y + 1, c])
interact(
blurred_y,
controls=[],
size=[input_image.width(), input_image.height(), 3],
)
This simple box blur reduces some of the noise in our image, but it also makes the output image quite distorted. We will try to improve upon it in future chapters.