roboflow/supervision · error · ValueError
Contour input must be a two-dimensional image
Error message
Contour input must be a two-dimensional image
What it means
`cv2.findContours` in OpenCV treats a 2-D single-channel image as input. The fallback at src/supervision/_cv2/_contours.py:150 enforces this explicitly: after `np.asarray(image)`, `values.ndim != 2` raises. A 3-D BGR frame or a 1-D signal array cannot be traced for borders.
Source
Thrown at src/supervision/_cv2/_contours.py:150
and np.any(following)
and np.array_equal(np.sign(previous), np.sign(following))
):
continue
keep.append(point)
return np.asarray(keep, dtype=np.int32)
def _find_contours(
image: npt.NDArray[Any], mode: int, method: int
) -> tuple[list[npt.NDArray[np.int32]], npt.NDArray[np.int32] | None]:
"""Find contours for the supported tree and SIMPLE modes."""
if mode != _RETR_TREE:
raise ValueError("Only RETR_TREE is supported by the fallback")
if method != _CHAIN_APPROX_SIMPLE:
raise ValueError("Only CHAIN_APPROX_SIMPLE is supported by the fallback")
values = np.asarray(image)
if values.ndim != 2:
raise ValueError("Contour input must be a two-dimensional image")
traced = [_compress_contour(contour) for contour in _trace_borders(values != 0)]
if not traced:
return [], None
return [contour.reshape(-1, 1, 2) for contour in traced], None
View on GitHub (pinned to 7f254d9784)
Solutions
- Convert to a 2-D single-channel mask first: `cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)` then threshold
- Squeeze channel axes: `mask.squeeze()` for (H, W, 1) masks
- Index the batch: `images[i]` for (N, H, W) arrays
Example fix
// before contours, _ = cv2.findContours(frame, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # frame is BGR // after gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) _, mask = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
Defensive patterns
Strategy: type-guard
Validate before calling
import numpy as np
def as_contour_mask(image) -> np.ndarray:
"""Coerce arbitrary image input to the 2-D mask findContours requires."""
arr = np.asarray(image)
if arr.ndim == 3 and arr.shape[-1] == 1:
arr = arr[..., 0]
elif arr.ndim == 3 and arr.shape[-1] == 3:
arr = cv2.cvtColor(arr, cv2.COLOR_BGR2GRAY)
if arr.ndim != 2:
raise ValueError(f"cannot reduce ndim={arr.ndim} to a 2-D mask")
return arr Type guard
def is_contour_input(image) -> bool:
"""findContours accepts only 2-D single-channel arrays."""
return np.asarray(image).ndim == 2 Prevention
- Always grayscale+threshold frames before findContours
- Squeeze (H, W, 1) masks and index (N, H, W) batches per image
When it happens
Trigger: Passing a color BGR frame (`shape (H, W, 3)`), an RGBA image, a batched array `(N, H, W)`, or a 1-D array to `cv2.findContours` on the fallback backend (real OpenCV errors differently, often with a cryptic assertion).
Common situations: Forgetting to grayscale/threshold a camera frame before contour extraction; masks stored as (H, W, 1); operating on an already-batched tensor converted to ndarray.
Related errors
- Drawing points must have shape (N, 2) or (N, 1, 2)
- Only None hierarchy is supported by the fallback
- Only RETR_TREE is supported by the fallback
- Only CHAIN_APPROX_SIMPLE is supported by the fallback
- Contour border tracing did not converge
AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15).
Data as JSON: /api/errors/d34cca6d5c7ae479.
Report an issue: GitHub.