[Pongsatorn K. 2025/08/30] worker ver 1.0.0 #4

Merged
taipong merged 12 commits from feat/tracker into dev 2025-08-30 15:26:58 +00:00
6 changed files with 0 additions and 7586 deletions
Showing only changes of commit 338bbb410e - Show all commits

View file

@ -1,172 +0,0 @@
# Multi-Camera Simulation Guide
This guide explains how to simulate 4 cameras using a single webcam for testing the LPR integration in a realistic multi-camera environment.
## 🎯 Purpose
Simulate 4 real-world cameras to test:
- Multiple camera streams with car detection
- LPR integration across different cameras
- Session management for multiple simultaneous detections
- Database updates from different camera sources
## 🚀 Quick Start
### Windows
```bash
# Option 1: Use batch file (opens 4 terminal windows)
start_4_cameras.bat
# Option 2: Manual start (run each in separate terminal)
python multi_camera_simulator.py 1
python multi_camera_simulator.py 2
python multi_camera_simulator.py 3
python multi_camera_simulator.py 4
```
### Linux/macOS
```bash
# Option 1: Use shell script (opens 4 terminal windows)
./start_4_cameras.sh
# Option 2: Manual start (run each in separate terminal)
python3 multi_camera_simulator.py 1
python3 multi_camera_simulator.py 2
python3 multi_camera_simulator.py 3
python3 multi_camera_simulator.py 4
```
## 📡 Camera URLs
Each simulated camera will have unique URLs:
| Camera | HTTP Snapshot | RTSP Stream | Visual ID |
|--------|--------------|-------------|-----------|
| 1 | `http://10.101.1.4:8080/snapshot` | `rtsp://10.101.1.4:8550/stream` | Yellow border |
| 2 | `http://10.101.1.4:8081/snapshot` | `rtsp://10.101.1.4:8551/stream` | Magenta border |
| 3 | `http://10.101.1.4:8082/snapshot` | `rtsp://10.101.1.4:8552/stream` | Green border |
| 4 | `http://10.101.1.4:8083/snapshot` | `rtsp://10.101.1.4:8553/stream` | Blue border |
## 🎨 Visual Differentiation
Each camera adds visual branding to help distinguish streams:
- **Camera 1**: Yellow border + "CAMERA 1" text
- **Camera 2**: Magenta border + "CAMERA 2" text
- **Camera 3**: Green border + "CAMERA 3" text
- **Camera 4**: Blue border + "CAMERA 4" text
- **All**: Timestamp with camera ID
## 🔧 Configuration Options
### Basic Usage
```bash
python multi_camera_simulator.py <camera_id>
```
### Advanced Options
```bash
python multi_camera_simulator.py 1 --webcam-index 0 --base-port 8080 --rtsp-base-port 8550
python multi_camera_simulator.py 2 --no-rtsp # HTTP only
```
### Parameters
- `camera_id`: Required (1-4)
- `--webcam-index`: Webcam device index (default: 0)
- `--base-port`: Base HTTP port (default: 8080)
- `--rtsp-base-port`: Base RTSP port (default: 8550)
- `--no-rtsp`: Disable RTSP streaming
## 🏗️ CMS Configuration
Configure each camera in your CMS with these settings:
### Camera 1
```
Camera Identifier: webcam-camera-01
Snapshot URL: http://10.101.1.4:8080/snapshot
RTSP URL: rtsp://10.101.1.4:8550/stream
Snapshot Interval: 2000
```
### Camera 2
```
Camera Identifier: webcam-camera-02
Snapshot URL: http://10.101.1.4:8081/snapshot
RTSP URL: rtsp://10.101.1.4:8551/stream
Snapshot Interval: 2000
```
### Camera 3
```
Camera Identifier: webcam-camera-03
Snapshot URL: http://10.101.1.4:8082/snapshot
RTSP URL: rtsp://10.101.1.4:8552/stream
Snapshot Interval: 2000
```
### Camera 4
```
Camera Identifier: webcam-camera-04
Snapshot URL: http://10.101.1.4:8083/snapshot
RTSP URL: rtsp://10.101.1.4:8553/stream
Snapshot Interval: 2000
```
## 🧪 Testing LPR Integration
With 4 cameras running, you can test:
1. **Multiple simultaneous detections** - Cars detected by different cameras
2. **Session isolation** - Each camera gets separate session IDs
3. **LPR processing** - License plate results for different cameras
4. **Database updates** - Multiple car records with different session IDs
### Test Scenario
1. Start all 4 cameras
2. Connect all 4 to detector worker
3. Show car to webcam (all cameras see it)
4. Each camera should get separate session ID
5. Send LPR results for each session ID
6. Verify database updates for each camera
## 🔧 Troubleshooting
### Port Already in Use
If you see port conflicts:
```bash
# Check what's using the port
netstat -an | findstr :8080
netstat -an | findstr :8550
# Use different base ports
python multi_camera_simulator.py 1 --base-port 9080 --rtsp-base-port 9550
```
### Webcam Access Issues
```bash
# Try different webcam indices
python multi_camera_simulator.py 1 --webcam-index 1
# Check available cameras
python -c "import cv2; print([i for i in range(10) if cv2.VideoCapture(i).isOpened()])"
```
### FFmpeg Issues
```bash
# Disable RTSP if FFmpeg unavailable
python multi_camera_simulator.py 1 --no-rtsp
```
## 💡 Pro Tips
1. **Start cameras in order** (1, 2, 3, 4) for easier tracking
2. **Use different terminals** for each camera to see individual logs
3. **Check status endpoints** for health monitoring:
- `http://10.101.1.4:8080/status`
- `http://10.101.1.4:8081/status`
- etc.
4. **Monitor logs** to see which camera is processing which detection
5. **Test LPR with unique session IDs** from each camera
This setup provides a realistic multi-camera environment for comprehensive LPR integration testing! 🎉

View file

@ -1,328 +0,0 @@
#!/usr/bin/env python3
"""
Multi-Camera Simulator
Creates 4 virtual cameras using the same webcam with different ports
Run this in 4 separate terminals to simulate real-world scenario with 4 cameras
"""
import cv2
import threading
import time
import logging
import socket
import sys
import os
import argparse
import subprocess
from http.server import BaseHTTPRequestHandler, HTTPServer
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] Camera%(camera_id)s: %(message)s"
)
class MultiCameraHandler(BaseHTTPRequestHandler):
"""HTTP handler for camera snapshot requests with camera-specific branding"""
def __init__(self, camera_id, webcam_cap, *args, **kwargs):
self.camera_id = camera_id
self.webcam_cap = webcam_cap
super().__init__(*args, **kwargs)
def do_GET(self):
if self.path == '/snapshot' or self.path == '/snapshot.jpg':
try:
# Capture fresh frame from webcam
ret, frame = self.webcam_cap.read()
if ret and frame is not None:
# Add camera branding overlay
frame = self.add_camera_branding(frame)
# Encode as JPEG
success, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 85])
if success:
self.send_response(200)
self.send_header('Content-Type', 'image/jpeg')
self.send_header('Content-Length', str(len(buffer)))
self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
self.send_header('Pragma', 'no-cache')
self.send_header('Expires', '0')
self.end_headers()
self.wfile.write(buffer.tobytes())
return
# Send error response
self.send_response(500)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(b'Failed to capture webcam frame')
except Exception as e:
logging.error(f"Camera{self.camera_id}: Error serving snapshot: {e}")
self.send_response(500)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(f'Error: {str(e)}'.encode())
elif self.path == '/status':
# Status endpoint for health checking
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
width = int(self.webcam_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(self.webcam_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = self.webcam_cap.get(cv2.CAP_PROP_FPS)
status = f'{{"status": "online", "camera_id": "{self.camera_id}", "width": {width}, "height": {height}, "fps": {fps}}}'
self.wfile.write(status.encode())
else:
# 404 for other paths
self.send_response(404)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(b'Not Found - Available endpoints: /snapshot, /snapshot.jpg, /status')
def add_camera_branding(self, frame):
"""Add visual branding to differentiate cameras"""
# Clone frame to avoid modifying original
branded_frame = frame.copy()
# Camera-specific colors and positions
colors = {
1: (0, 255, 255), # Yellow
2: (255, 0, 255), # Magenta
3: (0, 255, 0), # Green
4: (255, 0, 0) # Blue
}
# Add colored border
color = colors.get(self.camera_id, (255, 255, 255))
cv2.rectangle(branded_frame, (0, 0), (branded_frame.shape[1]-1, branded_frame.shape[0]-1), color, 10)
# Add camera ID text
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 2
thickness = 3
# Camera ID text
text = f"CAMERA {self.camera_id}"
text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
text_x = 30
text_y = 60
# Add background rectangle for text
cv2.rectangle(branded_frame,
(text_x - 10, text_y - text_size[1] - 10),
(text_x + text_size[0] + 10, text_y + 10),
(0, 0, 0), -1)
# Add text
cv2.putText(branded_frame, text, (text_x, text_y), font, font_scale, color, thickness)
# Add timestamp
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
ts_text = f"CAM{self.camera_id}: {timestamp}"
cv2.putText(branded_frame, ts_text, (30, branded_frame.shape[0] - 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2)
return branded_frame
def log_message(self, format, *args):
# Suppress default HTTP server logging
pass
def create_handler_class(camera_id, webcam_cap):
"""Create a handler class with bound camera_id and webcam_cap"""
def handler(*args, **kwargs):
return MultiCameraHandler(camera_id, webcam_cap, *args, **kwargs)
return handler
def check_port_available(port):
"""Check if a port is available"""
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('localhost', port))
return True
except OSError:
return False
def start_rtsp_stream(camera_id, webcam_index, rtsp_port):
"""Start RTSP streaming for a specific camera"""
try:
# Check if FFmpeg is available
result = subprocess.run(['ffmpeg', '-version'],
capture_output=True, text=True, timeout=5)
if result.returncode != 0:
logging.warning(f"Camera{camera_id}: FFmpeg not available, RTSP disabled")
return None
except Exception:
logging.warning(f"Camera{camera_id}: FFmpeg not available, RTSP disabled")
return None
try:
# Get camera device name for Windows
if sys.platform.startswith('win'):
# Use the integrated camera
camera_name = "Integrated Camera"
# FFmpeg command to stream webcam via RTSP
if sys.platform.startswith('win'):
cmd = [
'ffmpeg',
'-f', 'dshow',
'-i', f'video={camera_name}',
'-c:v', 'libx264',
'-preset', 'veryfast',
'-tune', 'zerolatency',
'-r', '15', # Lower FPS for multiple streams
'-s', '1280x720',
'-f', 'rtsp',
f'rtsp://localhost:{rtsp_port}/stream'
]
else:
cmd = [
'ffmpeg',
'-f', 'v4l2',
'-i', f'/dev/video{webcam_index}',
'-c:v', 'libx264',
'-preset', 'veryfast',
'-tune', 'zerolatency',
'-r', '15',
'-s', '1280x720',
'-f', 'rtsp',
f'rtsp://localhost:{rtsp_port}/stream'
]
logging.info(f"Camera{camera_id}: Starting RTSP stream on port {rtsp_port}")
rtsp_process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# Give FFmpeg a moment to start
time.sleep(2)
if rtsp_process.poll() is None:
logging.info(f"Camera{camera_id}: RTSP streaming started successfully")
return rtsp_process
else:
logging.error(f"Camera{camera_id}: RTSP streaming failed to start")
return None
except Exception as e:
logging.error(f"Camera{camera_id}: Failed to start RTSP stream: {e}")
return None
def main():
parser = argparse.ArgumentParser(description='Multi-Camera Simulator')
parser.add_argument('camera_id', type=int, choices=[1, 2, 3, 4],
help='Camera ID (1-4)')
parser.add_argument('--webcam-index', type=int, default=0,
help='Webcam device index (default: 0)')
parser.add_argument('--base-port', type=int, default=8080,
help='Base port for HTTP servers (default: 8080)')
parser.add_argument('--rtsp-base-port', type=int, default=8550,
help='Base port for RTSP servers (default: 8550)')
parser.add_argument('--no-rtsp', action='store_true',
help='Disable RTSP streaming (HTTP only)')
args = parser.parse_args()
camera_id = args.camera_id
webcam_index = args.webcam_index
# Calculate ports based on camera ID
http_port = args.base_port + camera_id - 1 # 8080, 8081, 8082, 8083
rtsp_port = args.rtsp_base_port + camera_id - 1 # 8550, 8551, 8552, 8553
# Check if ports are available
if not check_port_available(http_port):
logging.error(f"Camera{camera_id}: HTTP port {http_port} is already in use")
sys.exit(1)
if not args.no_rtsp and not check_port_available(rtsp_port):
logging.error(f"Camera{camera_id}: RTSP port {rtsp_port} is already in use")
sys.exit(1)
logging.info(f"=== Starting Camera {camera_id} Simulator ===")
# Initialize webcam
logging.info(f"Camera{camera_id}: Initializing webcam at index {webcam_index}...")
webcam_cap = cv2.VideoCapture(webcam_index)
if not webcam_cap.isOpened():
logging.error(f"Camera{camera_id}: Failed to open webcam at index {webcam_index}")
sys.exit(1)
# Set webcam properties
webcam_cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
webcam_cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
webcam_cap.set(cv2.CAP_PROP_FPS, 30)
width = int(webcam_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(webcam_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = webcam_cap.get(cv2.CAP_PROP_FPS)
logging.info(f"Camera{camera_id}: Webcam initialized: {width}x{height} @ {fps}fps")
# Start RTSP streaming (if enabled)
rtsp_process = None
if not args.no_rtsp:
rtsp_process = start_rtsp_stream(camera_id, webcam_index, rtsp_port)
# Start HTTP server
server_address = ('0.0.0.0', http_port)
handler_class = create_handler_class(camera_id, webcam_cap)
http_server = HTTPServer(server_address, handler_class)
# Get local IP
local_ip = "10.101.1.4" # Wireguard IP
logging.info(f"\n=== Camera {camera_id} URLs ===")
logging.info(f"HTTP Snapshot: http://{local_ip}:{http_port}/snapshot")
logging.info(f"Status: http://{local_ip}:{http_port}/status")
if rtsp_process:
logging.info(f"RTSP Stream: rtsp://{local_ip}:{rtsp_port}/stream")
else:
logging.info("RTSP Stream: Disabled")
logging.info(f"\n=== CMS Configuration for Camera {camera_id} ===")
logging.info(f"Camera Identifier: webcam-camera-0{camera_id}")
logging.info(f"Snapshot URL: http://{local_ip}:{http_port}/snapshot")
logging.info(f"Snapshot Interval: 2000")
if rtsp_process:
logging.info(f"RTSP URL: rtsp://{local_ip}:{rtsp_port}/stream")
logging.info(f"\nCamera {camera_id} is ready! Press Ctrl+C to stop")
try:
# Start HTTP server
http_server.serve_forever()
except KeyboardInterrupt:
logging.info(f"Camera{camera_id}: Shutting down...")
finally:
# Clean up
if webcam_cap:
webcam_cap.release()
if rtsp_process:
logging.info(f"Camera{camera_id}: Stopping RTSP stream...")
rtsp_process.terminate()
try:
rtsp_process.wait(timeout=5)
except subprocess.TimeoutExpired:
rtsp_process.kill()
http_server.server_close()
logging.info(f"Camera{camera_id}: Stopped")
if __name__ == "__main__":
main()

View file

@ -1,58 +0,0 @@
@echo off
echo ========================================
echo Starting 4-Camera Simulation
echo ========================================
echo.
echo This will open 4 terminal windows for 4 virtual cameras
echo Each camera will use the same webcam but with different ports
echo.
echo Camera URLs will be:
echo Camera 1: http://10.101.1.4:8080/snapshot
echo Camera 2: http://10.101.1.4:8081/snapshot
echo Camera 3: http://10.101.1.4:8082/snapshot
echo Camera 4: http://10.101.1.4:8083/snapshot
echo.
echo Press any key to start all cameras...
pause
echo Starting Camera 1...
start "Camera 1" cmd /k "python multi_camera_simulator.py 1 && pause"
echo Starting Camera 2...
start "Camera 2" cmd /k "python multi_camera_simulator.py 2 && pause"
echo Starting Camera 3...
start "Camera 3" cmd /k "python multi_camera_simulator.py 3 && pause"
echo Starting Camera 4...
start "Camera 4" cmd /k "python multi_camera_simulator.py 4 && pause"
echo.
echo ========================================
echo All 4 cameras started!
echo ========================================
echo.
echo Use these URLs in your CMS:
echo.
echo Camera 1 (Yellow border):
echo ID: webcam-camera-01
echo Snapshot: http://10.101.1.4:8080/snapshot
echo RTSP: rtsp://10.101.1.4:8550/stream
echo.
echo Camera 2 (Magenta border):
echo ID: webcam-camera-02
echo Snapshot: http://10.101.1.4:8081/snapshot
echo RTSP: rtsp://10.101.1.4:8551/stream
echo.
echo Camera 3 (Green border):
echo ID: webcam-camera-03
echo Snapshot: http://10.101.1.4:8082/snapshot
echo RTSP: rtsp://10.101.1.4:8552/stream
echo.
echo Camera 4 (Blue border):
echo ID: webcam-camera-04
echo Snapshot: http://10.101.1.4:8083/snapshot
echo RTSP: rtsp://10.101.1.4:8553/stream
echo.
echo Press any key to exit...
pause

View file

@ -1,78 +0,0 @@
#!/bin/bash
echo "========================================"
echo "Starting 4-Camera Simulation"
echo "========================================"
echo ""
echo "This will start 4 virtual cameras in background"
echo "Each camera will use the same webcam but with different ports"
echo ""
echo "Camera URLs will be:"
echo "Camera 1: http://10.101.1.4:8080/snapshot"
echo "Camera 2: http://10.101.1.4:8081/snapshot"
echo "Camera 3: http://10.101.1.4:8082/snapshot"
echo "Camera 4: http://10.101.1.4:8083/snapshot"
echo ""
# Function to start camera in new terminal
start_camera() {
local camera_id=$1
echo "Starting Camera $camera_id..."
# Try different terminal emulators
if command -v gnome-terminal &> /dev/null; then
gnome-terminal --title="Camera $camera_id" -- bash -c "python3 multi_camera_simulator.py $camera_id; read -p 'Press Enter to close...'"
elif command -v xterm &> /dev/null; then
xterm -title "Camera $camera_id" -e "python3 multi_camera_simulator.py $camera_id; read -p 'Press Enter to close...'" &
elif command -v konsole &> /dev/null; then
konsole --title "Camera $camera_id" -e bash -c "python3 multi_camera_simulator.py $camera_id; read -p 'Press Enter to close...'" &
elif [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
osascript -e "tell application \"Terminal\" to do script \"cd $(pwd) && python3 multi_camera_simulator.py $camera_id\""
else
echo "No suitable terminal emulator found. Starting in background..."
python3 multi_camera_simulator.py $camera_id &
echo "Camera $camera_id PID: $!"
fi
sleep 1
}
# Start all 4 cameras
start_camera 1
start_camera 2
start_camera 3
start_camera 4
echo ""
echo "========================================"
echo "All 4 cameras started!"
echo "========================================"
echo ""
echo "Use these URLs in your CMS:"
echo ""
echo "Camera 1 (Yellow border):"
echo " ID: webcam-camera-01"
echo " Snapshot: http://10.101.1.4:8080/snapshot"
echo " RTSP: rtsp://10.101.1.4:8550/stream"
echo ""
echo "Camera 2 (Magenta border):"
echo " ID: webcam-camera-02"
echo " Snapshot: http://10.101.1.4:8081/snapshot"
echo " RTSP: rtsp://10.101.1.4:8551/stream"
echo ""
echo "Camera 3 (Green border):"
echo " ID: webcam-camera-03"
echo " Snapshot: http://10.101.1.4:8082/snapshot"
echo " RTSP: rtsp://10.101.1.4:8552/stream"
echo ""
echo "Camera 4 (Blue border):"
echo " ID: webcam-camera-04"
echo " Snapshot: http://10.101.1.4:8083/snapshot"
echo " RTSP: rtsp://10.101.1.4:8553/stream"
echo ""
echo "To stop all cameras, close the terminal windows or press Ctrl+C in each"
echo ""
# Keep script running
read -p "Press Enter to exit..."

View file

@ -1,92 +0,0 @@
#!/usr/bin/env python3
"""
Test script for LPR (License Plate Recognition) integration.
This script simulates LPR service sending license plate results to Redis.
"""
import json
import time
import redis
import sys
import os
def main():
"""Test LPR integration by sending mock license plate results to Redis"""
# Redis configuration (should match pipeline.json)
redis_config = {
'host': '10.100.1.3',
'port': 6379,
'password': 'FBQgi0i5RevAAMO5Hh66',
'db': 0
}
try:
# Connect to Redis
print("🔌 Connecting to Redis...")
redis_client = redis.Redis(
host=redis_config['host'],
port=redis_config['port'],
password=redis_config['password'],
db=redis_config['db'],
decode_responses=True
)
# Test connection
redis_client.ping()
print(f"✅ Connected to Redis at {redis_config['host']}:{redis_config['port']}")
# Mock LPR results to send
test_cases = [
{
"session_id": "123",
"license_character": "ABC-1234"
},
{
"session_id": "124",
"license_character": "XYZ-5678"
},
{
"session_id": "125",
"license_character": "DEF-9999"
}
]
print("\n🧪 Sending mock LPR results...")
for i, test_case in enumerate(test_cases, 1):
print(f"\n📤 Test {i}: Sending LPR result for session {test_case['session_id']}")
print(f" License: {test_case['license_character']}")
# Publish to license_results channel
result = redis_client.publish("license_results", json.dumps(test_case))
print(f" 📡 Published to 'license_results' channel (subscribers: {result})")
if result == 0:
print(" ⚠️ Warning: No subscribers listening to 'license_results' channel")
print(" 💡 Make sure the detector worker is running and has loaded a model")
# Wait between test cases
if i < len(test_cases):
print(" ⏳ Waiting 3 seconds before next test...")
time.sleep(3)
print(f"\n✅ Completed {len(test_cases)} test cases")
print("🔍 Check detector worker logs for LPR processing messages:")
print(" - Look for '🚗 LPR Result received' messages")
print(" - Look for '✅ Updated detection' messages")
print(" - Look for '📤 Sent LPR update to backend' messages")
except redis.exceptions.ConnectionError as e:
print(f"❌ Failed to connect to Redis: {e}")
print("💡 Make sure Redis is running and accessible")
sys.exit(1)
except Exception as e:
print(f"❌ Error during testing: {e}")
sys.exit(1)
if __name__ == "__main__":
print("🧪 LPR Integration Test Script")
print("=" * 50)
main()

File diff suppressed because it is too large Load diff