vagheshpatel commited on
Commit
72ef152
·
verified ·
1 Parent(s): 0052bee

Sync loitering-detection from metro-analytics-catalog

Browse files
Files changed (2) hide show
  1. README.md +29 -13
  2. export_and_quantize.sh +16 -1
README.md CHANGED
@@ -1,6 +1,6 @@
1
  # Loitering Detection
2
 
3
- > **Validated with:** OpenVINO 2026.0.0, NNCF 3.0.0, DLStreamer 2026.0, Ultralytics 8.3.0, Python 3.11+
4
 
5
  | Property | Value |
6
  |---|---|
@@ -34,7 +34,7 @@ Smaller variants (`yolo26n`, `yolo26s`) are recommended for high-FPS edge deploy
34
  ## Prerequisites
35
 
36
  - Python 3.11+
37
- - [Install OpenVINO 2026.0.0](https://docs.openvino.ai/2026/get-started/install-openvino.html)
38
  - [Install Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/get_started/install/install_guide_ubuntu.html)
39
 
40
  Create and activate a Python virtual environment before running the scripts:
@@ -54,9 +54,17 @@ Run the provided script to download, export to OpenVINO IR, and optionally quant
54
 
55
  ```bash
56
  chmod +x export_and_quantize.sh
57
- ./export_and_quantize.sh yolo26n # default: FP16
 
 
 
 
 
 
 
58
  ./export_and_quantize.sh yolo26n FP32 # full-precision
59
  ./export_and_quantize.sh yolo26n INT8 # quantized
 
60
  ```
61
 
62
  Replace `yolo26n` with any variant (`yolo26s`, `yolo26m`, `yolo26l`, `yolo26x`).
@@ -82,9 +90,9 @@ Output files:
82
  | FP16 | Yes | Yes | Yes |
83
  | INT8 | Yes | Yes | Yes |
84
 
85
- > **Note:** For production accuracy, replace the random calibration tensors in
86
- > `export_and_quantize.sh` with a representative sample of frames from the
87
- > target deployment site.
88
 
89
  ### Defining the Region of Interest
90
 
@@ -97,9 +105,14 @@ A typical surveillance-zone configuration on a 1280x720 source might be:
97
 
98
  ```text
99
  roi=400,200,1100,650 # ROI for gvaattachroi (x_min,y_min,x_max,y_max)
100
- LOITERING_SECONDS = 5.0 # dwell threshold, in seconds
101
  ```
102
 
 
 
 
 
 
103
  Per-person dwell time is measured at the bottom-center of the bounding box
104
  (the foot anchor), which most closely approximates the person's ground position.
105
 
@@ -142,11 +155,12 @@ pipeline_str = (
142
  f"gvametaconvert add-empty-results=true ! queue ! "
143
  f"gvafpscounter ! "
144
  f"gvawatermark ! videoconvert ! video/x-raw,format=I420 ! "
145
- f"openh264enc ! h264parse ! "
146
  f"mp4mux ! filesink name=sink location=output.mp4"
147
  )
148
  pipeline = Gst.parse_launch(pipeline_str)
149
 
 
150
  dwell_state: dict[int, float] = defaultdict(float)
151
  last_seen: dict[int, float] = {}
152
  flagged: set[int] = set()
@@ -191,11 +205,15 @@ def on_buffer(pad, info):
191
  flush=True,
192
  )
193
 
 
 
 
194
  for stale in list(dwell_state):
195
  if stale not in seen_ids:
196
- dwell_state.pop(stale, None)
197
- last_seen.pop(stale, None)
198
- flagged.discard(stale)
 
199
 
200
  return Gst.PadProbeReturn.OK
201
 
@@ -223,8 +241,6 @@ LOITERING id=9 dwell=1.6s anchor=(527,250)
223
  The annotated video is saved to `output.mp4` with green bounding boxes and
224
  track IDs drawn by `gvawatermark` around every detected person.
225
 
226
- Increasing `LOITERING_SECONDS` back to its operational default (around 10 s) suppresses the events on this short walking clip; reproduce a real loitering scenario with a stationary subject in your own footage.
227
-
228
  ---
229
 
230
  ## License
 
1
  # Loitering Detection
2
 
3
+ > **Validated with:** OpenVINO 2026.1.0, NNCF 3.0.0, DLStreamer 2026.0, Ultralytics 8.4.46, Python 3.11+
4
 
5
  | Property | Value |
6
  |---|---|
 
34
  ## Prerequisites
35
 
36
  - Python 3.11+
37
+ - [Install OpenVINO](https://docs.openvino.ai/2026/get-started/install-openvino.html) (latest version)
38
  - [Install Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/get_started/install/install_guide_ubuntu.html)
39
 
40
  Create and activate a Python virtual environment before running the scripts:
 
54
 
55
  ```bash
56
  chmod +x export_and_quantize.sh
57
+ ./export_and_quantize.sh
58
+ ```
59
+
60
+ This exports the default **yolo26n** model in **FP16** precision.
61
+
62
+ #### Optional: Select a Different Variant or Precision
63
+
64
+ ```bash
65
  ./export_and_quantize.sh yolo26n FP32 # full-precision
66
  ./export_and_quantize.sh yolo26n INT8 # quantized
67
+ ./export_and_quantize.sh yolo26s # larger variant, default FP16
68
  ```
69
 
70
  Replace `yolo26n` with any variant (`yolo26s`, `yolo26m`, `yolo26l`, `yolo26x`).
 
90
  | FP16 | Yes | Yes | Yes |
91
  | INT8 | Yes | Yes | Yes |
92
 
93
+ > **Note:** The INT8 calibration uses frames from the bundled sample video.
94
+ > For production accuracy, replace it with a representative set of frames from
95
+ > the target deployment site.
96
 
97
  ### Defining the Region of Interest
98
 
 
105
 
106
  ```text
107
  roi=400,200,1100,650 # ROI for gvaattachroi (x_min,y_min,x_max,y_max)
108
+ LOITERING_SECONDS = 5.0 # dwell threshold, in seconds (demo value)
109
  ```
110
 
111
+ > **Note:** The sample uses a 5-second threshold so that loitering events are
112
+ > triggered quickly on the short demo video. For production deployments,
113
+ > increase this to 10--30 seconds depending on the site's operational
114
+ > requirements.
115
+
116
  Per-person dwell time is measured at the bottom-center of the bounding box
117
  (the foot anchor), which most closely approximates the person's ground position.
118
 
 
155
  f"gvametaconvert add-empty-results=true ! queue ! "
156
  f"gvafpscounter ! "
157
  f"gvawatermark ! videoconvert ! video/x-raw,format=I420 ! "
158
+ f"x264enc ! h264parse ! "
159
  f"mp4mux ! filesink name=sink location=output.mp4"
160
  )
161
  pipeline = Gst.parse_launch(pipeline_str)
162
 
163
+ STALE_TIMEOUT = 2.0 # seconds of absence before clearing dwell state
164
  dwell_state: dict[int, float] = defaultdict(float)
165
  last_seen: dict[int, float] = {}
166
  flagged: set[int] = set()
 
205
  flush=True,
206
  )
207
 
208
+ # Clean up stale tracks after STALE_TIMEOUT seconds of absence.
209
+ # Keep flagged entries to prevent duplicate alerts when a person
210
+ # briefly disappears (occlusion / tracker jitter) and reappears.
211
  for stale in list(dwell_state):
212
  if stale not in seen_ids:
213
+ elapsed_since = now - last_seen.get(stale, now)
214
+ if elapsed_since > STALE_TIMEOUT:
215
+ dwell_state.pop(stale, None)
216
+ last_seen.pop(stale, None)
217
 
218
  return Gst.PadProbeReturn.OK
219
 
 
241
  The annotated video is saved to `output.mp4` with green bounding boxes and
242
  track IDs drawn by `gvawatermark` around every detected person.
243
 
 
 
244
  ---
245
 
246
  ## License
export_and_quantize.sh CHANGED
@@ -68,12 +68,27 @@ if [[ "${PRECISION}" == "INT8" ]]; then
68
  import nncf
69
  import openvino as ov
70
  import numpy as np
 
71
 
72
  core = ov.Core()
73
  model = core.read_model('${MODEL_NAME}_openvino_model/${MODEL_NAME}.xml')
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  def transform_fn(data_item):
76
- return np.random.rand(1, 3, 640, 640).astype(np.float32)
77
 
78
  calibration_dataset = nncf.Dataset(list(range(300)), transform_fn)
79
 
 
68
  import nncf
69
  import openvino as ov
70
  import numpy as np
71
+ import cv2
72
 
73
  core = ov.Core()
74
  model = core.read_model('${MODEL_NAME}_openvino_model/${MODEL_NAME}.xml')
75
 
76
+ # Extract frames from the sample video for calibration.
77
+ cap = cv2.VideoCapture('VIRAT_S_000101.mp4')
78
+ frames = []
79
+ while len(frames) < 300:
80
+ ret, frame = cap.read()
81
+ if not ret:
82
+ cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
83
+ continue
84
+ img = cv2.resize(frame, (640, 640))
85
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
86
+ img = img.transpose(2, 0, 1)[np.newaxis, ...]
87
+ frames.append(img)
88
+ cap.release()
89
+
90
  def transform_fn(data_item):
91
+ return frames[data_item % len(frames)]
92
 
93
  calibration_dataset = nncf.Dataset(list(range(300)), transform_fn)
94