Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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
63 changes: 38 additions & 25 deletions webapp/backend.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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 = []
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"""<!DOCTYPE html>
<html><head><title>AI Backend</title></head>
<body style="font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#0f1419;color:#22c55e;">
<div style="text-align:center">
<h1>Certificate Accepted</h1>
<p>You can close this tab and return to the app.</p>
</div>
</body></html>"""
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)
Expand All @@ -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)
Expand Down
42 changes: 15 additions & 27 deletions webapp/cert.pem
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
-----BEGIN CERTIFICATE-----
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
3dR8+XyLXS0=
-----END CERTIFICATE-----
Loading