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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
+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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
+
+
+
+
+
+
SF
+
Nimverse Live Demo
+
+
+
+
+
+
+
+
+
+
+
+
+
WEBCAM - OFFLINE
+
+
+
+ Audio Level
+ 0%
+
+
+
Speech Recognition
+
Waiting for speech...
+
+
+
+
+ Status:
+ No person detected
+
+
+ Body Angle:
+ --
+
+
+ Action:
+ --
+
+
+
+
+
+
+
+
+
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
+
+
SF
+
Nimverse Monitor
+
+
+
-
-
-
-
- -- | 0 FPS
-
-
[ CLICK START CAM ]
-
-
+
-
-
-
-
+
+
+
+
+ AI-Powered Detection