Update to DepthAI v3 API

Day 81 - Fixed API compatibility! 🦊

Changes:
- Camera node with .build() pattern
- DetectionNetwork instead of MobileNetDetectionNetwork
- NNModelDescription for model loading
- createOutputQueue() on outputs
- Pipeline context management

Still uses face-detection-retail-0004 for face detection.
Now compatible with depthai 3.2.x on head-vixy!
This commit is contained in:
Alex Kazaiev
2026-01-21 15:24:21 -06:00
parent 59b466d896
commit ee22b18dbf

View File

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