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:
@@ -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__":
|
||||||
|
|||||||
Reference in New Issue
Block a user