vagheshpatel commited on
Commit
d9ece88
·
verified ·
1 Parent(s): 14df812

Sync vehicle-entry-exit-logging from metro-analytics-catalog

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ expected_output_dlstreamer.gif filter=lfs diff=lfs merge=lfs -text
37
+ expected_output_openvino.gif filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) Intel Corporation.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE
README.md ADDED
@@ -0,0 +1,423 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ license_link: LICENSE
4
+ library_name: openvino
5
+ pipeline_tag: object-detection
6
+ tags:
7
+ - openvino
8
+ - intel
9
+ - yolo
10
+ - yolo26
11
+ - vehicle-entry-exit
12
+ - tracking
13
+ - short-term-imageless
14
+ - line-crossing
15
+ - edge-ai
16
+ - metro
17
+ - dlstreamer
18
+ language:
19
+ - en
20
+ ---
21
+
22
+ # Vehicle Entry/Exit Logging
23
+
24
+ | Property | Value |
25
+ |---|---|
26
+ | **Category** | Object Detection + Tracking + Line Crossing |
27
+ | **Base Model** | [YOLO26](https://docs.ultralytics.com/models/yolo26/) (Ultralytics) |
28
+ | **Source Framework** | PyTorch (Ultralytics) |
29
+ | **Supported Precisions** | FP32, FP16, INT8 (mixed-precision) |
30
+ | **Inference Engine** | OpenVINO |
31
+ | **Hardware** | CPU, GPU, NPU |
32
+ | **Detected Class(es)** | `car` (2), `motorcycle` (3), `bus` (5), `truck` (7) |
33
+
34
+ ---
35
+
36
+ ## Overview
37
+
38
+ Vehicle Entry/Exit Logging is a Metro Analytics use case that detects vehicles,
39
+ tracks them across frames with BoT-SORT, and logs directional entry and exit
40
+ events when a tracked vehicle crosses a configurable virtual line.
41
+ It is built on [YOLO26](https://docs.ultralytics.com/models/yolo26/), a
42
+ state-of-the-art real-time object detector, quantized to INT8 for efficient
43
+ inference on Intel hardware.
44
+
45
+ The tracking and line-crossing logic runs as a thin post-processing layer on
46
+ top of the strongest vehicle detector available, keeping the solution accurate
47
+ and extensible to other zone shapes.
48
+
49
+ Typical Metro deployments include:
50
+
51
+ - **Parking Garage Management** -- count vehicles entering and leaving a lot.
52
+ - **Toll Gate Analytics** -- log each vehicle that passes through a toll point.
53
+ - **Depot and Fleet Monitoring** -- track bus/truck entry and exit from depots.
54
+ - **Traffic Flow Analysis** -- measure directional flow at intersections.
55
+
56
+ Available variants: `yolo26n`, `yolo26s`, `yolo26m`, `yolo26l`, `yolo26x`.
57
+ Smaller variants (`yolo26n`, `yolo26s`) are recommended for high-FPS edge
58
+ deployment; larger variants improve recall for distant vehicles.
59
+
60
+ ---
61
+
62
+ ## Prerequisites
63
+
64
+ - Python 3.11+
65
+ - [Install OpenVINO](https://docs.openvino.ai/2026/get-started/install-openvino.html) (latest version)
66
+ - [Install Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/get_started/install/install_guide_ubuntu.html) (latest version)
67
+
68
+ Create and activate a Python virtual environment before running the scripts:
69
+
70
+ ```bash
71
+ python3 -m venv .venv --system-site-packages
72
+ source .venv/bin/activate
73
+ ```
74
+
75
+ > **Note:** The `--system-site-packages` flag is required so the virtual
76
+ > environment can access the system-installed OpenVINO and DLStreamer Python
77
+ > packages.
78
+
79
+ ---
80
+
81
+ ## Getting Started
82
+
83
+ ### Download and Quantize Model
84
+
85
+ Run the provided script to download, export to OpenVINO IR, and optionally quantize:
86
+
87
+ ```bash
88
+ chmod +x export_and_quantize.sh
89
+ ./export_and_quantize.sh
90
+ ```
91
+
92
+ This exports the default **yolo26n** model in **FP16** precision.
93
+
94
+ #### Optional: Select a Different Variant or Precision
95
+
96
+ ```bash
97
+ ./export_and_quantize.sh yolo26n FP32 # full-precision
98
+ ./export_and_quantize.sh yolo26n INT8 # quantized
99
+ ./export_and_quantize.sh yolo26s # larger variant, default FP16
100
+ ```
101
+
102
+ The script performs the following steps:
103
+
104
+ 1. Installs dependencies (`openvino`, `ultralytics`; adds `nncf` for INT8).
105
+ 2. Downloads a sample test image (`test.jpg`) and the smart-parking sample video (`smart_parking_720p_30fps.mp4`).
106
+ 3. Downloads the PyTorch weights and exports to OpenVINO IR.
107
+ 4. *(INT8 only)* Quantizes the model using NNCF post-training quantization.
108
+
109
+ Output files:
110
+
111
+ - `yolo26n_openvino_model/` -- FP32 or FP16 OpenVINO IR model directory.
112
+ - `yolo26n_vehicle_entry_exit_int8.xml` / `.bin` -- INT8 quantized model *(only when `INT8` is selected)*.
113
+
114
+ #### Precision / Device Compatibility
115
+
116
+ | Precision | CPU | GPU | NPU |
117
+ |---|---|---|---|
118
+ | FP32 | Yes | Yes | No |
119
+ | FP16 | Yes | Yes | Yes |
120
+ | INT8 | Yes | Yes | Yes |
121
+
122
+ ### OpenVINO Sample
123
+
124
+ The sample below runs YOLO26 inference on a video, keeps only the `car` class,
125
+ applies simple centroid tracking with track IDs, and logs an entry or exit
126
+ event -- with the wall-clock timestamp inside the video -- when a tracked car's
127
+ centroid crosses a horizontal virtual line placed at 60% of the frame height.
128
+ The saved output video shows only the car detection bounding boxes (no counter
129
+ overlay or line).
130
+ Change the `device` string to run on CPU, GPU, or NPU.
131
+
132
+ ```python
133
+ import cv2
134
+ import numpy as np
135
+ import openvino as ov
136
+
137
+ VEHICLE_CLASS_IDS = {2: "car"}
138
+ CONF_THRESHOLD = 0.4
139
+ INPUT_SIZE = 640
140
+ LINE_RATIO = 0.6
141
+ MAX_DIST = 80
142
+
143
+ core = ov.Core()
144
+ model = core.read_model("yolo26n_openvino_model/yolo26n.xml")
145
+
146
+ # Change device to "GPU" or "NPU" to run on integrated GPU or NPU.
147
+ compiled = core.compile_model(model, "CPU")
148
+
149
+ cap = cv2.VideoCapture("smart_parking_720p_30fps.mp4")
150
+ fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
151
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
152
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
153
+ line_y = int(height * LINE_RATIO)
154
+
155
+
156
+ def fmt_time(seconds: float) -> str:
157
+ """Format elapsed video time as MM:SS.mmm."""
158
+ minutes, secs = divmod(seconds, 60)
159
+ return f"{int(minutes):02d}:{secs:06.3f}"
160
+
161
+
162
+ writer = cv2.VideoWriter(
163
+ "output_openvino.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height))
164
+
165
+ tracks: dict[int, tuple[int, int]] = {}
166
+ entry_time: dict[int, float] = {}
167
+ next_id = 0
168
+ entered = 0
169
+ exited = 0
170
+ frame_idx = 0
171
+
172
+ while True:
173
+ ok, frame = cap.read()
174
+ if not ok:
175
+ break
176
+ frame_idx += 1
177
+ h0, w0 = frame.shape[:2]
178
+ sx, sy = w0 / INPUT_SIZE, h0 / INPUT_SIZE
179
+
180
+ blob = cv2.resize(frame, (INPUT_SIZE, INPUT_SIZE))
181
+ blob = cv2.cvtColor(blob, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
182
+ blob = blob.transpose(2, 0, 1)[np.newaxis, ...]
183
+
184
+ output = compiled([blob])[compiled.output(0)][0]
185
+ mask = (output[:, 4] >= CONF_THRESHOLD) & np.isin(
186
+ output[:, 5].astype(int), list(VEHICLE_CLASS_IDS.keys()))
187
+ dets = output[mask]
188
+
189
+ centroids = []
190
+ for det in dets:
191
+ cx = int(((det[0] + det[2]) / 2) * sx)
192
+ cy = int(((det[1] + det[3]) / 2) * sy)
193
+ centroids.append((cx, cy))
194
+
195
+ new_tracks: dict[int, tuple[int, int]] = {}
196
+ used = set()
197
+ for tid, (px, py) in tracks.items():
198
+ best_d = MAX_DIST
199
+ best_j = -1
200
+ for j, (cx, cy) in enumerate(centroids):
201
+ if j in used:
202
+ continue
203
+ d = abs(cx - px) + abs(cy - py)
204
+ if d < best_d:
205
+ best_d = d
206
+ best_j = j
207
+ if best_j >= 0:
208
+ cx, cy = centroids[best_j]
209
+ used.add(best_j)
210
+ t = frame_idx / fps
211
+ if py < line_y <= cy:
212
+ exited += 1
213
+ enter_t = entry_time.pop(tid, None)
214
+ if enter_t is not None:
215
+ print(
216
+ f"EXIT track={tid:<3} entry={fmt_time(enter_t)} "
217
+ f"exit={fmt_time(t)}", flush=True)
218
+ else:
219
+ print(f"EXIT track={tid:<3} exit={fmt_time(t)}", flush=True)
220
+ elif py >= line_y > cy:
221
+ entered += 1
222
+ entry_time[tid] = t
223
+ print(f"ENTRY track={tid:<3} entry={fmt_time(t)}", flush=True)
224
+ new_tracks[tid] = (cx, cy)
225
+ for j, (cx, cy) in enumerate(centroids):
226
+ if j not in used:
227
+ new_tracks[next_id] = (cx, cy)
228
+ next_id += 1
229
+ tracks = new_tracks
230
+
231
+ for det in dets:
232
+ x1 = int(det[0] * sx)
233
+ y1 = int(det[1] * sy)
234
+ x2 = int(det[2] * sx)
235
+ y2 = int(det[3] * sy)
236
+ cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
237
+ cv2.putText(frame, "car", (x1, max(y1 - 6, 0)),
238
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
239
+ writer.write(frame)
240
+
241
+ cap.release()
242
+ writer.release()
243
+ print(f"Total: entered={entered} exited={exited}", flush=True)
244
+ ```
245
+
246
+ **Device targets:**
247
+
248
+ - `"CPU"` -- default, works on all Intel platforms.
249
+ - `"GPU"` -- Intel integrated or discrete GPU.
250
+ - `"NPU"` -- Intel NPU (validate with `benchmark_app -d NPU`).
251
+
252
+ #### Expected Output
253
+
254
+ Each line prints the track ID with the entry timestamp, and on exit the paired
255
+ entry and exit timestamps (`MM:SS.mmm` within the video):
256
+
257
+ ```text
258
+ ENTRY track=3 entry=00:02.400
259
+ ENTRY track=7 entry=00:05.133
260
+ EXIT track=3 entry=00:02.400 exit=00:09.867
261
+ ENTRY track=12 entry=00:11.267
262
+ EXIT track=7 entry=00:05.133 exit=00:14.700
263
+ EXIT track=12 entry=00:11.267 exit=00:18.933
264
+ Total: entered=3 exited=3
265
+ ```
266
+
267
+ ![OpenVINO expected output](expected_output_openvino.gif)
268
+
269
+ ### DLStreamer Sample
270
+
271
+ The pipeline below runs the FP16 YOLO26 detector with `gvatrack`
272
+ (BoT-SORT) for stable vehicle IDs, keeping only the `car` class.
273
+ A buffer probe reads the tracking metadata and fires entry/exit events
274
+ -- logging the entry and exit timestamps taken from each buffer's
275
+ presentation time -- when a tracked car crosses the virtual line.
276
+ The annotated result is saved to `output_dlstreamer.mp4`.
277
+
278
+ > **Notes on running this sample:**
279
+ >
280
+ > - Use the FP16 IR (`yolo26n_openvino_model/yolo26n.xml`). Class names are
281
+ > read automatically from the model's embedded `metadata.yaml` by
282
+ > DLStreamer 2026.0+ -- no external `labels-file` is required.
283
+ > - Detections are read with the `gstgva` `VideoFrame` API
284
+ > (`region.object_id()` carries the `gvatrack` ID).
285
+ > - Export `PYTHONPATH` so the DLStreamer Python module is importable:
286
+ >
287
+ > ```bash
288
+ > source /opt/intel/openvino_2026/setupvars.sh
289
+ > source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
290
+ > export PYTHONPATH=/opt/intel/dlstreamer/python:\
291
+ > /opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
292
+ > ```
293
+
294
+ ```python
295
+ import gi
296
+
297
+ gi.require_version("Gst", "1.0")
298
+ from gi.repository import Gst
299
+ from gstgva import VideoFrame
300
+
301
+ Gst.init([])
302
+
303
+ INPUT_VIDEO = "smart_parking_720p_30fps.mp4"
304
+ VEHICLE_LABELS = {"car"}
305
+ LINE_RATIO = 0.6
306
+
307
+ # For CPU: change device=GPU to device=CPU.
308
+ # For NPU: change device=GPU to device=NPU (batch-size=1, nireq=4 recommended).
309
+ pipeline_str = (
310
+ f"filesrc location={INPUT_VIDEO} ! decodebin3 ! "
311
+ "videoconvert ! "
312
+ "gvadetect model=yolo26n_openvino_model/yolo26n.xml "
313
+ "device=GPU "
314
+ "threshold=0.4 ! queue ! "
315
+ "gvatrack tracking-type=short-term-imageless ! queue ! "
316
+ "identity name=probe ! "
317
+ "gvawatermark displ-cfg=show-roi=car ! "
318
+ "videoconvert ! video/x-raw,format=I420 ! "
319
+ "openh264enc ! h264parse ! "
320
+ "mp4mux ! filesink location=output_dlstreamer.mp4"
321
+ )
322
+ pipeline = Gst.parse_launch(pipeline_str)
323
+
324
+ prev_positions: dict[int, int] = {}
325
+ entry_time: dict[int, float] = {}
326
+ entered = 0
327
+ exited = 0
328
+ frame_height = 0
329
+
330
+
331
+ def fmt_time(seconds: float) -> str:
332
+ """Format elapsed video time as MM:SS.mmm."""
333
+ minutes, secs = divmod(seconds, 60)
334
+ return f"{int(minutes):02d}:{secs:06.3f}"
335
+
336
+
337
+ def on_buffer(pad, info):
338
+ global entered, exited, frame_height
339
+ buf = info.get_buffer()
340
+ caps = pad.get_current_caps()
341
+ if caps and frame_height == 0:
342
+ frame_height = caps.get_structure(0).get_value("height") or 720
343
+ line_y = int(frame_height * LINE_RATIO)
344
+
345
+ t = buf.pts / Gst.SECOND if buf.pts != Gst.CLOCK_TIME_NONE else 0.0
346
+ frame = VideoFrame(buf, caps=caps)
347
+ current: dict[int, int] = {}
348
+ for region in frame.regions():
349
+ if region.label() not in VEHICLE_LABELS:
350
+ continue
351
+ rect = region.rect()
352
+ cy = int(rect.y + rect.h / 2)
353
+ tid = region.object_id()
354
+ current[tid] = cy
355
+ if tid in prev_positions:
356
+ py = prev_positions[tid]
357
+ if py < line_y <= cy:
358
+ exited += 1
359
+ enter_t = entry_time.pop(tid, None)
360
+ if enter_t is not None:
361
+ print(
362
+ f"EXIT track={tid:<3} entry={fmt_time(enter_t)} "
363
+ f"exit={fmt_time(t)}", flush=True)
364
+ else:
365
+ print(f"EXIT track={tid:<3} exit={fmt_time(t)}", flush=True)
366
+ elif py >= line_y > cy:
367
+ entered += 1
368
+ entry_time[tid] = t
369
+ print(f"ENTRY track={tid:<3} entry={fmt_time(t)}", flush=True)
370
+ prev_positions.clear()
371
+ prev_positions.update(current)
372
+ return Gst.PadProbeReturn.OK
373
+
374
+
375
+ probe = pipeline.get_by_name("probe")
376
+ probe.get_static_pad("src").add_probe(Gst.PadProbeType.BUFFER, on_buffer)
377
+
378
+ pipeline.set_state(Gst.State.PLAYING)
379
+ bus = pipeline.get_bus()
380
+ bus.timed_pop_filtered(
381
+ Gst.CLOCK_TIME_NONE,
382
+ Gst.MessageType.EOS | Gst.MessageType.ERROR,
383
+ )
384
+ pipeline.set_state(Gst.State.NULL)
385
+ print(f"Total: entered={entered} exited={exited}", flush=True)
386
+ ```
387
+
388
+ **Device targets:**
389
+
390
+ - `device=GPU` -- default in the sample code.
391
+ - `device=CPU` -- change `device=GPU` to `device=CPU`.
392
+ - `device=NPU` -- change `device=GPU` to `device=NPU`; use `batch-size=1` and `nireq=4` for best NPU utilization.
393
+
394
+ #### Expected Output
395
+
396
+ The terminal logs each vehicle's entry timestamp and, on exit, the paired
397
+ entry and exit timestamps (`MM:SS.mmm` within the video):
398
+
399
+ ```text
400
+ ENTRY track=1 entry=00:01.900
401
+ ENTRY track=4 entry=00:04.633
402
+ EXIT track=1 entry=00:01.900 exit=00:08.767
403
+ ENTRY track=9 entry=00:10.500
404
+ EXIT track=4 entry=00:04.633 exit=00:13.400
405
+ EXIT track=9 entry=00:10.500 exit=00:17.833
406
+ Total: entered=3 exited=3
407
+ ```
408
+
409
+ ![DLStreamer expected output](expected_output_dlstreamer.gif)
410
+
411
+ ---
412
+
413
+ ## License
414
+
415
+ Licensed under the MIT License. See [LICENSE](LICENSE) for details.
416
+
417
+ ## References
418
+
419
+ - [YOLO26 Documentation](https://docs.ultralytics.com/models/yolo26/)
420
+ - [Intel DLStreamer gvatrack](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/elements/gvatrack.html)
421
+ - [BoT-SORT: Robust Multi-Object Tracking](https://arxiv.org/abs/2206.14651)
422
+ - [OpenVINO Documentation](https://docs.openvino.ai/)
423
+ - [Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/index.html)
expected_output_dlstreamer.gif ADDED

Git LFS Details

  • SHA256: 8ef15568fe014644737c51a9bf235cd84a7161f7c392cad323a323e4fb8c4cab
  • Pointer size: 132 Bytes
  • Size of remote file: 6.94 MB
expected_output_openvino.gif ADDED

Git LFS Details

  • SHA256: 7e72936f69db693f5e4248fa9f25dacdb4c5390f760941ce5cb5cb3dab05f16c
  • Pointer size: 133 Bytes
  • Size of remote file: 15.3 MB
export_and_quantize.sh ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (C) Intel Corporation
4
+ #
5
+ # Export a YOLO26 detector to OpenVINO IR for the vehicle-entry-exit-logging
6
+ # use case with BoT-SORT tracking and virtual-line crossing.
7
+ # Usage: ./export_and_quantize.sh [MODEL_VARIANT] [PRECISION]
8
+ # Example: ./export_and_quantize.sh yolo26n FP16
9
+
10
+ set -euo pipefail
11
+
12
+ MODEL_NAME="${1:-yolo26n}"
13
+ PRECISION="${2:-FP16}"
14
+ PRECISION="$(echo "${PRECISION}" | tr '[:lower:]' '[:upper:]')"
15
+
16
+ if [[ "${PRECISION}" != "FP32" && "${PRECISION}" != "FP16" && "${PRECISION}" != "INT8" ]]; then
17
+ echo "ERROR: unsupported precision '${PRECISION}'. Choose FP32, FP16, or INT8." >&2
18
+ exit 1
19
+ fi
20
+
21
+ echo "--- Installing dependencies ---"
22
+ if [[ "${PRECISION}" == "INT8" ]]; then
23
+ pip install -qU openvino nncf ultralytics
24
+ else
25
+ pip install -qU openvino ultralytics
26
+ fi
27
+
28
+ # Ask for approval before downloading models and sample files
29
+ echo ""
30
+ echo "This script will download:"
31
+ echo " - Model weights and/or sample files"
32
+ echo ""
33
+ read -p "Continue with downloads? (yes/no): " APPROVAL
34
+ if [[ "${APPROVAL}" != "yes" ]]; then
35
+ echo "Download cancelled by user."
36
+ exit 0
37
+ fi
38
+ echo ""
39
+ echo "--- Downloading sample test image ---"
40
+ if [[ ! -f test.jpg ]]; then
41
+ wget -q -O test.jpg https://ultralytics.com/images/bus.jpg
42
+ echo "Downloaded: test.jpg"
43
+ else
44
+ echo "Already present: test.jpg"
45
+ fi
46
+ echo ""
47
+ echo "--- Downloading sample test video ---"
48
+ if [[ ! -f smart_parking_720p_30fps.mp4 ]]; then
49
+ wget -q -O smart_parking_720p_30fps.mp4 \
50
+ "https://github.com/open-edge-platform/edge-ai-resources/raw/main/videos/smart_parking_720p_30fps.mp4"
51
+ echo "Downloaded: smart_parking_720p_30fps.mp4"
52
+ else
53
+ echo "Already present: smart_parking_720p_30fps.mp4"
54
+ fi
55
+
56
+ if [[ "${PRECISION}" == "FP32" ]]; then
57
+ HALF_FLAG="False"
58
+ EXPORT_LABEL="FP32"
59
+ else
60
+ HALF_FLAG="True"
61
+ EXPORT_LABEL="FP16"
62
+ fi
63
+
64
+ echo "--- Exporting ${MODEL_NAME} to OpenVINO IR (${EXPORT_LABEL}) ---"
65
+ python3 -c "
66
+ from ultralytics import YOLO
67
+
68
+ model = YOLO('${MODEL_NAME}.pt')
69
+ model.export(format='openvino', half=${HALF_FLAG}, dynamic=False, imgsz=640)
70
+ print('Export complete: ${MODEL_NAME}_openvino_model/')
71
+ "
72
+
73
+ if [[ "${PRECISION}" == "INT8" ]]; then
74
+ echo "--- Quantizing to INT8 with NNCF ---"
75
+ python3 -c "
76
+ import nncf
77
+ import openvino as ov
78
+ import numpy as np
79
+ import cv2
80
+
81
+ core = ov.Core()
82
+ model = core.read_model('${MODEL_NAME}_openvino_model/${MODEL_NAME}.xml')
83
+
84
+ img = cv2.imread('test.jpg')
85
+ img = cv2.resize(img, (640, 640))
86
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
87
+ img = img.transpose(2, 0, 1)[np.newaxis, ...]
88
+
89
+ def transform_fn(data_item):
90
+ return img
91
+
92
+ calibration_dataset = nncf.Dataset(list(range(300)), transform_fn)
93
+
94
+ quantized = nncf.quantize(
95
+ model,
96
+ calibration_dataset,
97
+ preset=nncf.QuantizationPreset.MIXED,
98
+ subset_size=300,
99
+ )
100
+
101
+ ov.save_model(quantized, '${MODEL_NAME}_vehicle_entry_exit_int8.xml')
102
+ print('Quantization complete: ${MODEL_NAME}_vehicle_entry_exit_int8.xml')
103
+ "
104
+ fi
105
+ echo "--- Done ---"