Documentation

Using and training EvolveX

Install EvolveX, connect your cameras, then label your own footage and train both models in EvolveX Studio.

Overview

EvolveX detects loitering and fighting in CCTV streams and raises alerts with snapshot evidence. It is written from scratch on NumPy and the Python standard library. It uses no pretrained models: both models are trained only on footage you label yourself in EvolveX Studio.

ComponentWhat it doesWhere
MonitorPer camera: capture → detect → track → analyse → alert. Dashboard on port 8080suspect/main.py
EVX-DetPerson detector. RGB + motion channel in, person boxes out. ~1 M parameters, ~33 ms per 320×180 frame on 4 CPU threadsmodels/evx_person.npz
Fight classifierSmall CNN over 8 crops of a tracked person plus 8 frame differences (16×48×48)models/evx_fight.npz
Loitering rulesDwell time inside a zone with a confined pathsuspect/behaviour.py
EvolveX StudioLabel footage, train, test and promote models. Port 8090suspect/studio/

Generations. A Gen is a major version: Gen1 = 1.x.y, Gen2 = 2.0.0. Minor versions add features or retrained models; patch versions fix and tune. Every saved model file records the version that made it. See Generations.

Who uses what

Website visitors use neither. The monitor is for the people watching a site's cameras; Studio is for the people who train the models.

Monitor dashboardEvolveX Studio
WhoThe customer's security team (guards, control-room operators), and the EvolveX team while setting up and supporting a pilotThe EvolveX team that trains the models; at most a trusted person at the customer who helps label footage
What forWatch every camera live with tracked people; see loitering and fighting alerts as they happen, with a snapshot; alerts can also go by email or webhookUpload or record footage, box the people, mark the fights, train the person detector and the fight classifier, test them, and promote a better model to the monitor
How oftenAll day: it is the working screen at the siteDuring setup, then whenever new footage is labelled
WherePort 8080, e.g. monitor.evolvex.my, or a device on the customer's premisesPort 8090, e.g. studio.evolvex.my

Keep the logins separate. Give each app its own EVX_USER and EVX_PASSWORD, and give customers only the monitor login: Studio holds every site's training footage and can replace the models the monitor runs.

Install

You need Python 3 with NumPy, and the ffmpeg binary.

pip install numpy
sudo apt install ffmpeg
cp config.example.json config.json    # add your cameras
cp .env.example .env                  # login, database, R2 and email settings
python -m suspect.main --version
  • GPU (NVIDIA Jetson): install CuPy in its own virtual environment and point training.gpu_python in config.json at it. Training is about 6× faster.
  • Server: the monitor and Studio run as Docker containers. Set EVX_USER and EVX_PASSWORD whenever they are reachable from the internet.
  • Storage: local files by default; PostgreSQL and Cloudflare R2 when DATABASE_URL and R2_* are set.

Using the model

Start the monitor with python -m suspect.main --config config.json and open http://localhost:8080. Until you promote trained models, it finds people with its motion detector and scores fights with a motion-energy heuristic.

1. Add cameras

List them under cameras in config.json. Names must be unique.

SourceExample sourceExtra settings
IP camerartsp://user:password@192.168.1.10:554/stream1—
USB camera/dev/video0"input_args": ["-f","v4l2"]
Video filevideos/test.mp4"loop": true to replay

2. Choose the detector

Set processing.detector to "cnn" to use EVX-Det. It falls back to "motion" if the model file is missing. det_conf (default 0.3) is its score threshold.

3. Draw loitering zones

loiter_zones are polygons in 0–1 coordinates of the frame; the default is the whole frame.

4. Tune behaviour per camera

SettingDefaultMeaning
loiter_seconds30Dwell time in a zone before alerting
loiter_confine_frac0.2Path must stay within this fraction of the frame
loiter_repeat_seconds60Re-alert interval while still loitering
fight_threshold0.7Smoothed fight probability needed
fight_confirm3Consecutive positive evaluations needed
fight_every_frames3Run the fight CNN every N frames per person
fight_min_energy0.02Skip the CNN when the crop barely moves
fight_smooth0.6Smoothing of the fight score over time

5. Receive alerts

Each alert is written to evidence/alerts.jsonl with an annotated PNG snapshot. It can also be sent to a webhook_url and emailed with the snapshot (ALERT_EMAIL_TO and SMTP_*; test with python -m suspect.notify).

Using the models from Python

from suspect.nn.evxdet import load_evx_det
from suspect.nn.model import load_fight_model

det = load_evx_det("models/evx_person.npz")
for x0, y0, x1, y1, score in det.detect(rgb, motion, conf=0.3):
    ...   # rgb: (H,W,3) uint8, motion: (H,W) uint8

fight = load_fight_model("models/evx_fight.npz")

The motion input must come from BackgroundModel.motion_u8(), computed exactly as the monitor computes it.

Training

Start Studio with python -m suspect.studio and open http://localhost:8090. A model only knows the scenes it was trained on, so label footage from every camera you deploy on.

  1. Collect. Upload videos or record from a configured camera. Cover different times of day; people walking, standing and queueing; and fight look-alikes such as running, hugging and play.
  2. Prepare. Each video is decoded at the monitor's width and frame rate, keeping every 5th frame for labelling. If you change processing.width or fps, re-prepare and retrain.
  3. Label people. Drag a box around every person on each kept frame and press Enter. Press E for a frame with nobody in it. Press S for suggested boxes, then check them.
  4. Label fights. Press [ where a fight starts and ] where it ends, then tick Fight labelling complete. Only ticked videos train the fight classifier.
  5. Split. auto gives the first 70 % of a video to training, the next 15 % to validation and the last 15 % to test. With several videos, give each whole video to one split.
  6. Train. Train the person detector first, then the fight classifier. The best epoch is kept: the person detector by (AP50 + F1) / 2, the fight classifier by F1.
  7. Test. Run the test split. Person: AP at IoU 0.5 and precision, recall and F1 by threshold. Fight: accuracy, precision, recall, F1, AUC and the confusion matrix.
  8. Promote. Promote a run that beats production. The old model is archived and models/registry.json records the change. Restart the monitor.
  9. Evolve. Pre-label new footage with the production model, correct it, retrain, test and promote.

Command line

python -m suspect.training import clip.mp4
python -m suspect.training prepare --step 5
python -m suspect.training status
python -m suspect.training train person --epochs 60 --batch 16
python -m suspect.training train fight --epochs 30
python -m suspect.training test person-20260929-101500
python -m suspect.training promote person-20260929-101500

# GPU on the Jetson
SUSPECT_BACKEND=cupy ~/.venvs/suspect/bin/python -m suspect.training train person

Training parameters

Override any of them with --key value on the command line, or in Studio's form.

ParameterPersonFightMeaning
epochs6030Passes over the training set
batch1632Samples per step
lr0.0020.001Adam learning rate
width1616Network width; sets size and speed
seed00Random seed
motiontrue—Feed the motion channel (randomly dropped in training)
eval_every2—Validate every N epochs
conf0.3—Score threshold for validation F1
stride—4Sample a clip every N frames per person
min_energy—0.03Skip crops that barely move
detector—motionDetector used to replay the videos (motion or cnn)

Each run is saved in training/runs/<kind>-<timestamp>/. On a server CPU, expect about 0.2 s per labelled frame per epoch; start with a few hundred frames.

Smoke test without footage: python tools/make_synthetic.py && python -m suspect.training train person --epochs 12. The synthetic models are useless on real cameras; delete training/ afterwards.

Releasing a Gen

Each Gen has a permanent entry on the Generations page and a test report. python tools/gen_report.py --gen N writes the production models' test results, training-data counts and SHA-256 checksums into it. The model files are published as a GitHub release with a Zenodo DOI. The full checklist is docs/RELEASE.md in the repository.

Troubleshooting

SymptomCauseFix
Two people close together show as one boxThe motion detector merges nearby blobsTrain EVX-Det and set detector to "cnn"
Trees, cars or shadows are trackedThe motion detector treats any movement as a personTrain EVX-Det; narrow loiter_zones
Loitering timer restartsPerson lost for more than max_misses framesRaise max_misses (default 15)
Promoted model not usedThe monitor loads models at startupRestart the monitor
prepare refuses a videoIts labels were made at another width, fps or stepRe-prepare with the original step, or relabel
is not an evx-det-1 modelModel file from another architectureUse weights trained for the current architecture
Training slow on the JetsonAll 8 cores in useKeep OMP_NUM_THREADS=4 (the default), or use the GPU
Camera stream freezesThe camera stopped sendingThe monitor reconnects after read_timeout seconds

Citing EvolveX

Cite the generation you used. Each Gen's BibTeX, and its DOI once minted, is on the Generations page. List your paper on Publications.

@software{evolvex_gen1,
  title   = {EvolveX Gen1: Evolving Vigilance for Behavioural Exceptions},
  author  = {Alobaedy, Mustafa},
  year    = {2026},
  url     = {https://evolvex.my}
}

EvolveX is licensed under the PolyForm Noncommercial License 1.0.0.