Real-time viewer for 2,500+ Taiwan Highway Bureau CCTV feeds with YOLOv8 vehicle detection, tracking, database storage, WebSocket updates, and Stream Out integration.
- YOLOv8 Vehicle Detection: Cars, buses, trucks at 60% confidence with bounding boxes
- Object Tracking: Multi-object tracking with unique IDs and vehicle counting
- Database: SQLite storage for detections, tracks, and historical data
- WebSocket: Real-time push notifications for vehicle detections
- Stream Out Integration:
- CoT (Cursor on Target) via UDP for TAK/ATAK
- Lattice (Anduril) via REST API
- ChatSurfer (NRO) via REST API
- Performance Optimizations:
- Concurrent ingestion with connection pooling
- Selective YOLO processing (skip unchanged frames)
- Async operations throughout
- In-memory caching
- REST API: Comprehensive endpoints for feeds, stats, search, and map data
- Operational Monitoring:
- Health check endpoints for all system components
- Prometheus metrics export for performance monitoring
- Structured JSON logging for debugging
- Circuit breaker pattern for graceful degradation
- Alert management system for critical events
- Real-time operational dashboard UI
-
Grid View (index.html)
- Real-time vehicle detection badges ("TAI OCCUPIED")
- Auto-refresh thumbnails (2 second interval)
- Filter by status (All/Working Only/Vehicles Detected)
- Search by location/road/ID
- Stream Out configuration UI
- WebSocket real-time updates
-
Map View (map.html)
- Interactive Leaflet map with all camera locations
- Color-coded markers (green=online, red=vehicles, gray=offline)
- Marker clustering for performance
- Real-time marker updates via WebSocket
- Filter by road name and vehicle detection
- Click markers for live snapshots and details
-
Single Feed View (feed.html)
- High-frequency updates (500ms)
- Full camera details and location
- Vehicle detection indicators
-
Monitoring Dashboard (monitoring.html)
- Real-time system health visualization (auto-refresh every 5 seconds)
- Component status monitoring (Database, YOLO, HTTP client, WebSocket, Cache, Tracker, Feed source)
- System metrics (CPU, memory, disk usage)
- Feed statistics with availability progress bars
- Performance metrics (cache size, active connections, circuit breaker state)
- Database statistics (detection count, track count)
- Recent alerts display with severity levels
- Database reset functionality
- Dark theme responsive UI
┌─────────────────┐ ┌────────────────────────────┐ ┌─────────────────┐
│ Taiwan │ │ FastAPI Backend (8001) │ │ Web Client │
│ Highway │────────▶│ - YOLOv8n Detector │◀───────▶│ (Browser) │
│ Bureau │ HTTPS │ - Object Tracker │ HTTP │ Port 8000 │
│ (2500 feeds) │ │ - SQLite Database │ + WS │ │
└─────────────────┘ │ - WebSocket Manager │ └─────────────────┘
│ - Memory Cache │
└────────────────────────────┘
│
│ Concurrent Ingestion
│ (Batches of 240)
│
▼
┌──────────────────┐
│ SQLite DB │
│ - detections │
│ - tracks │
│ - feeds │
└──────────────────┘
│
▼
┌──────────────────┐
│ Stream Out │
│ (Optional) │──UDP──▶ TAK/ATAK (CoT)
│ │
│ │──HTTPS─▶ Lattice (Anduril)
│ │
│ │──HTTPS─▶ ChatSurfer (NRO)
└──────────────────┘
./start.shThen open: http://localhost:8000
The startup script will:
- Create/activate virtual environment
- Install all dependencies (including YOLOv8)
- Start backend server on port 8001
- Start frontend server on port 8000
- Handle graceful shutdown with Ctrl+C
Access the monitoring dashboard: http://localhost:8000/monitoring.html
Terminal 1 - Backend:
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python backend/main.pyTerminal 2 - Frontend:
cd client
python3 -m http.server 8000Then open: http://localhost:8000
Comprehensive health check for all system components
Response:
{
"status": "healthy",
"timestamp": "2025-11-21T10:30:00Z",
"components": [
{
"component": "database",
"status": "healthy",
"message": "Database connection healthy",
"latency_ms": 1.2
},
{
"component": "yolo_model",
"status": "healthy",
"message": "YOLO model loaded and ready"
}
],
"system": {
"cpu_percent": 25.3,
"memory": {
"total_gb": 16.0,
"used_gb": 8.5,
"percent": 53.1
},
"disk": {
"total_gb": 500.0,
"used_gb": 250.0,
"percent": 50.0
}
}
}Components monitored:
- Database connectivity & performance
- YOLO model availability
- HTTP client health
- WebSocket manager status
- Cache health & size
- Vehicle tracker status
- Feed source availability
Kubernetes liveness probe - simple check if server is running
Response: {"status": "alive", "timestamp": "..."}
Kubernetes readiness probe - check if system is ready for traffic
Response: {"status": "ready", "timestamp": "..."} (200 if ready, 503 if not)
Prometheus metrics export endpoint for monitoring and alerting
Metrics categories:
- Detection metrics:
cctv_detections_total,cctv_detection_confidence,cctv_tracks_total - Performance metrics:
cctv_yolo_inference_seconds,cctv_feed_fetch_seconds,cctv_cycle_duration_seconds - System metrics:
cctv_feeds_total,cctv_feeds_online,cctv_cache_size_bytes,cctv_active_websockets - Error metrics:
cctv_errors_total,cctv_feed_failures_total
Example queries:
# Feed availability percentage
(cctv_feeds_online / cctv_feeds_total) * 100
# Detection rate per minute
rate(cctv_detections_total[1m])
# P95 YOLO inference time
histogram_quantile(0.95, rate(cctv_yolo_inference_seconds_bucket[5m]))
Operational dashboard data endpoint
Response:
{
"uptime_seconds": 3600,
"uptime_human": "1h 0m",
"feeds": {
"total": 2402,
"online": 1876,
"offline": 526,
"online_percentage": 78.1,
"with_vehicles": 145
},
"cache": {
"size_mb": 450.5,
"items": 2402,
"avg_size_kb": 192.0
},
"components": {
"yolo_model": "healthy",
"database": "healthy",
"tracker": "healthy",
"websocket": "healthy"
},
"websocket": {
"active_connections": 2
},
"circuit_breakers": {
"feed_fetcher": {
"state": "closed",
"failure_count": 0
}
},
"alerts": {
"recent": [
{
"timestamp": "2025-11-21T10:30:00Z",
"type": "feed_source_degraded",
"severity": "warning",
"message": "Feed availability below threshold"
}
]
},
"database": {
"detections_count": 125000,
"tracks_count": 45000
}
}Returns all feed metadata, online status, and vehicle detection status
Response:
{
"feeds": [
{
"id": "CCTV-14-0620-009-002",
"streamUrl": "https://...",
"imageUrl": "https://.../snapshot",
"roadName": "台62線",
"locationMile": "9K+020",
"lat": "25.10529",
"lon": "121.7321",
"direction": "W",
"description": "..."
}
],
"status": {
"CCTV-14-0620-009-002": true
},
"vehicleDetected": {
"CCTV-14-0620-009-002": true
},
"lastUpdate": 1699999999.0
}Returns cached JPEG snapshot for a specific feed (with YOLO bounding boxes if vehicles detected)
Returns live MJPEG stream (for future use)
Returns system statistics including detection performance
Response:
{
"totalFeeds": 2402,
"cachedFeeds": 2402,
"workingFeeds": 1876,
"offlineFeeds": 526,
"vehiclesDetectedFeeds": 145,
"lastUpdate": 1699999999.0,
"cacheSize": 450.5,
"detectionStats": {
"processed": 1876,
"skipped_unchanged": 430,
"skipped_selective": 620
},
"websocket": {
"connections": 2,
"total_messages": 1234
}
}Search feeds with filters
Parameters:
road(optional): Road name filterhas_vehicles(optional): true/falselat_min,lat_max,lon_min,lon_max(optional): Bounding box
Response:
{
"query": {
"road": "國道1號",
"has_vehicles": true
},
"count": 42,
"feeds": [ /* enriched feed objects with status and vehicleDetected */ ]
}Get all feeds as GeoJSON for map display
Response:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [121.7321, 25.10529]
},
"properties": {
"id": "CCTV-14-0620-009-002",
"roadName": "台62線",
"isWorking": true,
"hasVehicles": false
}
}
]
}Configure Stream Out integration
Request (CoT):
{
"enabled": true,
"format": "cot",
"ip": "127.0.0.1",
"port": 8087
}Request (Lattice):
{
"enabled": true,
"format": "lattice",
"latticeUrl": "https://your-env.lattice.anduril.com",
"latticeToken": "your-env-token",
"latticeSandboxToken": "your-sandbox-token",
"latticeIntegration": "taiwan-cctv"
}Request (ChatSurfer):
{
"enabled": true,
"format": "chatsurfer",
"chatsurferSession": "your-session-cookie",
"chatsurferRoom": "your-room-name",
"chatsurferNickname": "CCTV_Bot",
"chatsurferDomain": "chatsurferxmppunclass"
}Get current Stream Out configuration
Reset database - clear all detections and tracks
Request: POST with empty body
Response:
{
"status": "success",
"message": "Database reset successfully",
"deleted": {
"detections": 125000,
"tracks": 45000
}
}Note: This triggers a warning alert in the alert system
Real-time updates for vehicle detections and feed status
Client → Server Messages:
{"action": "subscribe", "feed_id": "CCTV-14-0620-009-002"}
{"action": "unsubscribe", "feed_id": "CCTV-14-0620-009-002"}
{"action": "ping"}Server → Client Messages:
// Vehicle detection
{
"type": "detection",
"feed_id": "CCTV-14-0620-009-002",
"timestamp": "2025-11-12T21:30:45.123Z",
"data": {
"vehicle_count": 3,
"vehicle_types": ["car", "car", "truck"],
"tracked_vehicles": [...],
"track_counts": {"car": 2, "truck": 1}
}
}
// Feed status update
{
"type": "feed_status",
"feed_id": "CCTV-14-0620-009-002",
"timestamp": "2025-11-12T21:30:45.123Z",
"is_working": true,
"has_vehicles": true
}
// Server heartbeat (every 30s)
{
"type": "heartbeat",
"timestamp": "2025-11-12T21:30:45.123Z",
"connections": 2
}
// Stats broadcast
{
"type": "stats",
"timestamp": "2025-11-12T21:30:45.123Z",
"data": { /* stats object */ }
}Send vehicle detection events to TAK/ATAK systems via UDP.
Config: Format, IP, Port
Message: XML event with camera location, metadata, timestamp, video link
Example:
<event version="2.0" uid="TrafficCam-CCTV-14-0620-009-002" type="a-u-G" time="..." start="..." stale="...">
<point lat="25.10529" lon="121.7321" hae="0" ce="50" le="0"/>
<detail>
<contact callsign="TrafficCam-CCTV-14-0620-009-002"/>
<remarks>台62線 at 9K+020 - Vehicles detected</remarks>
<link url="http://localhost:8001/api/feeds/CCTV-14-0620-009-002/snapshot"/>
</detail>
</event>Publish vehicle track entities to Lattice platform.
Config: Format, URL, Environment Token, Sandbox Token (for sandboxes), Integration Name
Entity: Camera ID, location, VEHICLE platform type, 1-hour expiry
Note: Sandboxes need two tokens (Authorization + anduril-sandbox-authorization headers)
Send vehicle detection alerts to ChatSurfer chat rooms.
Config: Format, Session Cookie, Room Name, Nickname, Domain ID
Message format:
[VEHICLE DETECTION]
Road: 國道1號
Location: 12K+500
Camera: CCTV-42-0020-162-001
Coords: 25.0478, 121.5319
Time: 2026-02-04 15:30:22 UTC
Snapshot: http://192.168.1.100:8001/api/feeds/CCTV-42-0020-162-001/snapshot
Setup:
- Get your ChatSurfer SESSION cookie from browser dev tools
- Identify the room name you want to post to
- Configure via the Stream Out panel in the UI or POST to
/api/stream/config
Note: The snapshot URL must be accessible from where ChatSurfer users are located
- Model: YOLOv8n (nano - fast inference)
- Classes: Car, Bus, Truck (COCO dataset IDs: 2, 5, 7)
- Confidence: 60% minimum
- Min box size: 30px (filters false positives from distant objects)
- Processing: ~50-100ms per image
- Optimizations:
- Skip detection on unchanged frames (hash-based)
- Selective skip on empty feeds (every 2nd cycle)
- Async processing via thread pool
- Tracker: Custom IoU-based tracker
- Features:
- Unique track IDs for each vehicle
- Vehicle counting per class
- Track persistence across frames
- Confirmed tracks (minimum 3 hits)
- Storage: Tracks saved to database with timestamps
feeds
- id, road_name, location_mile, lat, lon, direction, last_online
detections
- id, feed_id, timestamp, vehicle_count, detection_data (JSON)
vehicle_tracks
- id, feed_id, track_id, first_seen, last_seen, vehicle_class, speed_estimate
Edit config.yaml to customize:
detection:
enabled: true
confidence_threshold: 0.6
min_box_size: 30
vehicle_classes: [2, 5, 7] # car, bus, truck
database:
enabled: true
path: "./data/detections.db"
tracking:
enabled: true
max_age: 30
min_hits: 3
iou_threshold: 0.3
websocket:
enabled: true
heartbeat_interval: 30
performance:
batch_size: 240
max_connections: 100
selective_skip_interval: 2The system provides comprehensive health monitoring for all components:
- Database: Connection health, query latency, connection count
- YOLO Model: Model availability and readiness
- HTTP Client: Feed fetcher operational status
- WebSocket Manager: Connection manager health
- Cache: Memory usage and size limits
- Vehicle Tracker: Tracking system status
- Feed Source: Feed availability and online percentage
Export metrics for monitoring with Prometheus/Grafana:
# Scrape metrics
curl http://localhost:8001/metrics
# Example Prometheus config
scrape_configs:
- job_name: 'cctv-viewer'
static_configs:
- targets: ['localhost:8001']
metrics_path: '/metrics'
scrape_interval: 15sKey metrics to monitor:
- Feed availability:
(cctv_feeds_online / cctv_feeds_total) * 100→ Target: >80% - Detection latency:
histogram_quantile(0.95, cctv_yolo_inference_seconds_bucket)→ Target: <200ms - Error rate:
rate(cctv_errors_total[5m])→ Target: <5% - Cache size:
cctv_cache_size_bytes / 1024^3→ Target: <1GB
All logs are output in JSON format for easy parsing:
# View logs with jq
tail -f logs/cctv.log | jq
# Filter by level
tail -f logs/cctv.log | jq 'select(.level == "ERROR")'
# Watch specific component
tail -f logs/cctv.log | jq 'select(.context.component == "database")'Protects against cascading failures:
- Monitors feed fetcher operations
- Opens after 10 consecutive failures
- 5-minute recovery timeout before retrying
- States: closed (normal), open (disabled), half_open (testing)
Check state: curl http://localhost:8001/api/operational/status | jq '.circuit_breakers'
Tracks critical system events with 5-minute cooldown:
Alert types:
startup_failure- Component failed to start (critical)database_init_failure- Database initialization error (critical)high_error_rate- Error rate threshold exceeded (error)feed_source_degraded- Too many feeds offline (warning)cache_overflow- Cache size exceeds limits (warning)
View recent alerts: curl http://localhost:8001/api/operational/status | jq '.alerts.recent'
Access the visual monitoring dashboard at http://localhost:8000/monitoring.html
Features:
- Real-time component health visualization
- System metrics (CPU, memory, disk)
- Feed statistics with animated progress bars
- Performance metrics
- Database statistics
- Recent alerts display
- Database reset button
- Auto-refresh every 5 seconds
# Check overall system status
curl http://localhost:8001/health | jq '.status'
# Get unhealthy components
curl http://localhost:8001/health | jq '.components[] | select(.status != "healthy")'
# Check feed availability percentage
curl http://localhost:8001/api/operational/status | jq '.feeds.online_percentage'
# Watch system health (refreshes every 2 seconds)
watch -n 2 'curl -s http://localhost:8001/health | jq ".status, .system"'- Feeds: 2,402 Taiwan highway camera feeds
- Backend: Python 3.8+, FastAPI, YOLOv8, SQLAlchemy, aiosqlite
- Frontend: Vanilla JavaScript, Leaflet.js for maps
- Memory: ~450 MB for cached JPEGs
- Refresh Rate: 2 seconds (grid), 0.5 seconds (single feed)
- Detection Rate: ~1876 feeds processed per cycle (~20-30 seconds)
- Observability: Prometheus metrics, structured logging, health checks, circuit breakers
# Install dependencies manually
python3 -m pip install -r requirements.txt
# Run with verbose logging
python3 backend/main.py- Check backend console for errors
- Verify network connectivity to Taiwan
- Some feeds may actually be offline (normal - typically ~500/2400)
- Check
/api/statsor/healthendpoint for system health - Check feed availability:
curl http://localhost:8001/api/operational/status | jq '.feeds' - View monitoring dashboard: http://localhost:8000/monitoring.html
- Check backend logs for "YOLO model loaded successfully"
- Ensure PyTorch and dependencies installed correctly
- Detection requires confidence ≥0.6 (60%) and minimum box size of 30 pixels
- Only detects cars, buses, and trucks (motorcycles excluded)
- Some cameras may not have vehicles in frame
- Check browser console for WebSocket errors
- Verify backend is running on port 8001
- Check firewall/proxy settings
- WebSocket URL:
ws://localhost:8001/ws
- Backend logs show actual fetch time + YOLO processing time
- Network latency to Taiwan servers affects speed
- YOLO adds ~50-100ms per image
- Adjust
REFRESH_INTERVALin frontend code if needed - Check
detectionStatsin/api/statsfor performance metrics
- Each cached JPEG is ~200 KB
- 2,400 feeds = ~480 MB baseline
- This is normal and expected
- Database grows over time (detections history)
- Monitor cache size:
curl http://localhost:8001/api/operational/status | jq '.cache' - Check system memory:
curl http://localhost:8001/health | jq '.system.memory'
- Check browser console for "Map: WebSocket connected"
- Markers update in real-time when vehicles detected
- Try refreshing the page to reconnect WebSocket
videoviewer/
├── backend/
│ ├── main.py # FastAPI server + YOLO + WebSocket + monitoring
│ ├── database.py # SQLAlchemy models and DB manager
│ ├── tracker.py # Multi-object tracking
│ ├── websocket_manager.py # WebSocket connection handler
│ └── observability.py # Health checks, metrics, logging, alerts
├── client/
│ ├── index.html # Grid view with vehicle detection
│ ├── map.html # Interactive map view
│ ├── feed.html # Single feed detailed view
│ └── monitoring.html # Operational monitoring dashboard
├── data/
│ └── detections.db # SQLite database (auto-created)
├── logs/
│ └── cctv.log # Structured JSON logs (auto-created)
├── venv/ # Python virtual environment
├── config.yaml # Configuration file
├── requirements.txt # Python dependencies
├── start.sh # Startup script
├── IMPLEMENTATION_ROADMAP.md # Feature roadmap
├── OPTIMIZATIONS.md # Performance optimizations guide
└── README.md # This file