Privacy-focused AI-powered people counting application with multi-camera support.
CountIn is a real-time people counting system that uses browser-based AI detection. It supports three operation modes:
- Standalone Mode - Single camera counting with line/area detection
- Hub Dashboard - Central dashboard to monitor multiple camera stations
- Camera Station - Connect cameras to a hub (sends only count data, no video)
- No video streaming between cameras and hub
- Only count data is transmitted
- All AI processing happens locally in the browser
- Camera stations send only numerical counts
- Hub dashboard aggregates data from multiple camera stations
- QR code pairing for easy setup
- Real-time WebSocket synchronization
- Camera management (rename, delete)
- Connection status monitoring
- Real-time person detection using TensorFlow.js COCO-SSD
- Line-based counting (crossing detection)
- Area-based counting (occupancy detection)
- Bidirectional counting (in/out)
- Session management and data export
- Vite + Vanilla JavaScript
- TensorFlow.js + COCO-SSD for person detection (client-side)
- Canvas API for drawing and visualization
- QRCode.js for pairing
- WebSocket for real-time updates
- FastAPI (Python 3.11)
- PostgreSQL 15
- SQLAlchemy ORM
- WebSocket support
- Pydantic for data validation
- Docker + Docker Compose
- Coolify-ready configuration
- Nginx/Traefik reverse proxy compatible
Hub Dashboard (Browser)
|
| WebSocket (counts only)
|
+-- Camera Station 1 (Browser + Webcam)
| [Local AI Detection]
|
+-- Camera Station 2 (Browser + Webcam)
| [Local AI Detection]
|
+-- Camera Station N (Browser + Webcam)
[Local AI Detection]
All communication flows through the FastAPI backend, but only count data is transmitted - never video.
- Set Environment Variables:
# Database
POSTGRES_USER=countin
POSTGRES_PASSWORD=<secure-password>
POSTGRES_DB=countin
DATABASE_URL=postgresql://countin:<password>@db:5432/countin
# Application
ENV=production
BACKEND_CORS_ORIGINS=https://countin.ignacio.tech,https://*.countin.ignacio.tech
VITE_API_URL=https://api.countin.ignacio.tech
# Coolify Service FQDNs
SERVICE_FQDN_FRONTEND=countin.ignacio.tech
SERVICE_FQDN_BACKEND=api.countin.ignacio.tech-
Configure DNS/Routing:
countin.ignacio.tech→ Frontend service (port 3000)api.countin.ignacio.tech→ Backend service (port 8000)
-
Deploy:
- Use
docker-compose.ymlin Coolify - Services will build and start automatically
- Database tables are created on first backend startup
- Use
# Clone repository
git clone https://github.com/yourusername/countin.git
cd countin
# Start all services
docker-compose up -d
# Access:
# - Frontend: http://localhost:3000
# - Backend API: http://localhost:8000
# - API Docs: http://localhost:8000/docs
# - Database: localhost:5432- Open the application and select "Standalone" mode
- Grant camera permissions
- Draw counting lines or areas on the video
- Switch to "Counting" mode to start detection
- Export data from the session
- Open the application and select "Hub Dashboard" mode
- A pairing code will be generated
- Share this code or QR code with camera stations
- Monitor all connected cameras in real-time
- Manage cameras (rename, delete) from the dashboard
- Open the application on a device with a camera
- Select "Camera Station" mode
- Enter the hub's pairing code
- Provide a camera name and location
- Grant camera permissions and start counting
- Counts are sent to the hub in real-time
POST /api/v1/sessions- Create counting sessionGET /api/v1/sessions- List all sessionsGET /api/v1/sessions/{id}- Get session detailsGET /api/v1/sessions/{id}/stats- Get session statisticsPATCH /api/v1/sessions/{id}- Update sessionDELETE /api/v1/sessions/{id}- Delete session
POST /api/v1/lines- Create counting line or areaGET /api/v1/lines/session/{session_id}- Get lines for sessionDELETE /api/v1/lines/{line_id}- Delete line
POST /api/v1/events- Log crossing eventGET /api/v1/events/session/{session_id}- Get session events
POST /api/v1/hubs- Create hub dashboard sessionGET /api/v1/hubs- List hub sessionsGET /api/v1/hubs/{hub_id}- Get hub detailsGET /api/v1/hubs/code/{pairing_code}- Find hub by codeGET /api/v1/hubs/{hub_id}/stats- Get aggregated hub statistics
POST /api/v1/cameras/pair- Pair camera with hubGET /api/v1/cameras/hub/{hub_id}- List hub camerasGET /api/v1/cameras/{camera_id}- Get camera detailsPATCH /api/v1/cameras/{camera_id}- Update camera (e.g., rename)DELETE /api/v1/cameras/{camera_id}- Delete cameraPOST /api/v1/cameras/heartbeat- Update camera connection statusPOST /api/v1/cameras/{camera_id}/increment- Increment count
WS /api/v1/ws/hub/{hub_id}- Hub dashboard real-time updatesWS /api/v1/ws/camera/{camera_id}- Camera station connection
Full interactive API documentation available at /docs when running.
Backend:
DATABASE_URL- PostgreSQL connection stringENV- Environment (development/production)BACKEND_CORS_ORIGINS- Allowed CORS origins (comma-separated)
Frontend (build-time):
VITE_API_URL- Backend API URL (embedded during build)
PostgreSQL tables are automatically created on backend startup using SQLAlchemy migrations.
Tables:
sessions- Counting sessionscounting_lines- Line/area definitionscrossing_events- Individual crossing eventscount_snapshots- Periodic count snapshotshub_sessions- Hub dashboard sessionscamera_stations- Connected camera stations
Symptom: "Cross-Origin Request Blocked" errors in browser console
Solution:
- Ensure
BACKEND_CORS_ORIGINSincludes your frontend domain - Verify both frontend and backend URLs use HTTPS in production
- Check that
allow_origin_regexin backend matches subdomain pattern
Symptom: Backend fails to start with database connection error
Solution:
- Verify
DATABASE_URLis correct - Ensure PostgreSQL container is running (
docker ps) - Check database logs for connection issues
- Verify database credentials match between services
Symptom: Camera access denied or not working
Solution:
- HTTPS is required for camera access (except localhost)
- Grant camera permissions when browser prompts
- Check browser camera settings
- Try a different browser if issues persist
Symptom: Camera cannot connect to hub with pairing code
Solution:
- Verify pairing code is entered correctly (6 characters)
- Check that hub session is still active
- Ensure backend WebSocket connection is working
- Check browser console for connection errors
Symptom: Frontend build fails or shows blank page
Solution:
- Verify all npm dependencies are installed
- Check that
VITE_API_URLis set correctly during build - Clear browser cache and hard reload
- Check browser console for JavaScript errors
- Verify Vite build completed successfully
countin/
├── backend/
│ ├── app/
│ │ ├── api/v1/endpoints/ # API route handlers
│ │ ├── core/ # Config and database
│ │ ├── models/ # SQLAlchemy models
│ │ └── schemas/ # Pydantic schemas
│ ├── Dockerfile
│ └── requirements.txt
├── frontend/
│ ├── src/
│ │ ├── components/ # UI components
│ │ ├── services/ # API and camera services
│ │ ├── main.js # Main application
│ │ ├── styles.css # Styling
│ │ └── rfdetr-adapter.js # AI detection adapter
│ ├── index.html
│ ├── Dockerfile
│ └── package.json
├── docker-compose.yml # Production compose
└── README.md
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
- All video processing happens locally in the browser
- Only count data is transmitted over the network
- No video storage or transmission to backend
- HTTPS required for production deployments
- Secure WebSocket connections (WSS)
- AI detection runs at ~3 FPS for optimal performance
- Lazy loading of TensorFlow.js models (loaded on demand)
- WebSocket for efficient real-time updates
- Database indexes for fast queries
- Minimal memory footprint per camera
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- Mobile browsers (iOS Safari, Chrome Mobile)
Requires:
- WebRTC support for camera access
- Canvas API
- WebSocket support
- ES6+ JavaScript
GNU Affero General Public License v3.0 (AGPL-3.0)
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/
For issues, questions, or contributions, please open an issue on GitHub.