Skip to content

Denoise and restore local contrast

CookbookPhotography

Reduce colour noise, then enhance luminance contrast without applying separate histogram equalization to each colour channel.

Try the recipe ↓

The pipeline

VISUAL WALKTHROUGHPhotography
Follow the images, then read what passes to the next algorithm.
STARTING IMAGEOriginal sample448 × 320
The unchanged source image. All steps use this same example.

The unchanged source image. All steps use this same example.

STEP 01 · 1 / 1Denoised colour448 × 320
Denoised colour: Nonlocal means reduces small colour fluctuations.

Nonlocal means reduces small colour fluctuations.

STEP 01 / 03

Remove similar-patch noise

Explore the algorithm →
fastNlMeansDenoisingColored

Colour nonlocal means averages matching patches.

Receives
An 8-bit BGR image.
Passes to the next step
A denoised colour image.

Why this step? Nonlocal means averages evidence from similar patches, allowing repeated image structure to contribute to denoising. The colour version treats brightness and colour noise separately internally. This stage comes before contrast enhancement so the next operation has less noise to amplify.

Actual OpenCV 5.0.0 results on the illustrated sample, using the lab’s default algorithm settings. Masks, overlays and normalized fields are labelled previews; the data contracts above describe what the algorithms really exchange. Try this chain with your images ↓

Why this chain works

Local contrast enhancement can amplify noise. Reduce noise first, then enhance brightness structure separately from colour to avoid independently distorting the colour channels.

  1. 01

    Remove similar-patch noise

    fastNlMeansDenoisingColored

    Nonlocal means averages evidence from similar patches, allowing repeated image structure to contribute to denoising. The colour version treats brightness and colour noise separately internally. This stage comes before contrast enhancement so the next operation has less noise to amplify.

    Receives
    An 8-bit BGR image.
    Passes on
    A denoised colour image.
  2. 02

    Work in Lab luminance

    cvtColorCOLOR_BGR2LabextractChannel

    Lab separates lightness from the a and b colour components. Extracting only L lets the contrast operation change lightness while retaining the two colour channels, rather than equalizing B, G and R independently.

    Receives
    The denoised colour image.
    Passes on
    An L-channel matrix and a Lab image holding the retained colour channels.
  3. 03

    Enhance local lightness

    createCLAHEinsertChannelCOLOR_Lab2BGR

    CLAHE adjusts local intensity distributions with a clip limit that restrains amplification. Inserting enhanced L back into Lab and converting to BGR produces the final colour result. This restores visibility of local contrast, not detail already removed by denoising.

    Receives
    The lightness channel.
    Passes on
    A colour image with reduced noise and enhanced local lightness contrast.

Tune and diagnose

Choose the parameters

Start with modest denoising strength, then increase the CLAHE clip limit only as needed. Compare fine texture before and after denoising: once erased, contrast enhancement cannot reconstruct it. Remaining noise often becomes visible at high clip limits.

Read the result

Inspect Denoised colour before judging the final contrast. Waxy texture points to excess denoising; grain that appears only in the final image points to contrast amplification.

Try it with your images

Choose your own image or start with the built-in sample. Run the recipe, then use the stage buttons to inspect intermediate results without rerunning it.

YOUR IMAGE · REAL OPENCV

Experiment at pixel level

Open full lab ↗

Reduce colour noise, then enhance luminance contrast without applying separate histogram equalization to each colour channel.

The engine loads on your first run. Your images stay in this browser.

Input448 × 320
OutputWaiting for a result

Scroll over either image to zoom at the pointer. Use the scrollbars to pan both views over the same relative area. Zoom is relative to the input; pixel coordinates belong to each image. Warps can change scene correspondence.

Pixel inspector RGBA · native values · matched scale · 9 × 9 output pixels
Hover to inspect. Click to pin a pixel.
Input
Select a pixel

Output
Select a pixel

Sample models and licenses

Assumptions and limits

Large denoising strengths erase texture; aggressive contrast enhancement can expose remaining noise.

TypeScript core chain

Initialize the shared engine once with await initOpenCV(), then use these named imports. In a bundled browser app, pass the WASM URL as shown in the quickstart. image is an 8-bit BGR Mat from that same engine; paired recipes receive an equally sized nextImage. Region recipes use an in-bounds pixel rect. Read outputs before the using scope ends. See matrix ownership.

import {
  COLOR_BGR2Lab,
  COLOR_Lab2BGR,
  Mat,
  createCLAHE,
  cvtColor,
  extractChannel,
  fastNlMeansDenoisingColored,
  insertChannel
} from '@banou/opencv-wasm'

// The engine is already initialized; image is an 8-bit BGR Mat.
// "using" releases native handles at scope exit; inspect or copy outputs before then.

// 1. Allocate denoised BGR, Lab colour, its lightness channel and final BGR output.
using denoised = new Mat(), lab = new Mat(), lightness = new Mat(), output = new Mat()
// Average similar patches to reduce noise before contrast enhancement amplifies it.
// The two 8s control brightness/colour denoising strength; 7 and 21 are the
// template-patch and search-window widths in pixels.
fastNlMeansDenoisingColored(image, denoised, 8, 8, 7, 21)
// 2. Split the denoised image into Lab lightness and two colour components.
cvtColor(denoised, lab, COLOR_BGR2Lab)
// Channel 0 is lightness (L); keep the a/b colour channels in lab unchanged.
extractChannel(lab, lightness, 0)
// 3. Limit local histogram contrast amplification with clip limit 2.
// The 8x8 size means an eight-by-eight grid of tiles across the image, not 8-pixel tiles.
using clahe = createCLAHE(2, { width: 8, height: 8 })
// Check the nullable factory result before applying the algorithm.
if (!clahe) throw new Error('CLAHE factory failed')
// Enhance only local lightness contrast; the same Mat is used as source and destination.
clahe.apply(lightness, lightness)
// Put enhanced L back into channel 0 beside the retained colour components.
insertChannel(lightness, lab, 0)
// Return to BGR for display or encoding. This cannot restore detail erased by denoising.
cvtColor(lab, output, COLOR_Lab2BGR)

The snippets isolate the core operations. The complete runnable recipes also include validation, filtering, overlays, intermediate previews and resource cleanup.