Fight and Violence Detection
| Property | Value |
|---|---|
| Category | Object Detection (Violence / Fight / Safety) |
| Base Model | Fight Detection YOLOv8 (community, Ultralytics YOLOv8-nano) |
| Source Framework | PyTorch (Ultralytics) |
| Supported Precisions | FP32, FP16, INT8 (mixed-precision) |
| Inference Engine | OpenVINO |
| Hardware | CPU, GPU, NPU |
| Detected Class(es) | violence, non_violence |
Overview
Fight and Violence Detection is a Metro Analytics use case that flags physically aggressive activity -- such as fighting, brawling, and sparring -- in images and video streams and raises an on-screen alert whenever violence is present. It is built on a community YOLOv8 fight/violence detector, exported to OpenVINO IR and optionally quantized to INT8 for efficient inference on Intel hardware.
The model is a single-stage detector trained on two classes, violence and
non_violence.
Rather than relying on pose estimation or hand-tuned kinematic features, it
directly localizes violent activity in each frame.
Both the OpenVINO and DLStreamer samples draw a box around any violence
detection and overlay a VIOLENCE DETECTED banner across the top of the frame,
so operators get an immediate, unambiguous alert.
Note on weights: This use case uses the
Yolo_nano_weights.ptfile, which Hugging Face's scanner reports as safe. The repository's largeryolo_small_weights.ptis flagged as unsafe (pickle) and is intentionally not used.
Typical Metro deployments include:
- Platform and Concourse Safety -- flag altercations on platforms, stairs, and concourses for rapid operator response.
- Ticket Hall and Gateline Monitoring -- detect fights and physical confrontations around fare gates and queues.
- Depot and Facility Security -- monitor restricted areas and back-of-house spaces for violent incidents.
- Automated Incident Escalation -- trigger alerts and video capture the moment violence is confirmed.
Prerequisites
- Python 3.11+
- Install OpenVINO (latest version)
- Install Intel DLStreamer (latest version)
- FFmpeg (used to transcode the sample video for the DLStreamer pipeline)
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-packagesflag 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 fight/violence 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:
- Installs dependencies (
openvino,ultralytics; addsnncffor INT8). - Downloads the community YOLOv8-nano fight/violence weights (
Yolo_nano_weights.pt). - Downloads a Pexels-licensed sample sparring video, transcoding it to
test_video.mp4. - Exports the PyTorch weights to OpenVINO IR.
- (INT8 only) Quantizes the model using NNCF post-training quantization.
Output files:
fight_detection_openvino_model/-- FP32 or FP16 OpenVINO IR model directory.fight_detection_int8.xml/.bin-- INT8 quantized model (only whenINT8is selected).test_video.mp4-- transcoded sample clip.
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 YOLOv8 fight/violence detector on the sample video.
For each frame it decodes the detector output, applies non-maximum suppression,
draws a box around any violence detection, and overlays a VIOLENCE DETECTED
banner across the top of the frame.
The annotated result is written to output_openvino.mp4.
Change the DEVICE string to run on CPU, GPU, or NPU.
import cv2
import numpy as np
import openvino as ov
# Change DEVICE to "GPU" or "NPU" to run on integrated GPU or NPU.
DEVICE = "CPU"
INPUT_SIZE = 640
MODEL = "fight_detection_openvino_model/fight_detection.xml"
CLASS_NAMES = {0: "non_violence", 1: "violence"}
VIOLENCE_ID = 1
CONF_THRESHOLD = 0.25
NMS_IOU = 0.45
core = ov.Core()
compiled = core.compile_model(core.read_model(MODEL), DEVICE)
output_port = compiled.output(0)
cap = cv2.VideoCapture("test_video.mp4")
fps = cap.get(cv2.CAP_PROP_FPS) or 25.0
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
sx, sy = width / INPUT_SIZE, height / INPUT_SIZE
writer = cv2.VideoWriter(
"output_openvino.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height)
)
frame_idx = 0
violence_frames = 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
# YOLOv8 output is [1, 4 + num_classes, 8400]; transpose to [8400, 6].
pred = compiled([blob])[output_port][0].T
boxes_xywh = pred[:, :4]
scores = pred[:, 4:]
class_ids = np.argmax(scores, axis=1)
confs = scores[np.arange(len(scores)), class_ids]
keep = confs > CONF_THRESHOLD
boxes_xywh, class_ids, confs = boxes_xywh[keep], class_ids[keep], confs[keep]
rects = []
for cx, cy, w, h in boxes_xywh:
rects.append([int((cx - w / 2) * sx), int((cy - h / 2) * sy),
int(w * sx), int(h * sy)])
detected = False
if rects:
for i in np.array(cv2.dnn.NMSBoxes(
rects, confs.tolist(), CONF_THRESHOLD, NMS_IOU)).flatten():
if int(class_ids[i]) != VIOLENCE_ID:
continue
detected = True
x, y, w, h = rects[i]
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv2.putText(frame, f"violence {confs[i]:.2f}", (x, max(0, y - 6)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
if detected:
violence_frames += 1
cv2.rectangle(frame, (0, 0), (width, 40), (0, 0, 200), -1)
cv2.putText(frame, "VIOLENCE DETECTED", (12, 28),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2)
if frame_idx % 30 == 0:
print(f"frame {frame_idx}: {'VIOLENCE DETECTED' if detected else 'normal'}",
flush=True)
writer.write(frame)
cap.release()
writer.release()
if violence_frames:
print(f"VIOLENCE DETECTED in {violence_frames}/{frame_idx} frames")
print("Saved: output_openvino.mp4")
Device targets:
"CPU"-- default, works on all Intel platforms."GPU"-- Intel integrated or discrete GPU."NPU"-- Intel NPU (validate withbenchmark_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 status to the console, and writes the annotated video to output_openvino.mp4.
Expected console output (representative):
frame 30: VIOLENCE DETECTED
frame 60: VIOLENCE DETECTED
frame 90: normal
Expected Output
DLStreamer Sample
The pipeline below runs the FP16 fight/violence detector on the sample video via
gvadetect.
DLStreamer parses the Ultralytics YOLOv8 model directly from the exported model
directory (its metadata.yaml provides the class names), so no model-proc file
is required.
Frames are pulled through an appsink; for each frame a callback reads the
detection metadata via GstAnalytics, draws a box around any violence
detection, and overlays a VIOLENCE DETECTED banner across the top of the frame.
The annotated result is written to output_dlstreamer.mp4.
Notes on running this sample:
Use the FP16 IR (
fight_detection_openvino_model/fight_detection.xml).Frames are converted to
BGRfor theappsinkand all overlays are drawn with OpenCV, so no additional GStreamer overlay plugin is required.Export
PYTHONPATHso 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 = "fight_detection_openvino_model/fight_detection.xml"
INPUT_VIDEO = "test_video.mp4"
VIOLENCE_LABEL = "violence"
# 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 model={MODEL} device=GPU threshold=0.25 ! 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, "violence": 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")
# Read gvadetect metadata: collect violence bounding boxes.
boxes = []
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())
_, x, y, w, h, conf = od.get_location()
if label == VIOLENCE_LABEL:
boxes.append((int(x), int(y), int(w), int(h), conf))
idx += 1
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)
detected = len(boxes) > 0
for x, y, w, h, conf in boxes:
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv2.putText(frame, f"violence {conf:.2f}", (x, max(0, y - 6)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
if detected:
state["violence"] += 1
cv2.rectangle(frame, (0, 0), (width, 40), (0, 0, 200), -1)
cv2.putText(frame, "VIOLENCE DETECTED", (12, 28),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2)
if state["writer"] is None:
ok_fps, fn, fd = caps.get_fraction("framerate")
fps = fn / fd if ok_fps and fd > 0 else 25.0
state["writer"] = cv2.VideoWriter(
"output_dlstreamer.mp4",
cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height),
)
state["writer"].write(frame)
state["frame"] += 1
if state["frame"] % 30 == 0:
print(f"frame {state['frame']}: "
f"{'VIOLENCE DETECTED' if detected else 'normal'}", 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()
if state["violence"]:
print(f"VIOLENCE DETECTED in {state['violence']}/{state['frame']} frames")
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 status and writes the annotated video.
Expected console output (representative):
frame 30: VIOLENCE DETECTED
frame 60: VIOLENCE DETECTED
frame 90: normal
The annotated video is saved to output_dlstreamer.mp4 with the alert banner and
violence boxes drawn by OpenCV.
Expected Output
Device targets:
device=GPU-- default in the sample code.device=CPU-- changedevice=GPUtodevice=CPU.device=NPU-- changedevice=GPUtodevice=NPU; usebatch-size=1andnireq=4for best NPU utilization.
License
Licensed under the MIT License. See LICENSE for details.
References
- Fight Detection YOLOv8 Model
- Ultralytics YOLO Documentation
- Sample video: "Men doing sparring" by cottonbro studio (Pexels License), via Pexels
- OpenVINO Documentation
- NNCF Post-Training Quantization
- Intel DLStreamer

