Fight and Violence Detection

Property Value
Category Vision-Language Alerting (Violence / Fight / Safety)
Base Model Qwen2-VL-2B-Instruct (Alibaba, Apache-2.0)
Source Framework PyTorch (Transformers)
Supported Precisions INT4, INT8 (weight-compressed)
Inference Engine OpenVINO GenAI
Hardware CPU, GPU, NPU
Detected Class(es) violence (via natural-language prompt)

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 Qwen2-VL-2B-Instruct, a compact, state-of-the-art vision-language model (VLM) from Alibaba released under the Apache-2.0 license, exported to OpenVINO and weight-compressed to INT4 (default) or INT8 so it runs efficiently and fully locally on an Intel Core Ultra processor.

Rather than relying on a narrow, single-purpose detector trained on an unverified dataset, the VLM is prompted in natural language -- for example "Is there a physical fight between people in this image?" -- and its yes/no answer drives the alert. Both the OpenVINO and DLStreamer samples overlay a VIOLENCE DETECTED banner across the top of each frame when violence is present, so operators get an immediate, unambiguous alert.

Because the model is a general vision-language model, the same use case can be re-targeted to related behaviours (for example vandalism or aggressive crowding) simply by editing the prompt -- no retraining is required.

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

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 Qwen2-VL-2B-Instruct model and export it to OpenVINO with weight compression:

chmod +x export_and_quantize.sh
./export_and_quantize.sh

This exports the model in INT4 precision (smallest, fastest on Core Ultra).

Optional: Select a Different Precision

./export_and_quantize.sh INT8   # higher accuracy, larger footprint
./export_and_quantize.sh FP16   # full-precision weights

The script performs the following steps:

  1. Installs dependencies (optimum[openvino], openvino, openvino-genai, nncf, transformers, qwen-vl-utils).
  2. Exports the Qwen2-VL-2B-Instruct weights to OpenVINO with the selected weight format via optimum-cli export openvino.
  3. Downloads a Pexels-licensed sample sparring video, transcoding it to test_video.mp4.

Output files:

  • qwen2_vl_2b_ov/ -- OpenVINO model directory (language model, vision encoder, tokenizer, and preprocessor config) ready for OpenVINO GenAI.
  • test_video.mp4 -- transcoded sample clip.

Precision / Device Compatibility

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

OpenVINO Sample

The sample below runs the Qwen2-VL-2B-Instruct VLM on the sample video with OpenVINO GenAI. To keep inference responsive, one frame is sampled every FRAME_STRIDE frames and sent to the VLM with a short violence prompt; the yes/no answer is held between samples and overlaid as an alert banner across the top of each 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
import openvino_genai

# Change DEVICE to "GPU" or "NPU" to run on integrated GPU or NPU.
DEVICE = "CPU"
MODEL_DIR = "qwen2_vl_2b_ov"
PROMPT = (
    "Does this image show people boxing, punching, or physically fighting "
    "each other? Answer with a single word: yes or no."
)
# Run the VLM every FRAME_STRIDE frames; the alert is held between inferences.
FRAME_STRIDE = 15

properties = {}
if DEVICE == "GPU":
    properties["CACHE_DIR"] = "vlm_cache"
pipe = openvino_genai.VLMPipeline(MODEL_DIR, DEVICE, **properties)

config = openvino_genai.GenerationConfig()
config.max_new_tokens = 8

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


def ask_vlm(frame_bgr: np.ndarray) -> bool:
    """Return True when the VLM reports a fight or violence in the frame."""
    rgb = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)
    image = ov.Tensor(np.ascontiguousarray(rgb))
    result = pipe.generate(PROMPT, images=[image], generation_config=config)
    answer = str(result).strip().lower()
    return answer.startswith("yes") or "violence" in answer or "fight" in answer


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

    if (frame_idx - 1) % FRAME_STRIDE == 0:
        detected = ask_vlm(frame)

    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. The language model runs on the NPU while the vision encoder runs on CPU; validate the model loads with a short clip first.

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

OpenVINO expected output showing a VIOLENCE DETECTED banner across the top of a sparring video

DLStreamer Sample

The pipeline below runs the same Qwen2-VL-2B-Instruct VLM on the sample video via the DLStreamer 2026 gvagenai element, which performs vision-language inference through OpenVINO GenAI. Frames are decoded, grouped into short chunks, and summarized by the VLM using a violence prompt; gvagenai attaches the answer to the buffer as JSON metadata. Frames are pulled through an appsink; for each frame a callback reads the latest VLM answer and overlays a VIOLENCE DETECTED banner across the top of the frame when violence is present. The annotated result is written to output_dlstreamer.mp4.

Notes on running this sample:

  • Use the OpenVINO model directory produced by export_and_quantize.sh (qwen2_vl_2b_ov); gvagenai reads it via its model-path property.

  • gvagenai requires an RGB input, so the decode chain converts to RGB before inference; the appsink then converts back to BGR and the banner is drawn with OpenCV, so no additional GStreamer overlay plugin is required.

  • frame-rate controls how many frames per second are sampled for the VLM and chunk-size how many sampled frames form one inference call; keep both small to stay responsive on Core Ultra.

  • The VLM answer is attached to the buffer as a GstGVAJSONMeta message and read in Python with gstgva.VideoFrame(buffer).messages().

  • Export PYTHONPATH so the DLStreamer Python modules (gi, gstgva) are 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 json

import gi

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

Gst.init([])

# Import cv2 and gstgva after Gst.init to avoid a re-initialization conflict.
import cv2
import numpy as np
from gstgva import VideoFrame

MODEL_DIR = "qwen2_vl_2b_ov"
INPUT_VIDEO = "test_video.mp4"
PROMPT = (
    "Does this image show people boxing, punching, or physically fighting "
    "each other? Answer with a single word: yes or no."
)
ALERT_KEYWORDS = ("yes", "boxing", "punch", "fight", "violence")

# For CPU: change device=GPU to device=CPU.
# NPU is not supported by gvagenai (OpenVINO does not yet run VLMs on NPU);
# use the OpenVINO GenAI sample above to target the NPU.
pipeline_str = (
    f"filesrc location={INPUT_VIDEO} ! decodebin3 ! "
    f"videoconvert ! video/x-raw,format=RGB ! "
    f"gvagenai name=genai model-path={MODEL_DIR} device=GPU "
    f'prompt="{PROMPT}" generation-config="max_new_tokens=8" '
    f"frame-rate=2 chunk-size=2 ! 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, "detected": False}


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 the latest VLM answer from the gvagenai JSON metadata (if present).
    for message in VideoFrame(buf).messages():
        try:
            answer = str(json.loads(message).get("result", "")).strip().lower()
        except (ValueError, TypeError):
            continue
        if answer:
            state["detected"] = any(k in answer for k in ALERT_KEYWORDS)

    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 = state["detected"]
    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 drawn by OpenCV.

Expected Output

DLStreamer expected output showing a VIOLENCE DETECTED banner across the top of a sparring video

Device targets:

  • device=GPU -- default in the sample code.
  • device=CPU -- change device=GPU to device=CPU.
  • device=NPU -- not supported by gvagenai; OpenVINO does not yet run vision-language models on the NPU. Target the NPU with the OpenVINO GenAI sample above instead.

License

Licensed under the MIT License. See LICENSE for details.

References

Downloads last month
22
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Collection including Intel/fight-and-violence-detection

Paper for Intel/fight-and-violence-detection