A production-ready, multi-threaded camera monitoring system designed for real-time occupancy tracking and surveillance. Built with Python, OpenCV, and PIL, this system provides enterprise-grade reliability with automatic failover, comprehensive logging, and flexible configuration.
BEFORE USING THIS SYSTEM, PLEASE NOTE:
- 🔒 Privacy Laws: Recording people without consent may be illegal in your jurisdiction. Check local laws before deployment.
- 📋 Signage Required: Post visible notices informing people they are being monitored.
- 🏢 Workplace Monitoring: Obtain proper authorization and inform employees before monitoring workspaces.
- 🔐 Data Protection: Captured images may contain personal data. Implement proper security measures.
- 🗑️ Data Retention: Establish and follow a clear data retention and deletion policy.
- 👤 Access Control: Restrict access to captured images and logs to authorized personnel only.
- ⚡ Camera Access: Ensure no other application is using the camera before running this system.
- 💾 Storage Space: Monitor disk space regularly. System will fail if disk is full.
- 🔌 Power Supply: Use reliable power source. Sudden shutdown may corrupt log files.
- 🌐 Network Cameras: Use secure credentials. Never expose camera streams to public internet.
- 🔄 Long Running Process: This system runs indefinitely. Use
Ctrl+Cto stop gracefully.
This tool is intended for:
- ✅ Occupancy monitoring in authorized spaces
- ✅ Security surveillance with proper authorization
- ✅ Research and development purposes
- ✅ Personal property monitoring
This tool should NOT be used for:
- ❌ Unauthorized surveillance
- ❌ Invasion of privacy
- ❌ Illegal monitoring activities
- ❌ Harassment or stalking
By using this system, you agree to comply with all applicable laws and regulations.
- Features
- Architecture
- Prerequisites
- Installation
- Configuration
- Usage
- Output Structure
- Advanced Configuration
- Performance Optimization
- Troubleshooting
- API Reference
- Contributing
- License
- Multi-Camera Support: Monitor unlimited cameras simultaneously using multi-threaded architecture
- Configurable Intervals: Independent capture intervals for each camera (1-3600 seconds)
- Image Processing Pipeline: Automatic resizing, format conversion, and timestamp overlay
- Dual Logging System: Maintains both CSV and JSON logs for analytics and debugging
- Auto-Reconnection: Intelligent reconnection with exponential backoff on camera failures
- Organized Storage: Hierarchical storage with camera-specific directories
- Real-Time Monitoring: Console output with status updates and error reporting
- Resource Efficient: Optimized memory usage with JPEG compression
- Thread-safe logging operations
- Graceful error handling and recovery
- Support for USB webcams and IP cameras (RTSP/HTTP)
- Cross-platform compatibility (Windows/Linux/macOS)
- Zero-dependency configuration (JSON-based)
- Production-ready daemon mode
╔═══════════════════════════════════════════════════════════════════╗
║ MAIN PROCESS ║
║ ║
║ ┌───────────────────────────────────────────────────────────┐ ║
║ │ Configuration Loader (camera_config.json) │ ║
║ └───────────────────────────────────────────────────────────┘ ║
║ │ ║
║ ▼ ║
║ ┌───────────────────────────────────────────────────────────┐ ║
║ │ Thread Manager (start_system) │ ║
║ └───────────────────────────────────────────────────────────┘ ║
║ │ ║
║ ┌────────────────┼────────────────┐ ║
║ ▼ ▼ ▼ ║
║ ┌────────────┐ ┌────────────┐ ┌────────────┐ ║
║ │ Camera 1 │ │ Camera 2 │ │ Camera N │ ║
║ │ Thread │ │ Thread │ │ Thread │ ║
║ └────────────┘ └────────────┘ └────────────┘ ║
║ │ │ │ ║
║ └────────────────┴────────────────┘ ║
║ │ ║
║ ▼ ║
║ ┌───────────────────────────────────────────────────────────┐ ║
║ │ Image Processing Pipeline │ ║
║ │ │ ║
║ │ Frame Capture → Color Conversion → Resize → │ ║
║ │ Timestamp Overlay → JPEG Compression → Save │ ║
║ └───────────────────────────────────────────────────────────┘ ║
║ │ ║
║ ▼ ║
║ ┌───────────────────────────────────────────────────────────┐ ║
║ │ Logging System │ ║
║ │ │ ║
║ │ CSV Logger │ JSON Logger │ ║
║ │ (capture_log.csv) │ (capture_log.json) │ ║
║ └───────────────────────────────────────────────────────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════════╝
| Component | Function | Thread-Safe |
|---|---|---|
load_camera_config() |
Loads and parses camera configuration from JSON | N/A |
camera_worker() |
Main capture loop for individual camera | ✓ Yes |
save_image() |
Processes and saves frames with overlay | ✓ Yes |
log_capture() |
Writes capture events to CSV and JSON | ✓ Yes |
write_json_log() |
Manages JSON log file operations | ✓ Yes |
start_system() |
Orchestrates all camera threads | ✓ Yes |
- Initialization: Load configuration → Create directories → Initialize CSV log
- Thread Creation: Spawn daemon thread for each camera
- Capture Loop: Read frame → Process → Save → Log → Wait interval
- Error Handling: Detect failure → Log error → Attempt reconnection
- Shutdown: Ctrl+C → Graceful thread termination
- Python: 3.7 or higher (3.9+ recommended)
- Operating System: Windows 10/11, Linux (Ubuntu 18.04+), macOS 10.14+
- RAM: 2GB minimum, 4GB+ recommended for 4+ cameras
- Storage: 10GB+ recommended (depends on retention policy)
- CPU: Multi-core processor recommended for concurrent camera processing
- USB Webcam(s) with UVC support
- IP Camera(s) with RTSP/HTTP streaming capability
- Adequate USB bandwidth for multiple USB cameras
- Network bandwidth: 2-8 Mbps per IP camera
# 1. Clone the repository
git clone https://github.com/palnirupam/occupancy_system.git
cd occupancy_system
# 2. Install dependencies
pip install -r requirements.txt
# 3. Configure cameras (edit camera_config.json)
# 4. Run the system
python main.py# Clone from GitHub
git clone https://github.com/palnirupam/occupancy_system.git
# Navigate to project directory
cd occupancy_system# Create virtual environment
python -m venv venv
# Activate it (Windows)
venv\Scripts\activatepip install -r requirements.txtVerify Installation:
python -c "import cv2; print(f'OpenCV: {cv2.__version__}')"
python -c "import PIL; print(f'Pillow: {PIL.__version__}')"Test if your camera is accessible:
python -c "import cv2; cap = cv2.VideoCapture(0); print('Camera 0:', 'OK' if cap.isOpened() else 'FAILED'); cap.release()"Edit camera_config.json to define your camera setup:
{
"cameras": [
{
"id": "room_101",
"source": 0,
"interval": 30
},
{
"id": "room_102",
"source": 1,
"interval": 60
}
]
}| Parameter | Type | Description | Example Values |
|---|---|---|---|
id |
string | Unique camera identifier (alphanumeric, underscores, hyphens) | "room_101", "entrance_cam" |
source |
int/string | Camera source index or stream URL | 0, 1, "rtsp://..." |
interval |
int | Capture interval in seconds (1-3600) | 30, 60, 300 |
USB Webcams:
{
"id": "webcam_primary",
"source": 0,
"interval": 30
}IP Cameras (RTSP):
{
"id": "ip_cam_lobby",
"source": "rtsp://admin:password@192.168.1.100:554/stream1",
"interval": 60
}IP Cameras (HTTP/MJPEG):
{
"id": "ip_cam_parking",
"source": "http://192.168.1.101:8080/video",
"interval": 45
}Video File (for testing):
{
"id": "test_video",
"source": "test_footage.mp4",
"interval": 5
}- Naming Convention: Use descriptive IDs that indicate location or purpose
- Interval Selection:
- High-traffic areas: 15-30 seconds
- Low-traffic areas: 60-300 seconds
- Storage-constrained: 300+ seconds
- Camera Ordering: List cameras in priority order (critical cameras first)
- Testing: Start with one camera, then scale up
Run with console output:
python main.pyExpected Output:
Starting camera room_101
Starting camera room_102
Occupancy Monitoring System Running...
room_101 captured at 2026-03-28_10-30-15
room_102 captured at 2026-03-28_10-31-20
Press Ctrl+C in the terminal to stop.
project-root/
├── images/ # Captured images
│ ├── room_101/
│ │ ├── 2026-03-28_10-30-15.jpg
│ │ ├── 2026-03-28_10-30-45.jpg
│ │ └── 2026-03-28_10-31-15.jpg
│ └── room_102/
│ ├── 2026-03-28_10-31-20.jpg
│ └── 2026-03-28_10-32-20.jpg
├── logs/ # System logs
│ ├── capture_log.csv
│ └── capture_log.json
├── camera_config.json # Configuration
├── main.py # Main application
├── requirements.txt # Dependencies
└── README.md # Documentation
Naming Convention: YYYY-MM-DD_HH-MM-SS.jpg
Image Properties:
- Format: JPEG
- Resolution: 640x480 pixels (configurable)
- Quality: 70% compression (configurable)
- Color Space: RGB
- Overlay: Camera ID + timestamp (top-left corner)
Example Image Metadata:
Filename: 2026-03-28_10-30-15.jpg
Size: ~50-150 KB (depends on content and quality)
Dimensions: 640x480
Text Overlay: "room_101 2026-03-28_10-30-15"
Schema:
timestamp,camera_id,image_path,statusExample Data:
timestamp,camera_id,image_path,status
2026-03-28_10-30-15,room_101,images/room_101/2026-03-28_10-30-15.jpg,OK
2026-03-28_10-30-45,room_101,images/room_101/2026-03-28_10-30-45.jpg,OK
ERROR,room_102,None,CAMERA_ERROR
2026-03-28_10-31-20,room_102,images/room_102/2026-03-28_10-31-20.jpg,OKStatus Values:
OK: Successful capture and saveSAVE_FAILED: Image capture succeeded but save failedCAMERA_ERROR: Camera access or frame capture error
Use Cases:
- Import into Excel/Google Sheets for analysis
- Process with pandas for data science workflows
- Generate reports and statistics
Schema:
[
{
"timestamp": "string (YYYY-MM-DD_HH-MM-SS)",
"camera_id": "string",
"image_path": "string",
"status": "string (OK|SAVE_FAILED|CAMERA_ERROR)"
}
]Example Data:
[
{
"timestamp": "2026-03-28_10-30-15",
"camera_id": "room_101",
"image_path": "images/room_101/2026-03-28_10-30-15.jpg",
"status": "OK"
},
{
"timestamp": "2026-03-28_10-30-45",
"camera_id": "room_101",
"image_path": "images/room_101/2026-03-28_10-30-45.jpg",
"status": "OK"
}
]Use Cases:
- Parse with
jqfor command-line analysis - Import into NoSQL databases
- Process with JavaScript/Node.js applications
- API integration
# Count total captures
(Get-Content logs/capture_log.csv).Count - 1
# Count captures for specific camera
(Select-String -Path logs/capture_log.csv -Pattern "room_101").Count
# Check for errors
Select-String -Path logs/capture_log.csv -Pattern "ERROR|FAILED"Edit line 52 in main.py:
# Current
img = img.resize((640, 480))
# High resolution
img = img.resize((1920, 1080))
# Low resolution (storage-efficient)
img = img.resize((320, 240))Edit line 57 in main.py:
# Current (balanced)
img.save(image_path, quality=70)
# High quality (larger files)
img.save(image_path, quality=95)
# Low quality (smaller files)
img.save(image_path, quality=40)Edit line 44 in main.py:
# Current format: 2026-03-28_10-30-15
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
# Unix timestamp: 1711619415
timestamp = str(int(time.time()))
# ISO 8601: 2026-03-28T10:30:15
timestamp = datetime.now().isoformat()Estimated Storage Requirements:
| Cameras | Interval | Quality | Daily Storage | Monthly Storage |
|---|---|---|---|---|
| 1 | 30s | 70% | ~100 MB | ~3 GB |
| 4 | 30s | 70% | ~400 MB | ~12 GB |
| 10 | 60s | 50% | ~500 MB | ~15 GB |
Symptoms:
room_101 failed to open
Diagnosis:
# Test camera access
python -c "import cv2; cap = cv2.VideoCapture(0); print(cap.isOpened()); cap.release()"Solutions:
- Verify camera is connected via Device Manager (Windows)
- Check if another application is using the camera
- Try different source indices (0, 1, 2)
- For IP cameras, verify network connectivity
Symptoms:
room_101 frame error, reconnecting...
Solutions:
- Check USB cable quality and connection
- Reduce capture interval to avoid overwhelming the camera
- Check system resources via Task Manager
- Update camera drivers
Symptoms:
Image save error: [Errno 28] No space left on device
Solutions:
- Check disk space via File Explorer or
Get-PSDrive(PowerShell) - Verify write permissions for the
imagesdirectory - Reduce image quality or resolution
Symptoms:
- System slowdown
- Dropped frames
Solutions:
- Increase capture intervals
- Reduce image resolution
- Limit number of concurrent cameras
Symptoms:
- System becomes slow over time
Solutions:
- Restart the system periodically
- Reduce number of cameras or increase intervals
- Check Task Manager for memory usage
Add print statements in main.py for more detailed output:
# In camera_worker function, add more logging
print(f"{camera_id} - Frame captured successfully")
print(f"{camera_id} - Processing image...")
print(f"{camera_id} - Image saved to {image_path}")Loads camera configuration from JSON file.
Returns: list[dict] - List of camera configuration objects
Raises:
FileNotFoundError- If camera_config.json doesn't existJSONDecodeError- If JSON is malformed
Example:
cameras = load_camera_config()
# [{'id': 'room_101', 'source': 0, 'interval': 30}, ...]Processes and saves a camera frame with timestamp overlay.
Parameters:
frame(numpy.ndarray): OpenCV frame in BGR formatcamera_id(str): Camera identifier for folder organization
Returns: tuple[str|None, str|None]
image_path(str): Path to saved image, or None on failuretimestamp(str): Formatted timestamp string, or None on failure
Processing Pipeline:
- Convert BGR to RGB color space
- Resize to 640x480 pixels
- Add text overlay with camera ID and timestamp
- Compress and save as JPEG (70% quality)
Example:
ret, frame = cap.read()
image_path, timestamp = save_image(frame, "room_101")Writes capture event to both CSV and JSON logs.
Parameters:
timestamp(str): Capture timestamp or "ERROR"camera_id(str): Camera identifierimage_path(str): Path to saved image or "None"status(str): Capture status ("OK", "SAVE_FAILED", "CAMERA_ERROR")
Side Effects:
- Appends row to CSV log
- Appends entry to JSON log array
Thread Safety: Uses file locking (implicit via Python's file operations)
Main worker function for individual camera thread.
Parameters:
camera(dict): Camera configuration object with keys:id,source,interval
Behavior:
- Infinite loop capturing frames at specified interval
- Automatic reconnection on failure (5-second delay)
- Error logging for all failure modes
- Graceful degradation on persistent errors
Example:
camera = {"id": "room_101", "source": 0, "interval": 30}
thread = threading.Thread(target=camera_worker, args=(camera,))
thread.start()Initializes and starts all camera threads.
Behavior:
- Loads configuration
- Spawns daemon thread for each camera
- Enters infinite monitoring loop
- Blocks until interrupted (Ctrl+C)
Thread Management:
- All threads are daemon threads (exit when main thread exits)
- No explicit thread joining required
Location: main.py, line 52
# Current
img = img.resize((640, 480))
# High resolution
img = img.resize((1920, 1080))
# Low resolution (storage-efficient)
img = img.resize((320, 240))Location: main.py, line 57
# Current (balanced)
img.save(image_path, quality=70)
# High quality (larger files)
img.save(image_path, quality=95)
# Low quality (smaller files)
img.save(image_path, quality=40)Location: main.py, line 44
# Current format: 2026-03-28_10-30-15
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
# Unix timestamp: 1711619415
timestamp = str(int(time.time()))
# ISO 8601: 2026-03-28T10:30:15
timestamp = datetime.now().isoformat()
# Custom: 28-Mar-2026_10h30m15s
timestamp = datetime.now().strftime("%d-%b-%Y_%Hh%Mm%Ss")Edit line 54-55 in main.py:
# Current
draw = ImageDraw.Draw(img)
draw.text((10, 10), f"{camera_id} {timestamp}", fill=(255, 0, 0))
# Change color to white
draw.text((10, 10), f"{camera_id} {timestamp}", fill=(255, 255, 255))
# Change position
draw.text((10, 450), f"{camera_id} {timestamp}", fill=(255, 0, 0)) # Bottom leftContributions are welcome! To contribute:
- Fork the repository
- Create a feature branch (
git checkout -b feature-name) - Commit your changes (
git commit -m 'Add feature') - Push to the branch (
git push origin feature-name) - Open a Pull Request
MIT License - see the LICENSE file for details.
Nirupam Pal
GitHub: @palnirupam
For issues or questions, open an issue on GitHub Issues.
Made with ❤️ by Nirupam Pal