RakshAI
RakshAI is an AI-powered construction-site safety analysis system that analyzes CCTV or other video footage for PPE compliance violations and generates a structured safety report in PDF format.
The core idea is to turn frame-level computer-vision detections into meaningful, deduplicated safety events that can be reviewed by site safety personnel.
Concept
Video Upload
↓
Analysis Job
↓
Video Processing (OpenCV)
↓
PPE Detection (YOLO + best.onnx)
↓
Worker Tracking
↓
Violation Event Detection
↓
Analytics
↓
Evidence Frames + Charts
↓
PDF Safety Report
A key design principle is that raw model detections should not directly be treated as violations. The application layer should associate PPE detections with individual workers, track those workers over time, and convert persistent missing-PPE observations into deduplicated violation events.
Current Stack
- Backend: FastAPI (single-worker job queue, in-memory results)
- Computer Vision: OpenCV
- Object Detection: Ultralytics YOLO11n using
models/best.onnx(10 classes:Hardhat, Mask, NO-Hardhat, NO-Mask, NO-Safety Vest, Person, Safety Cone, Safety Vest, machinery, vehicle) - Inference: Ultralytics, with a plain ONNX Runtime fallback
- Worker tracking: ByteTrack (Ultralytics' tracker, fed with our own person detections)
- Reporting: ReportLab + matplotlib (charts, worker profiles, in-memory evidence frames)
- Frontend: vanilla HTML + Tailwind (Play CDN) + JS in
frontend_static/
Repository Structure
RakshAI/
├── backend/
│ ├── app.py # FastAPI app, job queue, API routes
│ ├── models/schemas.py # dataclasses + PPE/class mappings
│ ├── services/
│ │ ├── video.py # probing + timestamp-based frame sampling (VFR-safe)
│ │ ├── tracking.py # ByteTrack wrapper, duplicate person suppression
│ │ ├── association.py # PPE / NO-PPE boxes -> tracked worker
│ │ ├── violations.py # per-worker persistence/cooldown state machine
│ │ ├── hazards.py # worker <-> machinery/vehicle proximity (2D)
│ │ ├── evidence.py # in-memory thumbnails + annotated event frames
│ │ ├── analytics.py # per-worker summaries, PPE breakdown, timeline, findings
│ │ └── pipeline.py # runs the whole analysis for one video
│ ├── utils/
│ │ ├── predict.py # model loading + predict_frame()
│ │ └── pdf_generation.py # PDF report
│ ├── tests/ # pytest unit tests (no model needed)
│ └── requirements.txt
├── frontend_static/ # index.html + app.js (served at / and /frontend)
├── models/best.onnx
└── storage/
├── uploads/ # legacy sample clips only; new uploads are never stored
└── reports/ # generated PDFs
Uploaded videos are written to a temporary file, analysed, and deleted. Evidence images only exist in memory while the PDF is built. The web UI reviews footage from the user's local copy of the video.
A single-worker thread pool runs one analysis at a time; further uploads wait in a queue. Redis/Celery can be introduced later if concurrent analysis jobs become necessary.
API Design
All routes are under /api/v1.
| Method & path | Purpose |
|---|---|
GET /health |
Model status, class list, queue length |
POST /analysis |
Upload a video and queue an analysis |
GET /analysis/{id} |
Status: status, stage, progress, queue_position, frames_processed, eta_seconds, error |
DELETE /analysis/{id} |
Cancel a queued or running analysis |
GET /analysis/{id}/results |
Summary, PPE breakdown, timeline buckets, occupancy, scene context, key findings |
GET /analysis/{id}/persons |
Per-worker summaries |
GET /analysis/{id}/events |
Violation events (filter with ?type=NO_MASK&worker_id=2) |
GET /analysis/{id}/tracks |
Per-worker boxes over time for video overlays: rows of [t, x1, y1, x2, y2, active_types], 0–1 normalized |
GET /analysis/{id}/frames?start=&end=&limit= |
Raw per-frame detections (debugging) |
GET /analysis/{id}/report |
PDF report |
POST /analysis form fields:
video <file> required
site_name Construction Site A required
camera_id CAM-03 required
analysis_fps 5 2 | 5 | 10 | 0 (= every frame)
required_ppe hardhat,vest,mask which items count as violations
conf_threshold 0.25
min_violation_s 1.0 shorter lapses are reported as "brief"
debug false per-frame server logs
Statuses: queued, processing, completed, failed, cancelled.
Example person (/persons):
{
"worker_id": 1,
"label": "Worker 1",
"first_seen": 0.0,
"last_seen": 15.9,
"visible_s": 12.1,
"status": "non_compliant",
"severity": {"score": 31.6, "level": "high"},
"violation_s": 12.0,
"ppe": {
"hardhat": {"required": true, "status": "violation", "compliance": 0.43, "present_s": 5.0, "missing_s": 6.6, "unseen_s": 0.5, "events": 1},
"vest": {"required": true, "status": "ok", "compliance": 1.0}
},
"exposure": {"machinery": 2.4, "vehicle": 0.0},
"best_frame": {"t": 11.0, "bbox": [0.58, 0.07, 0.83, 0.93]}
}
Worker status is non_compliant (at least one violation event), compliant (required PPE seen being worn), or unconfirmed (PPE never seen clearly enough). Per-item status is violation, brief (missing, but shorter than min_violation_s), ok, or unseen.
Example event (/events):
{
"event_id": "ev0023",
"worker_id": 1,
"type": "NO_HARDHAT",
"t_start": 10.81,
"t_end": 15.5,
"duration": 4.69,
"peak_conf": 0.8,
"near_hazard": true,
"severity": 21.1
}
Processing Pipeline
1. Video service
Responsible for:
- Saving uploaded videos
- Reading video metadata
- Getting FPS, frame count and duration
- Sampling frames at a configurable analysis FPS
- Extracting evidence frames
Useful functions:
get_video_metadata(path)
iter_video_frames(path, target_fps=5)
extract_frame(path, timestamp)
save_frame(frame, path)
A starting point of around 5 FPS can significantly reduce inference cost compared with processing every frame of a 25/30 FPS CCTV recording. This should be benchmarked against detection accuracy for the target footage.
2. Inference service
best.onnx should be loaded once when the application/worker starts rather than once per request.
The inference layer should expose an application-independent interface such as:
predict_frame(frame)
normalize_detections(results)
Normalize model output into a common detection representation:
Detection
├── class_id
├── class_name
├── confidence
└── bbox [x1, y1, x2, y2]
This keeps the rest of RakshAI independent from the exact model implementation.
3. Worker tracking
Frame-by-frame detection is not enough for useful violation counts. A worker visible without a helmet for 100 consecutive frames should generally represent one continuous event, not 100 separate violations.
Use a tracking layer, such as ByteTrack through the Ultralytics pipeline, to maintain worker identities:
Worker #17
├── first_seen: 83.2s
├── last_seen: 94.8s
├── helmet_present: false
└── vest_present: true
Useful functions:
update_tracks(detections)
get_active_workers()
close_stale_tracks()
4. Violation service
The violation service converts detections + worker tracking into safety events.
Example:
Person #17
├── helmet: false
├── vest: true
└── boots: true
becomes:
NO_HELMET
start: 83.2s
end: 88.7s
duration: 5.5s
track_id: 17
Useful functions:
associate_ppe_with_workers()
detect_missing_ppe()
start_violation()
update_violation()
close_violation()
deduplicate_events()
A cooldown/persistence threshold should be used to prevent brief model flicker from generating excessive events.
Violation Event Model
A normalized event should contain enough information for analytics and evidence generation:
@dataclass
class ViolationEvent:
event_id: str
timestamp_start: float
timestamp_end: float
duration: float
track_id: int
violation_type: str
confidence: float
frame_start: int
frame_end: int
Analytics
The analytics layer should calculate more than a raw violation count.
Basic metrics
- Total workers detected
- Total violation events
- Unique workers involved in violations
- Average violation duration
- Longest violation
- Analysis duration
PPE breakdown
No helmet 31
No safety vest 17
No boots 4
Rates
- Violations per worker
- Violations per hour
- PPE-specific violation rate
- Percentage of observed worker-time associated with violations
Raw counts need context. For example, 100 violations across 5 workers for one hour is not directly comparable to 100 violations across 50 workers for ten hours.
Timeline analysis
Aggregate events into time buckets to identify periods with higher numbers of observed violations.
08:00 → 3
09:00 → 12
10:00 → 6
11:00 → 4
This can be represented as a line/bar chart in the report.
Evidence Frames
Each violation event keeps its highest-confidence frame, annotated with the worker box and the missing item (up to the 60 most severe events), plus one thumbnail per worker. These images are held in memory only, embedded in the PDF and then discarded. Nothing is written to storage/processed/.
PDF Report
The report should contain:
Executive summary
- Site
- Camera
- Analysis date
- Video duration
- Workers observed
- Total PPE violations
- Violation rate
- Average event duration
PPE breakdown
- No helmet
- No safety vest
- No boots
- Other model-supported PPE categories
Violations over time
- Timeline chart
- Time buckets with event counts
Violation events
- Timestamp
- Violation type
- Duration
- Confidence
- Track ID where appropriate
Evidence
- Representative frames for significant events
Methodology
- Model used
- Sampling FPS
- Detection threshold
- Processing information
The report should clearly communicate that AI-generated observations are subject to model limitations and should be reviewed by qualified safety personnel before being used for compliance decisions.
Database
For the MVP, SQLite is sufficient.
analyses
id
filename
site_name
camera_id
duration
status
created_at
completed_at
violations
id
analysis_id
track_id
type
start_time
end_time
duration
confidence
evidence_path
PostgreSQL can replace SQLite later without changing the core processing architecture.
Recommended Development Phases
Phase 1 — Functional MVP
Upload video
→ process video
→ YOLO inference
→ detect PPE
→ count violations
→ generate PDF
Phase 2 — Reliable event detection
Add:
- Worker tracking
- Violation duration
- Evidence frames
- Timeline analytics
- Charts
- Event deduplication
Phase 3 — Production-style application
Add:
- Asynchronous analysis jobs
- Progress reporting
- Persistent database
- Frontend dashboard
- Analysis history
- Downloadable reports
Phase 4 — Advanced monitoring
Potential future features:
- Multiple CCTV cameras
- Live CCTV streams
- Real-time safety alerts
- Historical site analytics
- Worker-level trends where appropriate
- Configurable site-specific PPE rules
Engineering Priorities
The most important part of RakshAI is the detection → tracking → event layer. The object detector provides raw computer-vision observations, but the application needs to transform those observations into consistent, deduplicated and explainable safety events.
Keep inference, tracking, violation logic, analytics and report generation as separate services so each component can be tested and replaced independently.
Also use an absolute model path based on the project location rather than relying on the current working directory:
from pathlib import Path
from ultralytics import YOLO
BASE_DIR = Path(__file__).resolve().parents[2]
MODEL_PATH = BASE_DIR / "models" / "best.onnx"
model = YOLO(str(MODEL_PATH))
This avoids failures caused by launching the application from a different working directory.
Goal
RakshAI aims to provide a practical way to analyze construction-site footage, quantify observed PPE safety violations, surface when and how violations occur, and provide evidence-backed PDF reports for human review.
Quickstart
- Python 3.9+. From the repo root:
python -m venv venv && source venv/bin/activate(optional)pip install -r backend/requirements.txt
- The model must be at
models/best.onnx. - Run:
./start_all.sh(oruvicorn backend.app:app --port 8000), then open http://localhost:8000. - Tests:
python -m pytest backend/tests - Notes:
- Analyses and results live in memory and are lost on restart; PDFs in
storage/reports/remain. - If the model can't be loaded, analyses fail with an error rather than reporting "no detections".
- Analyses and results live in memory and are lost on restart; PDFs in