motion-detection / README.md
vagheshpatel's picture
Sync motion-detection from metro-analytics-catalog
f51e1ae verified
|
Raw
History Blame Contribute Delete
8.56 kB
---
license: mit
license_link: LICENSE
library_name: opencv
tags:
- opencv
- intel
- motion-detection
- background-subtraction
- edge-ai
- metro
- dlstreamer
language:
- en
---
# Motion Detection
| Property | Value |
|---|---|
| **Category** | Motion Analytics (classical computer vision) |
| **Base Model** | Not applicable -- uses classical background subtraction |
| **Source Framework** | OpenCV |
| **Supported Precisions** | Not applicable |
| **Inference Engine** | OpenCV (CPU) / GStreamer decode via DLStreamer |
| **Hardware** | CPU, GPU (OpenCV UMat optional) |
| **Detected Class(es)** | Generic foreground motion regions |
---
## Overview
Motion Detection is a Metro Analytics use case that flags moving regions in a video stream without requiring a deep-learning model.
It uses the OpenCV MOG2 adaptive background subtractor to separate moving foreground pixels from a learned background, then groups them into bounding boxes.
A neural detector such as YOLO26 is the best choice when you need to know *what* is moving (person, vehicle, etc.).
For raw "something changed in the frame" triggering, classical background subtraction is the most efficient and reliable choice, so this use case intentionally avoids a model.
Typical Metro deployments include:
- **Idle-camera Triggering** -- wake heavier analytics only when motion is present.
- **Perimeter and After-hours Monitoring** -- alert on any movement in a restricted area.
- **Bandwidth Reduction** -- record or stream only frames that contain motion.
- **Pre-filter for Detection** -- gate an expensive YOLO26 pipeline behind a cheap motion check.
---
## Prerequisites
- Python 3.11+
- [Install OpenVINO](https://docs.openvino.ai/2026/get-started/install-openvino.html) (latest version)
- [Install Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/get_started/install/install_guide_ubuntu.html) (latest version)
Create and activate a Python virtual environment before running the scripts:
```bash
python3 -m venv .venv --system-site-packages
source .venv/bin/activate
```
> **Note:** The `--system-site-packages` flag is required so the virtual
> environment can access the system-installed OpenVINO and DLStreamer Python
> packages.
---
## Getting Started
### Download the Sample Video
This use case does not export or quantize a model.
Run the provided script to download the sample test video:
```bash
chmod +x export_and_quantize.sh
./export_and_quantize.sh
```
The script downloads `test_video.mp4` into the current directory.
### OpenCV Sample
The sample below reads `test_video.mp4`, applies MOG2 background subtraction,
removes shadows and noise, groups foreground pixels into bounding boxes, and
writes the annotated result to `output_opencv.mp4`.
It prints one line per frame with the number of motion regions found.
```python
import cv2
import numpy as np
INPUT_VIDEO = "test_video.mp4"
MIN_AREA = 500 # ignore motion blobs smaller than this many pixels
cap = cv2.VideoCapture(INPUT_VIDEO)
fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
bg = cv2.createBackgroundSubtractorMOG2(
history=200, varThreshold=25, detectShadows=True)
writer = cv2.VideoWriter(
"output_opencv.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height))
kernel = np.ones((3, 3), np.uint8)
frame_idx = 0
motion_frames = 0
while True:
ok, frame = cap.read()
if not ok:
break
frame_idx += 1
fg = bg.apply(frame)
# MOG2 marks shadows as 127; keep only strong foreground (255).
fg = cv2.threshold(fg, 200, 255, cv2.THRESH_BINARY)[1]
fg = cv2.morphologyEx(fg, cv2.MORPH_OPEN, kernel)
contours, _ = cv2.findContours(
fg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
regions = [c for c in contours if cv2.contourArea(c) >= MIN_AREA]
if regions:
motion_frames += 1
for c in regions:
x, y, w, h = cv2.boundingRect(c)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
status_text = "Motion Detected" if regions else "No Motion"
status_color = (0, 0, 255) if regions else (0, 255, 0)
cv2.putText(frame, status_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, status_color, 2)
cv2.putText(frame, f"Motion regions: {len(regions)}", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
print(f"Frame {frame_idx}: motion regions={len(regions)}", flush=True)
writer.write(frame)
cap.release()
writer.release()
print(f"Motion detected in {motion_frames} frames", flush=True)
```
**Device targets:**
- `"CPU"` -- default for OpenCV background subtraction.
- `"GPU"` -- enable OpenCV transparent API by wrapping frames in `cv2.UMat(frame)` on systems with an OpenCL-capable Intel GPU.
- `"NPU"` -- not applicable; background subtraction is not a neural workload.
#### Expected Output
![OpenVINO expected output](expected_output_openvino.gif)
### DLStreamer Sample
The sample below uses the DLStreamer GStreamer decode stack
(`decodebin3 ! videoconvert`) to pull frames into Python via `appsink`,
applies the same MOG2 background subtraction, and encodes the annotated
result to `output_dlstreamer.mp4`.
Using `appsink` keeps the pipeline headless-safe and avoids VA-API
zero-copy elements that fail over SSH.
```python
import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst
import numpy as np
Gst.init([])
# Import cv2 after Gst.init to avoid GStreamer re-initialization conflicts.
import cv2
INPUT_VIDEO = "test_video.mp4"
MIN_AREA = 500
# Decode with the DLStreamer/GStreamer stack and hand BGR frames to OpenCV.
pipeline_str = (
f"filesrc location={INPUT_VIDEO} ! decodebin3 ! videoconvert ! "
"video/x-raw,format=BGR ! "
"appsink name=sink emit-signals=false sync=false"
)
pipeline = Gst.parse_launch(pipeline_str)
sink = pipeline.get_by_name("sink")
pipeline.set_state(Gst.State.PLAYING)
bg = cv2.createBackgroundSubtractorMOG2(
history=200, varThreshold=25, detectShadows=True)
kernel = np.ones((3, 3), np.uint8)
writer = None
frame_idx = 0
motion_frames = 0
while True:
sample = sink.emit("pull-sample")
if sample is None:
break
buf = sample.get_buffer()
caps = sample.get_caps().get_structure(0)
width = caps.get_value("width")
height = caps.get_value("height")
ok, mapinfo = buf.map(Gst.MapFlags.READ)
if not ok:
continue
frame = np.ndarray((height, width, 3), dtype=np.uint8,
buffer=mapinfo.data).copy()
buf.unmap(mapinfo)
frame_idx += 1
fg = bg.apply(frame)
fg = cv2.threshold(fg, 200, 255, cv2.THRESH_BINARY)[1]
fg = cv2.morphologyEx(fg, cv2.MORPH_OPEN, kernel)
contours, _ = cv2.findContours(
fg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
regions = [c for c in contours if cv2.contourArea(c) >= MIN_AREA]
if regions:
motion_frames += 1
for c in regions:
x, y, w, h = cv2.boundingRect(c)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
status_text = "Motion Detected" if regions else "No Motion"
status_color = (0, 0, 255) if regions else (0, 255, 0)
cv2.putText(frame, status_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, status_color, 2)
if writer is None:
writer = cv2.VideoWriter(
"output_dlstreamer.mp4", cv2.VideoWriter_fourcc(*"mp4v"),
30.0, (width, height))
writer.write(frame)
print(f"Frame {frame_idx}: motion regions={len(regions)}", flush=True)
pipeline.set_state(Gst.State.NULL)
if writer:
writer.release()
print(f"Motion detected in {motion_frames} frames", flush=True)
```
The decode stack runs on the CPU; to offload decode to an Intel GPU, install
the DLStreamer VA-API plugins and prepend `vaapidecodebin` in environments that
support it (not recommended on headless or SSH systems).
#### Expected Output
![DLStreamer expected output](expected_output_dlstreamer.gif)
---
## License
Licensed under the MIT License. See [LICENSE](LICENSE) for details.
## References
- [OpenCV Background Subtraction Tutorial](https://docs.opencv.org/4.x/d1/dc5/tutorial_background_subtraction.html)
- [OpenCV MOG2 Background Subtractor](https://docs.opencv.org/4.x/d7/d7b/classcv_1_1BackgroundSubtractorMOG2.html)
- [Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/index.html)
- [OpenVINO Documentation](https://docs.openvino.ai/)