Fire and Smoke Detection

Property Value
Category Object Detection (Fire & Smoke / Safety)
Base Model YOLOv26 Fire Detection (community, Ultralytics YOLOv26-S)
Source Framework PyTorch (Ultralytics)
Supported Precisions FP32, FP16, INT8 (mixed-precision)
Inference Engine OpenVINO
Hardware CPU, GPU, NPU
Detected Class(es) fire, smoke

Overview

Fire and Smoke Detection is a Metro Analytics use case that detects open flames and smoke plumes in images and video streams and raises an on-screen alert whenever fire or smoke is present. It is built on a community YOLOv26 fire/smoke detector, exported to OpenVINO IR and optionally quantized to INT8 for efficient inference on Intel hardware.

The model was trained to recognize the fire and smoke classes. Rather than drawing bounding boxes, both the OpenVINO and DLStreamer samples overlay a banner across the top of each frame that reports whether fire or smoke has been detected, so operators get an immediate, unambiguous alert.

Typical Metro deployments include:

  • Depot and Tunnel Safety -- raise an early alarm when open flame or smoke appears in a rail depot, tunnel, or maintenance bay.
  • Trackside Vegetation Fires -- detect brush and wildfire near the right of way before it spreads to infrastructure.
  • Facility Fire Watch -- continuous monitoring of substations, storage yards, and platforms for ignition and smoke events.
  • Automated Incident Escalation -- trigger alerts and video capture the moment a fire or smoke detection is confirmed.

Prerequisites

Create and activate a Python virtual environment before running the scripts:

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 and Quantize Model

Run the provided script to download the fire/smoke model, export it to OpenVINO IR, and optionally quantize:

chmod +x export_and_quantize.sh
./export_and_quantize.sh

This exports the model in FP16 precision.

Optional: Select a Different Precision

./export_and_quantize.sh FP32   # full-precision
./export_and_quantize.sh INT8   # quantized

The script performs the following steps:

  1. Installs dependencies (openvino, ultralytics; adds nncf for INT8).
  2. Downloads the community YOLOv26 fire/smoke weights (yolov26_fire.pt).
  3. Downloads a Pexels-licensed sample wildfire video, transcoding it to test_video.mp4.
  4. Exports the PyTorch weights to OpenVINO IR.
  5. (INT8 only) Quantizes the model using NNCF post-training quantization.

Output files:

  • yolov26_fire_openvino_model/ -- FP32 or FP16 OpenVINO IR model directory.
  • yolov26_fire_int8.xml / .bin -- INT8 quantized model (only when INT8 is selected).

Precision / Device Compatibility

Precision CPU GPU NPU
FP32 Yes Yes No
FP16 Yes Yes Yes
INT8 Yes Yes Yes

OpenVINO Sample

The sample below runs the YOLOv26 fire/smoke detector on the sample video. For each frame it checks whether any fire or smoke detection is present and overlays an alert banner across the top of the frame -- no bounding boxes are drawn. The annotated result is written to output_openvino.mp4. YOLOv26 is NMS-free end-to-end, so no non-maximum suppression is required. Change the device string to run on CPU, GPU, or NPU.

import cv2
import numpy as np
import openvino as ov

# YOLOv26 fire/smoke detector classes. Alert on "fire" and "smoke".
CLASS_NAMES = {0: "fire", 1: "smoke", 2: "other"}
ALERT_CLASS_IDS = {0, 1}
CONF_THRESHOLD = 0.4
INPUT_SIZE = 640

core = ov.Core()
model = core.read_model("yolov26_fire_openvino_model/yolov26_fire.xml")

# Change device to "GPU" or "NPU" to run on integrated GPU or NPU.
compiled = core.compile_model(model, "CPU")
output_port = compiled.output(0)

cap = cv2.VideoCapture("test_video.mp4")
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))
writer = cv2.VideoWriter(
    "output_openvino.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height)
)

frame_idx = 0
while True:
    ok, frame = cap.read()
    if not ok:
        break
    frame_idx += 1

    blob = cv2.resize(frame, (INPUT_SIZE, INPUT_SIZE))
    blob = cv2.cvtColor(blob, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
    blob = blob.transpose(2, 0, 1)[np.newaxis, ...]  # NCHW

    # YOLOv26 is NMS-free: output is [1, 300, 6] = [x1, y1, x2, y2, conf, class_id].
    detections = compiled([blob])[output_port][0]

    detected = set()
    for _x1, _y1, _x2, _y2, conf, class_id in detections:
        if conf >= CONF_THRESHOLD and int(class_id) in ALERT_CLASS_IDS:
            detected.add(CLASS_NAMES[int(class_id)])

    if detected:
        text = f"{' & '.join(sorted(detected)).upper()} DETECTED"
        color = (0, 0, 255)  # red alert
    else:
        text = "NO FIRE / SMOKE"
        color = (0, 180, 0)  # green

    # Draw the alert banner across the top of the frame (no bounding boxes).
    cv2.rectangle(frame, (0, 0), (width, 60), (0, 0, 0), -1)
    cv2.putText(frame, text, (20, 42),
                cv2.FONT_HERSHEY_SIMPLEX, 1.2, color, 3)

    if frame_idx % 30 == 0:
        print(f"frame {frame_idx}: {text}", flush=True)

    writer.write(frame)

cap.release()
writer.release()
print("Saved: output_openvino.mp4")

Device targets:

  • "CPU" -- default, works on all Intel platforms.
  • "GPU" -- Intel integrated or discrete GPU.
  • "NPU" -- Intel NPU (validate with benchmark_app -d NPU).

Try It on a Sample Video

The export_and_quantize.sh script downloads and transcodes test_video.mp4 automatically. Re-run the OpenVINO sample above. The script reads test_video.mp4, prints a periodic alert status to the console, and writes the annotated video to output_openvino.mp4.

Expected console output (representative):

frame 30: FIRE & SMOKE DETECTED
frame 60: FIRE & SMOKE DETECTED
frame 90: SMOKE DETECTED

Expected Output

OpenVINO expected output showing a FIRE & SMOKE DETECTED alert banner across the top of a wildfire frame

DLStreamer Sample

The pipeline below runs the FP16 fire/smoke detector on the sample video via gvadetect. Frames are pulled through an appsink; for each frame a callback reads the detection metadata and, instead of drawing bounding boxes, overlays an alert banner across the top of the frame reporting whether fire or smoke is detected. The annotated result is written to output_dlstreamer.mp4.

Notes on running this sample:

  • Use the FP16 IR (yolov26_fire_openvino_model/yolov26_fire.xml).

  • Frames are converted to BGR for the appsink and the banner is drawn with OpenCV, so no additional GStreamer overlay plugin is required.

  • A threshold=0.4 is used for the video stream to keep the alert stable across frames.

  • Export PYTHONPATH so the DLStreamer Python module is importable:

    source /opt/intel/openvino_2026/setupvars.sh
    source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
    export PYTHONPATH=/opt/intel/dlstreamer/python:\
    /opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
    
import gi

gi.require_version("Gst", "1.0")
gi.require_version("GstAnalytics", "1.0")
from gi.repository import Gst, GLib, GstAnalytics

Gst.init([])

# Import cv2 after Gst.init to avoid a GStreamer re-initialization conflict.
import cv2
import numpy as np

MODEL_XML = "yolov26_fire_openvino_model/yolov26_fire.xml"
INPUT_VIDEO = "test_video.mp4"
ALERT_LABELS = {"fire", "smoke"}

# For CPU: change device=GPU to device=CPU.
# For NPU: change device=GPU to device=NPU (batch-size=1, nireq=4 recommended).
pipeline_str = (
    f"filesrc location={INPUT_VIDEO} ! decodebin3 ! "
    f"videoconvert ! "
    f"gvadetect name=detect model={MODEL_XML} "
    f"device=GPU threshold=0.4 ! queue ! "
    f"videoconvert ! video/x-raw,format=BGR ! "
    f"appsink name=sink emit-signals=true sync=false max-buffers=4 drop=false"
)
pipeline = Gst.parse_launch(pipeline_str)
appsink = pipeline.get_by_name("sink")

state = {"writer": None, "frame": 0}


def on_sample(sink):
    sample = sink.emit("pull-sample")
    if sample is None:
        return Gst.FlowReturn.OK

    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:
        return Gst.FlowReturn.OK
    frame = np.frombuffer(mapinfo.data, np.uint8).reshape(height, width, 3).copy()
    buf.unmap(mapinfo)

    # Read the gvadetect metadata and collect fire/smoke labels (no boxes drawn).
    labels = set()
    rmeta = GstAnalytics.buffer_get_analytics_relation_meta(buf)
    if rmeta is not None:
        idx = 1
        while True:
            found, od = rmeta.get_od_mtd(idx)
            if not found:
                break
            label = GLib.quark_to_string(od.get_obj_type())
            if label in ALERT_LABELS:
                labels.add(label)
            idx += 1

    if labels:
        text = f"{' & '.join(sorted(labels)).upper()} DETECTED"
        color = (0, 0, 255)  # red alert
    else:
        text = "NO FIRE / SMOKE"
        color = (0, 180, 0)  # green

    # Draw the alert banner across the top of the frame (no bounding boxes).
    cv2.rectangle(frame, (0, 0), (width, 60), (0, 0, 0), -1)
    cv2.putText(frame, text, (20, 42),
                cv2.FONT_HERSHEY_SIMPLEX, 1.2, color, 3)

    if state["writer"] is None:
        state["writer"] = cv2.VideoWriter(
            "output_dlstreamer.mp4",
            cv2.VideoWriter_fourcc(*"mp4v"), 30.0, (width, height),
        )
    state["writer"].write(frame)

    state["frame"] += 1
    if state["frame"] % 30 == 0:
        print(f"frame {state['frame']}: {text}", flush=True)
    return Gst.FlowReturn.OK


appsink.connect("new-sample", on_sample)

pipeline.set_state(Gst.State.PLAYING)
bus = pipeline.get_bus()
bus.timed_pop_filtered(
    Gst.CLOCK_TIME_NONE,
    Gst.MessageType.EOS | Gst.MessageType.ERROR,
)
pipeline.set_state(Gst.State.NULL)

if state["writer"] is not None:
    state["writer"].release()
print("Saved: output_dlstreamer.mp4")

Try It on a Sample Video

The export_and_quantize.sh script downloads and transcodes test_video.mp4 automatically. Run the DLStreamer sample above. The callback prints a periodic alert status and writes the annotated video.

Expected console output (representative):

frame 30: FIRE DETECTED
frame 60: FIRE DETECTED
frame 90: FIRE & SMOKE DETECTED

The annotated video is saved to output_dlstreamer.mp4 with the alert banner drawn across the top by OpenCV -- no bounding boxes are drawn.

Expected Output

DLStreamer expected output showing a fire and smoke alert banner across the top of an aerial wildfire video

Device targets:

  • device=GPU -- default in the sample code.
  • device=CPU -- change device=GPU to device=CPU.
  • device=NPU -- change device=GPU to device=NPU; use batch-size=1 and nireq=4 for best NPU utilization.

License

Licensed under the MIT License. See LICENSE for details.

References

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Collection including Intel/fire-and-smoke-detection