Skip to content

Crop the largest foreground object

CookbookRegions and masks

Threshold and clean a mask, find the largest external contour, then crop its bounding box with padding.

Try the recipe ↓

The pipeline

VISUAL WALKTHROUGHRegions and masks
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 / 1Foreground mask448 × 320
Foreground mask: Foreground pixels used for external contour extraction.

Foreground pixels used for external contour extraction.

STEP 01 / 03

Build a clean mask

Explore the algorithm →
thresholdmorphologyExMORPH_CLOSE

Threshold and close small gaps.

Receives
Grayscale intensity.
Passes to the next step
A cleaned foreground mask.

Why this step? Thresholding identifies bright foreground and closing fills small breaks. Repairing the mask before finding contours reduces the chance that one intended object is split into multiple smaller candidates.

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

Automatic cropping combines a foreground decision with a simple selection rule: keep the largest silhouette. Its bounding rectangle determines which original pixels to retain.

  1. 01

    Build a clean mask

    thresholdmorphologyExMORPH_CLOSE

    Thresholding identifies bright foreground and closing fills small breaks. Repairing the mask before finding contours reduces the chance that one intended object is split into multiple smaller candidates.

    Receives
    Grayscale intensity.
    Passes on
    A cleaned foreground mask.
  2. 02

    Find the largest object

    findContourscontourArea

    External contours describe each candidate silhouette. Comparing contour areas chooses the largest foreground object, which is a useful heuristic when one subject dominates the frame but is not a semantic subject detector.

    Receives
    The cleaned mask.
    Passes on
    The contour with the largest enclosed area.
  3. 03

    Crop its bounding rectangle

    boundingRectMat.roiMat.copyTo

    An axis-aligned bounding box includes the complete contour. Padding adds context, clipping keeps the rectangle inside the image, and copying the ROI creates an independently owned crop. No resizing is needed.

    Receives
    The selected contour and original colour image.
    Passes on
    A smaller colour image with the selected bounds and crop dimensions reported.

Tune and diagnose

Choose the parameters

Fix the foreground threshold before adjusting crop padding. Larger closing kernels can merge unrelated bright areas and change which object wins. Padding is measured in processed-image pixels.

Read the result

Selected bounds should surround the intended subject. A bright wall or frame may be larger than that subject. Cropping retains background inside the rectangle; use the cutout recipe when you need transparency.

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 ↗

Threshold and clean a mask, find the largest external contour, then crop its bounding box with padding.

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

The largest bright component is not always the subject. This produces a rectangular crop, not a cutout.

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 {
  CHAIN_APPROX_SIMPLE,
  COLOR_BGR2GRAY,
  Mat,
  MatVector,
  RETR_EXTERNAL,
  THRESH_BINARY,
  boundingRect,
  contourArea,
  cvtColor,
  findContours,
  threshold
} from '@banou/opencv-wasm'

// The engine is already initialized; image is an 8-bit BGR Mat.
// rect is an in-bounds { x, y, width, height } rectangle in processed pixels.
// "using" releases native handles at scope exit; inspect or copy outputs before then.

// 1. Allocate grayscale, a silhouette mask and hierarchy metadata for contours.
using gray = new Mat(), mask = new Mat(), hierarchy = new Mat()
// Keep contour coordinates in a vector and reserve an independent output Mat.
using contours = new MatVector(), output = new Mat()
// Convert colour to brightness for the simple bright-foreground selection.
cvtColor(image, gray, COLOR_BGR2GRAY)
// Mark values greater than 127 as white foreground.
threshold(gray, mask, 127, 255, THRESH_BINARY)
// 2. Extract external outlines; straight boundary runs are compressed.
// The full lab also closes small gaps in the mask before this extraction.
findContours(mask, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE)
// Remember the largest enclosed area and its axis-aligned bounds.
// The initial rect is only a placeholder; a successful search replaces it.
let largest = 0, bounds = rect
// Consider each candidate foreground outline.
for (let i = 0; i < contours.size(); i++) {
  // Release each vector-returned contour handle when this iteration ends.
  using contour = contours.get(i)!
  // Measure enclosed contour area to choose the largest foreground candidate.
  const area = contourArea(contour)
  // When a larger object wins, keep its bounding rectangle as the crop region.
  if (area > largest) { largest = area; bounds = boundingRect(contour) }
}
// Do not silently return an unrelated crop when thresholding found no object.
if (!largest) throw new Error('No foreground found')
// 3. An ROI selects existing pixels without resizing and shares the source storage.
using crop = image.roi(bounds)
// Copy into independent storage so the output remains valid after the source is released.
// The full lab adds configurable padding and clips those bounds to the image.
crop.copyTo(output)

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