← all tasks

task 07 / cv

bilateral image denoising

classic and learned bilateral filters, live on the gpu.

grainy photos are noisy photos. this page adds grain to a picture and then removes it again, with three versions of the same classic trick. everything runs in your browser, nothing is uploaded.

1pick a photo, or use your own

2pick how to clean it

a tiny neural network picks the filter settings separately for every pixel, so flat sky and sharp edges get different treatment.

noisynoisy‹›

drag across the photo to compare before and after.

qualityn/a→...dB
noise to add60

see what the network chose

how well does it work?

average quality score (PSNR, higher is better) on standard test photos.

  • noisy photo20.5
  • classic, hand-tuned27.0
  • classic, auto26.9
  • neural (this page)28.4
  • BM3D28.6
  • DnCNN29.2

the neural version beats every classic setting by about 1.4 dB and comes close to BM3D, while staying a filter that only averages nearby pixels. a bigger plain network (DnCNN) is still better.

full results table and plot+

PSNR in dB on grayscale test sets with gaussian noise (fixed seed), and on 320 real-noise crops from the SIDD validation set. (a)-(d) are bilateral variants, (e) are reference denoisers. the last three (d) rows are ablations of the network.

methodσ=15σ=25σ=50SIDD
-noisy input24.7920.4814.9127.02
abilateral, tuned per sigma29.7126.9623.9830.47
bbilateral, blind auto params29.6026.9023.9730.47
citerated bilateral29.8127.2024.3630.83
cjoint bilateral (smoothed guide)30.0027.4524.0530.75
dneural-guided bilateral (ours)30.8228.3825.4827.27
dnbf, no guide30.7628.2825.2728.27
dnbf, one pass30.8628.3525.1927.21
dsame unet, plain residual30.8328.3625.4527.98
enon-local means (cv2)29.3726.6823.6932.59
eBM3D31.1528.6325.7128.21
eDnCNN-B (pretrained)31.6229.1626.2327.48
2325272931σ=15σ=25σ=50
  • bilateral, tuned per sigma
  • joint bilateral (smoothed guide)
  • neural-guided bilateral (ours)
  • BM3D
  • DnCNN-B (pretrained)
further details+

the bilateral filter

it replaces each pixel by a weighted average of its neighbours. a neighbour counts less the further away it is (σ_s, in pixels) and the more its brightness differs (σ_r). so flat areas get smoothed, while across an edge the neighbours differ a lot and are mostly ignored.

the catch: one σ_s and σ_r for the whole image has to suit both flat sky and fine texture, and with heavy noise the filter can no longer tell noise from edges.

how auto picks its settings

it measures the noise with immerkaer's method, a small filter that cancels smooth image content so mostly noise is left. that number goes into a table of the best settings per noise level, found by a grid search on 32 held-out training photos. on artificial noise the estimate is within about 1 grey level.

the neural version

a small u-net (93k weights, 460 kB) looks at the noisy photo and outputs σ_s, σ_r and a guide image for every pixel. two bilateral passes then use those maps. the result is still an average of nearby noisy pixels, so it cannot invent detail. it was trained on 400 photos with random noise levels and never gets told how noisy the input is.

it runs with onnx runtime in a background thread, on your gpu (webgpu) if possible and on the cpu otherwise, in 256 px tiles so it fits in a phone's memory.

where it falls short

the same network without the bilateral structure scores just as well, so the structure buys explainable maps rather than quality. a bigger network (DnCNN) is 0.8 dB better. on real camera noise the network barely helps, because it was trained on artificial noise only.

report, code and model

the full write-up with all experiments is in the report. the model is on the hugging face hub.