roboflow/supervision · error · ValueError
Contours must have shape (N, 2) or (N, 1, 2)
Error message
Contours must have shape (N, 2) or (N, 1, 2)
What it means
The fallback geometry helpers (contour area, approxPolyDP, etc.) normalize OpenCV contour inputs — either an (N, 2) point array or OpenCV's (N, 1, 2) contour layout — to (N, 2) float64. Any other rank or last dimension (e.g. (N, 3) keypoints, (N,) flat arrays) is rejected because shoelace/polygon math is undefined for it.
Source
Thrown at src/supervision/_cv2/_geometry.py:17
"""Private polygon geometry fallbacks."""
from __future__ import annotations
from typing import Any
import numpy as np
import numpy.typing as npt
def _as_points(contour: npt.NDArray[Any]) -> npt.NDArray[np.float64]:
"""Normalize an OpenCV contour to an ``(N, 2)`` float64 array."""
points = np.asarray(contour)
if points.size == 0:
return np.empty((0, 2), dtype=np.float64)
if points.ndim not in (2, 3) or points.shape[-1] != 2:
raise ValueError("Contours must have shape (N, 2) or (N, 1, 2)")
return points.reshape(-1, 2).astype(np.float64, copy=False)
def _contour_area(contour: npt.NDArray[Any], oriented: bool = False) -> float:
"""Compute a contour's signed or absolute shoelace area."""
points = _as_points(contour)
if len(points) < 3:
return 0.0
x = points[:, 0]
y = points[:, 1]
area = 0.5 * float(np.dot(x, np.roll(y, -1)) - np.dot(y, np.roll(x, -1)))
return area if oriented else abs(area)
def _simplify_slices(
points: npt.NDArray[np.float64], epsilon_squared: float, closed: bool
) -> npt.NDArray[np.float64]:
"""Run OpenCV's stack-based Douglas-Peucker slice traversal."""View on GitHub (pinned to 7f254d9784)
Solutions
- Reshape to (N, 2): points = np.asarray(contour).reshape(-1, 2) — verify N is even first for flat inputs.
- For OpenCV-style callers, pass contours as (N, 1, 2) arrays as returned by findContours.
- Drop extra columns before use: contour = xyz[:, :2].
Example fix
# before area = cv2.contourArea(np.array(flat_xy_list)) # shape (2N,) # after points = np.asarray(flat_xy_list, dtype=np.float64).reshape(-1, 2) area = cv2.contourArea(points)
Defensive patterns
Strategy: type-guard
Validate before calling
pts = np.asarray(contour)
if pts.ndim not in (2, 3) or pts.shape[-1] != 2:
pts = pts.reshape(-1, 2)
area = cv2.contourArea(pts) Type guard
def is_valid_contour(a: np.ndarray) -> bool:
a = np.asarray(a)
return a.ndim in (2, 3) and a.shape[-1] == 2 and a.size in (0,) or (a.ndim in (2, 3) and a.shape[-1] == 2) Prevention
- Reshape point data to (N, 2) at ingestion
- Drop keypoint/xyz extra columns before contour ops
- Keep findContours output shape (N, 1, 2) untouched
When it happens
Trigger: Passing a (N, 3) array (xyz points or 3-column detections), a flat (2N,) coordinate list, or an (N, 2, 1) wrongly-shaped tensor to cv2.contourArea / cv2.approxPolyDP via the fallback.
Common situations: Feeding keypoint or detection xy arrays directly as contours, forgetting reshape after flattening polygon coordinates from JSON, or transposed (2, N) point lists from math code.
Related errors
- addWeighted inputs must have equal shapes
- Shape of np.ndarray for key '{key}' must be ({n},)
- First dimension of np.ndarray for key '{key}' must have size
- class_id must be 1d np.ndarray with (n, ) shape
- confidence must be 1d np.ndarray with (n, ) shape
AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15).
Data as JSON: /api/errors/ef66130acb9f7136.
Report an issue: GitHub.