ultralytics/ultralytics · error · ConnectionError

{st}Failed to read images from {s}

Error message

{st}Failed to read images from {s}

What it means

Raised by LoadStreams when a capture opens successfully (isOpened() true) but the very first cap.read() returns success=False or a None frame. Some sources advertise themselves as open yet deliver no data — empty/corrupt video files, cameras that negotiate but never send frames, or streams where the first frame times out. The constructor guarantees a first frame before background reading starts, so failure here is fatal.

Source

Thrown at ultralytics/data/loaders.py:147

                if s == 0 and (IS_COLAB or IS_KAGGLE):
                    raise NotImplementedError(
                        "'source=0' webcam not supported in Colab and Kaggle notebooks. "
                        "Try running 'source=0' in a local environment."
                    )
                self.caps[i] = cv2.VideoCapture(s)  # store video capture object
                if not self.caps[i].isOpened():
                    raise ConnectionError(f"{st}Failed to open {s}")
                w = int(self.caps[i].get(cv2.CAP_PROP_FRAME_WIDTH))
                h = int(self.caps[i].get(cv2.CAP_PROP_FRAME_HEIGHT))
                fps = self.caps[i].get(cv2.CAP_PROP_FPS)  # warning: may return 0 or nan
                self.frames[i] = max(int(self.caps[i].get(cv2.CAP_PROP_FRAME_COUNT)), 0) or float(
                    "inf"
                )  # infinite stream fallback
                self.fps[i] = max((fps if math.isfinite(fps) else 0) % 100, 0) or 30  # 30 FPS fallback

                success, im = self.caps[i].read()  # guarantee first frame
                if not success or im is None:
                    raise ConnectionError(f"{st}Failed to read images from {s}")
                im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)[..., None] if self.cv2_flag == cv2.IMREAD_GRAYSCALE else im
                self.imgs[i].append(im)
                self.shape[i] = im.shape
                self.threads[i] = Thread(target=self.update, args=([i, self.caps[i], s]), daemon=True)
                LOGGER.info(f"{st}Success ✅ ({self.frames[i]} frames of shape {w}x{h} at {self.fps[i]:.2f} FPS)")
                self.threads[i].start()
        except Exception:
            self.close()  # release opened captures and stop started threads before re-raising
            raise
        LOGGER.info("")  # newline

    def update(self, i: int, cap: cv2.VideoCapture, stream: str):
        """Read stream frames in daemon thread and update image buffer."""
        n, f = 0, self.frames[i]  # frame number, total frames
        while self.running and cap.isOpened() and n < (f - 1):
            if len(self.imgs[i]) < 30:  # keep a <=30-image buffer
                n += 1
                cap.grab()  # .read() = .grab() followed by .retrieve()

View on GitHub (pinned to 0449ea011c)

Solutions

  1. Verify the file/stream independently: ffprobe file.mp4 (or ffplay for live sources) to confirm it actually contains decodable frames.
  2. For cameras, try the other substream URL (e.g. /stream2 vs /stream1) or lower resolution path that the camera serves reliably.
  3. Re-download or re-record corrupted video files; check file size is plausible.
  4. For flaky networks, add a small retry wrapper that reconstructs LoadStreams; transient first-frame timeouts often succeed on a second attempt.

Example fix

# before
cap = cv2.VideoCapture("corrupt.mp4")  # opens, but read() -> (False, None)

# after
import subprocess
subprocess.run(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=nb_frames", "corrupt.mp4"], check=True)
# re-export if ffprobe also fails: ffmpeg -i broken.mp4 -c:v libx264 -c:a copy fixed.mp4
Defensive patterns

Strategy: retry

Validate before calling

import cv2

def stream_yields_frame(source: str) -> bool:
    cap = cv2.VideoCapture(source)
    try:
        return cap.isOpened() and cap.read()[0]
    finally:
        cap.release()

Try / catch

import time
for attempt in range(2):
    try:
        results = model.predict(source=src, stream=True)
        break
    except ConnectionError as e:
        if attempt == 1 or "Failed to read" not in str(e):
            raise
        time.sleep(2)  # first-frame timeouts on live sources are often transient

Prevention

When it happens

Trigger: Passing a zero-byte or header-only video file; an RTSP source that completes handshake but the camera sends no media (e.g. wrong substream path); a stream whose codec opens but decoding the first packet fails. Raised after the same close-and-reraise cleanup as other LoadStreams errors, so partial resources are released.

Common situations: Corrupted downloads of .mp4 files; IP cameras whose main stream is disabled so the URL opens but yields nothing; multicast streams where the client joined but no traffic arrives; network hiccup at exactly the first read.

Related errors


AI-assisted analysis of ultralytics/ultralytics@0449ea011c (2026-08-15). Data as JSON: /api/errors/72d7c531cbac2ab4. Report an issue: GitHub.