roboflow/supervision · error · ValueError
Expected shape (H,W), (H,W,3), or (H,W,4), got {image.shape}
Error message
Expected shape (H,W), (H,W,3), or (H,W,4), got {image.shape}. What it means
Raised by cv2_to_pillow() in supervision.utils.conversion when the input NumPy array is not a 2-D grayscale image, a 3-D 3-channel BGR image, or a 3-D 4-channel BGRA image. The function converts OpenCV-convention arrays to Pillow images, so any other rank or channel count has no defined conversion. The message echoes the offending shape so you can see exactly which dimension is wrong.
Source
Thrown at src/supervision/utils/conversion.py:235
>>> from supervision.utils.conversion import cv2_to_pillow
>>> scene = np.zeros((10, 10, 3), dtype=np.uint8)
>>> scene[:, :, 2] = 255
>>> image = cv2_to_pillow(scene)
>>> image.size
(10, 10)
>>> image.getpixel((0, 0))
(255, 0, 0)
```
"""
if image.ndim == 2:
return Image.fromarray(np.ascontiguousarray(image))
if image.ndim == 3 and image.shape[2] == 3:
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
return Image.fromarray(rgb_image)
if image.ndim == 3 and image.shape[2] == 4:
return Image.fromarray(np.ascontiguousarray(image[..., [2, 1, 0, 3]]))
raise ValueError(f"Expected shape (H,W), (H,W,3), or (H,W,4), got {image.shape}.")
View on GitHub (pinned to 7f254d9784)
Solutions
- Squeeze spurious dimensions first: np.squeeze(image) or image[0] if you accidentally passed a batch.
- If the image is (H,W,1) grayscale, reshape with image.reshape(image.shape[0], image.shape[1]).
- If channels > 4 (e.g. multispectral data), slice to the first 3 or 4 channels: image[..., :3].
- Print image.shape right before the call and verify it is exactly (H,W), (H,W,3), or (H,W,4).
Example fix
// before img = model_output # shape (1, 480, 640, 3) pil = cv2_to_pillow(img) # ValueError // after img = np.squeeze(model_output) # shape (480, 640, 3) pil = cv2_to_pillow(img)
Defensive patterns
Strategy: validation
Validate before calling
def is_valid_image_shape(image: np.ndarray) -> bool:
return image.ndim == 2 or (image.ndim == 3 and image.shape[2] in (3, 4))
if not is_valid_image_shape(image):
image = np.squeeze(image)
if image.ndim == 3 and image.shape[2] == 1:
image = image[..., 0]
assert is_valid_image_shape(image), image.shape Type guard
from typing import TypeGuard
def is_convertible_image(image: object) -> TypeGuard[np.ndarray]:
return isinstance(image, np.ndarray) and (
image.ndim == 2 or (image.ndim == 3 and image.shape[2] in (3, 4))
) Try / catch
try:
pil = cv2_to_pillow(image)
except ValueError as e:
raise ValueError(f'Bad image for annotation: {image.shape}') from e Prevention
- Always print/check image.shape before image-format conversions in new pipelines.
- Standardize on (H, W, 3) uint8 BGR frames at system boundaries.
- Squeeze batch dimensions immediately after model inference.
When it happens
Trigger: Calling cv2_to_pillow(image) (or an annotator decorated with ensure_pil_image_for_* that routes through it) with an array of shape (H,W,2), (H,W,N) where N>4, (H,W,3,1) (extra batch axis), a 1-D flattened array, or a 0-D scalar. Also happens when you pass a stacked batch of frames (N,H,W,3) instead of a single frame.
Common situations: Feeding a batched model-output tensor converted to NumPy directly into a Pillow-based annotator; images loaded with unusual loaders that keep an extra dimension; grayscale images manually expanded to (H,W,1); sliced arrays like frame[None] passed by mistake.
Related errors
- Unsupported image type: {type(image)}
- 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/20717bef49b84051.
Report an issue: GitHub.