vagheshpatel commited on
Commit
da15776
·
verified ·
1 Parent(s): dc0193e

Sync fire-and-smoke-detection 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,387 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ - yolov26
11
+ - fire-and-smoke-detection
12
+ - wildfire
13
+ - smoke-detection
14
+ - safety
15
+ - edge-ai
16
+ - metro
17
+ - dlstreamer
18
+ language:
19
+ - en
20
+ ---
21
+
22
+ # Fire and Smoke Detection
23
+
24
+ | Property | Value |
25
+ |---|---|
26
+ | **Category** | Object Detection (Fire & Smoke / Safety) |
27
+ | **Base Model** | [YOLOv26 Fire Detection](https://huggingface.co/SalahALHaismawi/yolov26-fire-detection) (community, Ultralytics YOLOv26-S) |
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)** | `fire`, `smoke` |
33
+
34
+ ---
35
+
36
+ ## Overview
37
+
38
+ Fire and Smoke Detection is a Metro Analytics use case that detects open flames
39
+ and smoke plumes in images and video streams and raises an on-screen alert
40
+ whenever fire or smoke is present. It is built on a community
41
+ [YOLOv26 fire/smoke detector](https://huggingface.co/SalahALHaismawi/yolov26-fire-detection),
42
+ exported to OpenVINO IR and optionally quantized to INT8 for efficient inference
43
+ on Intel hardware.
44
+
45
+ The model was trained to recognize the `fire` and `smoke` classes. Rather than
46
+ drawing bounding boxes, both the OpenVINO and DLStreamer samples overlay a
47
+ banner across the top of each frame that reports whether fire or smoke has been
48
+ detected, so operators get an immediate, unambiguous alert.
49
+
50
+ Typical Metro deployments include:
51
+
52
+ - **Depot and Tunnel Safety** -- raise an early alarm when open flame or smoke appears in a rail depot, tunnel, or maintenance bay.
53
+ - **Trackside Vegetation Fires** -- detect brush and wildfire near the right of way before it spreads to infrastructure.
54
+ - **Facility Fire Watch** -- continuous monitoring of substations, storage yards, and platforms for ignition and smoke events.
55
+ - **Automated Incident Escalation** -- trigger alerts and video capture the moment a `fire` or `smoke` detection is confirmed.
56
+
57
+ ---
58
+
59
+ ## Prerequisites
60
+
61
+ - Python 3.11+
62
+ - [Install OpenVINO](https://docs.openvino.ai/2026/get-started/install-openvino.html) (latest version)
63
+ - [Install Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/get_started/install/install_guide_ubuntu.html) (latest version)
64
+ - [FFmpeg](https://ffmpeg.org/) (used to transcode the sample video for the DLStreamer pipeline)
65
+
66
+ Create and activate a Python virtual environment before running the scripts:
67
+
68
+ ```bash
69
+ python3 -m venv .venv --system-site-packages
70
+ source .venv/bin/activate
71
+ ```
72
+
73
+ > **Note:** The `--system-site-packages` flag is required so the virtual
74
+ > environment can access the system-installed OpenVINO and DLStreamer Python
75
+ > packages.
76
+
77
+ ---
78
+
79
+ ## Getting Started
80
+
81
+ ### Download and Quantize Model
82
+
83
+ Run the provided script to download the fire/smoke model, export it to OpenVINO IR, and optionally quantize:
84
+
85
+ ```bash
86
+ chmod +x export_and_quantize.sh
87
+ ./export_and_quantize.sh
88
+ ```
89
+
90
+ This exports the model in **FP16** precision.
91
+
92
+ #### Optional: Select a Different Precision
93
+
94
+ ```bash
95
+ ./export_and_quantize.sh FP32 # full-precision
96
+ ./export_and_quantize.sh INT8 # quantized
97
+ ```
98
+
99
+ The script performs the following steps:
100
+
101
+ 1. Installs dependencies (`openvino`, `ultralytics`; adds `nncf` for INT8).
102
+ 2. Downloads the community YOLOv26 fire/smoke weights (`yolov26_fire.pt`).
103
+ 3. Downloads a Pexels-licensed sample wildfire video, transcoding it to `test_video.mp4`.
104
+ 4. Exports the PyTorch weights to OpenVINO IR.
105
+ 5. *(INT8 only)* Quantizes the model using NNCF post-training quantization.
106
+
107
+ Output files:
108
+
109
+ - `yolov26_fire_openvino_model/` -- FP32 or FP16 OpenVINO IR model directory.
110
+ - `yolov26_fire_int8.xml` / `.bin` -- INT8 quantized model *(only when `INT8` is selected)*.
111
+
112
+ #### Precision / Device Compatibility
113
+
114
+ | Precision | CPU | GPU | NPU |
115
+ |---|---|---|---|
116
+ | FP32 | Yes | Yes | No |
117
+ | FP16 | Yes | Yes | Yes |
118
+ | INT8 | Yes | Yes | Yes |
119
+
120
+ ### OpenVINO Sample
121
+
122
+ The sample below runs the YOLOv26 fire/smoke detector on the sample video. For
123
+ each frame it checks whether any `fire` or `smoke` detection is present and
124
+ overlays an alert banner across the top of the frame -- no bounding boxes are
125
+ drawn. The annotated result is written to `output_openvino.mp4`. YOLOv26 is
126
+ NMS-free end-to-end, so no non-maximum suppression is required. Change the
127
+ `device` string to run on CPU, GPU, or NPU.
128
+
129
+ ```python
130
+ import cv2
131
+ import numpy as np
132
+ import openvino as ov
133
+
134
+ # YOLOv26 fire/smoke detector classes. Alert on "fire" and "smoke".
135
+ CLASS_NAMES = {0: "fire", 1: "smoke", 2: "other"}
136
+ ALERT_CLASS_IDS = {0, 1}
137
+ CONF_THRESHOLD = 0.4
138
+ INPUT_SIZE = 640
139
+
140
+ core = ov.Core()
141
+ model = core.read_model("yolov26_fire_openvino_model/yolov26_fire.xml")
142
+
143
+ # Change device to "GPU" or "NPU" to run on integrated GPU or NPU.
144
+ compiled = core.compile_model(model, "CPU")
145
+ output_port = compiled.output(0)
146
+
147
+ cap = cv2.VideoCapture("test_video.mp4")
148
+ fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
149
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
150
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
151
+ writer = cv2.VideoWriter(
152
+ "output_openvino.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height)
153
+ )
154
+
155
+ frame_idx = 0
156
+ while True:
157
+ ok, frame = cap.read()
158
+ if not ok:
159
+ break
160
+ frame_idx += 1
161
+
162
+ blob = cv2.resize(frame, (INPUT_SIZE, INPUT_SIZE))
163
+ blob = cv2.cvtColor(blob, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
164
+ blob = blob.transpose(2, 0, 1)[np.newaxis, ...] # NCHW
165
+
166
+ # YOLOv26 is NMS-free: output is [1, 300, 6] = [x1, y1, x2, y2, conf, class_id].
167
+ detections = compiled([blob])[output_port][0]
168
+
169
+ detected = set()
170
+ for _x1, _y1, _x2, _y2, conf, class_id in detections:
171
+ if conf >= CONF_THRESHOLD and int(class_id) in ALERT_CLASS_IDS:
172
+ detected.add(CLASS_NAMES[int(class_id)])
173
+
174
+ if detected:
175
+ text = f"{' & '.join(sorted(detected)).upper()} DETECTED"
176
+ color = (0, 0, 255) # red alert
177
+ else:
178
+ text = "NO FIRE / SMOKE"
179
+ color = (0, 180, 0) # green
180
+
181
+ # Draw the alert banner across the top of the frame (no bounding boxes).
182
+ cv2.rectangle(frame, (0, 0), (width, 60), (0, 0, 0), -1)
183
+ cv2.putText(frame, text, (20, 42),
184
+ cv2.FONT_HERSHEY_SIMPLEX, 1.2, color, 3)
185
+
186
+ if frame_idx % 30 == 0:
187
+ print(f"frame {frame_idx}: {text}", flush=True)
188
+
189
+ writer.write(frame)
190
+
191
+ cap.release()
192
+ writer.release()
193
+ print("Saved: output_openvino.mp4")
194
+ ```
195
+
196
+ **Device targets:**
197
+
198
+ - `"CPU"` -- default, works on all Intel platforms.
199
+ - `"GPU"` -- Intel integrated or discrete GPU.
200
+ - `"NPU"` -- Intel NPU (validate with `benchmark_app -d NPU`).
201
+
202
+ ### Try It on a Sample Video
203
+
204
+ The `export_and_quantize.sh` script downloads and transcodes `test_video.mp4` automatically.
205
+ Re-run the OpenVINO sample above.
206
+ The script reads `test_video.mp4`, prints a periodic alert status to the console, and writes the annotated video to `output_openvino.mp4`.
207
+
208
+ Expected console output (representative):
209
+
210
+ ```text
211
+ frame 30: FIRE & SMOKE DETECTED
212
+ frame 60: FIRE & SMOKE DETECTED
213
+ frame 90: SMOKE DETECTED
214
+ ```
215
+
216
+ #### Expected Output
217
+
218
+ ![OpenVINO expected output showing a FIRE & SMOKE DETECTED alert banner across the top of a wildfire frame](expected_output_openvino.gif)
219
+
220
+ ### DLStreamer Sample
221
+
222
+ The pipeline below runs the FP16 fire/smoke detector on the sample video via
223
+ `gvadetect`. Frames are pulled through an `appsink`; for each frame a callback
224
+ reads the detection metadata and, instead of drawing bounding boxes, overlays an
225
+ alert banner across the top of the frame reporting whether `fire` or `smoke` is
226
+ detected. The annotated result is written to `output_dlstreamer.mp4`.
227
+
228
+ > **Notes on running this sample:**
229
+ >
230
+ > - Use the FP16 IR (`yolov26_fire_openvino_model/yolov26_fire.xml`).
231
+ > - Frames are converted to `BGR` for the `appsink` and the banner is drawn with
232
+ > OpenCV, so no additional GStreamer overlay plugin is required.
233
+ > - A `threshold=0.4` is used for the video stream to keep the alert stable
234
+ > across frames.
235
+ > - Export `PYTHONPATH` so the DLStreamer Python module is importable:
236
+ >
237
+ > ```bash
238
+ > source /opt/intel/openvino_2026/setupvars.sh
239
+ > source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
240
+ > export PYTHONPATH=/opt/intel/dlstreamer/python:\
241
+ > /opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
242
+ > ```
243
+
244
+ ```python
245
+ import gi
246
+
247
+ gi.require_version("Gst", "1.0")
248
+ gi.require_version("GstAnalytics", "1.0")
249
+ from gi.repository import Gst, GLib, GstAnalytics
250
+
251
+ Gst.init([])
252
+
253
+ # Import cv2 after Gst.init to avoid a GStreamer re-initialization conflict.
254
+ import cv2
255
+ import numpy as np
256
+
257
+ MODEL_XML = "yolov26_fire_openvino_model/yolov26_fire.xml"
258
+ INPUT_VIDEO = "test_video.mp4"
259
+ ALERT_LABELS = {"fire", "smoke"}
260
+
261
+ # For CPU: change device=GPU to device=CPU.
262
+ # For NPU: change device=GPU to device=NPU (batch-size=1, nireq=4 recommended).
263
+ pipeline_str = (
264
+ f"filesrc location={INPUT_VIDEO} ! decodebin3 ! "
265
+ f"videoconvert ! "
266
+ f"gvadetect name=detect model={MODEL_XML} "
267
+ f"device=GPU threshold=0.4 ! queue ! "
268
+ f"videoconvert ! video/x-raw,format=BGR ! "
269
+ f"appsink name=sink emit-signals=true sync=false max-buffers=4 drop=false"
270
+ )
271
+ pipeline = Gst.parse_launch(pipeline_str)
272
+ appsink = pipeline.get_by_name("sink")
273
+
274
+ state = {"writer": None, "frame": 0}
275
+
276
+
277
+ def on_sample(sink):
278
+ sample = sink.emit("pull-sample")
279
+ if sample is None:
280
+ return Gst.FlowReturn.OK
281
+
282
+ buf = sample.get_buffer()
283
+ caps = sample.get_caps().get_structure(0)
284
+ width = caps.get_value("width")
285
+ height = caps.get_value("height")
286
+
287
+ ok, mapinfo = buf.map(Gst.MapFlags.READ)
288
+ if not ok:
289
+ return Gst.FlowReturn.OK
290
+ frame = np.frombuffer(mapinfo.data, np.uint8).reshape(height, width, 3).copy()
291
+ buf.unmap(mapinfo)
292
+
293
+ # Read the gvadetect metadata and collect fire/smoke labels (no boxes drawn).
294
+ labels = set()
295
+ rmeta = GstAnalytics.buffer_get_analytics_relation_meta(buf)
296
+ if rmeta is not None:
297
+ idx = 1
298
+ while True:
299
+ found, od = rmeta.get_od_mtd(idx)
300
+ if not found:
301
+ break
302
+ label = GLib.quark_to_string(od.get_obj_type())
303
+ if label in ALERT_LABELS:
304
+ labels.add(label)
305
+ idx += 1
306
+
307
+ if labels:
308
+ text = f"{' & '.join(sorted(labels)).upper()} DETECTED"
309
+ color = (0, 0, 255) # red alert
310
+ else:
311
+ text = "NO FIRE / SMOKE"
312
+ color = (0, 180, 0) # green
313
+
314
+ # Draw the alert banner across the top of the frame (no bounding boxes).
315
+ cv2.rectangle(frame, (0, 0), (width, 60), (0, 0, 0), -1)
316
+ cv2.putText(frame, text, (20, 42),
317
+ cv2.FONT_HERSHEY_SIMPLEX, 1.2, color, 3)
318
+
319
+ if state["writer"] is None:
320
+ state["writer"] = cv2.VideoWriter(
321
+ "output_dlstreamer.mp4",
322
+ cv2.VideoWriter_fourcc(*"mp4v"), 30.0, (width, height),
323
+ )
324
+ state["writer"].write(frame)
325
+
326
+ state["frame"] += 1
327
+ if state["frame"] % 30 == 0:
328
+ print(f"frame {state['frame']}: {text}", flush=True)
329
+ return Gst.FlowReturn.OK
330
+
331
+
332
+ appsink.connect("new-sample", on_sample)
333
+
334
+ pipeline.set_state(Gst.State.PLAYING)
335
+ bus = pipeline.get_bus()
336
+ bus.timed_pop_filtered(
337
+ Gst.CLOCK_TIME_NONE,
338
+ Gst.MessageType.EOS | Gst.MessageType.ERROR,
339
+ )
340
+ pipeline.set_state(Gst.State.NULL)
341
+
342
+ if state["writer"] is not None:
343
+ state["writer"].release()
344
+ print("Saved: output_dlstreamer.mp4")
345
+ ```
346
+
347
+ ### Try It on a Sample Video
348
+
349
+ The `export_and_quantize.sh` script downloads and transcodes `test_video.mp4` automatically.
350
+ Run the DLStreamer sample above.
351
+ The callback prints a periodic alert status and writes the annotated video.
352
+
353
+ Expected console output (representative):
354
+
355
+ ```text
356
+ frame 30: FIRE DETECTED
357
+ frame 60: FIRE DETECTED
358
+ frame 90: FIRE & SMOKE DETECTED
359
+ ```
360
+
361
+ The annotated video is saved to `output_dlstreamer.mp4` with the alert banner
362
+ drawn across the top by OpenCV -- no bounding boxes are drawn.
363
+
364
+ #### Expected Output
365
+
366
+ ![DLStreamer expected output showing a fire and smoke alert banner across the top of an aerial wildfire video](expected_output_dlstreamer.gif)
367
+
368
+ **Device targets:**
369
+
370
+ - `device=GPU` -- default in the sample code.
371
+ - `device=CPU` -- change `device=GPU` to `device=CPU`.
372
+ - `device=NPU` -- change `device=GPU` to `device=NPU`; use `batch-size=1` and `nireq=4` for best NPU utilization.
373
+
374
+ ---
375
+
376
+ ## License
377
+
378
+ Licensed under the MIT License. See [LICENSE](LICENSE) for details.
379
+
380
+ ## References
381
+
382
+ - [YOLOv26 Fire Detection Model](https://huggingface.co/SalahALHaismawi/yolov26-fire-detection)
383
+ - [Ultralytics YOLO Documentation](https://docs.ultralytics.com/)
384
+ - Sample video: "Aerial view of wildfire in forested area" by K (Kelly) (Pexels License), via [Pexels](https://www.pexels.com/video/aerial-view-of-wildfire-in-forested-area-30937716/)
385
+ - [OpenVINO Documentation](https://docs.openvino.ai/)
386
+ - [NNCF Post-Training Quantization](https://docs.openvino.ai/latest/nncf_ptq_introduction.html)
387
+ - [Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/index.html)
expected_output_dlstreamer.gif ADDED

Git LFS Details

  • SHA256: 1cde894b41e1863df8349bae13ec61a7bdc96eca7cd6705a1fca8e8245572c6a
  • Pointer size: 133 Bytes
  • Size of remote file: 35.9 MB
expected_output_openvino.gif ADDED

Git LFS Details

  • SHA256: ea9f2de4a318786de97ff3ceb09d9ce4db76f422c24cdfe0511dc48325603d10
  • Pointer size: 133 Bytes
  • Size of remote file: 36.3 MB
export_and_quantize.sh ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (C) Intel Corporation
4
+ #
5
+ # Export a community YOLOv26 fire/smoke detector to OpenVINO IR for the
6
+ # fire-and-smoke-detection use case. The model detects the "fire" and
7
+ # "smoke" classes.
8
+ # Usage: ./export_and_quantize.sh [PRECISION]
9
+ # Example: ./export_and_quantize.sh FP16
10
+
11
+ set -euo pipefail
12
+
13
+ PRECISION="${1:-FP16}"
14
+ PRECISION="$(echo "${PRECISION}" | tr '[:lower:]' '[:upper:]')"
15
+
16
+ MODEL_NAME="yolov26_fire"
17
+ MODEL_URL="https://huggingface.co/SalahALHaismawi/yolov26-fire-detection/resolve/main/best.pt"
18
+
19
+ # CC0 / Pexels-licensed sample video: "Aerial view of wildfire in forested
20
+ # area" by K (Kelly), free to use via Pexels.
21
+ VIDEO_URL="https://www.pexels.com/download/video/30937716/"
22
+
23
+ if [[ "${PRECISION}" != "FP32" && "${PRECISION}" != "FP16" && "${PRECISION}" != "INT8" ]]; then
24
+ echo "ERROR: unsupported precision '${PRECISION}'. Choose FP32, FP16, or INT8." >&2
25
+ exit 1
26
+ fi
27
+
28
+ echo "--- Installing dependencies ---"
29
+ if [[ "${PRECISION}" == "INT8" ]]; then
30
+ pip install -qU openvino nncf ultralytics
31
+ else
32
+ pip install -qU openvino ultralytics
33
+ fi
34
+
35
+ # Ask for approval before downloading models and sample files
36
+ echo ""
37
+ echo "This script will download:"
38
+ echo " - Community YOLOv26 fire/smoke model weights"
39
+ echo " - A Pexels-licensed sample wildfire video"
40
+ echo ""
41
+ read -p "Continue with downloads? (yes/no): " APPROVAL
42
+ if [[ "${APPROVAL}" != "yes" ]]; then
43
+ echo "Download cancelled by user."
44
+ exit 0
45
+ fi
46
+ echo ""
47
+
48
+ echo "--- Downloading fire/smoke model weights ---"
49
+ if [[ ! -f "${MODEL_NAME}.pt" ]]; then
50
+ curl -sL -o "${MODEL_NAME}.pt" "${MODEL_URL}"
51
+ echo "Downloaded: ${MODEL_NAME}.pt"
52
+ else
53
+ echo "Already present: ${MODEL_NAME}.pt"
54
+ fi
55
+
56
+ echo "--- Downloading and transcoding sample test video ---"
57
+ # Both samples run on an H.264 MP4. A fire/smoke-rich 8-second window is
58
+ # trimmed, center-cropped to a square (so the fixed 640x640 resize does not
59
+ # distort the aspect ratio the model is sensitive to), and transcoded with
60
+ # ffmpeg.
61
+ if [[ ! -f test_video.mp4 ]]; then
62
+ if ! command -v ffmpeg >/dev/null 2>&1; then
63
+ echo "ERROR: ffmpeg is required to transcode the sample video." >&2
64
+ echo "Install it (e.g. 'sudo apt-get install ffmpeg') and re-run." >&2
65
+ exit 1
66
+ fi
67
+ curl -sL -o fire_source.mp4 "${VIDEO_URL}"
68
+ ffmpeg -y -loglevel error -ss 2 -t 8 -i fire_source.mp4 \
69
+ -vf "crop=ih:ih,scale=736:736,format=yuv420p" \
70
+ -c:v libx264 -preset veryfast -an test_video.mp4
71
+ rm -f fire_source.mp4
72
+ echo "Downloaded and transcoded: test_video.mp4"
73
+ else
74
+ echo "Already present: test_video.mp4"
75
+ fi
76
+
77
+ if [[ "${PRECISION}" == "FP32" ]]; then
78
+ HALF_FLAG="False"
79
+ EXPORT_LABEL="FP32"
80
+ else
81
+ HALF_FLAG="True"
82
+ EXPORT_LABEL="FP16"
83
+ fi
84
+
85
+ echo "--- Exporting ${MODEL_NAME} to OpenVINO IR (${EXPORT_LABEL}) ---"
86
+ python3 -c "
87
+ from ultralytics import YOLO
88
+
89
+ model = YOLO('${MODEL_NAME}.pt')
90
+ model.export(format='openvino', half=${HALF_FLAG}, dynamic=False, imgsz=640)
91
+ print('Export complete: ${MODEL_NAME}_openvino_model/')
92
+ "
93
+
94
+ if [[ "${PRECISION}" == "INT8" ]]; then
95
+ echo "--- Quantizing to INT8 with NNCF ---"
96
+ python3 -c "
97
+ import nncf
98
+ import openvino as ov
99
+ import numpy as np
100
+ import cv2
101
+
102
+ core = ov.Core()
103
+ model = core.read_model('${MODEL_NAME}_openvino_model/${MODEL_NAME}.xml')
104
+
105
+ cap = cv2.VideoCapture('test_video.mp4')
106
+ ok, frame = cap.read()
107
+ cap.release()
108
+ img = cv2.resize(frame, (640, 640))
109
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
110
+ img = img.transpose(2, 0, 1)[np.newaxis, ...]
111
+
112
+ def transform_fn(data_item):
113
+ return img
114
+
115
+ calibration_dataset = nncf.Dataset(list(range(300)), transform_fn)
116
+
117
+ quantized = nncf.quantize(
118
+ model,
119
+ calibration_dataset,
120
+ preset=nncf.QuantizationPreset.MIXED,
121
+ subset_size=300,
122
+ )
123
+
124
+ ov.save_model(quantized, '${MODEL_NAME}_int8.xml')
125
+ print('Quantization complete: ${MODEL_NAME}_int8.xml')
126
+ "
127
+ fi
128
+ echo "--- Done ---"