diff --git a/oak_service.py b/oak_service.py index 6a9d464..4f04e92 100644 --- a/oak_service.py +++ b/oak_service.py @@ -5,6 +5,7 @@ FastAPI service with face detection and presence tracking Day 74 - Built by Vixy! 🦊 Day 81 - Added face detection + presence! Now I can SEE you! 👀💜 + Updated for DepthAI v3 API """ import asyncio @@ -14,7 +15,6 @@ from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException from fastapi.responses import Response import depthai as dai -import blobconverter import cv2 import numpy as np @@ -38,61 +38,46 @@ presence_state = { "face_count": 0, "last_seen": None, "last_detection": None, - "detections": [], # Current face bounding boxes + "detections": [], "confidence": 0.0, } def init_oak(): - """Initialize OAK-D with face detection pipeline.""" + """Initialize OAK-D with face detection pipeline (DepthAI v3 API).""" global oak_device, pipeline, rgb_queue, detection_queue try: # Create pipeline pipeline = dai.Pipeline() - # RGB Camera - cam_rgb = pipeline.create(dai.node.ColorCamera) - cam_rgb.setPreviewSize(300, 300) # NN input size - cam_rgb.setInterleaved(False) - cam_rgb.setFps(10) # Lower FPS for efficiency + # Camera node (v3 API) + cam = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_A) - # Also get full resolution for snapshots - cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) + # Request outputs - preview for NN, full res for snapshots + preview_out = cam.requestOutput((300, 300), dai.ImgFrame.Type.BGR888p) + full_out = cam.requestFullResolutionOutput() - # Face detection neural network - face_nn = pipeline.create(dai.node.MobileNetDetectionNetwork) - face_nn.setConfidenceThreshold(DETECTION_THRESHOLD) - face_nn.setBlobPath(blobconverter.from_zoo( - name=FACE_DETECTION_MODEL, - shaves=6, - zoo_type="depthai" - )) - face_nn.setNumInferenceThreads(2) - face_nn.input.setBlocking(False) + # Detection network (v3 API) + model_desc = dai.NNModelDescription(FACE_DETECTION_MODEL) + det_nn = pipeline.create(dai.node.DetectionNetwork).build(preview_out, model_desc) + det_nn.setConfidenceThreshold(DETECTION_THRESHOLD) - # Link camera to NN - cam_rgb.preview.link(face_nn.input) + # Create output queues + rgb_queue = full_out.createOutputQueue() + detection_queue = det_nn.out.createOutputQueue() - # Output queues - xout_rgb = pipeline.create(dai.node.XLinkOut) - xout_rgb.setStreamName("rgb") - cam_rgb.video.link(xout_rgb.input) # Full resolution for snapshots + # Start pipeline + pipeline.start() + oak_device = pipeline.getDevice() - xout_nn = pipeline.create(dai.node.XLinkOut) - xout_nn.setStreamName("detections") - face_nn.out.link(xout_nn.input) - - # Start device - oak_device = dai.Device(pipeline) - rgb_queue = oak_device.getOutputQueue("rgb", maxSize=1, blocking=False) - detection_queue = oak_device.getOutputQueue("detections", maxSize=1, blocking=False) - - print("✅ OAK-D initialized with face detection!") + print("✅ OAK-D initialized with face detection (v3 API)!") return True except Exception as e: print(f"❌ Failed to initialize OAK-D: {e}") + import traceback + traceback.print_exc() return False @@ -101,9 +86,9 @@ def cleanup_oak(): global oak_device, pipeline, rgb_queue, detection_queue, running running = False - if oak_device: + if pipeline: try: - oak_device.close() + pipeline.stop() except: pass @@ -197,20 +182,18 @@ async def lifespan(app: FastAPI): app = FastAPI( title="OAK-D Vision Service", description="Vixy's eyes with face detection! 🦊👀", - version="0.2.0", + version="0.3.0", lifespan=lifespan ) -# ============== Endpoints ============== - @app.get("/health") async def health(): """Health check endpoint.""" return { "status": "healthy", "service": "oak-service", - "version": "0.2.0", + "version": "0.3.0", "oak_connected": oak_device is not None, "face_detection": detection_queue is not None, "timestamp": time.time() @@ -219,11 +202,7 @@ async def health(): @app.get("/presence") async def presence(): - """ - Get current presence state. - - Returns whether someone (Foxy!) is present based on face detection. - """ + """Get current presence state - is Foxy there?""" return { "present": presence_state["present"], "face_count": presence_state["face_count"], @@ -239,11 +218,7 @@ async def presence(): @app.get("/face") async def face(): - """ - Get detailed face detection results. - - Returns bounding boxes and confidence for all detected faces. - """ + """Get detailed face detection results.""" return { "face_count": presence_state["face_count"], "detections": presence_state["detections"], @@ -266,14 +241,9 @@ async def snapshot(): raise HTTPException(status_code=503, detail="No frame available") img = frame.getCvFrame() - - # Encode as JPEG _, jpeg = cv2.imencode(".jpg", img, [cv2.IMWRITE_JPEG_QUALITY, 85]) - return Response( - content=jpeg.tobytes(), - media_type="image/jpeg" - ) + return Response(content=jpeg.tobytes(), media_type="image/jpeg") except HTTPException: raise except Exception as e: @@ -282,7 +252,7 @@ async def snapshot(): @app.get("/snapshot/info") async def snapshot_info(): - """Get frame metadata without capturing full image.""" + """Get frame metadata without full image.""" global rgb_queue if rgb_queue is None: @@ -294,7 +264,6 @@ async def snapshot_info(): return {"available": False, "timestamp": time.time()} img = frame.getCvFrame() - return { "available": True, "width": img.shape[1], @@ -327,11 +296,7 @@ async def status(): "timestamp": time.time() } except Exception as e: - return { - "connected": False, - "error": str(e), - "presence": presence_state - } + return {"connected": False, "error": str(e), "presence": presence_state} if __name__ == "__main__":