Skip to main content

video-event-detector

Flag sudden movement, possible falls, crowding and objects left behind in a sequence of frames from a fixed camera, with no model download and no OpenCV. These are motion heuristics on a running background model, not activity recognition: the package knows nothing about people or bags, only about regions of the picture that move, change shape or stop moving. Every event it reports is a hypothesis for a person to review, not a verdict.

Install

pip install video-event-detector

It depends on numpy and Pillow only.

It does not decode video files. There is no video codec without a heavy dependency, so it reads still frames: a list of numpy arrays or PIL images, or a directory of numbered image files. Extract frames from a clip first, for example with ffmpeg:

ffmpeg -i clip.mp4 -vf fps=10 frames/%05d.png

Quickstart

import numpy as np
from video_event_detector import detect_events

frames = [np.full((120, 160), 90, np.uint8) for _ in range(80)]   # 8 s of an empty, still view
for frame in frames[40:]:
    frame[60:84, 70:100] = 200                                   # a box is put down at 4.0 s and left
print(detect_events(frames, fps=10, dwell=2).summary())
video events: 1 found in 80 frames, 0.0-7.9 s at 10 fps
  abandoned      4.0-7.9 s        confidence 0.95  at x=70 y=60 w=30 h=24
      a new region of about 720 px (3.8% of the view) appeared at 4.0 s and has not moved since 4.1 s (dwell 2.0 s); a person standing still, a parked vehicle or a moved piece of furniture look the same, so review it
looked for, not seen: sudden_motion, fall, crowding
background: learned from the first 30 frames (0.0-2.9 s); events can start after 2.9 s
these are motion heuristics, not activity recognition: every event is a hypothesis for a person to review, not a verdict

For a folder of frames it is the same call: detect_events("frames/", fps=10).

What it does

The first background_frames frames (30 by default) are used to learn the empty scene as their per-pixel median; nothing can be reported before that. From then on every frame is compared with the background, and with the frame before it. Foreground regions with changing pixels are moving; foreground regions that have stopped changing are still. Four rules read those regions:

  • sudden_motion - the share of the view that is moving jumps by at least 8% of the view, and at least doubles, with most of the jump inside three frames. A door swinging open or a vehicle driving in qualifies as much as a person running in.
  • fall - a tracked moving region, about as tall as it is wide or taller, loses enough of its height (to 55% or less) within a short window while its width holds and its lowest point does not rise, and then stays low for hold. A brief dip that recovers is not reported.
  • crowding - the share of the view in motion rises past 20% and stays there for hold. It measures how much of the picture moves, not how many people are in it: one large vehicle close to the camera counts too.
  • abandoned - a new region appears and then does not move for dwell. An object taken away leaves a hole in the background that looks similar; it is told apart by where the edges are (in the background, not in the frame) and is not reported.

sensitivity (0-1, default 0.5) scales every threshold together: higher finds weaker, smaller and shorter events, and raises more false alarms.

Event labels are hypotheses, especially falls

A "fall" here is a shape change in a moving region and nothing more. Sitting down, bending over, lying down on purpose and a person partly hidden behind furniture can all look the same, and a real fall that happens away from the camera, behind something, in a crowd, or too slowly can be missed. Both mistakes matter, so use this to point a person at the moments worth looking at, never as the only safeguard. The same goes for the other labels: a person standing still for the dwell time is an "abandoned" region, and a camera aimed at trees in wind will see motion everywhere.

What it guards against

  • Camera shake. When most of the frame differs from the background, a phase correlation checks whether the whole picture simply shifted. If undoing the shift removes most of the difference, the frame is counted as camera motion and is not used for any rule, so a brief shake does not raise a crowd of events. If the camera settles in a new position, the background is moved with it and the rules restart.
  • Lighting. A robust gain-and-offset fit matches each frame's brightness to the background, and the background follows slow changes where nothing is in front of it, so a sunrise or a dimmer does not read as motion.
  • Mixed frame sizes. Frames that differ in size from the first are resized to match, and the report says how many were.
  • Short clips. A sequence shorter than the background window still runs; the report says the background is still learning and suggests a smaller background_frames.
  • Your data. Frames are never modified, and the same input always gives the same result.

Limits

It expects a fixed camera and a mostly static scene. Water, foliage, screens and flickering lights are motion to it. Small or distant figures (under about 8% of the frame height) are not checked for falls, and falls are not looked for while two regions are merging or splitting, because people crossing change box heights in exactly the same way. Confidence values are strength-of-evidence scores between 0 and 1, not calibrated probabilities.

API

detect_events(frames, *, fps=None, **kw) -> EventReport

frames is a list (or any iterable) of numpy arrays or PIL images, a numpy stack shaped (frames, height, width[, channels]), a list of image paths, or a directory of numbered image files (read in natural order, so 2.png comes before 10.png). With fps, every time is in seconds; without it, times are frame numbers. **kw goes to Detector.

Detector(*, sensitivity=0.5, background_frames=30, dwell=None, hold=None)
    .update(frame, *, timestamp=None) -> list[Event]   # events confirmed on this frame
    .events -> list[Event]
    .report() -> EventReport
    .summary() -> str
    .to_dict() -> dict

Feed update one frame at a time, in order, for live use. Give a timestamp in seconds for every frame or for none. dwell (how long a new region must stay still to be abandoned) and hold (how long crowding or a collapsed shape must last) are on the same clock; left as None they are background_frames frames and max(3, background_frames // 6) frames. An event is returned once, when it is confirmed; while it lasts, the detector keeps moving end forward on that same object.

Event(kind, start, end, confidence, region, message)

kind is sudden_motion, fall, crowding or abandoned; region is (x, y, width, height) in the pixels of the first frame; message says in plain words what was measured. .to_dict() is JSON-safe.

EventReport has .events, .by_kind (every kind present, possibly empty), .timeline (a plain list of (time, kind) pairs in time order), .summary() and .to_dict(), plus frames, analysed_frames, camera_motion_frames, background, notes, settings and stats.

CLI

video-event-detector frames/ --fps 10
video-event-detector frames/ --fps 10 --dwell 30 --json
video-event-detector frames/ --fps 10 --output events.json
video-event-detector frame_001.png frame_002.png frame_003.png

It prints the summary, or the full result as JSON with --json, and --output also writes the JSON to a file (UTF-8). Options: --sensitivity, --background-frames, --dwell, --hold. Exit status is 0 when nothing was found, 1 when at least one event was reported and 2 when the input could not be read.

License

MIT

Metadata

Release files for video-event-detector 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for video-event-detector 0.1.0
File Size Uploaded
video_event_detector-0.1.0.tar.gz 44.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for video-event-detector 0.1.0
File Interpreter ABI Platform
video_event_detector-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 84.1 kB

Release files / video_event_detector-0.1.0.tar.gz

Download URL video_event_detector-0.1.0.tar.gz
Size 44.1 kB
Tags Source
SHA-256 checksum
How to use checksums
b3189dd8858318999182d26ab2f36b0af5a7d2c9356424398cd20b8a72d52699
BLAKE2b-256 checksum
How to use checksums
c5289b0e909fff50563615bdc46935437db8e60d716e59988a8039c58c207f17
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / video_event_detector-0.1.0-py3-none-any.whl

Download URL video_event_detector-0.1.0-py3-none-any.whl
Size 40.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7b0b2c1981b390f2b901a429e2f1c84dd28ca4284598dc9b687eab86be45c3ee
BLAKE2b-256 checksum
How to use checksums
483073718c79b2591628bbfd835c998fa520a6d58e09481d28b39c1fe09f9554
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page