No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-01 19:24:13 +02:00
tests Make OpenVINO default 2026-10-01 19:24:13 +02:00
.gitignore Initial commit 2026-10-01 18:22:45 +02:00
detect_person.py Make OpenVINO default 2026-10-01 19:24:13 +02:00
README.md Make OpenVINO default 2026-10-01 19:24:13 +02:00
requirements.txt Make OpenVINO default 2026-10-01 19:24:13 +02:00

alarm-yolo

Detect whether a person appears in a local MP4 video clip, using YOLO26m with OpenVINO via Ultralytics on CPU. The script is detect_person.py.

The script answers is anyone on screen? and prints a presence result, confidence when detected, and the number of frames analysed. It uses bounding boxes internally but does not save annotated images, video, or segmentation masks. The command-line interface accepts a local file, not a live camera URL.

Setup

python -m pip install --upgrade -r requirements.txt

Then run python detect_person.py clip.mp4. The first invocation automatically:

  1. Installs OpenVINO and the cache-lock dependency if missing, using Ultralytics' installer with uv or the current Python interpreter's pip.
  2. Downloads the pretrained yolo26m.pt weights.
  3. Exports a dynamic-shape OpenVINO model with FP32 weights and starts detection.

No manual model download or export is needed. The export is cached in ~/.cache/alarm-yolo/yolo26m_openvino_model, or under $XDG_CACHE_HOME/alarm-yolo when set. Later invocations reuse it without downloading or exporting again. The first invocation needs network access and takes longer. Failed exports are not published to the cache; simultaneous invocations share one setup operation. The dynamic export is reused when --input-size changes.

Usage

# YOLO26m / OpenVINO, automatically prepared on first use
python detect_person.py clip.mp4

# Recall-focused starting point: lower threshold, every frame
python detect_person.py clip.mp4 --conf 0.1 --stride 1

# Explicit model override: YOLO26 small with PyTorch
python detect_person.py clip.mp4 --model yolo26s.pt --conf 0.1

# Analyse the whole clip instead of stopping at the first hit
python detect_person.py clip.mp4 --scan-all

Options

Flag Default Meaning
--model Automatic YOLO26m / OpenVINO Override with weights or an exported model, such as yolo26m.pt (PyTorch) or yolo26m.onnx
--conf 0.5 Minimum confidence to count a person
--input-size 640 Inference image size in pixels, passed as Ultralytics imgsz
--stride 1 Process every Nth frame; must be at least 1
--scan-all off Scan the entire clip rather than stopping at the first detection
--threads 0 PyTorch CPU threads; 0 leaves the framework default
--profile off Print timing breakdown and library versions to stderr

Inference is fixed to CPU. Benchmark --threads 1, 2, and 4 on your hardware; more threads are not always faster. The setting is reapplied after Ultralytics prediction setup, which otherwise resets it. It does not configure the separate inference thread pools used by ONNX Runtime or the default OpenVINO backend.

By default, the reported confidence is the best person score in the first qualifying processed frame. With --scan-all, it is the best score across all processed frames. The reported first-hit frame is a zero-based index of processed frames, not the original video frame number when --stride exceeds 1.

Exit codes

Code Meaning
0 A person was detected
1 No person was detected
2 An error occurred, such as invalid arguments or a model/inference failure

Example shell integration that distinguishes errors from negative detections:

if python detect_person.py clip.mp4 --conf 0.1 --stride 1; then
    echo "Person detected"
else
    status=$?
    case "$status" in
        1) echo "No person detected" ;;
        *) echo "Detection failed (exit $status)" >&2 ;;
    esac
fi

CPU performance

Identify the bottleneck

If changing inference backends does not improve total time, collect a breakdown before tuning further:

python detect_person.py clip.mp4 --model yolo26m.pt --conf 0.1 --stride 5 --scan-all --profile
python detect_person.py clip.mp4 --model yolo26m.onnx --conf 0.1 --stride 5 --scan-all --profile

The profile reports Ultralytics import time, model construction time, prediction setup/warmup, and average preprocessing, inference, and postprocessing time per analysed frame. scan total includes prediction setup/warmup and all frame processing. The remaining scan time is labelled video I/O/loop/cleanup residual; it includes frame fetching/decoding, including skipped frames, but is not a pure decoder measurement. ONNX runtime initialization is counted in prediction setup, not model construction. First-use preparation of the default OpenVINO model is included in model construction time. Python interpreter startup and shutdown are outside these timings; use shell time for total process duration.

Large inference time points toward model/backend costs. Large setup time points toward reusing a loaded model across clips. A large residual warrants measuring video decoding and storage access. The profile does not change detection settings or exit codes, and is written to stderr.

Tune PyTorch threads

Keep the model, resolution, confidence, and frame stride identical while comparing thread counts. Use --scan-all so early detection does not shorten one run:

time python detect_person.py clip.mp4 --model yolo26m.pt --conf 0.1 --stride 5 --scan-all --threads 1
time python detect_person.py clip.mp4 --model yolo26m.pt --conf 0.1 --stride 5 --scan-all --threads 2
time python detect_person.py clip.mp4 --model yolo26m.pt --conf 0.1 --stride 5 --scan-all --threads 4

These stride-5 examples are for timing an existing configuration, not a recommendation for maximum recall. Compare the same number of analysed frames. Shell time includes Python imports, model loading, setup, video decoding, and inference. Repeat runs after weights have downloaded. When comparing with another detector, inspect its full output and exit status: filtering output through grep | wc -l can hide an error rather than prove a successful negative detection.

Try ONNX Runtime

The script also accepts an exported ONNX model through --model. ONNX Runtime can be faster than PyTorch on CPU, but measure on your deployment hardware. Install the optional dependencies and export once, outside the timed detection:

python -m pip install onnx onnxruntime onnxslim
python -c 'from ultralytics import YOLO; YOLO("yolo26m.pt").export(format="onnx", imgsz=640, dynamic=True, half=False)'

time python detect_person.py clip.mp4 --model yolo26m.onnx --conf 0.1 --stride 5 --scan-all

This keeps the medium model and uses FP32 rather than quantization. Dynamic shapes allow rectangular inference on widescreen footage instead of forcing every frame to a square export shape. Compare detections as well as timing on representative clips; changing backend can still produce numerical differences.

Each invocation loads the model again. For many short clips, a long-running worker that loads the model once could reduce overhead; the current CLI processes one clip per invocation.

Default OpenVINO backend

OpenVINO was the fastest tested backend for the medium model on the benchmark machine below, including on its AMD CPU. It is automatically prepared and used when --model is omitted:

time python detect_person.py clip.mp4 --conf 0.1 --stride 5 --scan-all --profile

The automatic export uses half=False to keep FP32 weights, but this does not force OpenVINO to compute exclusively in FP32. On the benchmark CPU, OpenVINO automatically selected BF16 internally. Validate detections on representative person-containing footage before adopting this backend for a recall-sensitive alarm.

Measured example

Local benchmark of 0-01-20250910142251.mp4 (2304×1296, 384 source frames), on an AMD Ryzen 7 8700G exposed as 4 vCPUs in a KVM VM. Every run used --conf 0.1 --stride 5 --scan-all --profile, analysed 76 frames, and used 640×384 network inputs. Downloads and exports were completed before timing.

Versions: Python 3.13.5, Ultralytics 8.4.66, PyTorch 2.12.0, ONNX Runtime 1.30.0, OpenVINO 2026.4.1. Times below are medians of three separate process invocations; total time includes startup and shutdown.

Model/backend PyTorch threads Total time Inference per frame
YOLO26m / PyTorch default (3) 14.51 s 149.10 ms
YOLO26m / ONNX Runtime default (3) 12.25 s 96.38 ms
YOLO26m / OpenVINO, automatic BF16 default (3) 7.12 s 50.01 ms
YOLO26s / PyTorch 4 7.42 s 54.72 ms
YOLO26n / PyTorch 4 5.13 s 24.46 ms

OpenVINO selected 4 inference threads independently of PyTorch. In separate single-run checks, YOLO26m/PyTorch with 4 threads took 13.08 s, while 1 and 2 threads took 31.34 s and 18.47 s. A diagnostic OpenVINO run overriding its compile configuration to INFERENCE_PRECISION_HINT=f32 took 10.38 s (94.19 ms/frame); the script does not expose that override as a CLI option.

Inference dominated the PyTorch baseline; video I/O/loop/cleanup residual was about 0.46 s. All tested configurations returned no person detected on this clip. This measures speed and agreement on this example, not person-detection recall. Results on a different CPU, video, or library version may differ.

Choosing settings for CCTV

  • Smaller alternatives: use --model yolo26n.pt or --model yolo26s.pt to compare smaller PyTorch models with the default YOLO26m/OpenVINO configuration. Benchmark speed and missed detections on your own footage.
  • Prioritise recall: try a lower --conf, such as 0.1, if false alarms are acceptable. This is a tuning starting point, not a guarantee against misses. Confidence scores are not calibrated probabilities.
  • Avoid skipped appearances: keep --stride 1. Increasing stride saves compute but can miss a person who appears only in skipped frames.
  • Small or distant people: test a larger --input-size, such as 960, against the additional processing cost.
  • Trigger on one detection: the script already does this. Early exit saves work once presence is established; --scan-all is unnecessary for a yes/no alarm.
  • Validate on your cameras: compare missed person appearances, false alarms, and processing time, including night/IR footage, occlusion, and distant people. No detector or confidence threshold guarantees zero misses.

Results are filtered to the COCO person class (id 0). This does not turn the pretrained model into a person-only network or eliminate computation for other classes. Frames are streamed to avoid holding the whole clip in memory.