Files
mule-image/backend/app/services/vision/insightface_processor.py
dtoro bbb8e4850c feat: GPU acceleration support for ONNX Runtime inference
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>
2026-04-12 22:12:21 +02:00

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