Skip to content

About

Local YOLO camera review queue for visible firearms. Optional Grok crop review. Not a lock-down system.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Camera Weapon Review

Camera Weapon Review is a local review queue for visible firearms and knives in video files, webcam feeds, and RTSP camera streams.

A YOLO model scans each frame. When the same object class appears in several nearby frames, the app saves a cropped image and can ask Grok whether the object appears to be a real weapon. You then confirm or dismiss the event in a web interface that is available only on the local machine by default.

This is a review aid, not a security or lockdown system. It cannot detect concealed weapons, and it may mistake phones, tools, umbrellas, and other objects for weapons. A person must make the final decision.

Demo

Watch detecting a close pistol, then a street sample

30-second screen recording. Click for the full MP4.

Requirements

  • Python 3.11+
  • ffmpeg
  • An NVIDIA GPU is optional; CPU processing also works but is slower.
  • An xAI API key is optional and enables Grok to review cropped images. A SuperGrok chat subscription does not include API access.

Ultralytics YOLO is licensed under AGPL-3.0. The code in this repository is licensed under MIT.

Setup

python3 -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
cp .env.example .env
pytest
python -m camera_weapon_detect serve

Open http://127.0.0.1:8080, then upload a clip or select a configured camera. See Run for command-line examples.

Docker on a home server

The included Compose configuration runs the review interface as an unprivileged user, restarts it after a reboot, and keeps events and model weights on the host. CPU inference is the default.

Start the service

Install Docker Engine with the Compose plugin, then run:

cp .env.example .env
mkdir -p data models

# Generate a password, then paste it after AUTH_PASSWORD= in .env.
openssl rand -base64 24

# Change PUID and PGID in .env if these commands do not print 1000.
id -u
id -g

docker compose up --detach --build
docker compose logs --follow

The first build is large because Ultralytics installs PyTorch. The first detection run may also download default YOLO weights. Compose refuses to start if AUTH_PASSWORD is missing or shorter than 16 characters. Run docker compose ps to check the service; when it reports healthy, open http://127.0.0.1:8080 on the server and sign in with AUTH_USERNAME and AUTH_PASSWORD.

The default port mapping is deliberately restricted to the server's loopback address. To use the interface from another computer without exposing it on the network, create an SSH tunnel:

ssh -L 8080:127.0.0.1:8080 user@home-server

Then open http://127.0.0.1:8080 on that computer. Alternatively, set HOST in .env to the home server's specific LAN or Tailscale address and add that exact address or hostname to the JSON ALLOWED_HOSTS list. Authentication is enabled in Docker, but HTTP Basic credentials are not encrypted in transit. Use an SSH tunnel, Tailscale, or a TLS reverse proxy; do not publish this service directly to the internet.

Compose stores application data in ./data and detector weights in ./models. Back up data/events.sqlite and data/snapshots/ if the review history matters. Stop or update the service with:

docker compose down

git pull
docker compose up --detach --build

docker compose down does not remove the bind-mounted data or model directories.

RTSP and camera VLANs

Set the camera values in .env, then restart with docker compose up --detach. The container uses the home server's routed network access, so the server must be allowed to initiate RTSP connections to the camera VLAN. No inbound connection from the camera VLAN to the container is required.

Test connectivity from inside the running container:

docker compose exec camera-review \
  ffprobe -rtsp_transport tcp \
  -i "rtsp://USER:PASSWORD@CAMERA_VLAN_IP:554/h264Preview_01_sub"

Linux webcam

For a directly attached webcam, find its device and group IDs:

ls -l /dev/video*
stat -c '%g' /dev/video0

Set VIDEO_GID in .env, then uncomment the devices and group_add blocks in compose.yaml. Rebuild and restart the service. Device paths and group IDs vary by host; RTSP cameras do not need this mapping.

NVIDIA GPU

Install the host's NVIDIA driver and the NVIDIA Container Toolkit. Start Compose with the GPU override:

docker compose -f compose.yaml -f compose.gpu.yaml up --detach --build

Confirm that PyTorch can see the GPU:

docker compose exec camera-review \
  python -c "import torch; available = torch.cuda.is_available(); print('CUDA available:', available); print(torch.cuda.get_device_name(0) if available else 'CPU only')"

If this reports False, the service still runs on the CPU. GPU availability depends on the host driver, container toolkit, and PyTorch build.

Detector weights

The app chooses detector weights in the following order:

  1. The path set in WEAPON_MODEL.
  2. models/firearm_yolov8s_ft.pt, if it exists.
  3. models/firearm_yolov8n.pt, if it exists.
  4. Open-vocabulary YOLO-World, using the classes firearm, handgun, rifle, and knife.

YOLO-World is the default when no local weights are available, but it is relatively weak at detecting pistols. To install the dedicated nano firearm checkpoint used by the third option:

mkdir -p models
python - <<'PY'
from huggingface_hub import hf_hub_download
from pathlib import Path
import shutil
src = hf_hub_download(
    repo_id="Subh775/Firearm_Detection_Yolov8n",
    filename="weights/best.pt",
)
dest = Path("models/firearm_yolov8n.pt")
shutil.copy2(src, dest)
print(dest)
PY

The app finds this file automatically. To select it explicitly, calculate its checksum and set both values in .env:

WEAPON_MODEL=models/firearm_yolov8n.pt
WEAPON_MODEL_SHA256=replace-with-output-from-sha256sum
sha256sum models/firearm_yolov8n.pt

After you have reviewed enough events, the export-train and train commands create models/firearm_yolov8s_ft.pt. The app also finds that file automatically, or you can select it explicitly with WEAPON_MODEL. Before running that model in Docker, set WEAPON_MODEL_SHA256 to the output of sha256sum models/firearm_yolov8s_ft.pt.

Ultralytics includes huggingface_hub. If the import fails, install it with pip install huggingface_hub.

Low-spec alternatives

Nano models are the best starting point for CPU-only computers and low-power GPUs. The following community checkpoints use labels that this app accepts:

Checkpoint Architecture Classes Approximate weight size Notes
Subh775/Firearm_Detection_Yolov8n YOLOv8n Gun 6.2 MB The documented fallback above. Detects firearms but not knives. Its model card and repository metadata list different licenses; clarify the applicable terms before redistribution or commercial use.
cosgun99/gun-knife-yolo11n YOLO11n gun, knife 5.5 MB Covers both target categories and has fewer parameters and FLOPs than YOLOv8n, but it has not been validated by this project.

To try the YOLO11n checkpoint:

python - <<'PY'
from huggingface_hub import hf_hub_download
from pathlib import Path
import shutil

src = hf_hub_download(
    repo_id="cosgun99/gun-knife-yolo11n",
    filename="best.pt",
)
dest = Path("models/gun-knife-yolo11n.pt")
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dest)
print(dest)
PY

Then calculate the file's SHA-256 digest and set WEAPON_MODEL=models/gun-knife-yolo11n.pt and WEAPON_MODEL_SHA256=... in .env. Docker requires this checksum for explicitly selected weights.

These are third-party checkpoints, not interchangeable accuracy tiers. Their published metrics use different datasets and do not predict performance on a particular camera. Test each model on representative day, night, indoor, outdoor, and partially occluded footage, and compare missed detections and false positives before choosing one. Only load .pt files from sources you trust, and review both the checkpoint license and the underlying Ultralytics license for your use case.

Reducing IMGSZ from 640 can also lower inference cost, but it makes small or distant weapons harder to detect. Treat that setting as a measured speed-versus-recall tradeoff rather than a general optimization.

Configure

Edit .env to change the defaults. Never put real credentials or secrets in .env.example.

Variable Purpose
XAI_API_KEY Enables optional Grok review. Leave it empty to skip cloud review.
WEAPON_MODEL Path to custom .pt detector weights. Leave it empty to use the automatic selection order above.
WEAPON_MODEL_SHA256 SHA-256 digest for selected local weights. Docker requires it for WEAPON_MODEL and auto-selected fine-tuned or nano weights.
REQUIRE_LOCAL_MODEL_CHECKSUM Reject selected local weights without a checksum. Compose enforces this as true; native runs default to false.
CONFIDENCE YOLO threshold (default 0.45).
HOST / PORT UI bind. Default 127.0.0.1:8080.
AUTH_USERNAME / AUTH_PASSWORD Review UI login. Docker requires a password of at least 16 characters.
ALLOWED_HOSTS JSON list of accepted HTTP hostnames or addresses.
MAX_UPLOAD_BYTES Maximum decoded upload body size; default 100 MiB.
MAX_CONCURRENT_UPLOADS Maximum uploads being parsed or processed concurrently; default 1.
MAX_PARALLEL_JOBS Maximum concurrent inference jobs; default 1.
CAMERA_HOST Optional camera hostname or LAN IP. Leave it empty to disable live RTSP.
CAMERA_USER / CAMERA_PASSWORD Camera login.
CAMERA_PATH RTSP path. Reolink substream example: /h264Preview_01_sub.
CAMERA_URL Full rtsp://... URL. Overrides the CAMERA_* pieces if set.

The interface lists a camera source only when CAMERA_HOST or CAMERA_URL is set. Camera passwords are removed from stored event sources and API responses. Avoid putting a credentialed RTSP URL directly on a command line because it can be retained in shell history or visible to other local processes.

Run

Start the review interface:

python -m camera_weapon_detect serve

Open http://127.0.0.1:8080. From there, you can upload a clip or watch a configured camera and then confirm or dismiss detected events.

You can also process a source directly from the command line:

# Local video file
python -m camera_weapon_detect process path/to/clip.mp4

# Webcam at device index 0
python -m camera_weapon_detect process 0

# Camera configured in .env (keeps credentials out of shell history)
python -m camera_weapon_detect process camera

Place reusable sample videos in data/clips/, which is excluded from Git. Supported file extensions are .mp4, .mkv, .webm, .avi, and .mov.

Cameras and RTSP

Tested sources

Source Notes
Reolink Lumus Requires RTSP to be enabled on the camera and uses digest authentication. Testing used the sub stream (/h264Preview_01_sub, 640x360); a main stream is also available.
Local video files .mp4 / .mkv / .webm / .avi / .mov in data/clips/ or via upload.
Webcam Device index 0 (python -m camera_weapon_detect process 0).

Other camera brands may work if their RTSP URLs open in VLC or ffprobe. The project does not currently have a compatibility matrix beyond the Reolink Lumus, local files, and a webcam.

Do not expose RTSP (TCP/UDP port 554) to the public internet. Use the local network or Tailscale.

Recommended VLAN layout

Place cameras on a dedicated camera or IoT VLAN rather than on the trusted user network. The machine running this app can be on that VLAN or on a separate trusted VLAN with narrowly scoped firewall access to the cameras.

For a typical routed setup:

  • Allow the detection machine to initiate only the traffic required to reach each camera, including RTSP. Prefer RTSP over TCP when the camera supports it because the firewall rules are easier to control.
  • Allow camera administration only from a designated management device or VLAN.
  • Block cameras from initiating connections to trusted user networks.
  • Block internet access from the camera VLAN unless a required camera feature or controlled update process depends on it.
  • Give cameras fixed DHCP reservations so that firewall rules and CAMERA_HOST values remain stable.

The exact rules depend on the router and camera. Test the RTSP stream from the detection machine after applying the VLAN firewall policy; discovery and mobile-app features may require additional, vendor-specific rules.

URL format

rtsp://USERNAME:PASSWORD@HOST:554/PATH

Set the complete URL as CAMERA_URL, or let the app build it from the following values:

CAMERA_HOST=192.168.1.50          # example LAN IP, not a real host
CAMERA_USER=admin
CAMERA_PASSWORD=your-camera-login
CAMERA_PATH=/h264Preview_01_sub

Reolink standalone cameras (including Lumus):

Stream Path Use
Sub /h264Preview_01_sub Preferred for detection because it is smaller and generally more stable.
Main /h264Preview_01_main Higher resolution, with more load on both the CPU and the camera.

Reolink's documentation also lists /Preview_01_sub and /Preview_01_main without the h264 prefix. If one path returns a 401 response or shows no video, try the other format. Channel 01 identifies camera 1; an NVR uses 02, 03, and so on for additional channels.

For an NVR or Home Hub, use the NVR's username and password in the URL rather than the credentials for an individual camera. See Reolink: Introduction to RTSP.

How to find your camera's RTSP feed

  1. Enable RTSP. In the Reolink app or Windows client, open device settings > Network > Advanced > Server settings (sometimes called Port settings). Enable RTSP on its default port, 554. ONVIF is not required because this app connects directly over RTSP rather than using ONVIF discovery.
  2. Get the camera IP. Open device information in the Reolink app, or check your router's DHCP client list. If the camera is on a dedicated VLAN, use its address on that VLAN and confirm that the firewall permits the detection machine to reach it. Remote access can use Tailscale as described below.
  3. Use the camera login. The default username is often admin. Use the camera password configured in the Reolink app, not your Reolink cloud account password.
  4. Test the URL before using Watch. Run the following command on the detection machine:
ffprobe -rtsp_transport tcp -i "rtsp://USER:PASSWORD@CAMERA_LAN_IP:554/h264Preview_01_sub"

The output should describe a valid video stream, including its codec, dimensions, and frame rate. Then add the same host and path to .env. For a graphical alternative, use VLC > Media > Open Network Stream.

If ffprobe times out, RTSP may be disabled, the IP address may be wrong, the camera may have reached its client limit, or the detection machine may not be connected to the camera's network. A 401 response usually means the username, password, or stream path is incorrect.

Remote RTSP over Tailscale

Cameras do not run Tailscale. A subnet-router machine with access to the camera VLAN advertises a route; your remote detector or laptop then reaches the camera's private VLAN address through the tailnet. Traffic to the subnet router stays on Tailscale, and port 554 remains closed to the public internet.

Official reference: Tailscale subnet routers (validated Jan 2026).

1. Install Tailscale on both sides

Install Tailscale on a computer that can reach the camera VLAN and on the machine that will run detection if it is a different computer:

sudo tailscale up

Sign in to the same tailnet. Confirm each device appears at console.tailscale.com/admin/machines.

If the detector already has local access to the camera VLAN, you do not need a Tailscale subnet route for RTSP. Set CAMERA_HOST to the camera's VLAN address. Use Tailscale only when the detector or another authorized device needs remote access.

2. Camera-network computer: subnet router (detector is elsewhere)

On a Linux computer that can reach the camera VLAN:

echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf

Advertise only the camera (/32) when you can. Replace the example address with yours:

sudo tailscale set --advertise-routes=192.168.1.50/32

To advertise the entire camera VLAN instead, replace the example subnet with the actual camera VLAN. This grants access to more devices and should be paired with restrictive Tailscale access controls:

sudo tailscale set --advertise-routes=192.168.1.0/24

On Windows, tailscale set --advertise-routes=... (no sudo). On macOS, enable the CLI or run the same flag via the Tailscale app; IP forwarding is enabled for you.

3. Approve the route

  1. Open Machines.
  2. Find the camera-network computer with the subnet badge.
  3. Open Edit route settings, approve the advertised route, and select Save.

Default access controls may allow broad access to advertised subnets. Restrict the route to authorized users and devices under Access controls, especially when advertising an entire camera VLAN.

4. Detector machine: accept routes (Linux)

Android, iOS, macOS, Windows, and tvOS pick up subnet routes automatically. Linux does not, until:

sudo tailscale set --accept-routes

Do not set --accept-routes on the home subnet router itself.

5. Point this app at the LAN IP

CAMERA_HOST stays the camera's private VLAN address (for example 192.168.1.50), not a 100.x Tailscale IP. The camera has no Tailscale address; the subnet router makes its VLAN address reachable from the detector.

ffprobe -rtsp_transport tcp -i "rtsp://USER:PASSWORD@192.168.1.50:554/h264Preview_01_sub"
python -m camera_weapon_detect serve

Tailscale notes

  • Prefer a /32 for one camera over advertising the whole home subnet.
  • RTSP is still the camera's own protocol; Tailscale only encrypts the path to the LAN. Use a strong camera password in .env (chmod 600 .env).
  • Reolink cameras allow few simultaneous RTSP sessions. Close VLC/ffprobe before Watch.
  • Do not use a Tailscale exit node for this. You need a subnet router, not the option to send all internet traffic through your home network.
  • Disable key expiry on the always-on home node if the route dies after 180 days (key expiry).

Fine-tune on your reviews

python -m camera_weapon_detect export-train
python -m camera_weapon_detect train

These commands build data/trainset from confirmed crops, dismissed hard negatives, and sampled demo clips, and then train YOLOv8s. The resulting weights are saved as models/firearm_yolov8s_ft.pt and selected automatically on subsequent runs. The nano weights remain the fallback if the fine-tuned file is missing.

A GPU is strongly recommended for a reasonable training time. Do not publish fine-tuned weights trained on private camera footage.

Security

  • Native runs bind to loopback by default. Docker additionally requires HTTP Basic authentication, validates Host headers, and requires CSRF tokens for state-changing requests. Use a unique password and encrypted transport whenever the UI leaves loopback.
  • Uploaded files have a 100 MiB default limit, server-generated names, extension and content-type checks, and an ffprobe decode check. Uploaded clips, snapshots, and SQLite data are created with private permissions.
  • Browser assets use a restrictive Content Security Policy and DOM text APIs for detector and cloud-review text. API responses omit camera credentials and absolute storage paths.
  • The container runs without Linux capabilities or privilege escalation, with a read-only root filesystem, a bounded temporary filesystem, process/CPU/memory limits, and hash-locked Python dependencies. The Python base image is pinned by digest.
  • Ultralytics safe loading is enabled. Selected local weights require WEAPON_MODEL_SHA256 in Docker, including auto-selected fine-tuned and nano files. A .pt file is executable-model input: download it only from a trusted publisher and verify its provenance as well as its checksum.
  • Place cameras on a dedicated camera or IoT VLAN. Permit only the required traffic from the detection and management devices, and prevent cameras from initiating connections to trusted networks.
  • Compose must reach cameras and, when enabled, xAI and model-download hosts, so it cannot use a fully internal Docker network. Restrict egress by destination at the router/VLAN firewall or in the host's Docker-aware firewall policy; ordinary host firewall rules may not filter published Docker traffic as expected.
  • Full videos are never sent to the cloud. Only cropped event images are sent to Grok, and only when XAI_API_KEY is set.
  • Store camera credentials in .env; on Linux and macOS, restrict access with chmod 600 .env.
  • Do not port-forward RTSP. Use Tailscale to reach a remote camera instead of exposing port 554 to the public internet.
  • Treat every detection as a proposal. Require human confirmation before connecting the project to an alert channel.

About

Local YOLO camera review queue for visible firearms. Optional Grok crop review. Not a lock-down system.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages