roboflow/supervision · error · ValueError

GRAY2BGR conversion requires a two-dimensional image

Error message

GRAY2BGR conversion requires a two-dimensional image

What it means

GRAY2BGR expands a 2-D single-channel image to 3 channels by repetition. The fallback at src/supervision/_cv2/_color.py:30 requires `image.ndim == 2`; a 3-D (H, W, 1) array or a batched (N, H, W) array is rejected because the expansion axis is ambiguous.

Source

Thrown at src/supervision/_cv2/_color.py:30

from supervision._cv2.constants import (
    _COLOR_BGR2GRAY,
    _COLOR_BGR2RGB,
    _COLOR_GRAY2BGR,
    _COLOR_HSV2BGR,
    _COLOR_RGB2BGR,
)


def _cvt_color(image: npt.NDArray[Any], code: int) -> npt.NDArray[Any]:
    """Convert the BGR, RGB, grayscale, and 8-bit HSV formats used by Supervision."""
    if code in (_COLOR_BGR2RGB, _COLOR_RGB2BGR):
        if image.ndim != 3 or image.shape[2] != 3:
            raise ValueError("BGR/RGB conversion requires a three-channel image")
        return np.ascontiguousarray(image[..., ::-1])

    if code == _COLOR_GRAY2BGR:
        if image.ndim != 2:
            raise ValueError("GRAY2BGR conversion requires a two-dimensional image")
        return np.repeat(image[..., np.newaxis], 3, axis=2)

    if code == _COLOR_BGR2GRAY:
        if image.ndim != 3 or image.shape[2] != 3:
            raise ValueError("BGR2GRAY conversion requires a three-channel image")
        if image.dtype == np.uint8:
            values = image.astype(np.uint32)
            weighted = (
                values[..., 0] * 3735
                + values[..., 1] * 19235
                + values[..., 2] * 9798
                + (1 << 14)
            ) >> 15
            return weighted.astype(np.uint8)
        float_values = (
            image[..., 0].astype(np.float64) * 0.114
            + image[..., 1].astype(np.float64) * 0.587
            + image[..., 2].astype(np.float64) * 0.299

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Squeeze to 2-D first: `gray = gray.squeeze()` (or `gray[..., 0]`)
  2. Index the batch for (N, H, W): `gray = batch[i]`
  3. Build the 3-channel array directly: `np.repeat(gray[..., None], 3, axis=2)`

Example fix

// before
bgr = cv2.cvtColor(mask[..., None], cv2.COLOR_GRAY2BGR)  # (H, W, 1)

// after
bgr = cv2.cvtColor(np.squeeze(mask), cv2.COLOR_GRAY2BGR)
Defensive patterns

Strategy: type-guard

Validate before calling

import numpy as np

def as_grayscale_2d(image):
    """Return a 2-D single-channel array for GRAY2BGR expansion."""
    arr = np.asarray(image)
    if arr.ndim == 3 and arr.shape[-1] == 1:
        arr = arr[..., 0]
    if arr.ndim != 2:
        raise ValueError(f"expected 2-D grayscale, got shape {arr.shape}")
    return arr

Type guard

def is_grayscale_2d(image) -> bool:
    """GRAY2BGR requires a 2-D array."""
    return np.asarray(image).ndim == 2

Prevention

When it happens

Trigger: `cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)` where gray has shape (H, W, 1) (common after `np.expand_dims`/`[..., None]`) or (N, H, W) from a batch.

Common situations: Model output masks stored with a trailing 1-sized channel axis; preprocessing code that uniformly adds a channel axis then calls cvtColor.

Related errors


AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15). Data as JSON: /api/errors/92006bed582b3748. Report an issue: GitHub.