invoke-ai/InvokeAI · error

Cannot achieve the target of num_channels=

Error message

Cannot achieve the target of num_channels={num_channels}.

What it means

prepare_control_image slices the resized control-image tensor down to exactly `num_channels`. If the tensor has fewer channels than requested (or num_channels is <= 0), the target channel count is unreachable and this ValueError is raised instead of silently producing a malformed control input.

Solutions

  1. Check the control image's channel count and convert it to RGB (3 channels) before passing it in
  2. Verify the ControlNet/T2I-Adapter model config has a correct positive channels field; re-import or fix the model record
  3. Ensure the resize_mode branch you took actually produces a tensor with >= num_channels channels
  4. Pass an explicit valid num_channels when calling prep_control_data instead of deriving it from a bad config

Example fix

// before
img = Image.open('mask.png')  # mode 'L', 1 channel
control_data = prep_control_data(..., control_image=img, ...)  # ValueError
// after
img = Image.open('mask.png').convert('RGB')
control_data = prep_control_data(..., control_image=img, ...)
Defensive patterns

Strategy: validation

Validate before calling

import numpy as np
from PIL import Image

def ensure_channels_ok(image, num_channels):
    if num_channels is None or num_channels <= 0:
        raise ValueError(f"num_channels must be positive, got {num_channels}")
    arr = np.asarray(Image.open(image) if isinstance(image, str) else image)
    if arr.ndim == 2:
        ch = 1
    elif arr.shape[-1] in (1, 2, 3, 4):
        ch = arr.shape[-1]
    else:
        ch = 3
    if ch < num_channels:
        raise ValueError(f"image has {ch} channels; need {num_channels}")

Type guard

def has_enough_channels(t, num_channels):
    return num_channels > 0 and t.dim() >= 3 and t.shape[1] >= num_channels

Try / catch

try:
    control_data = prep_control_data(..., control_image=img, ...)
except ValueError as e:
    if 'num_channels' in str(e):
        img = img.convert('RGB')
        control_data = prep_control_data(..., control_image=img, ...)
    else:
        raise

Prevention

When it happens

Trigger: Calling prep_control_data / run_t2i_adapters / prepare_controlnet_cond with a control image whose effective channel count after resizing is less than the model's required channels (e.g. 1-channel or 4-channel image against 3 channels required is fine, but 3-channel against 4 required fails), or passing num_channels <= 0 via a misconfigured ControlNet/T2I-Adapter model field.

Common situations: Using a grayscale single-channel control image with a model expecting more channels; a ControlNet model record whose target channels value is 0 or negative due to a bad/legacy config; feeding an alpha-only PNG loaded with 2 channels.

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/0013c626d83b40b7. Report an issue: GitHub.

Appendix: source

Thrown at invokeai/app/util/controlnet_utils.py:426

        nimage = np.array(nimage).astype(np.float32) / 255.0
        nimage = nimage.transpose(0, 3, 1, 2)
        timage = torch.from_numpy(nimage)

    # use fancy lvmin controlnet resizing
    elif resize_mode == "just_resize" or resize_mode == "crop_resize" or resize_mode == "fill_resize":
        nimage = np.array(image)
        timage, nimage = np_img_resize(
            np_img=nimage,
            resize_mode=resize_mode,
            h=height,
            w=width,
            device=torch.device(device),
        )
    else:
        raise ValueError(f"Unsupported resize_mode: '{resize_mode}'.")

    if timage.shape[1] < num_channels or num_channels <= 0:
        raise ValueError(f"Cannot achieve the target of num_channels={num_channels}.")
    timage = timage[:, :num_channels, :, :]

    timage = timage.to(device=device, dtype=dtype)
    cfg_injection = control_mode == "more_control" or control_mode == "unbalanced"
    if do_classifier_free_guidance and not cfg_injection:
        timage = torch.cat([timage] * 2)
    return timage

View on GitHub (pinned to 0b6a024f2f)