File size: 8,561 Bytes
f51e1ae | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | ---
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

### 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

---
## 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/)
|