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 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__":