Skip to content

About

Live webcam object detection, tracking and line-crossing counts. FastAPI and Ultralytics YOLO, React + Vite front end, SQLite.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

3 Commits

Folders and files

Repository files navigation

πŸ‘ YOLO Vision Dashboard

CI License: MIT Python 3.10+ React 18 FastAPI Ultralytics YOLO

Real-time object detection from your webcam β€” FastAPI + Ultralytics YOLO on the back, React + Vite + Tailwind on the front, SQLite underneath.

Detects objects live, tracks them across frames, and counts people, cars and motorcycles crossing a line you draw on the video.

Built as a first practical computer-vision project, so every non-obvious decision is explained in the code and in docs/YOLO_EXPLAINED.md β€” a ground-up walkthrough of every important YOLO component, written for someone who has not done CV before.


What it does

  • Live detection β€” webcam frames stream to the backend over WebSocket and come back annotated, at whatever rate your hardware sustains (see Performance).
  • Bounding boxes β€” drawn in the browser over the live video (smooth, low bandwidth) or server-rendered into the returned JPEG. Toggle between them.
  • Object statistics β€” per-class counts for the current frame, confidence levels, all-time totals.
  • FPS + timing breakdown β€” where every millisecond goes: pre-process, inference, NMS, annotation.
  • Object tracking β€” each object gets a stable ID across frames.
  • Virtual counting line β€” drag a line across the scene; it counts people, cars and motorcycles that cross it, with direction, each counted exactly once.
  • Persistent history β€” every detection is written to SQLite with timestamp, object name and confidence.

Quick start

Requirements: Python 3.10+, Node 18+, a webcam.

Windows PowerShell users: run each command on its own line. Windows PowerShell 5.1 (the default in VS Code's terminal) does not support && for chaining β€” it fails with Β«&&Β» n'est pas un sΓ©parateur d'instruction valide. Use ; instead, or just run the lines separately as shown below.

1. Backend

cd backend
python -m venv .venv

Activate it:

# Windows (PowerShell)
.venv\Scripts\Activate.ps1
# macOS / Linux
source .venv/bin/activate

Install and run:

pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000

If activation gives you trouble on Windows (execution policy, wrong shell), skip it entirely and call the venv's Python directly β€” this always works:

.\.venv\Scripts\python.exe -m uvicorn app.main:app --reload --port 8000

The first start downloads yolov8n.pt (~6 MB) into backend/models/. Wait for:

Model loaded in 2.77s (80 classes, task=detect)
Ready on http://0.0.0.0:8000 (device=cpu)

2. Frontend

In a second terminal:

cd frontend
npm install
npm run dev

3. Open it

http://localhost:5173 β†’ Start camera β†’ Start detection.

The browser will ask for camera permission. getUserMedia only works in a secure context β€” HTTPS or localhost. Video is sent only to your own backend; nothing leaves your machine.


Docker

docker compose up --build

Then open http://localhost:8080.

The first build downloads PyTorch and takes several minutes. The compose file pulls the CPU-only PyTorch wheel (~200 MB instead of ~2.5 GB with CUDA), and pre-downloads the model weights at build time so the container starts fast and works offline.

docker compose logs -f backend   # follow logs
docker compose down              # stop
docker compose down -v           # stop and delete the database

Named volumes persist the SQLite database and the model cache across rebuilds.


Project structure

yolo-app/
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ main.py              app factory, startup/shutdown ordering
β”‚   β”‚   β”œβ”€β”€ core/                config (pydantic-settings), logging
β”‚   β”‚   β”œβ”€β”€ api/routes/          health, detect (REST), stream (WS), stats
β”‚   β”‚   β”œβ”€β”€ services/            detector, tracker, line counter, annotator,
β”‚   β”‚   β”‚                        fps, session, writer, pipeline
β”‚   β”‚   β”œβ”€β”€ db/                  engine, ORM models, queries
β”‚   β”‚   └── schemas/             the wire contract
β”‚   β”œβ”€β”€ tests/                   116 tests
β”‚   β”œβ”€β”€ requirements.txt
β”‚   └── Dockerfile
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ hooks/               useWebcam, useDetectionStream, useStats
β”‚   β”‚   β”œβ”€β”€ components/          VideoStage, ControlPanel, stats panels
β”‚   β”‚   β”œβ”€β”€ lib/                 API client, config, formatting
β”‚   β”‚   └── types/api.ts         mirrors the backend schemas
β”‚   β”œβ”€β”€ nginx.conf               production proxy for /api and /ws
β”‚   └── Dockerfile
β”œβ”€β”€ docs/
β”‚   β”œβ”€β”€ YOLO_EXPLAINED.md        ← start here if YOLO is new to you
β”‚   └── ARCHITECTURE.md          how and why the pieces fit together
β”œβ”€β”€ data/                        SQLite database (created on first run)
└── docker-compose.yml

Configuration

Copy backend/.env.example to backend/.env and edit. Everything has a sensible default β€” the file documents each option.

The settings you are most likely to change:

Variable Default Effect
MODEL_NAME yolov8n.pt yolov8s.pt = more accurate, ~2Γ— slower
IMAGE_SIZE 640 320 β‰ˆ 2.4Γ— faster, misses small objects
DEVICE auto cpu, cuda, mps
CONFIDENCE_THRESHOLD 0.35 higher = fewer false positives
IOU_THRESHOLD 0.45 NMS duplicate removal aggressiveness
COUNTING_CLASSES person,car,motorcycle what the line counts

Confidence, IoU, frame rate and class filter are also adjustable live in the dashboard sidebar β€” changing them while watching the video is the fastest way to build intuition for what they do.

GPU

The default install is CPU-only. For an NVIDIA GPU:

pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121

then DEVICE=cuda and HALF_PRECISION=true. Expect an order-of-magnitude speedup β€” inference stops being the bottleneck entirely.


Performance

Measured on the development machine (8-core CPU, no GPU, yolov8n.pt). Two columns, because the difference matters: inference on an idle machine is roughly twice as fast as inference while you are actually using the dashboard, with a browser encoding frames and a dev server running alongside it.

IMAGE_SIZE Inference (idle) Inference (dashboard running)
320 ~31 ms ~53 ms
416 ~46 ms ~109 ms
640 ~74 ms ~145 ms

End-to-end through the browser at 640px β€” capture, JPEG-encode, upload, detect, respond β€” lands around 4–7 FPS on this machine. Benchmark numbers you see quoted for YOLO are almost always the idle-machine kind; treat them as a ceiling, not a forecast.

Expect wide variance on a loaded machine: inference at 640px ranged from 138 ms to 982 ms in the same run depending on what else was competing for CPU.

Inference dominates; everything else (decode, NMS, drawing) is under 10 ms combined. If it feels slow, lower IMAGE_SIZE first β€” compute scales with area, so halving it is roughly a 4Γ— speedup.

The dashboard shows the full breakdown per frame, so you can verify this rather than guess.


Tests

cd backend
pytest

116 tests, ~18 seconds, no model download needed (the API tests use a stub detector). They cover the parts most likely to be silently wrong:

  • Tracker β€” identity stability, class-consistent matching, occlusion survival, fast-object association.
  • Counting line β€” no double counting, correct rejection of objects passing beyond the line's endpoints, direction, per-class filtering.
  • FPS meter β€” sliding-window averaging, staleness.
  • API β€” every endpoint, plus malformed input and WebSocket error recovery.
  • Persistence β€” detections actually reach SQLite.
  • End-to-end β€” a car driven across the counting line through the real pipeline is counted exactly once, keeps one track ID, and lands in the database.

Integration tests against the real weights:

pytest -m slow

How it works, briefly

webcam ──▢ canvas ──▢ base64 JPEG ──WebSocket──▢ FastAPI
                                                    β”‚
                            decode β–Ά DETECT β–Ά TRACK β–Ά COUNT β–Ά ANNOTATE
                                       β”‚        β”‚       β”‚
                                       └── queued batch writes ──▢ SQLite
                                                    β”‚
   boxes + stats + FPS β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Three decisions worth knowing about:

Backpressure. A webcam produces 30 fps; a CPU handles ~13. The client sends one frame and waits for its result before capturing the next. Sending on a fixed timer would build a queue and the video would drift further behind reality every second. This way latency stays at exactly one frame.

Tracking is separate from detection. YOLO is stateless β€” it does not know the car in frame 51 is the car from frame 50. Counting needs identity, so a per-session tracker assigns stable IDs. Without it, one car crossing the line would be counted once per frame.

Writes are batched. 25 FPS Γ— 8 objects = 200 INSERTs/second. Committing each inside the request would dominate the frame budget, so rows go onto a bounded queue and a background task flushes them in batches.

The full reasoning is in docs/ARCHITECTURE.md.


API

Interactive docs at http://localhost:8000/docs.

curl -X POST http://localhost:8000/api/detect \
  -F "file=@photo.jpg" \
  -F "confidence=0.4"

Get an annotated image straight back:

curl -X POST http://localhost:8000/api/detect/image -F "file=@photo.jpg" -o out.jpg

Key endpoints: POST /api/detect, WS /ws/detect, GET /api/stats/summary, GET /api/stats/detections, PUT /api/detect/sessions/{id}/line. Full table in docs/ARCHITECTURE.md.


Troubleshooting

"Cannot reach the backend" β€” the model takes a few seconds to load on first start. Check http://localhost:8000/api/health shows "model_loaded": true, then reload the page.

Camera permission denied β€” allow it in the browser's site settings. The page must be on localhost or HTTPS; plain HTTP to another machine will not work.

Camera in use by another application β€” close Zoom/Teams/OBS and retry.

Low FPS β€” set IMAGE_SIZE=320, lower the max frame rate slider, or restrict the class filter. See Performance.

Boxes flicker β€” the confidence threshold is close to the model's score for those objects. Lower it, or raise TRACK_MAX_AGE so tracks coast through gaps.

One object counted several times β€” track identity is breaking. Raise TRACK_MAX_AGE or TRACK_MAX_CENTER_DISTANCE, or send frames faster so objects move less between them.

Nothing appears in the history table β€” only detections at or above DB_MIN_CONFIDENCE (default 0.50) are persisted. Lower it to store more.


Not included

This is a learning project, deliberately scoped. Before deploying anywhere real you would need: authentication (every endpoint is open, including DELETE /api/stats/detections), rate limiting, HTTPS, and β€” if scaling past one worker β€” shared session state, since it is currently in-memory per process.


Learn more


Licence

The code in this repository is MIT licensed.

One dependency is not: Ultralytics YOLO is AGPL-3.0, a copyleft licence that extends to network use β€” deploying a derivative of this project as a public service means making the source available to its users. Ultralytics sells a commercial licence for cases where that does not work.

Full breakdown in NOTICE.md. Worth two minutes before you build anything commercial on top of this.

About

Live webcam object detection, tracking and line-crossing counts. FastAPI and Ultralytics YOLO, React + Vite front end, SQLite.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages