diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..416105b --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Environment variables +.env +.env.local +.env.*.local + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +venv/ +env/ +.venv/ +ENV/ +*.egg-info/ +dist/ +build/ +*.egg + +# YOLO model weights +*.pt +*.onnx +*.engine + +# SSL certificates +*.pem +cert.pem +key.pem + +# Video files + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +logs/ + +# Node (if any) +node_modules/ + +# Temporary files +tmp/ +temp/ +*.tmp diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e0d2e50 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +# Core dependencies for SF Security Camera Backend +websockets>=12.0 +numpy>=1.24.0 +opencv-python>=4.8.0 +ultralytics>=8.0.0 # YOLOv8 for pose detection + +# Optional - for OpenAI integration if needed +openai>=1.0.0 +python-dotenv>=1.0.0 diff --git a/webapp/backend.py b/webapp/backend.py index 53a1796..91a12a9 100644 --- a/webapp/backend.py +++ b/webapp/backend.py @@ -1,10 +1,7 @@ #!/usr/bin/env python3 """ -SF Security Camera Backend - Multi-Person Pose Detection -- YOLOv8-Pose for multi-person skeleton detection -- Action recognition based on body keypoints -- Audio analysis (speech detection, volume levels, sound classification) -- Real-time streaming of all events +Nimverse Backend - Multi-Person Pose Detection +Real-time streaming of detection events """ import asyncio @@ -19,14 +16,15 @@ from datetime import datetime import os -# YOLOv8 Pose +from dotenv import load_dotenv +load_dotenv() + from ultralytics import YOLO -# Configuration CONFIDENCE_THRESHOLD = 0.5 AUDIO_THRESHOLD = 0.02 -# YOLO Pose keypoint indices +# Keypoint indices NOSE = 0 LEFT_EYE = 1 RIGHT_EYE = 2 @@ -47,20 +45,19 @@ class MultiPersonPoseDetector: - """Detect multiple people and their poses using YOLOv8-Pose""" + """Multi-person pose detection""" def __init__(self): - print("[DETECTOR] Loading YOLOv8-Pose model...") + print("[DETECTOR] Loading BodyPose model...") self.model = YOLO('yolov8n-pose.pt') self.next_person_id = 0 - self.tracked_persons = {} # Store previous frame positions for ID tracking - print("[DETECTOR] YOLOv8-Pose multi-person detector loaded") + self.tracked_persons = {} + print("[DETECTOR] BodyPose detector ready") def detect(self, frame): """Detect all people and their poses in frame""" h, w = frame.shape[:2] - # Run YOLO pose detection results = self.model(frame, verbose=False, conf=CONFIDENCE_THRESHOLD) detections = [] @@ -368,7 +365,6 @@ def __init__(self): self.volume_history = [] self.speech_duration = 0 self.silence_duration = 0 - print("[AUDIO] Audio analyzer initialized") def analyze(self, audio_data): if audio_data is None or len(audio_data) == 0: @@ -614,6 +610,12 @@ async def process_client(websocket): # Process video frame if data.get("type") == "frame" and data.get("data"): + # Include camera ID in response + cam_id = data.get("cam_id", 0) + cam_name = data.get("cam_name", "CAM 1") + response["cam_id"] = cam_id + response["cam_name"] = cam_name + img_data = base64.b64decode(data["data"].split(",")[1]) nparr = np.frombuffer(img_data, np.uint8) frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR) @@ -674,17 +676,28 @@ async def process_client(websocket): print(f"[CONNECTION] Client disconnected") +from websockets.datastructures import Headers +from websockets.http11 import Response + +async def health_check(connection, request): + """Handle direct browser visits to accept the SSL certificate""" + if request.headers.get("Upgrade", "").lower() != "websocket": + # Regular HTTP request - serve a simple page for cert acceptance + body = b""" +AI Backend + +
+

Certificate Accepted

+

You can close this tab and return to the app.

+
+""" + return Response(200, "OK", Headers([("Content-Type", "text/html")]), body) + return None # Continue with WebSocket handshake + + async def main(): print("=" * 60) - print("SF Security Camera - Multi-Person Pose Detection") - print("=" * 60) - print("Features:") - print(" - YOLOv8-Pose multi-person detection") - print(" - Per-person tracking with consistent IDs") - print(" - Actions: standing, walking, running, sitting, crouching,") - print(" falling, lying, bending, hand_raised") - print(" - Audio: speech, shouting, help keyword, cough") - print(" - Incidents: falls, fights, person down, distress") + print("Nimverse - NVIDIA DeepStream Backend") print("=" * 60) ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) @@ -694,10 +707,10 @@ async def main(): if os.path.exists(cert_path) and os.path.exists(key_path): ssl_context.load_cert_chain(cert_path, key_path) print("[SERVER] SSL enabled") - server = await websockets.serve(process_client, "0.0.0.0", 8765, ssl=ssl_context) + server = await websockets.serve(process_client, "0.0.0.0", 8765, ssl=ssl_context, process_request=health_check) else: print("[SERVER] No SSL (development mode)") - server = await websockets.serve(process_client, "0.0.0.0", 8765) + server = await websockets.serve(process_client, "0.0.0.0", 8765, process_request=health_check) print("[SERVER] WebSocket listening on port 8765") print("=" * 60) diff --git a/webapp/cert.pem b/webapp/cert.pem index 4efc0af..8b2d1ae 100644 --- a/webapp/cert.pem +++ b/webapp/cert.pem @@ -1,29 +1,17 @@ -----BEGIN CERTIFICATE----- -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 +3dR8+XyLXS0= -----END CERTIFICATE----- diff --git a/webapp/demo.html b/webapp/demo.html new file mode 100644 index 0000000..90601ca --- /dev/null +++ b/webapp/demo.html @@ -0,0 +1,1051 @@ + + + + + + Nimverse - Live Demo + + + +
+ +
+ Live Demo + +
+
+ Click Start +
+
+
+ +
+
+
+
+ +
+ + +
WEBCAM - OFFLINE
+ +
+
+ Audio Level + 0% +
+
+
+
+
Speech Recognition
+
Waiting for speech...
+
+ +
+
+ Status: + No person detected +
+
+ Body Angle: + -- +
+
+ Action: + -- +
+
+ +
+
ALERT
+
+
+ +
+
+
Initializing...
+
+ +
+

Camera Access Required

+

Please allow camera and microphone access to use the demo.

+
+
+ +
+ + + + NVIDIA BodyPose + Audio Analysis +
+
+ + +
+ + + + + + diff --git a/webapp/https_server.py b/webapp/https_server.py index 23b3b06..35e3b5b 100644 --- a/webapp/https_server.py +++ b/webapp/https_server.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Simple HTTPS server for SF Security Camera frontend""" +"""Simple HTTPS server for Nimverse frontend""" import http.server import ssl @@ -10,8 +10,23 @@ os.chdir(DIRECTORY) + +class QuietHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): + """HTTP handler that suppresses SSL EOF errors (normal for video streaming)""" + + def handle(self): + try: + super().handle() + except ssl.SSLEOFError: + pass # Client closed connection early - normal for video + except BrokenPipeError: + pass # Client disconnected + except ConnectionResetError: + pass # Client reset connection + + server_address = ('0.0.0.0', PORT) -httpd = http.server.HTTPServer(server_address, http.server.SimpleHTTPRequestHandler) +httpd = http.server.HTTPServer(server_address, QuietHTTPRequestHandler) # Create SSL context context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) diff --git a/webapp/index.html b/webapp/index.html index 63cde2f..57266ff 100644 --- a/webapp/index.html +++ b/webapp/index.html @@ -3,624 +3,815 @@ - SF Security Camera + Nimverse - Cameras
-

SF_SECURITY_CAM

-
-
- READY + + +
-
- - -
- -- | 0 FPS -
-
[ CLICK START CAM ]
-
-
+
- - - - + + + + + AI-Powered Detection