Centralize execution provider selection in providers.py with auto-detection and graceful fallback. All ONNX sessions (embedder, detector, face processor, recognizer) now use the configured providers. - New VISION_EXECUTION_PROVIDERS env var: "auto" for GPU auto-detect, or explicit "CUDAExecutionProvider,CPUExecutionProvider" - Provider priority: CUDA > ROCm > OpenVINO > CPU (when set to "auto") - docker-compose.yml includes commented-out NVIDIA GPU deploy section - Supports onnxruntime-gpu as a drop-in replacement for onnxruntime Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
74 lines
2.2 KiB
Python
74 lines
2.2 KiB
Python
"""
|
|
Face detection + recognition using InsightFace (RetinaFace + ArcFace).
|
|
|
|
Uses the buffalo_l model pack which auto-downloads on first use (~300MB).
|
|
Produces 512-d ArcFace embeddings. Non-commercial research license —
|
|
fine for homelab self-hosting.
|
|
"""
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
|
|
from app.config import VisionSettings
|
|
from app.services.vision.base import FaceProcessor, FaceDetection
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class InsightFaceProcessor(FaceProcessor):
|
|
def __init__(self, settings: VisionSettings):
|
|
from insightface.app import FaceAnalysis
|
|
|
|
model_root = str(Path(settings.models_dir) / "face" / "insightface")
|
|
logger.info("Loading InsightFace buffalo_l from %s", model_root)
|
|
|
|
from app.services.vision.providers import get_providers
|
|
providers = get_providers(settings.execution_providers)
|
|
|
|
self._app = FaceAnalysis(
|
|
name="buffalo_l",
|
|
root=model_root,
|
|
providers=providers,
|
|
)
|
|
self._app.prepare(ctx_id=-1, det_size=(640, 640))
|
|
self._min_det_score = settings.faces.recognition_threshold
|
|
|
|
def process(self, image: np.ndarray) -> list[FaceDetection]:
|
|
orig_h, orig_w = image.shape[:2]
|
|
|
|
# InsightFace expects BGR
|
|
bgr = image[:, :, ::-1].copy()
|
|
|
|
faces = self._app.get(bgr)
|
|
|
|
if not faces:
|
|
return []
|
|
|
|
results = []
|
|
for face in faces:
|
|
if face.det_score < self._min_det_score:
|
|
continue
|
|
|
|
# face.bbox is [x1, y1, x2, y2] in pixel coords
|
|
x1, y1, x2, y2 = face.bbox
|
|
bbox = [
|
|
max(0, float(x1) / orig_w),
|
|
max(0, float(y1) / orig_h),
|
|
min(1, float(x2) / orig_w),
|
|
min(1, float(y2) / orig_h),
|
|
]
|
|
|
|
embedding = face.normed_embedding # already L2-normalized, 512-d
|
|
results.append(FaceDetection(
|
|
bbox=bbox,
|
|
embedding=embedding.astype(np.float32),
|
|
quality=float(face.det_score),
|
|
))
|
|
|
|
return results
|
|
|
|
@property
|
|
def embedding_dim(self) -> int:
|
|
return 512
|