Edge Preserving Blur
Bilateral Filter
Image Processing
Primitive Operations
Computer Vision

How to create an edge preserving blur similar to a bilateral filter using a limited set of primitive operations

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

If you need a blur that preserves edges but you cannot afford a true bilateral filter, the practical answer is to switch to an approximation built from cheaper operations. A guided-filter style blur is often the best fit because it uses local averages and pointwise arithmetic while still respecting strong edges.

Why a True Bilateral Filter Is Expensive

A bilateral filter mixes two ideas at once:

  • nearby pixels matter more than far pixels
  • pixels with similar intensity matter more than very different ones

That second part, the range weighting, is what makes it edge-preserving and also what makes it expensive. If your primitive operations are limited to additions, multiplications, divisions, and box-like local averages, an exact bilateral filter is awkward.

So the engineering question becomes: what approximation preserves the important visual behavior while staying inside the allowed operation set?

A Guided-Filter Style Approximation

A guided filter is a strong candidate because it relies mostly on local means and pointwise arithmetic. It is not identical to a bilateral filter, but it produces a similar “smooth flat regions, keep strong edges” effect.

Here is a minimal grayscale implementation using NumPy and simple box averaging built from slicing and local windows:

python
1import numpy as np
2
3
4def box_mean(img, radius):
5    h, w = img.shape
6    out = np.zeros_like(img, dtype=np.float64)
7
8    for y in range(h):
9        y0 = max(0, y - radius)
10        y1 = min(h, y + radius + 1)
11        for x in range(w):
12            x0 = max(0, x - radius)
13            x1 = min(w, x + radius + 1)
14            out[y, x] = img[y0:y1, x0:x1].mean()
15
16    return out
17
18
19def guided_filter(gray, radius=2, eps=0.01):
20    I = gray.astype(np.float64)
21    p = I
22
23    mean_I = box_mean(I, radius)
24    mean_p = box_mean(p, radius)
25    mean_II = box_mean(I * I, radius)
26    mean_Ip = box_mean(I * p, radius)
27
28    var_I = mean_II - mean_I * mean_I
29    cov_Ip = mean_Ip - mean_I * mean_p
30
31    a = cov_Ip / (var_I + eps)
32    b = mean_p - a * mean_I
33
34    mean_a = box_mean(a, radius)
35    mean_b = box_mean(b, radius)
36
37    q = mean_a * I + mean_b
38    return q
39
40
41img = np.array([
42    [0.1, 0.1, 0.1, 0.9, 0.9],
43    [0.1, 0.1, 0.1, 0.9, 0.9],
44    [0.1, 0.1, 0.1, 0.9, 0.9],
45], dtype=np.float64)
46
47print(guided_filter(img, radius=1, eps=0.001))

Even with this small example, you can see the idea: local smoothing happens mostly inside regions of similar intensity, while the strong jump between dark and bright areas is preserved better than with an ordinary blur.

Why This Feels Similar to Bilateral Filtering

The output looks bilateral-like because edges reduce how much neighboring pixels influence one another. The mechanism is different, but the visual result is often close enough for real-time graphics or restricted compute environments.

That is why many systems use approximations rather than exact bilateral filtering:

  • better runtime
  • easier implementation with basic primitives
  • more predictable memory and control-flow cost

A Simpler Heuristic if You Need Even Less

If a guided filter is still too heavy, a rougher approximation is:

  1. compute a normal blur
  2. compute an edge-strength map from local differences
  3. blend original pixels back in where the edge map is strong

That can be implemented with only local averages, subtraction, absolute value, and interpolation. It is less principled than a guided filter, but it often gives a useful “do not wash out edges completely” effect.

Parameter Tuning

Two parameters matter most in the guided-filter approach:

  • radius controls spatial smoothing area
  • epsilon controls how strongly edges are preserved versus smoothed

A larger radius produces broader smoothing. A larger epsilon makes the filter less sensitive to local variance and therefore more blur-like. These parameters do not map one-to-one to bilateral filter settings, so they need visual tuning.

Common Pitfalls

The biggest mistake is expecting a perfect bilateral match from a primitive-operation approximation. Similar does not mean identical.

Another issue is tuning only on flat synthetic images. Edge-preserving filters should be tested on textured regions, soft gradients, and hard boundaries because their behavior differs across those cases.

Developers also sometimes use a simple Gaussian or box blur and then wonder why edges vanish. Without some edge-aware term, the blur has no reason to preserve boundaries.

Finally, do not ignore performance details. A naive box mean implemented with nested Python loops is fine for explanation, but production code should use vectorized operations, shaders, or integral-image style acceleration.

Summary

  • A true bilateral filter is expensive because it uses both spatial and range weighting.
  • A guided-filter style blur is a practical edge-preserving approximation built from simpler primitives.
  • Local means plus pointwise arithmetic can preserve strong edges while smoothing flat regions.
  • For even tighter constraints, blur-plus-edge-blending can work as a rough heuristic.
  • Tune radius and edge sensitivity visually, because the approximation is not an exact bilateral substitute.

Course illustration
Course illustration

All Rights Reserved.