Real-time multi-camera factory floor visualisation — detects people across overlapping camera feeds and renders a unified top-down bird's-eye view.
┌───────────────────────────────────────────────────────────────────────┐
│ factory-floor-viz │
│ │
│ ┌──────────┐ ┌────────────────────────────────────────────────┐ │
│ │ config/ │ │ Calibration (One-time setup) │ │
│ │ *.json │──▶│ lens_correction → homography → ocr_region │ │
│ └──────────┘ └───────────────────┬────────────────────────────┘ │
│ │ (cameras.json, edges.json) │
│ ┌───────────────────▼────────────────────────────┐ │
│ Camera Feeds │ Per-Camera Processing (detection/ & pipeline/)│ │
│ (Video/RTSP) ──▶ YOLOv8 Detection + OCR Timestamp Extractor │ │
│ │ ↓ Homography Projection ↓ │ │
│ │ Line Crossing Detector → Per-Camera CSV │ │
│ └───────────────────┬────────────────────────────┘ │
│ │ {cam_id}_crossings.csv │
│ ┌───────────────────▼────────────────────────────┐ │
│ │ Multi-Camera Fusion (fusion/multi_camera...) │ │
│ │ Hungarian Matching + Overlap Deduplication │ │
│ └───────────────────┬────────────────────────────┘ │
│ │ fused_crossings.csv │
│ ┌───────────────────▼────────────────────────────┐ │
│ │ Visualisation (visualization/) │ │
│ │ floor_renderer.py — OpenCV top-down canvas │ │
│ └────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────┘
Camera Feeds (Video/RTSP)
│
▼
[Lens Undistortion] ← correct warp using camera intrinsics
│
▼
[YOLOv8 + OCR Reader] ← bounding box (foot point) + datetime extraction
│
▼
[Homography Projector] ← pixel_x, pixel_y → floor_x, floor_y (metres)
│
▼
[Line Crossing Detector] ← cross-product vector matching over edges.json
│
▼
[Per-Camera CSV] ← one tracking file per individual camera
│
▼
[Multi-Camera Fuser] ← Hungarian temporal/spatial deduplication
│
▼
[Fused Output CSV] ← unified multi-camera crossover events
factory-floor-viz/
├── config/
│ ├── floor_config.json Floor dimensions and grid settings
│ ├── cameras.json Camera sources, intrinsics, homographies
│ └── overlap_zones.json Cross-camera overlap region definitions
│
├── calibration/
│ ├── lens_correction.py Chessboard intrinsic calibration (--intrinsic)
│ ├── homography.py Perspective homography computation & mapping
│ └── calibrate.py Interactive floor-point calibration (--calibrate)
│
├── detection/
│ └── detector.py YOLOv8 wrapper → Detection dataclass
│
├── fusion/
│ ├── overlap.py OverlapZone geometry (Shapely)
│ └── fuse.py DetectionFuser — confidence-weighted merge
│
├── visualization/
│ ├── floor_renderer.py OpenCV top-down floor canvas
│ └── demo_simulator.py Synthetic multi-agent demo (no cameras needed)
│
├── main.py CLI entry point (argparse)
├── requirements.txt
└── README.md
cd factory-floor-vizpython3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activatepip install -r requirements.txtNote:
opencv-contrib-pythonincludes the extra calibration modules. If you already haveopencv-pythoninstalled you may need to uninstall it first to avoid conflicts:pip uninstall opencv-python.
The first time you run any --phase 3 / 4 / --run command, Ultralytics will
automatically download yolov8n.pt (~6 MB) to ~/.cache/ultralytics/.
You can also pre-download it:
python -c "from ultralytics import YOLO; YOLO('yolov8n.pt')"Step 1 — Lens Calibration (Optional but recommended)
Hold a chessboard pattern in front of your camera and run:
python main.py --intrinsic cam_1Computes and saves intrinsics allowing 3D flattening without barrel distortion.
Step 2 — Floor-point Homography Calibration
Mark ≥4 points of known metric floor coordinates to transform pixel space to floor space:
python main.py --calibrate cam_1Step 3 — Define Floor Coverage
Define the area the camera can genuinely see by drawing a polygon:
python main.py --coverage cam_1Step 4 — Auto Configure Overlaps
Let the system compute the overlap_zones.json automatically based on the geometry mapping:
python main.py --auto-configStep 5 — Select OCR Timestamp Region
Draw an ROI over the digital timestamp baked into your security feeds:
python main.py --ocr-region cam_1Step 6 — Verify Configurations
Run --phase 1 to assert config validity. Run --ocr-test <cam> to preview extraction on 10 frames:
python main.py --phase 1
python main.py --ocr-test cam_1Step 7 — Process Data
Process all videos through YOLO detection, floor tracking, line crossing, and spatial fusion:
python main.py --process(Or sequentially: --process-camera cam_1 followed by --fuse-only)
Step 8 — Visualize and Review Output
Inspect output/fused_crossings.csv inside your project directory for all unified crowd movement metrics!
To play back this generated data directly onto the virtual 2D floor map without re-running any models, use the visualization tool:
python main.py --visualize output/fused_crossings.csv --playback-speed 2.0You can avoid the CLI entirely by using our built-in web dashboard. This allows you to calibrate cameras, configure overlap zones, and playback generated visualization .mp4 files seamlessly in your browser (compatible with AWS hosting).
# Start the web interface
python3 web_ui/app.pyThen navigate to http://localhost:5001 and login with admin / password123.
The --process mode logs to CSV defining 8 strict columns:
| Column | Example | Description |
|---|---|---|
| timestamp | 2025-03-14 10:32:15 |
UTC or Localized DateTime Extracted By OCR |
| track_id | 12 |
The YOLO internal tracker identity |
| class_name | person |
Detected bounding-box category |
| edge_id | x_5.0 |
The edges.json physical floor threshold crossed |
| direction | forward |
Relative crossover direction (forward/backward) |
| crossing_x | 12.55 |
Floor X-axis position (in Metres) |
| crossing_y | 5.00 |
Floor Y-axis position (in Metres) |
| camera_id | fused:cam1+cam2 |
Original camera ID, or hybrid when duplicate counts merge |
--intrinsic <cam>: Lens distortion calibration.--calibrate <cam>: Homography mapping.--coverage <cam>: Define floor-viewable polygon.--ocr-region <cam>: Define OCR timestamp block.--auto-config: Calculate intersection overlap zones.
--process: Master. Runs OCR + YOLO + Crossing algorithms on all cameras, then fuses results.--process-camera <cam>: Generates crossing CSVs solely for the input camera.--fuse-only: Deduplicate existing CSV records ignoring YOLO models.--visualize <csv_path>: Play back generated data mapped to the 2D floor renderer.--run: Legacy live-tracking operation viewing mode.--demo: Legacy synthetic data generator.
--phase [1/2/3/4]: Pipeline debugging verifications.--ocr-test <cam>: Sample 10 frames of OCR data parsing.--timestamp-tolerance <s>: Override overlapping fusion tolerances (defaults to 1.0 seconds).--playback-speed <float>: Multiplier to speed up the--visualizeoffline viewer (default1.0).
Edit config/cameras.json to:
- Change RTSP URLs (
sourcefield) - Adjust
floor_coverage_polygon(floor-space coordinates in metres) - Change camera display colours (
colorarray as[R, G, B])
Edit config/overlap_zones.json to:
- Add or remove overlap regions
- Tune
distance_threshold_m(how close two detections must be to merge) - Change
fusion_strategy("weighted_average"is the only built-in strategy)
| Package | Purpose |
|---|---|
opencv-python + opencv-contrib-python |
Video capture, calibration, rendering |
ultralytics |
YOLOv8 person detection & tracking |
numpy |
Numerical operations, homography math |
scipy |
Pairwise distance matrix in fusion |
shapely |
Overlap zone polygon geometry |
matplotlib |
Auxiliary plots (reprojection error visualisation) |
RTSP stream won't open
Verify the camera IP/credentials in VLC first. Use --source test.mp4 to route physical videos during development.
Poor OCR Accuracy / Timestamps Return None
Ensure your --ocr-region correctly encapsulates the digital text overlay. Try to avoid dynamic/cluttered video backgrounds inside the crop zone. Text is parsed using pytesseract + generic adaptive thresholding—adjust params in TimestampExtractor if using inverted darker backgrounds. Assure fonts are reasonably scaled.
Deduplication False-Positives (Or Missed Merges)
If cameras are not deduplicating the same person correctly during crossover, adjust your matching sensitivities:
- Spatial Limit: Tweak the individual overlap zone's
distance_threshold_mproperty withinfusion_config.json/overlap_zones.json(defaults to auto-computed bounds). - Temporal Limit: Use
--timestamp-tolerance <seconds>or modifytimestamp_tolerance_sin configurations to allow greater leniency if cameras have heavily unsynchronized local clocks.
Missing Crossing Output Rows
Check --phase 3 to verify homography projection correctly lands people on the virtual grid. Confirm edges.json segment coordinates physically break the tracking path on the map overlay. Verify YOLO is establishing continuous track_id values per camera feed.