keras-team/keras · error · ValueError

Invalid channel size: expected 3 (RGB) or 1 (Grayscale). Rec

Error message

Invalid channel size: expected 3 (RGB) or 1 (Grayscale). Received input with shape: images.shape={tuple(images_shape)}

What it means

After the rank check, this image op reads the channels axis (last for channels_last, third-from-last for channels_first) and requires exactly 1 (grayscale) or 3 (RGB). Any other channel count (2, 4, 255, None excluded) raises this ValueError because the op only defines grayscale/RGB semantics.

Source

Thrown at keras/src/ops/image.py:31

        self.data_format = backend.standardize_data_format(data_format)

    def call(self, images):
        return backend.image.rgb_to_grayscale(
            images, data_format=self.data_format
        )

    def compute_output_spec(self, images):
        images_shape = list(images.shape)
        if len(images_shape) not in (3, 4):
            raise ValueError(
                "Invalid images rank: expected rank 3 (single image) "
                "or rank 4 (batch of images). "
                f"Received: images.shape={images_shape}"
            )
        channels_axis = -1 if self.data_format == "channels_last" else -3
        channels = images_shape[channels_axis]
        if channels is not None and channels not in (1, 3):
            raise ValueError(
                "Invalid channel size: expected 3 (RGB) or 1 (Grayscale). "
                f"Received input with shape: images.shape={tuple(images_shape)}"
            )
        images_shape[channels_axis] = 1
        return KerasTensor(shape=images_shape, dtype=images.dtype)


@keras_export("keras.ops.image.rgb_to_grayscale")
def rgb_to_grayscale(images, data_format=None):
    """Convert RGB images to grayscale.

    This function converts RGB images to grayscale images. It supports both
    3D and 4D tensors.

    Args:
        images: Input image or batch of images. Must be 3D or 4D.
        data_format: A string specifying the data format of the input tensor.
            It can be either `"channels_last"` or `"channels_first"`.

View on GitHub (pinned to 7a34a03db6)

Solutions

  1. Convert images before the op: Image.open(p).convert('RGB') or convert('L') for grayscale
  2. Slice off extra channels: images = images[..., :3] for RGBA
  3. If the extra axis is not channels, move it out of the channel position or fix data_format

Example fix

# before
img = np.array(Image.open(p))  # RGBA (H,W,4)
y = op(img)  # ValueError

# after
img = np.array(Image.open(p).convert('RGB'))  # (H,W,3)
y = op(img)
Defensive patterns

Strategy: validation

Validate before calling

c = images.shape[-1 if data_format == 'channels_last' else -3]
assert c in (1, 3), f'bad channels: {c}'

Type guard

def has_valid_channels(images, data_format='channels_last') -> bool:
    c = images.shape[-1 if data_format == 'channels_last' else -3]
    return c is None or c in (1, 3)

Prevention

When it happens

Trigger: Passing RGBA images (channels=4), palette images, or tensors whose channel axis holds something else (e.g. classes); reading PNGs with alpha via PIL without convert().

Common situations: Loading RGBA PNGs or palette-mode images with PIL and feeding the raw array; data saved as (H,W,4); accidentally putting time or class axis in the channel position.

Related errors


AI-assisted analysis of keras-team/keras@7a34a03db6 (2026-08-25). Data as JSON: /api/errors/22579220cdd19a12. Report an issue: GitHub.