Skip to main content

Pythonic video streaming made simple - turn OpenCV feeds into clean generators

Project description

Vision Stream Toolkit

Pythonic video streaming made simple. Turn messy OpenCV video loops into clean Python generators.


The Problem

Working with video in Python (OpenCV) is painful. You need 40+ lines of boilerplate just to handle threading, frame drops, and cleanup properly.

Traditional OpenCV Approach

import cv2
import threading
from queue import Queue, Empty

# ========== 40+ LINES OF BOILERPLATE ==========

class ThreadedCamera:
    def __init__(self, src=0):
        self.cap = cv2.VideoCapture(src)
        self.q = Queue(maxsize=128)
        self.stopped = False

    def start(self):
        t = threading.Thread(target=self._reader)
        t.daemon = True
        t.start()
        return self

    def _reader(self):
        while not self.stopped:
            ret, frame = self.cap.read()
            if not ret:
                self.stopped = True
                break
            if self.q.full():
                try:
                    self.q.get_nowait()
                except Empty:
                    pass
            self.q.put(frame)

    def read(self):
        return self.q.get(timeout=1.0)

    def stop(self):
        self.stopped = True
        self.cap.release()

# ========== FINALLY, YOUR ACTUAL CODE ==========

cam = ThreadedCamera(0)
cam.start()

try:
    while True:
        try:
            frame = cam.read()
        except Empty:
            break

        # Your AI inference here
        results = model.predict(frame)

        cv2.imshow("Detection", frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
finally:
    cam.stop()
    cv2.destroyAllWindows()

With Vision Stream Toolkit

from vision_stream_toolkit import stream
import cv2

for frame in stream(source=0):
    # Your AI inference here
    results = model.predict(frame)

    cv2.imshow("Detection", frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

That's it. 6 lines instead of 50+.


Why Use Vision Stream Toolkit?

Problem Traditional OpenCV Vision Stream Toolkit
Boilerplate code 40+ lines for threading 1 line: stream(source=0)
Frame lag Manual queue management Automatic buffer with smart dropping
Resource cleanup try/finally everywhere Automatic via context manager + atexit
Frame skipping Implement yourself Built-in skip_frames parameter
FPS control Manual sleep calculations Built-in fps_limit parameter
Transforms Separate processing loop Built-in transform parameter
Crash safety Resources often leak Auto-cleanup on exit/crash
Code style while True loops Pythonic generators
Stream monitoring Build your own metrics Built-in stats with FPS, drops, latency
Connection drops Manual reconnect logic auto_reconnect=True handles it
Event handling Polling for state changes Callbacks: on_connect, on_disconnect

Installation

pip install vision-stream-toolkit

Quick Start

Basic Webcam Stream

from vision_stream_toolkit import stream

for frame in stream(source=0):
    cv2.imshow("Webcam", frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

From Video File

for frame in stream(source="video.mp4", drop_frames=False):
    process_frame(frame)

RTSP / IP Camera

for frame in stream(source="rtsp://192.168.1.100:554/stream"):
    process_frame(frame)

Features

1. Threaded Capture (No Frame Lag)

The camera runs in a background thread, so your slow AI model never blocks frame acquisition.

Without threading:
[Capture] → [Process 200ms] → [Capture] → [Process 200ms]
                               ↑ Camera blocked, frames lost!

With Vision Stream Toolkit:
[Capture Thread] → [Queue] → [Your Code]
     ↓                            ↓
  Continuous                 Process when ready
  30+ FPS                    Always fresh frames

2. Smart Frame Dropping (drop_frames)

Control whether to drop old frames (real-time) or keep all frames (offline processing).

# Real-time mode (default) - always get fresh frames
for frame in stream(source=0, drop_frames=True):
    slow_model.predict(frame)  # Old frames dropped, always current

# Offline mode - process EVERY frame
for frame in stream(source="video.mp4", drop_frames=False):
    slow_model.predict(frame)  # No frames lost, takes longer

Visual explanation:

Camera: 30 FPS → [1][2][3][4][5][6][7][8][9][10]...
Model:  5 FPS (200ms per inference)

drop_frames=True (Real-time):
  Model sees: [1]...[4]...[7]...[10]  → Always fresh, frames 2,3,5,6,8,9 dropped

drop_frames=False (Offline):
  Model sees: [1][2][3][4][5][6][7][8][9][10]  → All frames, 6x slower than real-time

3. Frame Skipping (skip_frames)

Intentionally process every Nth frame to reduce compute load.

# Process every 3rd frame (10 FPS from 30 FPS source)
for frame in stream(source=0, skip_frames=3):
    expensive_model.predict(frame)

# Process every 10th frame (for thumbnails, time-lapse)
for frame in stream(source="video.mp4", skip_frames=10):
    save_thumbnail(frame)

Visual explanation:

Source: [1][2][3][4][5][6][7][8][9][10][11][12]...

skip_frames=1:  [1][2][3][4][5][6][7][8][9][10][11][12]  (all frames)
skip_frames=3:  [3]      [6]      [9]         [12]       (every 3rd)
skip_frames=5:  [5]               [10]                   (every 5th)

4. FPS Limiting (fps_limit)

Cap the frame rate to reduce CPU/GPU usage.

# Limit to 15 FPS (saves CPU when you don't need 30+ FPS)
for frame in stream(source=0, fps_limit=15):
    process(frame)

5. Transform Pipeline (transform)

Apply transformations before frames reach your code.

def preprocess(frame):
    frame = cv2.resize(frame, (640, 480))
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    return frame

for frame in stream(source=0, transform=preprocess):
    # Frames already resized and converted to RGB
    model.predict(frame)

6. Frame Metadata

Access timestamps and frame numbers for analysis.

from vision_stream_toolkit import VideoStream

with VideoStream(source=0) as vs:
    for frame_data in vs.frames(include_metadata=True):
        print(f"Frame #{frame_data.frame_number}")
        print(f"Timestamp: {frame_data.timestamp:.2f}s")
        process(frame_data.frame)

7. Auto-Cleanup

Resources are automatically released on:

  • Normal exit
  • Exceptions/crashes
  • Ctrl+C (KeyboardInterrupt)
  • Context manager exit
# No try/finally needed!
for frame in stream(source=0):
    if some_condition:
        raise Exception("Oops!")  # Camera still released properly

8. Real-Time Statistics (stats)

Monitor stream performance with detailed metrics.

from vision_stream_toolkit import VideoStream

with VideoStream(source=0) as vs:
    for i, frame in enumerate(vs):
        if i % 100 == 0:  # Print stats every 100 frames
            stats = vs.stats
            print(f"Capture FPS: {stats.fps_capture:.1f}")
            print(f"Process FPS: {stats.fps_processing:.1f}")
            print(f"Dropped: {stats.frames_dropped}")
            print(f"Latency: {stats.latency_ms:.1f}ms")

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    # Print final statistics
    vs.print_stats()

Available Statistics:

Metric Description
fps_capture Actual frames per second being captured
fps_processing Frames per second being consumed
frames_captured Total frames captured from source
frames_processed Total frames yielded to user
frames_dropped Frames dropped (buffer full)
frames_skipped Frames skipped (skip_frames setting)
latency_ms Capture-to-process latency in milliseconds
uptime_seconds Total seconds stream has been running
reconnect_count Number of reconnection attempts
is_connected Current connection status

9. Auto-Reconnect (auto_reconnect)

Automatically reconnect when RTSP streams or cameras disconnect.

# RTSP stream with auto-reconnect
for frame in stream(
    source="rtsp://192.168.1.100:554/stream",
    auto_reconnect=True,
    reconnect_delay=2.0,        # Wait 2s between attempts
    max_reconnect_attempts=10,  # Try 10 times (0 = infinite)
):
    process(frame)

With Callbacks:

def on_connect():
    print("Stream connected!")

def on_disconnect():
    print("Stream disconnected! Attempting reconnect...")

def on_reconnect(attempt):
    print(f"Reconnected after {attempt} attempts!")

vs = VideoStream(
    source="rtsp://camera.local/stream",
    auto_reconnect=True,
    max_reconnect_attempts=0,  # Infinite retries
    on_connect=on_connect,
    on_disconnect=on_disconnect,
    on_reconnect=on_reconnect,
)

with vs:
    for frame in vs:
        process(frame)

API Reference

stream() - Simple Generator Function

from vision_stream_toolkit import stream

for frame in stream(
    source=0,                    # Camera index or video path
    queue_size=128,              # Frame buffer size
    fps_limit=None,              # Optional FPS cap
    transform=None,              # Optional transform function
    drop_frames=True,            # Drop old frames when buffer full
    skip_frames=1,               # Process every Nth frame
    auto_reconnect=False,        # Auto-reconnect on disconnect
    reconnect_delay=1.0,         # Delay between reconnect attempts
    max_reconnect_attempts=5,    # Max reconnect attempts (0 = infinite)
):
    process(frame)
Parameter Type Default Description
source int | str 0 Camera index, video file path, or RTSP URL
queue_size int 128 Maximum frames to buffer
fps_limit float | None None Cap frame rate (None = unlimited)
transform Callable | None None Function to transform each frame
drop_frames bool True Drop old frames to stay real-time
skip_frames int 1 Process every Nth frame (1 = all)
auto_reconnect bool False Auto-reconnect on disconnect
reconnect_delay float 1.0 Seconds between reconnect attempts
max_reconnect_attempts int 5 Max attempts (0 = infinite)

VideoStream - Full Control Class

from vision_stream_toolkit import VideoStream

with VideoStream(source=0) as vs:
    print(f"FPS: {vs.fps}")
    print(f"Size: {vs.frame_size}")
    print(f"Running: {vs.is_running}")

    for frame in vs:
        process(frame)

Methods:

Method Description
start() Start the capture thread
stop() Stop and release resources
read(timeout=1.0) Read a single frame
frames(include_metadata=False) Generator for frames
print_stats() Print current statistics to console

Properties:

Property Type Description
fps float Source video FPS
frame_size tuple Frame dimensions (width, height)
is_running bool Whether stream is active
is_connected bool Whether stream is connected to source
stats StreamStats Current performance statistics

FrameData - Frame Container

@dataclass
class FrameData:
    frame: np.ndarray    # The video frame (BGR)
    timestamp: float     # Seconds since stream start
    frame_number: int    # Sequential frame count

StreamStats - Performance Metrics

@dataclass
class StreamStats:
    fps_capture: float       # Capture FPS
    fps_processing: float    # Processing FPS
    frames_captured: int     # Total captured
    frames_processed: int    # Total processed
    frames_dropped: int      # Dropped frames
    frames_skipped: int      # Skipped frames
    latency_ms: float        # Current latency
    uptime_seconds: float    # Stream uptime
    reconnect_count: int     # Reconnection count
    is_connected: bool       # Connection status

Parameter Reference (Detailed)

source - Video Source

Specifies where to capture video from. Accepts camera index, file path, or RTSP URL.

# Webcam (default camera)
stream(source=0)

# Secondary camera
stream(source=1)

# Video file
stream(source="/path/to/video.mp4")
stream(source="recording.avi")

# RTSP stream (IP camera)
stream(source="rtsp://192.168.1.100:554/stream")
stream(source="rtsp://admin:password@camera.local/live")

# HTTP stream
stream(source="http://camera.local/video.mjpg")

queue_size - Frame Buffer Size

Controls how many frames are buffered between capture and processing. Larger buffers handle processing spikes but use more memory.

# Small buffer (low memory, may drop frames if processing is slow)
stream(source=0, queue_size=16)

# Default buffer (balanced)
stream(source=0, queue_size=128)

# Large buffer (high memory, handles processing spikes)
stream(source=0, queue_size=512)

When to adjust:

Scenario Recommended queue_size
Low memory device (Raspberry Pi) 16-32
Normal desktop 128 (default)
Bursty processing (batch inference) 256-512
Video file processing 64-128

fps_limit - Frame Rate Cap

Limits the capture frame rate to reduce CPU/GPU usage. Set to None for maximum FPS.

# No limit (capture as fast as possible)
stream(source=0, fps_limit=None)

# Limit to 30 FPS
stream(source=0, fps_limit=30)

# Limit to 15 FPS (saves CPU for slow models)
stream(source=0, fps_limit=15)

# Limit to 1 FPS (time-lapse, periodic capture)
stream(source=0, fps_limit=1)

Example: Reduce CPU for lightweight tasks

# Only need 10 FPS for motion detection
for frame in stream(source=0, fps_limit=10):
    if detect_motion(frame):
        alert()

transform - Frame Preprocessing

Apply a function to every frame before it reaches your code. Runs in the capture thread for efficiency.

import cv2

# Resize frames
def resize_frame(frame):
    return cv2.resize(frame, (640, 480))

for frame in stream(source=0, transform=resize_frame):
    process(frame)  # Already 640x480
# Convert to RGB (for ML models)
def to_rgb(frame):
    return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

for frame in stream(source=0, transform=to_rgb):
    model.predict(frame)  # RGB format
# Chain multiple transforms
def preprocess(frame):
    frame = cv2.resize(frame, (224, 224))           # Resize
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  # BGR to RGB
    frame = frame / 255.0                            # Normalize
    return frame

for frame in stream(source=0, transform=preprocess):
    model.predict(frame)
# Crop region of interest
def crop_center(frame):
    h, w = frame.shape[:2]
    return frame[h//4:3*h//4, w//4:3*w//4]

for frame in stream(source=0, transform=crop_center):
    process(frame)  # Center 50% of frame

drop_frames - Buffer Overflow Strategy

Controls what happens when the frame buffer is full (processing is slower than capture).

# Real-time mode (default): Drop old frames, always get fresh ones
stream(source=0, drop_frames=True)

# Offline mode: Keep ALL frames, block capture if buffer full
stream(source="video.mp4", drop_frames=False)

Visual comparison:

Camera: 30 FPS, Model: 5 FPS

drop_frames=True:
  Buffer: [new][new][new] → Always fresh, older frames discarded
  Result: Real-time, but only ~17% of frames processed

drop_frames=False:
  Buffer: [1][2][3]...[waiting] → Keeps all, capture waits
  Result: All frames processed, but 6x slower than real-time

When to use each:

Use Case drop_frames
Live webcam True (default)
Security camera True
Robotics True
Video file analysis False
Dataset creation False
Frame extraction False

skip_frames - Intentional Frame Skipping

Process only every Nth frame to reduce compute load. Unlike drop_frames, this is intentional and predictable.

# Process all frames (default)
stream(source=0, skip_frames=1)

# Process every 2nd frame (15 FPS from 30 FPS source)
stream(source=0, skip_frames=2)

# Process every 5th frame (6 FPS from 30 FPS source)
stream(source=0, skip_frames=5)

# Process every 30th frame (1 FPS from 30 FPS source)
stream(source=0, skip_frames=30)

Example: Reduce GPU load for heavy model

# YOLOv8 is slow, only run on every 3rd frame
for frame in stream(source=0, skip_frames=3):
    detections = yolo_model.predict(frame)
    draw_boxes(frame, detections)

Example: Create thumbnails

# Extract 1 frame per second from 30 FPS video
for i, frame in enumerate(stream(source="video.mp4", skip_frames=30, drop_frames=False)):
    cv2.imwrite(f"thumbnail_{i:04d}.jpg", frame)

auto_reconnect - Automatic Reconnection

Automatically attempt to reconnect when the stream disconnects (camera unplugged, network drop, etc.).

# No reconnection (default): Stop on disconnect
stream(source=0, auto_reconnect=False)

# Auto-reconnect: Try to reconnect on failure
stream(source="rtsp://camera/stream", auto_reconnect=True)

Example: 24/7 surveillance camera

for frame in stream(
    source="rtsp://192.168.1.100/stream",
    auto_reconnect=True,
    reconnect_delay=5.0,
    max_reconnect_attempts=0,  # Infinite retries
):
    record_frame(frame)

reconnect_delay - Delay Between Reconnection Attempts

How long to wait before attempting to reconnect after a failure.

# Quick reconnect (local camera)
stream(source=0, auto_reconnect=True, reconnect_delay=0.5)

# Moderate delay (network camera)
stream(source="rtsp://...", auto_reconnect=True, reconnect_delay=2.0)

# Slow reconnect (remote server, rate limiting)
stream(source="rtsp://...", auto_reconnect=True, reconnect_delay=10.0)

max_reconnect_attempts - Maximum Retry Attempts

How many times to try reconnecting before giving up. Set to 0 for infinite retries.

# Try 5 times then stop (default)
stream(source=0, auto_reconnect=True, max_reconnect_attempts=5)

# Try 3 times
stream(source=0, auto_reconnect=True, max_reconnect_attempts=3)

# Never give up (24/7 systems)
stream(source="rtsp://...", auto_reconnect=True, max_reconnect_attempts=0)

on_connect - Connection Callback

Function called when stream successfully connects.

def handle_connect():
    print("Stream connected!")
    send_notification("Camera online")

vs = VideoStream(
    source="rtsp://camera/stream",
    on_connect=handle_connect,
)

on_disconnect - Disconnection Callback

Function called when stream disconnects.

def handle_disconnect():
    print("Stream lost!")
    log_event("Camera disconnected")

vs = VideoStream(
    source="rtsp://camera/stream",
    auto_reconnect=True,
    on_disconnect=handle_disconnect,
)

on_reconnect - Reconnection Callback

Function called when stream successfully reconnects. Receives the attempt number.

def handle_reconnect(attempt_number):
    print(f"Reconnected after {attempt_number} attempts!")
    send_alert(f"Camera back online (took {attempt_number} tries)")

vs = VideoStream(
    source="rtsp://camera/stream",
    auto_reconnect=True,
    on_reconnect=handle_reconnect,
)

Complete Example: All Parameters

import cv2
from vision_stream_toolkit import VideoStream

def preprocess(frame):
    """Resize and convert to RGB."""
    frame = cv2.resize(frame, (640, 480))
    return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

def on_connect():
    print("Connected to camera!")

def on_disconnect():
    print("Lost connection, attempting reconnect...")

def on_reconnect(attempts):
    print(f"Reconnected after {attempts} attempt(s)")

# Full-featured stream configuration
vs = VideoStream(
    source="rtsp://192.168.1.100:554/stream",  # RTSP camera
    queue_size=64,                              # Moderate buffer
    fps_limit=15,                               # Cap at 15 FPS
    transform=preprocess,                       # Resize + RGB
    drop_frames=True,                           # Real-time mode
    skip_frames=2,                              # Every 2nd frame
    auto_reconnect=True,                        # Auto-reconnect
    reconnect_delay=3.0,                        # Wait 3s between attempts
    max_reconnect_attempts=0,                   # Never give up
    on_connect=on_connect,
    on_disconnect=on_disconnect,
    on_reconnect=on_reconnect,
)

with vs:
    for frame in vs:
        # Process frame (already resized, RGB, every 2nd frame)
        results = model.predict(frame)

        # Monitor performance
        if vs.stats.frames_processed % 100 == 0:
            print(f"FPS: {vs.stats.fps_processing:.1f}, Dropped: {vs.stats.frames_dropped}")

Real-World Examples

Object Detection with YOLO

from vision_stream_toolkit import stream
from ultralytics import YOLO
import cv2

model = YOLO("yolov8n.pt")

for frame in stream(source=0, fps_limit=30):
    results = model.predict(frame, verbose=False)
    annotated = results[0].plot()

    cv2.imshow("YOLOv8 Detection", annotated)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

Face Detection with MediaPipe

from vision_stream_toolkit import stream
import mediapipe as mp
import cv2

face_detection = mp.solutions.face_detection.FaceDetection()

for frame in stream(source=0, skip_frames=2):
    rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    results = face_detection.process(rgb)

    if results.detections:
        for detection in results.detections:
            # Draw bounding box
            pass

    cv2.imshow("Face Detection", frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

Video File Processing

from vision_stream_toolkit import stream

# Process all frames from a video file
frame_count = 0
for frame in stream(source="input.mp4", drop_frames=False):
    # Your processing here
    frame_count += 1

print(f"Processed {frame_count} frames")

Multi-Camera Setup

from vision_stream_toolkit import VideoStream
import cv2

# Open multiple cameras
cam1 = VideoStream(source=0).start()
cam2 = VideoStream(source=1).start()

try:
    while cam1.is_running and cam2.is_running:
        frame1 = cam1.read()
        frame2 = cam2.read()

        if frame1 and frame2:
            combined = cv2.hconcat([frame1.frame, frame2.frame])
            cv2.imshow("Dual Camera", combined)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
finally:
    cam1.stop()
    cam2.stop()

When to Use Each Parameter

Scenario Parameters
Live webcam + fast model stream(source=0) (defaults)
Live webcam + slow model stream(source=0, skip_frames=3)
Video file analysis stream(source="video.mp4", drop_frames=False)
Reduce CPU usage stream(source=0, fps_limit=15)
Thumbnail extraction stream(source="video.mp4", skip_frames=30, drop_frames=False)
Pre-resize frames stream(source=0, transform=resize_fn)
RTSP security camera stream(source="rtsp://...", auto_reconnect=True)
Monitor performance VideoStream(source=0) then access vs.stats
24/7 surveillance stream(source="rtsp://...", auto_reconnect=True, max_reconnect_attempts=0)

Comparison Summary

Metric Traditional OpenCV Vision Stream Toolkit
Lines of code 50+ 6
Threading Manual Automatic
Queue management Manual Automatic
Frame dropping Manual drop_frames=True/False
Frame skipping Manual skip_frames=N
FPS limiting Manual fps_limit=N
Transforms Separate loop transform=fn
Cleanup try/finally Automatic
Crash safety Often leaks Always cleans up
Statistics Implement yourself Built-in stats property
Auto-reconnect Manual retry logic auto_reconnect=True
Event callbacks Not available on_connect, on_disconnect
Learning curve High Minimal

License

MIT License - see LICENSE file for details.


Contributing

Contributions welcome! Please open an issue or PR on GitHub.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vision_stream_toolkit-0.1.0.tar.gz (13.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vision_stream_toolkit-0.1.0-py3-none-any.whl (14.6 kB view details)

Uploaded Python 3

File details

Details for the file vision_stream_toolkit-0.1.0.tar.gz.

File metadata

  • Download URL: vision_stream_toolkit-0.1.0.tar.gz
  • Upload date:
  • Size: 13.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for vision_stream_toolkit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f8d64f8a990361151b94243814af8e369ba04ca4c5875ced1745bf1a6d9a673d
MD5 019d86be6cae104e051c28e29ebb4c31
BLAKE2b-256 dd1399277bb3e4c3aaa27f5b3bbd91dd3ea89f68aa9e5a2c7b9d030a337e9b84

See more details on using hashes here.

File details

Details for the file vision_stream_toolkit-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for vision_stream_toolkit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ebb9a4c0406676f360cd308e26636db4074bd134765b347b031875f535d9bad9
MD5 3300c05ceb827e081a4c0993f99c59d3
BLAKE2b-256 b0866ecd5ed0b0774ee99e41258592d71b1b7f2c9e38cf6c42a99fbc33aad236

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page