Skip to content

Simplify an image into colour regions

CookbookPhotography

Smooth small colour fluctuations, cluster colours with k-means and render the reduced palette.

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 / 1Smoothed colours448 × 320
Smoothed colours: Bilateral filtering reduces within-region variation before clustering.

Bilateral filtering reduces within-region variation before clustering.

STEP 01 / 03

Smooth within colour regions

Explore the algorithm →
bilateralFilter

Bilateral filtering reduces small variations while retaining boundaries.

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

Why this step? Bilateral filtering averages nearby pixels according to both position and colour similarity. This reduces fluctuations within colour regions while preserving some strong boundaries, giving clustering fewer small variations to spend palette entries on.

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

To summarize an image with a small set of colours, first reduce incidental local variation, then learn representative colours and assign every pixel to one of them.

  1. 01

    Smooth within colour regions

    bilateralFilter

    Bilateral filtering averages nearby pixels according to both position and colour similarity. This reduces fluctuations within colour regions while preserving some strong boundaries, giving clustering fewer small variations to spend palette entries on.

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

    Learn a palette

    kmeansKMEANS_PP_CENTERS

    K-means alternates assigning pixels to their nearest colour centre and updating centres from assigned samples. K-means++ chooses spread-out initial centres. The lab clusters colour only, so disconnected areas of similar colour can share the same palette entry.

    Receives
    A float32 sample matrix with one BGR triplet per pixel.
    Passes on
    One integer cluster ID per pixel and K learned BGR centres.
  3. 03

    Recolour the image

    Palette lookup

    Replacing each pixel by its assigned centre turns the learned clusters into a visible reduced-palette image. Keeping the label field allows later grouping, recolouring or measuring palette usage without reclustering.

    Receives
    Cluster labels and colour centres.
    Passes on
    A quantized colour image and inspectable palette IDs.

Tune and diagnose

Choose the parameters

Increase palette colours to retain more distinctions; decrease them for stronger simplification. Increase bilateral colour sigma to smooth a broader range of colours before clustering, while watching for merging of important boundaries.

Read the result

K-means optimizes sample distances, not semantic importance or perceptual colour difference. A small but important accent colour may disappear. For perceptual palette work, consider clustering in Lab and handling conversion and gamut deliberately.

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 ↗

Smooth small colour fluctuations, cluster colours with k-means and render the reduced palette.

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

Results depend on the image’s colour distribution. Small but significant colours may disappear into larger clusters.

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 {
  CV_32F,
  KMEANS_PP_CENTERS,
  Mat,
  TERM_CRITERIA_COUNT,
  TERM_CRITERIA_EPS,
  bilateralFilter,
  kmeans
} 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 an image with reduced variation inside colour regions.
using smooth = new Mat()
// Use a 7-pixel neighbourhood, colour sigma 40 and spatial sigma 5 pixels.
// Bilateral weights favour nearby, similar colours to smooth while retaining strong edges.
bilateralFilter(image, smooth, 7, 40, 5)
// 2. Each pixel becomes one sample row with three BGR values. CV_32F
// stores each component as a float, the format expected by k-means.
using samples = new Mat(smooth.rows * smooth.cols, 3, CV_32F)
// Copy interleaved 8-bit BGR components into float storage without changing their order.
samples.data32F.set(smooth.data)
// Allocate each sample's integer cluster ID and the learned BGR colour centres.
using labels = new Mat(), centres = new Mat()
// 3. Learn six colour groups. Stop after at most 20 updates or centre motion
// within epsilon 0.5; the flags enable both criteria. Use one attempt and
// k-means++ initialization to spread the starting centres across the samples.
kmeans(samples, 6, labels,
  { type: TERM_CRITERIA_COUNT | TERM_CRITERIA_EPS, maxCount: 20, epsilon: 0.5 },
  1, KMEANS_PP_CENTERS, centres)
// labels.data32S chooses a BGR triplet from centres.data32F for each pixel.

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