roboflow/supervision · error · ValueError
Image must have 3 or 4 channels.
Error message
Image must have 3 or 4 channels.
What it means
Raised by draw_image (draw/utils.py) when the image to paste is not a 3-D array with exactly 3 or 4 channels. The function supports RGB/BGR (3-channel) and RGBA/BGRA (4-channel, using the alpha channel for blending); grayscale, single-channel, or multi-frame tensors are rejected before any resizing happens.
Source
Thrown at src/supervision/draw/utils.py:448
>>> scene.shape
(100, 100, 3)
```
"""
# Validate and load image
if isinstance(image, str):
if not os.path.exists(image):
raise FileNotFoundError(f"Image path ('{image}') does not exist.")
loaded_image = cv2.imread(image, cv2.IMREAD_UNCHANGED)
if loaded_image is None:
raise OSError(f"Could not decode image path ('{image}').")
image_np = cast(npt.NDArray[np.uint8], loaded_image)
else:
image_np = image
if image_np.ndim != 3 or image_np.shape[2] not in (3, 4):
raise ValueError("Image must have 3 or 4 channels.")
# Validate opacity
if not 0.0 <= opacity <= 1.0:
raise ValueError("Opacity must be between 0.0 and 1.0.")
rect_x = int(rect.x)
rect_y = int(rect.y)
rect_width = int(rect.width)
rect_height = int(rect.height)
# Validate rectangle dimensions
if (
rect_x < 0
or rect_y < 0
or rect_x + rect_width > scene.shape[1]
or rect_y + rect_height > scene.shape[0]
):
raise ValueError("Invalid rectangle dimensions.")
View on GitHub (pinned to 7f254d9784)
Solutions
- Convert grayscale to BGR: cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
- Load color images without IMREAD_GRAYSCALE: cv2.imread(path) yields 3 channels
- Convert framework tensors to HWC uint8 numpy before calling (e.g. tensor.permute(1,2,0).numpy())
Example fix
# before logo = cv2.imread(path, cv2.IMREAD_GRAYSCALE) # (H, W) scene = draw_image(scene, logo, opacity=0.8, rect=rect) # after logo = cv2.imread(path, cv2.IMREAD_UNCHANGED) # (H, W, 3|4) scene = draw_image(scene, logo, opacity=0.8, rect=rect)
Defensive patterns
Strategy: validation
Validate before calling
import cv2
image = np.asarray(image)
if image.ndim == 2:
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
if image.ndim != 3 or image.shape[2] not in (3, 4):
raise ValueError(f'unsupported image shape {image.shape}')
scene = draw_image(scene, image, opacity=opacity, rect=rect) Type guard
import numpy as np
def is_blendable_image(img: np.ndarray) -> bool:
"""True when img is (H, W, 3|4) uint8-like, as draw_image requires."""
return img.ndim == 3 and img.shape[2] in (3, 4) Try / catch
try:
scene = draw_image(scene, image, opacity=opacity, rect=rect)
except ValueError as e:
if '3 or 4 channels' in str(e):
scene = draw_image(scene, cv2.cvtColor(image, cv2.COLOR_GRAY2BGR), opacity=opacity, rect=rect)
else:
raise Prevention
- Never use cv2.IMREAD_GRAYSCALE for assets destined for draw_image
- Convert framework tensors (CHW) to HWC numpy before drawing
When it happens
Trigger: Calling draw_image(scene, image, ...) with a (H, W) grayscale array, a (H, W, 1) array, or an (N, H, W, 3) batched tensor.
Common situations: Loading an image with cv2.imread(path, cv2.IMREAD_GRAYSCALE) or cv2.IMREAD_UNCHANGED on a PNG with transparency already stripped; palette/label images generated programmatically as 2-D arrays; passing a torch tensor without converting to HWC numpy.
Related errors
- Failed to save image to path: {image_path}
- Drawing points must have shape (N, 2) or (N, 1, 2)
- Could not decode image path ('{image}').
- All sigma values must be positive
- color length ({len(color_seq)}) must match sigma length ({le
AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15).
Data as JSON: /api/errors/6058b3696ea4fd05.
Report an issue: GitHub.