vagheshpatel commited on
Commit
10c09b1
·
verified ·
1 Parent(s): 8ba2b2f

Sync loitering-detection from metro-analytics-catalog

Browse files
Files changed (2) hide show
  1. README.md +41 -92
  2. expected_output_dlstreamer.gif +2 -2
README.md CHANGED
@@ -127,7 +127,7 @@ only -- no Python polygon math required.
127
  A typical surveillance-zone configuration on a 1280x720 source might be:
128
 
129
  ```text
130
- roi=400,200,1100,650 # ROI for gvaattachroi (x_min,y_min,x_max,y_max)
131
  LOITERING_SECONDS = 5.0 # dwell threshold, in seconds (demo value)
132
  ```
133
 
@@ -141,141 +141,90 @@ Per-person dwell time is measured at the bottom-center of the bounding box
141
 
142
  ### DLStreamer Sample
143
 
144
- - The DLStreamer Python module is not on `sys.path` by default. Export `PYTHONPATH` before running:
145
 
146
  ```bash
147
  source /opt/intel/openvino_2026/setupvars.sh
148
  source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
149
- export PYTHONPATH=/opt/intel/dlstreamer/python:\
150
- /opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
151
  ```
152
 
153
- **Video-based loitering detection** (requires video for dwell-time tracking):
154
 
155
  ```python
156
  from collections import defaultdict
157
-
158
  import gi
159
-
160
  gi.require_version("Gst", "1.0")
161
- gi.require_version("GstVideo", "1.0")
162
  from gi.repository import Gst
163
- from gstgva import VideoFrame
164
 
165
  Gst.init(None)
166
 
167
- MODEL_XML = "yolo26n_openvino_model/yolo26n.xml"
168
- INPUT_VIDEO = "VIRAT_S_000101.mp4"
169
- ROI = "0,200,300,400" # x_min,y_min,x_max,y_max
 
 
 
 
170
  LOITERING_SECONDS = 5.0
171
 
172
- pipeline_str = (
173
- f"filesrc location={INPUT_VIDEO} ! decodebin3 ! "
174
- f"videoconvert ! "
175
  f"gvaattachroi roi={ROI} ! "
176
- f"gvadetect inference-region=1 model={MODEL_XML} device=GPU "
177
- f"threshold=0.5 ! queue ! "
178
  f"gvatrack tracking-type=short-term-imageless ! queue ! "
179
- f"gvametaconvert add-empty-results=true ! queue ! "
180
- f"gvafpscounter ! "
181
- f"gvawatermark ! videoconvert ! video/x-raw,format=I420 ! "
182
- f"openh264enc ! h264parse ! "
183
- f"mp4mux ! filesink name=sink location=output_dlstreamer.mp4"
184
  )
185
- pipeline = Gst.parse_launch(pipeline_str)
186
-
187
- STALE_TIMEOUT = 2.0 # seconds of absence before clearing dwell state
188
- dwell_state: dict[int, float] = defaultdict(float)
189
- last_seen: dict[int, float] = {}
190
- flagged: set[int] = set()
191
 
 
 
 
192
 
193
  def on_buffer(pad, info):
194
  buf = info.get_buffer()
195
- caps = pad.get_current_caps()
196
- frame = VideoFrame(buf, caps=caps)
197
-
198
  now = buf.pts / Gst.SECOND if buf.pts != Gst.CLOCK_TIME_NONE else 0.0
199
- seen_ids: set[int] = set()
200
 
201
  for region in frame.regions():
202
- # gvaattachroi attaches a frame-level ROI region; skip it.
203
  if region.label() != "person":
204
  continue
205
- object_id = region.object_id()
206
- if object_id <= 0:
207
  continue
208
 
209
- rect = region.rect()
210
- foot_x = int(rect.x + rect.w / 2)
211
- foot_y = int(rect.y + rect.h)
212
- seen_ids.add(object_id)
213
-
214
- # gvadetect inference-region=1 already constrains detections to the
215
- # gvaattachroi zone, so every tracked person here is "in zone".
216
- prev = last_seen.get(object_id, now)
217
- dwell_state[object_id] += now - prev
218
- last_seen[object_id] = now
219
-
220
- if (
221
- dwell_state[object_id] >= LOITERING_SECONDS
222
- and object_id not in flagged
223
- ):
224
- flagged.add(object_id)
225
- print(
226
- f"LOITERING id={object_id} "
227
- f"dwell={dwell_state[object_id]:.1f}s "
228
- f"anchor=({foot_x},{foot_y})",
229
- flush=True,
230
- )
231
-
232
- # Clean up stale tracks after STALE_TIMEOUT seconds of absence.
233
- # Keep flagged entries to prevent duplicate alerts when a person
234
- # briefly disappears (occlusion / tracker jitter) and reappears.
235
- for stale in list(dwell_state):
236
- if stale not in seen_ids:
237
- elapsed_since = now - last_seen.get(stale, now)
238
- if elapsed_since > STALE_TIMEOUT:
239
- dwell_state.pop(stale, None)
240
- last_seen.pop(stale, None)
241
 
242
- return Gst.PadProbeReturn.OK
 
 
 
243
 
 
 
 
 
244
 
245
- sink = pipeline.get_by_name("sink")
246
- sink_pad = sink.get_static_pad("sink")
247
- sink_pad.add_probe(Gst.PadProbeType.BUFFER, on_buffer)
248
 
 
249
  pipeline.set_state(Gst.State.PLAYING)
250
- bus = pipeline.get_bus()
251
- bus.timed_pop_filtered(
252
- Gst.CLOCK_TIME_NONE,
253
- Gst.MessageType.EOS | Gst.MessageType.ERROR,
254
- )
255
  pipeline.set_state(Gst.State.NULL)
256
  ```
257
 
258
- Expected output with the sample video and the zone/threshold above
259
- (exact track IDs and anchor coordinates may vary between runs due to
260
- tracker non-determinism):
261
 
262
  ```text
263
- LOITERING id=26 dwell=5.0s anchor=(147,341)
264
- LOITERING id=27 dwell=5.0s anchor=(122,337)
265
- LOITERING id=29 dwell=5.0s anchor=(90,322)
266
  ...
267
  ```
268
 
269
- Approximately 10–12 loitering events are expected over the full video.
270
-
271
- The annotated video is saved to `output_dlstreamer.mp4` with green bounding boxes and
272
- track IDs drawn by `gvawatermark` around every detected person.
273
-
274
- > **Known warning:** The `openh264enc` element prints
275
- > `[OpenH264] this = 0x..., Error:CWelsH264SVCEncoder::EncodeFrame(), cmInitParaError.`
276
- > on the first frame. This is a benign initialization message — the output
277
- > video is encoded correctly. The warning comes from the OpenH264 library's
278
- > internal logging and does not indicate a real error.
279
 
280
  #### Expected Output
281
 
 
127
  A typical surveillance-zone configuration on a 1280x720 source might be:
128
 
129
  ```text
130
+ roi=0,200,300,400 # ROI for gvaattachroi (x_min,y_min,x_max,y_max)
131
  LOITERING_SECONDS = 5.0 # dwell threshold, in seconds (demo value)
132
  ```
133
 
 
141
 
142
  ### DLStreamer Sample
143
 
144
+ Set up the environment:
145
 
146
  ```bash
147
  source /opt/intel/openvino_2026/setupvars.sh
148
  source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
149
+ export PYTHONPATH=/opt/intel/dlstreamer/python:/opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
 
150
  ```
151
 
152
+ Run loitering detection:
153
 
154
  ```python
155
  from collections import defaultdict
156
+ import ctypes
157
  import gi
 
158
  gi.require_version("Gst", "1.0")
 
159
  from gi.repository import Gst
160
+ from gstgva import Tensor, VideoFrame
161
 
162
  Gst.init(None)
163
 
164
+ libgst = ctypes.CDLL("libgstreamer-1.0.so.0")
165
+ libgst.gst_structure_new_empty.argtypes = [ctypes.c_char_p]
166
+ libgst.gst_structure_new_empty.restype = ctypes.c_void_p
167
+
168
+ MODEL = "yolo26n_openvino_model/yolo26n.xml"
169
+ VIDEO = "VIRAT_S_000101.mp4"
170
+ ROI = "0,200,300,400"
171
  LOITERING_SECONDS = 5.0
172
 
173
+ pipeline = Gst.parse_launch(
174
+ f"filesrc location={VIDEO} ! decodebin3 ! videoconvert ! "
 
175
  f"gvaattachroi roi={ROI} ! "
176
+ f"gvadetect inference-region=1 model={MODEL} device=GPU threshold=0.5 ! queue ! "
 
177
  f"gvatrack tracking-type=short-term-imageless ! queue ! "
178
+ f"gvafpscounter ! identity name=probe ! gvawatermark ! videoconvert ! video/x-raw,format=I420 ! "
179
+ f"openh264enc ! h264parse ! mp4mux ! filesink location=output_dlstreamer.mp4"
 
 
 
180
  )
 
 
 
 
 
 
181
 
182
+ dwell = defaultdict(float)
183
+ last_seen = {}
184
+ flagged = set()
185
 
186
  def on_buffer(pad, info):
187
  buf = info.get_buffer()
188
+ frame = VideoFrame(buf, caps=pad.get_current_caps())
 
 
189
  now = buf.pts / Gst.SECOND if buf.pts != Gst.CLOCK_TIME_NONE else 0.0
 
190
 
191
  for region in frame.regions():
 
192
  if region.label() != "person":
193
  continue
194
+ oid = region.object_id()
195
+ if oid <= 0:
196
  continue
197
 
198
+ dwell[oid] += now - last_seen.get(oid, now)
199
+ last_seen[oid] = now
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
+ # Show dwell time on the bounding box (rendered by gvawatermark)
202
+ t = Tensor(libgst.gst_structure_new_empty(b"dwell"))
203
+ t.set_label(f" {dwell[oid]:.1f}s")
204
+ region.add_tensor(t)
205
 
206
+ if dwell[oid] >= LOITERING_SECONDS and oid not in flagged:
207
+ flagged.add(oid)
208
+ rect = region.rect()
209
+ print(f"LOITERING id={oid} dwell={dwell[oid]:.1f}s pos=({int(rect.x + rect.w/2)},{int(rect.y + rect.h)})")
210
 
211
+ return Gst.PadProbeReturn.OK
 
 
212
 
213
+ pipeline.get_by_name("probe").get_static_pad("src").add_probe(Gst.PadProbeType.BUFFER, on_buffer)
214
  pipeline.set_state(Gst.State.PLAYING)
215
+ pipeline.get_bus().timed_pop_filtered(Gst.CLOCK_TIME_NONE, Gst.MessageType.EOS | Gst.MessageType.ERROR)
 
 
 
 
216
  pipeline.set_state(Gst.State.NULL)
217
  ```
218
 
219
+ Expected output:
 
 
220
 
221
  ```text
222
+ LOITERING id=26 dwell=5.0s pos=(147,341)
223
+ LOITERING id=27 dwell=5.0s pos=(122,337)
 
224
  ...
225
  ```
226
 
227
+ The annotated video is saved to `output.mp4`.
 
 
 
 
 
 
 
 
 
228
 
229
  #### Expected Output
230
 
expected_output_dlstreamer.gif CHANGED

Git LFS Details

  • SHA256: c98bd40e3550127577f465d8d73a5308eb6e02830908e6600c36da805fe44484
  • Pointer size: 132 Bytes
  • Size of remote file: 4.51 MB

Git LFS Details

  • SHA256: c6573ac55eac081f030322da4b70745da96eb49d61c07d57477cfc658ce13e1c
  • Pointer size: 132 Bytes
  • Size of remote file: 1.51 MB