mpen
mpen

Reputation: 283163

How to do a gaussian blur without modifying the original pixels?

Given an image such as below, I want to blur it such that none of the original black pixels change in value. The blur should have a nice S curve fade-off to white.

enter image description here

If I apply a 15px gaussian blur to this image I get this:

enter image description here

It fades out nicely, but if I put a red square where the original black square was, you can see that actually the blur has exposed some parts of it:

enter image description here

Now I recently found out that a 15px gaussian blur means 1 std deviation is 15px, the actual full radius of affected pixels is closer to ¾√2π*σ. Technically infinite, but most programs limit it to that, from what I hear. So for a 15px blur, if I expand my box by 29px then gaussian blur by 15px, the extend of the blur ought to just barely reach back to my original box, leaving it undisturbed, right?

enter image description here

That looks pretty good, but if I put my red box back again we can see that there is still a difference:

enter image description here

The red doesn't show through (much), but I can still see a hard outline.

enter image description here

Is there a better suited blur algorithm for this than gaussian? What I'm imagining is something like we find the normals for the edges of the box/shape and then draw an S curve along that normal. This would probably be easier if I had a vector image, but I have bitmaps, so I don't know what the closest equivalent would be.

Upvotes: -1

Views: 123

Answers (3)

mpen
mpen

Reputation: 283163

Given the original image:

enter image description here

I came up with this:

enter image description here

It's a bit easier to see when colorized:

enter image description here

The red is the original mask, the blue is the blur. The blue fade should begin where the red edge stops.

Applying a simple convolution to the whole image causes the 'fade' to cut into the original mask, so to fix this I expanded/dilated the mask by taking the max value over the same radius as the kernel. This is built into torch as max_pool2d.

Here's the full code (in Python):

def run(self, *, mask: torch.Tensor, radius: int):
    assert mask.ndim == 3, "Expected mask to have shape [B,H,W]"
    assert radius > 0, "Radius must be positive"

    # Define kernel size (2 * radius + 1 to cover the full area)
    kernel_size = 2 * radius + 1

    # Create a round Gaussian kernel
    y, x = torch.meshgrid(
        torch.arange(-radius, radius + 1, device=mask.device),
        torch.arange(-radius, radius + 1, device=mask.device),
        indexing="ij"
    )
    distance = torch.sqrt(x ** 2 + y ** 2)
    gaussian_kernel = torch.exp(-0.5 * (distance / radius) ** 2)
    gaussian_kernel /= gaussian_kernel.sum()
    gaussian_kernel = gaussian_kernel.view(1, 1, kernel_size, kernel_size)

    # Expand the mask by replicating the edges before applying max_pool2d
    padded_mask = F.pad(mask, (radius, radius, radius, radius), mode="replicate")

    # Apply max pooling to simulate dilation
    dilated_mask = F.max_pool2d(padded_mask.unsqueeze(1), kernel_size, stride=1, padding=radius, ceil_mode=True)

    # Apply Gaussian blur to the dilated mask
    blurred_mask = F.conv2d(dilated_mask, gaussian_kernel, padding=0)  # Implicitly crops back to original size

    # Remove the channel dimension
    blurred_mask = blurred_mask.squeeze(1)

    # Shouldn't be necessary...
    # final = torch.clamp(torch.maximum(blurred_mask, mask), 0, 1)

    return self.output({"blurred_mask": blurred_mask})

The only thing I don't love is that the circles get squared off a bit, but it's not too bad.

Upvotes: 0

Christoph Rackwitz
Christoph Rackwitz

Reputation: 15510

The problem is that a Gaussian never becomes zero. Just very close to it.

No matter of how much dilation (shifting) you apply, it'll never stop.

The only thing making a Gaussian "stop" is quantization, e.g. having values 0 to 255 instead of "actual" real numbers.

You could pick something other than a Gaussian. Anything with a finite support region would do. "Good" old box blur is such a thing. Repeated application of a box blur approaches a Gaussian, yet stays finite in the strict mathematical sense.

If you're going the path of a distance transform and apply a curve to the distance to get your "fading out", the input data will have to be binary/binarized. On a distance transform, you can apply any function you can think of. Even the positive half of a Gaussian, because the distance is a scalar, not an image.

That "S-curve" you speak of sounds somewhat like a "Raised cosine".

Upvotes: 1

haievyiivan
haievyiivan

Reputation: 56

The problem is that design of Gaussian blur is isotropic, meaning it diffuses the intensity of each pixel equally in all directions.

You should try generating a binary mask of the black region, computing its distance transform, and than applying a scaling curve (exponential decay) to map the distance values to intensity values.

If I understood correctly what you are trying to achieve, reading this might help you with the realization.

Upvotes: 2

Related Questions