Build a Fight Detection System

Detect violence in real-time from webcam or video using YOLOv8 and Streamlit.

Introduction

30 Second Summary

Security cameras are everywhere, but most just record footage for someone to review later. What if a camera could instantly recognize when a fight breaks out and alert you in real-time?

In this project, you will build a real-time fight detection system that uses AI to spot violence in video feeds. You will create a web dashboard that monitors live webcam or uploaded video and automatically logs incidents with screenshots.

What You'll Build

A browser-based surveillance application that uses a pre-trained YOLOv8 model to detect fights and violence from webcam or uploaded video, running entirely on your CPU.

By the end of this project, you'll have:

  • A violence detection engine powered by YOLOv8-nano that runs on CPU without needing a GPU.
  • A Streamlit web dashboard with two input modes: video upload and live webcam monitoring.
  • An automated incident log that captures timestamped screenshots whenever violence is detected.
  • Secret Mission: Add a statistics dashboard with detection charts and CSV export.

Are there any prerequisites?

You should have basic Python knowledge (variables, functions, and loops). You also need a webcam connected to your computer.

Set Up Your Project Environment

Before you can detect fights in video, you need a clean workspace with the right tools installed.

In this step, you will create a dedicated project folder, set up a virtual environment to isolate your packages, install the libraries your detection system needs, and confirm your webcam is ready to go.

In this step, get ready to:
  • Create a project folder with a Python virtual environment.
  • Install all required packages (YOLOv8, Streamlit, OpenCV).
  • Verify your webcam works with a quick test script.
Create your project folder and virtual environment

A virtual environment keeps your project's packages separate from your system Python. This prevents version conflicts between different projects on your machine.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir fight-detection
cd fight-detection
  • Create a Python virtual environment by running this command:
python -m venv venv

What is a virtual environment?

A virtual environment is an isolated copy of Python that lives inside your project folder. Packages you install here stay contained to this project and do not affect anything else on your system.

  • Activate the virtual environment by running the command for your operating system:

macOS

source venv/bin/activate

Windows

venv\Scripts\activate

You should see (venv) appear at the beginning of your terminal prompt. This confirms the virtual environment is active.

Don't see (venv) in your prompt?

On Windows, if you see a "running scripts is disabled" error, run this command first, then try activating again:

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

On macOS, make sure you typed source before the path. Without it, the activation script will not run in your current terminal session.

Help me activate my Python virtual environment

Install the required packages

Your fight detection system relies on five key libraries. Ultralytics provides the YOLOv8 model for object detection. Streamlit builds the web dashboard. OpenCV handles video processing. HuggingFace Hub downloads the pre-trained model weights.

  • Install all required packages by running this command:
pip install ultralytics streamlit opencv-python numpy huggingface-hub

This command downloads and installs all five packages plus their dependencies. It may take 2-3 minutes depending on your internet connection.

What is each package for?

  • ultralytics provides the YOLOv8 object detection framework.
  • streamlit creates web apps with Python (no HTML or JavaScript needed).
  • opencv-python reads video frames and draws bounding boxes.
  • numpy handles numerical operations on image arrays.
  • huggingface-hub downloads pre-trained model files from HuggingFace.
  • Verify the packages installed correctly by running:
pip list | findstr ultralytics

You should see ultralytics followed by a version number (e.g. 8.0.0 or higher) in the output.

Seeing installation errors?

Make sure you see (venv) in your prompt before running pip install. If it is missing, your virtual environment is not active and packages will install globally.

If you see a "permission denied" error, do not use sudo. Instead, deactivate and reactivate your virtual environment, then try installing again.

Help me fix my pip install errors

Now create a requirements.txt file to record which packages your project depends on. This makes it easy to reinstall everything on a different machine.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Antigravity and press Enter to open it.
  • Open the fight-detection folder from your Desktop in Antigravity.
  • Create a new file called requirements.txt inside your fight-detection folder and add the following content:
ultralytics>=8.0.0
streamlit>=1.30.0
opencv-python>=4.8.0
numpy>=1.24.0
huggingface-hub>=0.20.0

What does this file do?

The >= syntax means "this version or newer". Anyone who clones your project can run pip install -r requirements.txt to install the exact same packages you are using.

  • Save requirements.txt.
  • In your terminal, confirm the file was created by running:
ls

You should see requirements.txt and the venv folder listed in the output.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete requirements.txt file:

ultralytics>=8.0.0
streamlit>=1.30.0
opencv-python>=4.8.0
numpy>=1.24.0
huggingface-hub>=0.20.0
Verify your webcam

Your fight detection system needs access to your webcam for live monitoring. A quick test now saves you from debugging camera issues later when the full app is running.

  • In Antigravity, create a new file called test_cam.py inside your fight-detection folder and add the following code:
import cv2

# Open the default webcam (device 0)
cap = cv2.VideoCapture(0)
ret, frame = cap.read()

if ret:
    # Display the captured frame in a window
    cv2.imshow("Webcam Test - Press any key to close", frame)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
else:
    print("ERROR: Could not access webcam")

# Release the camera resource
cap.release()

What does this code do?

  • cv2.VideoCapture(0) opens your default webcam (device index 0).
  • cap.read() captures a single frame from the camera.
  • cv2.imshow() displays the frame in a popup window so you can confirm the camera is working.
  • cv2.waitKey(0) keeps the window open until you press any key on your keyboard.
  • Save test_cam.py.
  • In your terminal, run the test script:
python test_cam.py

A window should appear showing a live image from your webcam. You should see yourself (or whatever the camera is pointed at) in the window titled "Webcam Test - Press any key to close".

  • Press any key on your keyboard to close the webcam window.

Seeing "ERROR: Could not access webcam"?

Make sure your webcam is connected and not being used by another application (like a video call). Close any other apps that might be using the camera and try again.

On Windows, check that your camera privacy settings allow Python to access the webcam. Go to Settings, then Privacy & Security, then Camera and make sure camera access is turned on.

Help me fix my webcam not working with OpenCV

Now that you have confirmed your webcam works, this test file is no longer needed.

  • Delete the test file by running:

macOS

rm test_cam.py

Windows

del test_cam.py
  • Confirm it is gone by running:
ls

You should only see requirements.txt and the venv folder. The test file is gone.

Your environment is ready to go. You have a clean project folder, an isolated virtual environment with all five packages installed, and a confirmed working webcam. Next up, you will build the violence detection engine that powers the entire system.

Build the Violence Detection Engine

Your environment is set up and your webcam is confirmed working.

Now you need a detection engine that can look at a video frame and decide whether violence is happening. In this step, you will build a Python class that downloads a pre-trained YOLOv8 model, runs inference on individual frames, draws bounding boxes around detected violence, and saves incident screenshots to disk.

In this step, get ready to:
  • Write a model download function that fetches pre-trained weights from HuggingFace.
  • Build a ViolenceDetector class that runs YOLOv8 inference and draws bounding boxes.
  • Add incident logging that saves timestamped screenshots of detected violence.
Create the detector file and model download function

The detection engine needs a pre-trained model to know what violence looks like. Instead of training your own model (which would require thousands of labeled images and a GPU), you will download weights that someone has already trained and published on HuggingFace.

  • Create a new file called detector.py in your fight-detection folder using Antigravity.
  • Add the imports and the model download function by pasting this code:
import cv2
import os
from datetime import datetime
from ultralytics import YOLO
from huggingface_hub import hf_hub_download


def download_model():
    """Download pre-trained violence detection model from HuggingFace."""
    print("Downloading violence detection model (first run only)...")
    model_path = hf_hub_download(
        repo_id="Musawer14/fight_detection_yolov8",
        filename="best.pt"
    )
    print(f"Model ready: {model_path}")
    return model_path

What does this code do?

The imports bring in the tools you need: OpenCV for image processing, YOLO for running the detection model, and hf_hub_download for fetching model files from HuggingFace.

The download_model() function calls hf_hub_download() to fetch a file called best.pt from the Musawer14/fight_detection_yolov8 repository. HuggingFace caches the file locally, so it only downloads once.

  • Save detector.py.

You won't see a change yet. The next chunk builds the class that uses this function.

Build the ViolenceDetector class

The ViolenceDetector class wraps everything into a reusable package. When you create an instance, it automatically downloads the model and loads it into memory.

  • In detector.py, add the class definition and __init__ method below the download_model() function by pasting this code:
class ViolenceDetector:
    def __init__(self, model_path=None):
        if model_path is None:
            model_path = download_model()
        self.model = YOLO(model_path)
        os.makedirs("incidents", exist_ok=True)

What does this code do?

  • If no model path is provided, __init__ calls download_model() to fetch the weights automatically.
  • self.model = YOLO(model_path) loads the YOLOv8 model into memory so it is ready to run predictions.
  • os.makedirs("incidents", exist_ok=True) creates an incidents/ folder if one does not already exist. This is where screenshots will be saved later.
  • Save detector.py.
  • Test the model download and loading by running this command in your terminal:
python -c "from detector import ViolenceDetector; d = ViolenceDetector(); print('Detector ready!')"

The first time you run this, HuggingFace downloads the model weights (about 6 MB). You should see "Downloading violence detection model (first run only)..." followed by "Model ready:" and then "Detector ready!" at the end.

Seeing a download error?

  • Make sure you have an active internet connection. The model downloads from HuggingFace servers.
  • If you see "ModuleNotFoundError", confirm your virtual environment is active. You should see (venv) at the start of your terminal prompt.
  • If you see "No module named detector", make sure your terminal is inside the fight-detection folder where detector.py lives.

Why is my detector failing to load?

Now you need the core detection logic. The detect() method takes a single video frame, runs it through the YOLOv8 model, filters out non-violence classes, and draws red bounding boxes around anything it flags as violent.

  • In detector.py, add the detect() method inside the ViolenceDetector class (indented one level under the class) by pasting this code below the __init__ method:
    def detect(self, frame, confidence=0.5):
        results = self.model(frame, conf=confidence, device="cpu", verbose=False)
        detections = []
        annotated_frame = frame.copy()

        for result in results:
            for box in result.boxes:
                conf = float(box.conf[0])
                cls = int(box.cls[0])
                class_name = result.names[cls]

                # Skip "no violence" or "no fight" class
                if "no" in class_name.lower():
                    continue

                x1, y1, x2, y2 = map(int, box.xyxy[0])
                detections.append({
                    "bbox": (x1, y1, x2, y2),
                    "confidence": conf,
                    "class": class_name
                })

                # Draw red bounding box
                cv2.rectangle(annotated_frame, (x1, y1), (x2, y2), (0, 0, 255), 3)
                label = f"{class_name}: {conf:.0%}"
                label_size = cv2.getTextSize(
                    label, cv2.FONT_HERSHEY_SIMPLEX, 0.8, 2
                )[0]
                cv2.rectangle(
                    annotated_frame,
                    (x1, y1 - label_size[1] - 10),
                    (x1 + label_size[0], y1),
                    (0, 0, 255),
                    -1
                )
                cv2.putText(
                    annotated_frame, label, (x1, y1 - 5),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2
                )

        return annotated_frame, detections

What does this code do?

  • self.model(frame, conf=confidence, device="cpu", verbose=False) runs YOLOv8 inference on the frame. The device="cpu" flag forces CPU mode so no GPU is needed.
  • The model has two classes: violence and no-violence. The if "no" in class_name.lower() check filters out safe detections so you only see actual violence alerts.
  • For each violent detection, the code extracts the bounding box coordinates and draws a red rectangle with a label showing the class name and confidence percentage.
  • The method returns both the annotated frame (with boxes drawn) and a list of detection dictionaries containing coordinates, confidence, and class.
  • Save detector.py.

You won't see a visible change yet. The next chunk adds incident saving, then you will test the full detection pipeline.

Add incident saving and test the detector

When the detector spots violence, you want a record of it. The save_incident() method captures a screenshot of the frame with a timestamp and stores it in the incidents/ folder.

  • In detector.py, add the save_incident() method below the detect() method (still inside the class, at the same indentation level) by pasting this code:
    def save_incident(self, frame, detections):
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"incidents/incident_{timestamp}.jpg"
        cv2.imwrite(filename, frame)
        return {
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "filename": filename,
            "num_detections": len(detections),
            "max_confidence": max(d["confidence"] for d in detections)
        }

What does this code do?

  • datetime.now().strftime("%Y%m%d_%H%M%S") creates a unique timestamp like 20260505_143022 so each incident file has a distinct name.
  • cv2.imwrite() saves the frame as a JPEG image in the incidents/ folder.
  • The method returns a dictionary with the timestamp, filename, number of detections, and highest confidence score. This dictionary powers the incident log in the dashboard you will build next.
  • Save detector.py.
  • Test the complete detector by running this command:
python -c "from detector import ViolenceDetector; import numpy as np; d = ViolenceDetector(); frame = np.zeros((480,640,3), dtype=np.uint8); result, dets = d.detect(frame); print(f'Detections on blank frame: {len(dets)}'); print('Detector working!')"

You should see "Detections on blank frame: 0" followed by "Detector working!" in your terminal. Zero detections is correct because a blank black frame has no violence in it.

Seeing a syntax or import error?

  • If you see "IndentationError", make sure the detect() and save_incident() methods are indented with exactly 4 spaces under class ViolenceDetector:.
  • If you see "AttributeError: 'NoneType'", check that your download_model() function has a return model_path line at the end.
  • Double check the full code using the tab below to compare against your file.

Help me debug my detector.py file.

Why YOLOv8 on CPU?

YOLOv8-nano is specifically designed to be lightweight. It runs fast enough for real-time detection even without a GPU. Heavier models like YOLOv8-large deliver more accuracy but require a dedicated GPU to run at usable speeds.

The nano variant hits the sweet spot for this project: you get reasonable accuracy at 5-10 frames per second on a standard laptop CPU.

✔️ Awesome, I've got everything!

Great. Double check you have saved detector.py and confirmed the test prints "Detector working!" in your terminal.

ⓧ I'd like to double check the full code

Here is the complete detector.py file. Compare it against yours to spot any differences.

import cv2
import os
from datetime import datetime
from ultralytics import YOLO
from huggingface_hub import hf_hub_download


def download_model():
    """Download pre-trained violence detection model from HuggingFace."""
    print("Downloading violence detection model (first run only)...")
    model_path = hf_hub_download(
        repo_id="Musawer14/fight_detection_yolov8",
        filename="best.pt"
    )
    print(f"Model ready: {model_path}")
    return model_path


class ViolenceDetector:
    def __init__(self, model_path=None):
        if model_path is None:
            model_path = download_model()
        self.model = YOLO(model_path)
        os.makedirs("incidents", exist_ok=True)

    def detect(self, frame, confidence=0.5):
        results = self.model(frame, conf=confidence, device="cpu", verbose=False)
        detections = []
        annotated_frame = frame.copy()

        for result in results:
            for box in result.boxes:
                conf = float(box.conf[0])
                cls = int(box.cls[0])
                class_name = result.names[cls]

                # Skip "no violence" or "no fight" class
                if "no" in class_name.lower():
                    continue

                x1, y1, x2, y2 = map(int, box.xyxy[0])
                detections.append({
                    "bbox": (x1, y1, x2, y2),
                    "confidence": conf,
                    "class": class_name
                })

                # Draw red bounding box
                cv2.rectangle(annotated_frame, (x1, y1), (x2, y2), (0, 0, 255), 3)
                label = f"{class_name}: {conf:.0%}"
                label_size = cv2.getTextSize(
                    label, cv2.FONT_HERSHEY_SIMPLEX, 0.8, 2
                )[0]
                cv2.rectangle(
                    annotated_frame,
                    (x1, y1 - label_size[1] - 10),
                    (x1 + label_size[0], y1),
                    (0, 0, 255),
                    -1
                )
                cv2.putText(
                    annotated_frame, label, (x1, y1 - 5),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2
                )

        return annotated_frame, detections

    def save_incident(self, frame, detections):
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"incidents/incident_{timestamp}.jpg"
        cv2.imwrite(filename, frame)
        return {
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "filename": filename,
            "num_detections": len(detections),
            "max_confidence": max(d["confidence"] for d in detections)
        }

Your detection engine is complete. Next up, you will wrap it in a Streamlit web dashboard so you can upload videos and watch the detector analyze them frame by frame in your browser.

Create the Streamlit Dashboard with Video Upload Mode

Your detection engine is built and ready to analyze video frames. Now you need a way for users to actually interact with it.

Streamlit lets you build a professional web dashboard with zero frontend code. In this step, you will create a browser-based interface where users can upload a video file and watch the detection engine analyze it frame by frame.

In this step, get ready to:
  • Set up the Streamlit page configuration and sidebar controls.
  • Build a video upload and frame-by-frame analysis pipeline.
  • Run and test the dashboard in your browser.
Set up the Streamlit app shell

Every Streamlit app starts with page configuration and layout elements. You will set up the page title, a sidebar with detection controls, and the model loading logic that persists across browser refreshes.

  • Create a new file called app.py in your fight-detection folder.
  • Add the imports, page configuration, title, and sidebar controls by pasting this code:
import cv2
import os
import time
import streamlit as st
from detector import ViolenceDetector

st.set_page_config(
    page_title="Fight Detection System",
    page_icon="\ud83d\udea8",
    layout="wide"
)

st.title("\ud83d\udea8 Real-Time Fight Detection System")
st.caption("AI-powered anomaly detection for surveillance using YOLOv8")

# Sidebar configuration
st.sidebar.header("\u2699\ufe0f Configuration")
confidence = st.sidebar.slider(
    "Detection Confidence Threshold",
    min_value=0.25,
    max_value=0.95,
    value=0.50,
    step=0.05
)
mode = st.sidebar.radio("Select Input Mode", ["\ud83d\udcf9 Upload Video", "\ud83d\udcf7 Live Webcam"])

What does this code do?

This sets up the skeleton of your web app.

  • st.set_page_config() configures the browser tab title, icon, and uses the full width of the screen.
  • st.sidebar.slider() creates a draggable slider that controls how confident the model needs to be before flagging violence.
  • st.sidebar.radio() gives users a toggle between uploading a video file and using their webcam.
  • Below the sidebar code in app.py, add the model loading function and session state initialization:

@st.cache_resource
def load_model():
    return ViolenceDetector()


detector = load_model()

# Initialize session state for incidents
if "incidents" not in st.session_state:
    st.session_state.incidents = []

What does this code do?

  • @st.cache_resource tells Streamlit to load the model only once and reuse it across page refreshes. Without this, the model would reload on every interaction.
  • st.session_state.incidents is a persistent list that accumulates incident records as the app runs. It survives Streamlit reruns so your log does not reset.
  • Save app.py.

Seeing a red error about imports?

  • Make sure your virtual environment is activated. You should see (venv) at the start of your terminal prompt.
  • Confirm detector.py is in the same folder as app.py. The import from detector import ViolenceDetector looks for a file named detector.py in the current directory.

Help me fix my Streamlit import error

Build the video upload mode

The upload mode lets users drag in a video file and watch the detection engine process it frame by frame. You will use OpenCV to read frames from the uploaded video, run each one through your detector, and display results live in the browser.

  • In app.py, add the video upload mode logic below the session state initialization:
if mode == "\ud83d\udcf9 Upload Video":
    st.header("Upload Video for Analysis")
    uploaded_file = st.file_uploader(
        "Choose a video file", type=["mp4", "avi", "mov"]
    )

    if uploaded_file is not None:
        temp_path = "temp_upload.mp4"
        with open(temp_path, "wb") as f:
            f.write(uploaded_file.read())

        st.success("Video uploaded successfully!")

        if st.button("\ud83d\udd0d Analyze Video"):
            cap = cv2.VideoCapture(temp_path)
            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

            progress_bar = st.progress(0)
            frame_display = st.empty()
            alert_display = st.empty()

            frame_count = 0
            incident_count = 0

            while cap.isOpened():
                ret, frame = cap.read()
                if not ret:
                    break

                frame_count += 1
                progress_bar.progress(frame_count / max(total_frames, 1))

                if frame_count % 3 != 0:
                    continue

                annotated_frame, detections = detector.detect(frame, confidence)

                if detections:
                    incident_count += 1
                    max_conf = max(d["confidence"] for d in detections)
                    alert_display.error(
                        f"\u26a0\ufe0f VIOLENCE DETECTED! Confidence: {max_conf:.0%}"
                    )
                    if incident_count % 10 == 1:
                        incident = detector.save_incident(frame, detections)
                        st.session_state.incidents.append(incident)
                else:
                    alert_display.success("\u2705 Scene is safe - No anomaly detected")

                display_frame = cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB)
                frame_display.image(
                    display_frame, channels="RGB", use_container_width=True
                )
                time.sleep(0.01)

            cap.release()
            if os.path.exists(temp_path):
                os.remove(temp_path)

            st.balloons()
            st.success(
                f"Analysis complete! Processed {frame_count} frames. "
                f"Detected {incident_count} violent frames."
            )

What does this code do?

This is the core analysis pipeline. Here is what happens when a user uploads a video and clicks Analyze:

  • The uploaded file is saved temporarily as temp_upload.mp4, then opened with OpenCV's VideoCapture.
  • frame_count % 3 != 0 skips two out of every three frames. This keeps CPU inference fast without missing important action.
  • When violence is detected, the alert turns red with a confidence percentage. Every 10th violent frame, a screenshot is saved to the incidents log.
  • st.empty() creates a placeholder that gets overwritten each loop iteration, creating a live video effect in the browser.
  • After processing, the temp file is deleted and a summary is shown.
  • Save app.py.

Why skip frames?

Running YOLOv8 inference on CPU takes roughly 100-200ms per frame. Processing every single frame of a 30 FPS video would make the analysis painfully slow. Skipping to every 3rd frame keeps things moving while still catching violent actions that span multiple frames.

Seeing a syntax error?

  • Check that the if mode == line is at the top level of the file (no indentation). It should not be inside the load_model() function.
  • Make sure you have matching quotes on all f-strings. The emoji characters inside strings can sometimes cause copy-paste issues.

Help me fix my app.py syntax error

Run and test your dashboard

Streamlit apps run as a local web server. One terminal command spins up the dashboard in your browser.

  • Make sure your terminal is in the fight-detection folder with the virtual environment active.
  • Launch the Streamlit app by running this command:
streamlit run app.py
  • Your browser should automatically open to http://localhost:8501. You should see the app title, a sidebar with the confidence slider and mode selector, and a file upload area.
  • Upload any short video file (MP4, AVI, or MOV) and click the Analyze Video button.
  • Confirm the progress bar advances and video frames display in the browser.

What should I see?

The first time you run the app, the model downloads from HuggingFace (this takes a minute). After that, you should see frames appearing one by one in the browser with either a green "Scene is safe" banner or a red "VIOLENCE DETECTED" alert depending on the video content.

Browser did not open automatically?

  • Copy http://localhost:8501 from your terminal output and paste it into your browser manually.
  • If you see ModuleNotFoundError, confirm your virtual environment is active (you should see (venv) in your terminal prompt).
  • If the page loads but shows a blank screen, check your terminal for Python errors and fix any typos in app.py.

Help me get my Streamlit app running

✔️ Awesome, I've got everything!

Great. Double check you have saved app.py and your Streamlit app is running in the browser.

ⓧ I'd like to double check the full code

Here is the complete app.py file at this point:

import cv2
import os
import time
import streamlit as st
from detector import ViolenceDetector

st.set_page_config(
    page_title="Fight Detection System",
    page_icon="\ud83d\udea8",
    layout="wide"
)

st.title("\ud83d\udea8 Real-Time Fight Detection System")
st.caption("AI-powered anomaly detection for surveillance using YOLOv8")

# Sidebar configuration
st.sidebar.header("\u2699\ufe0f Configuration")
confidence = st.sidebar.slider(
    "Detection Confidence Threshold",
    min_value=0.25,
    max_value=0.95,
    value=0.50,
    step=0.05
)
mode = st.sidebar.radio("Select Input Mode", ["\ud83d\udcf9 Upload Video", "\ud83d\udcf7 Live Webcam"])


@st.cache_resource
def load_model():
    return ViolenceDetector()


detector = load_model()

# Initialize session state for incidents
if "incidents" not in st.session_state:
    st.session_state.incidents = []

if mode == "\ud83d\udcf9 Upload Video":
    st.header("Upload Video for Analysis")
    uploaded_file = st.file_uploader(
        "Choose a video file", type=["mp4", "avi", "mov"]
    )

    if uploaded_file is not None:
        temp_path = "temp_upload.mp4"
        with open(temp_path, "wb") as f:
            f.write(uploaded_file.read())

        st.success("Video uploaded successfully!")

        if st.button("\ud83d\udd0d Analyze Video"):
            cap = cv2.VideoCapture(temp_path)
            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

            progress_bar = st.progress(0)
            frame_display = st.empty()
            alert_display = st.empty()

            frame_count = 0
            incident_count = 0

            while cap.isOpened():
                ret, frame = cap.read()
                if not ret:
                    break

                frame_count += 1
                progress_bar.progress(frame_count / max(total_frames, 1))

                if frame_count % 3 != 0:
                    continue

                annotated_frame, detections = detector.detect(frame, confidence)

                if detections:
                    incident_count += 1
                    max_conf = max(d["confidence"] for d in detections)
                    alert_display.error(
                        f"\u26a0\ufe0f VIOLENCE DETECTED! Confidence: {max_conf:.0%}"
                    )
                    if incident_count % 10 == 1:
                        incident = detector.save_incident(frame, detections)
                        st.session_state.incidents.append(incident)
                else:
                    alert_display.success("\u2705 Scene is safe - No anomaly detected")

                display_frame = cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB)
                frame_display.image(
                    display_frame, channels="RGB", use_container_width=True
                )
                time.sleep(0.01)

            cap.release()
            if os.path.exists(temp_path):
                os.remove(temp_path)

            st.balloons()
            st.success(
                f"Analysis complete! Processed {frame_count} frames. "
                f"Detected {incident_count} violent frames."
            )

Your dashboard is live and analyzing uploaded videos. Next up, you will add live webcam monitoring and an incident log that records every detected event with timestamps.

Add Live Webcam Monitoring and Incident Log

Your video upload mode is working. You can feed a recorded video into the detection engine and watch it analyze frames in real time.

A real surveillance system needs to process a live camera feed, not just uploaded recordings. In this step, you will add a second mode that streams your webcam directly through the detector. You will also build an incident log that saves and displays evidence whenever violence is detected.

In this step, get ready to:
  • Add live webcam monitoring with a start/stop checkbox.
  • Build an incident log that displays saved screenshots with detection details.
  • Test the complete app with both video upload and webcam modes.
Add the live webcam monitoring mode

The webcam mode uses a Streamlit checkbox as a start/stop toggle. When checked, it opens the webcam and processes every frame through the detector in a continuous loop.

  • In app.py, find the end of the if mode == "📹 Upload Video": block (the last line inside it is the st.success() call after st.balloons()).
  • Add the following webcam mode code directly below the upload block, at the same indentation level as the original if statement:
elif mode == "📷 Live Webcam":
    st.header("Live Webcam Monitoring")
    st.info("Check the box below to start monitoring. Uncheck to stop.")

    # Create a checkbox toggle and placeholder containers
    run = st.checkbox("Start Monitoring")
    frame_display = st.empty()
    alert_display = st.empty()
    metrics_display = st.empty()

    if run:
        cap = cv2.VideoCapture(0)

        if not cap.isOpened():
            st.error(
                "Could not access webcam. "
                "Please check your camera connection."
            )
        else:
            incident_count = 0
            frame_count = 0

            while cap.isOpened():
                ret, frame = cap.read()
                if not ret:
                    st.error("Failed to read from webcam.")
                    break

                frame_count += 1
                annotated_frame, detections = detector.detect(frame, confidence)

                if detections:
                    incident_count += 1
                    max_conf = max(d["confidence"] for d in detections)
                    alert_display.error(
                        f"⚠️ VIOLENCE DETECTED! Confidence: {max_conf:.0%}"
                    )
                    # Save every 30th violent frame to avoid flooding disk
                    if incident_count % 30 == 1:
                        incident = detector.save_incident(frame, detections)
                        st.session_state.incidents.append(incident)
                else:
                    alert_display.success("✅ Scene is safe - No anomaly detected")

                # Convert BGR to RGB for Streamlit display
                display_frame = cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB)
                frame_display.image(
                    display_frame, channels="RGB", use_container_width=True
                )
                metrics_display.caption(
                    f"Frames: {frame_count} | Incidents: {incident_count}"
                )

            cap.release()

What does this code do?

The checkbox acts as a start/stop switch. When checked, Streamlit keeps the script running in the while loop. When unchecked, Streamlit triggers a rerun that exits the loop.

  • cv2.VideoCapture(0) opens your default webcam.
  • Every frame passes through detector.detect() with the sidebar confidence threshold.
  • incident_count % 30 == 1 saves a screenshot only every 30th violent frame so the incidents/ folder does not fill up with near-identical images.
  • metrics_display.caption() shows a running tally of frames processed and incidents detected.
  • Save app.py.

Seeing an indentation error?

The elif must start at the same indentation level as the original if mode == "📹 Upload Video": line. If it is indented further, Python treats it as nested inside the upload block.

help me fix an indentation error in my elif block for the webcam mode

Build the incident log section

Both modes save incident records to st.session_state.incidents. You need a dedicated section at the bottom of the page that displays these records as a visual timeline.

  • In app.py, scroll to the very bottom of the file (below the elif block you just added).
  • Add the incident log code at the top level (no indentation inside the if/elif blocks). This section runs regardless of which mode is active:
# Incident Log Section
st.divider()
st.header("📋 Incident Log")

if st.session_state.incidents:
    for incident in reversed(st.session_state.incidents):
        col1, col2 = st.columns([1, 3])
        with col1:
            if os.path.exists(incident["filename"]):
                st.image(incident["filename"], width=200)
        with col2:
            st.write(f"**Time:** {incident['timestamp']}")
            st.write(f"**Max Confidence:** {incident['max_confidence']:.0%}")
            st.write(f"**Detections in frame:** {incident['num_detections']}")
        st.divider()
else:
    st.info("No incidents recorded yet. Upload a video or start webcam monitoring.")

What does this code do?

  • st.divider() draws a horizontal line separating the incident log from the video/webcam area above.
  • reversed(st.session_state.incidents) shows the newest incident first.
  • st.columns([1, 3]) creates a two-column layout. The left column (narrow) shows the screenshot thumbnail. The right column (wide) shows metadata.
  • The os.path.exists() check prevents errors if a screenshot file was manually deleted.
  • Save app.py.

Incident log not appearing?

Make sure the incident log code is not indented inside the elif block. It should be at the same level as the if mode == "📹 Upload Video": line (column 0). If it is nested inside the webcam mode, it only renders when that mode is selected.

help me position the incident log code correctly

Test both modes end to end
  • Run the app by executing this command in your terminal:
streamlit run app.py
  • In the sidebar, select 📷 Live Webcam.
  • Check the Start Monitoring checkbox.
  • You should see your live webcam feed displayed in the browser with a green "Scene is safe" alert below it.
  • Try waving your fists or playing a fight video on your phone in front of the webcam to trigger detections.
  • Adjust the Detection Confidence Threshold slider in the sidebar. Lowering it makes the model more sensitive (more detections). Raising it makes it stricter (fewer false positives).
  • Uncheck Start Monitoring to stop the webcam feed.
  • Scroll down to the Incident Log section. If any violence was detected, you should see timestamped screenshot thumbnails with confidence scores.

No violence to test with?

Search for a short fight clip on YouTube and play it on your phone screen in front of your webcam. The model detects violence in the video displayed to the camera, so this is an easy way to confirm detections work.

Webcam not opening?

If you see the "Could not access webcam" error, check that no other application (Zoom, Teams, another browser tab) is using your camera. Only one program can access the webcam at a time.

On Windows, also confirm your browser has camera permissions. Go to Settings and search for "Camera" to verify access is allowed.

help me troubleshoot why my webcam is not opening in Streamlit

✔️ Awesome, I've got everything!

Great. Double check you have saved app.py and that both modes are working.

ⓧ I'd like to double check the full code

Here is the complete app.py file. Compare it with yours to spot any differences.

import cv2
import os
import time
import streamlit as st
from detector import ViolenceDetector

st.set_page_config(
    page_title="Fight Detection System",
    page_icon="🚨",
    layout="wide"
)

st.title("🚨 Real-Time Fight Detection System")
st.caption("AI-powered anomaly detection for surveillance using YOLOv8")

# Sidebar configuration
st.sidebar.header("⚙️ Configuration")
confidence = st.sidebar.slider(
    "Detection Confidence Threshold",
    min_value=0.25,
    max_value=0.95,
    value=0.50,
    step=0.05
)
mode = st.sidebar.radio("Select Input Mode", ["📹 Upload Video", "📷 Live Webcam"])


@st.cache_resource
def load_model():
    return ViolenceDetector()


detector = load_model()

# Initialize session state for incidents
if "incidents" not in st.session_state:
    st.session_state.incidents = []

if mode == "📹 Upload Video":
    st.header("Upload Video for Analysis")
    uploaded_file = st.file_uploader(
        "Choose a video file", type=["mp4", "avi", "mov"]
    )

    if uploaded_file is not None:
        temp_path = "temp_upload.mp4"
        with open(temp_path, "wb") as f:
            f.write(uploaded_file.read())

        st.success("Video uploaded successfully!")

        if st.button("🔍 Analyze Video"):
            cap = cv2.VideoCapture(temp_path)
            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

            progress_bar = st.progress(0)
            frame_display = st.empty()
            alert_display = st.empty()

            frame_count = 0
            incident_count = 0

            while cap.isOpened():
                ret, frame = cap.read()
                if not ret:
                    break

                frame_count += 1
                progress_bar.progress(frame_count / max(total_frames, 1))

                if frame_count % 3 != 0:
                    continue

                annotated_frame, detections = detector.detect(frame, confidence)

                if detections:
                    incident_count += 1
                    max_conf = max(d["confidence"] for d in detections)
                    alert_display.error(
                        f"⚠️ VIOLENCE DETECTED! Confidence: {max_conf:.0%}"
                    )
                    if incident_count % 10 == 1:
                        incident = detector.save_incident(frame, detections)
                        st.session_state.incidents.append(incident)
                else:
                    alert_display.success("✅ Scene is safe - No anomaly detected")

                display_frame = cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB)
                frame_display.image(
                    display_frame, channels="RGB", use_container_width=True
                )
                time.sleep(0.01)

            cap.release()
            if os.path.exists(temp_path):
                os.remove(temp_path)

            st.balloons()
            st.success(
                f"Analysis complete! Processed {frame_count} frames. "
                f"Detected {incident_count} violent frames."
            )

elif mode == "📷 Live Webcam":
    st.header("Live Webcam Monitoring")
    st.info("Check the box below to start monitoring. Uncheck to stop.")

    run = st.checkbox("Start Monitoring")
    frame_display = st.empty()
    alert_display = st.empty()
    metrics_display = st.empty()

    if run:
        cap = cv2.VideoCapture(0)

        if not cap.isOpened():
            st.error(
                "Could not access webcam. "
                "Please check your camera connection."
            )
        else:
            incident_count = 0
            frame_count = 0

            while cap.isOpened():
                ret, frame = cap.read()
                if not ret:
                    st.error("Failed to read from webcam.")
                    break

                frame_count += 1
                annotated_frame, detections = detector.detect(frame, confidence)

                if detections:
                    incident_count += 1
                    max_conf = max(d["confidence"] for d in detections)
                    alert_display.error(
                        f"⚠️ VIOLENCE DETECTED! Confidence: {max_conf:.0%}"
                    )
                    if incident_count % 30 == 1:
                        incident = detector.save_incident(frame, detections)
                        st.session_state.incidents.append(incident)
                else:
                    alert_display.success("✅ Scene is safe - No anomaly detected")

                display_frame = cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB)
                frame_display.image(
                    display_frame, channels="RGB", use_container_width=True
                )
                metrics_display.caption(
                    f"Frames: {frame_count} | Incidents: {incident_count}"
                )

            cap.release()

# Incident Log Section
st.divider()
st.header("📋 Incident Log")

if st.session_state.incidents:
    for incident in reversed(st.session_state.incidents):
        col1, col2 = st.columns([1, 3])
        with col1:
            if os.path.exists(incident["filename"]):
                st.image(incident["filename"], width=200)
        with col2:
            st.write(f"**Time:** {incident['timestamp']}")
            st.write(f"**Max Confidence:** {incident['max_confidence']:.0%}")
            st.write(f"**Detections in frame:** {incident['num_detections']}")
        st.divider()
else:
    st.info("No incidents recorded yet. Upload a video or start webcam monitoring.")

Your surveillance system is fully functional. It can process uploaded videos, monitor live webcam feeds, and keep a running log of every violent incident it detects. Next up, you will extend it with a statistics dashboard and CSV export in the Secret Mission.

Secret mission

Add a Statistics Dashboard with Charts and CSV Export

Your fight detection system catches incidents in real-time. But what if you could visualize detection patterns over time and export a full incident report? Build a statistics dashboard that turns raw detections into actionable charts and downloadable CSV data.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local machine, so there are no ongoing cloud costs. Decide whether to keep your resources as they are, pause them to free up disk space, or delete them entirely.

Resources you used:

  • The fight-detection project folder with all Python files.
  • A Python virtual environment (venv folder) inside your project.
  • Installed pip packages (ultralytics, streamlit, opencv-python, numpy, huggingface-hub).
  • Downloaded YOLOv8 model weights cached by huggingface_hub on your system.
  • The incidents/ folder with any saved incident screenshots.

Keep everything running

No action needed. Choose this if you plan to keep experimenting with the fight detection system or want to demo it soon.

  • Your project folder, virtual environment, and cached model weights stay as they are.
  • You can relaunch the app anytime by activating the virtual environment and running streamlit run app.py.

Pause - I'll come back to this later

Free up disk space by removing the cached model and incident screenshots while keeping your code intact.

  • Delete the incidents/ folder inside your project to remove saved screenshots.
  • Clear the HuggingFace cache to remove the downloaded model weights. The default cache location on Windows is C:\Users\YourName\.cache\huggingface.
  • Your code files and virtual environment remain ready for when you return.
  • When you come back, the model downloads again automatically on first run.

Delete - I don't want to use this again

Remove all project resources completely and start fresh.

  • Delete the entire fight-detection folder from your Desktop. This removes all code, the virtual environment, and the incidents folder.
  • Clear the HuggingFace cache at C:\Users\YourName\.cache\huggingface to remove the downloaded model weights.
  • Optionally, uninstall Python packages globally if you installed them outside the virtual environment.

That's a Wrap!

That's a Wrap!

Nice work! You just built a complete real-time fight detection surveillance system using YOLOv8 and Streamlit. You now have a working computer vision application that processes video, detects violence, and logs incidents automatically.

Here's what you learned:

  • Built a violence detection engine using a pre-trained YOLOv8 model downloaded from HuggingFace, running inference entirely on CPU.
  • Created a professional Streamlit web dashboard with two input modes: video file upload and live webcam monitoring.
  • Implemented an automated incident logging system that captures timestamped screenshots and displays them with confidence scores and detection counts.
  • Secret Mission: Added a statistics dashboard with time-series charts tracking detections over time and a CSV export for incident reports.

Ready to quiz yourself?