#!/usr/bin/env python3 """ 🎯 Face Detection API для Hugging Face Spaces Используется для создания маски inpainting в "ИИ фотосессии" ✅ MediaPipe 0.10.21 (solutions API) ✅ Точный bounding box для маски ✅ Быстрая детекция (<2 сек) """ import io import logging import os import sys from typing import Optional import numpy as np import cv2 from PIL import Image from fastapi import FastAPI, File, UploadFile, HTTPException, Header from fastapi.responses import JSONResponse # ============================================================================= # 🔑 IMPORT MEDIAPIPE 0.10.21 (solutions API) # ============================================================================= try: import mediapipe as mp print(f"📦 MediaPipe version: {mp.__version__}", flush=True) # 🔍 Проверяем что solutions.face_detection доступен if not hasattr(mp, 'solutions') or not hasattr(mp.solutions, 'face_detection'): print("❌ FATAL: mp.solutions.face_detection not available!", flush=True) print(f"💡 Required: mediapipe==0.10.21", flush=True) sys.exit(1) print("✅ Using MediaPipe solutions API (0.10.21)", flush=True) except ImportError as e: print(f"❌ FATAL: Failed to import mediapipe: {e}", flush=True) sys.exit(1) # ============================================================================= # НАСТРОЙКА ЛОГИРОВАНИЯ # ============================================================================= logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", force=True ) logger = logging.getLogger(__name__) app = FastAPI(title="AI Face Detection API", version="1.0.0") API_SECRET = os.getenv("API_SECRET", "") # ============================================================================= # 🔑 ЛЕНИВАЯ ИНИЦИАЛИЗАЦИЯ (solutions API) # ============================================================================= _face_detector = None def get_face_detector(): """Инициализация через solutions API""" global _face_detector if _face_detector is None: logger.info("🔧 Initializing mp.solutions.face_detection...") # 🔑 ПРАВИЛЬНЫЙ доступ: mp.solutions.face_detection.FaceDetection _face_detector = mp.solutions.face_detection.FaceDetection( model_selection=1, # 1=full-range (до 5м) min_detection_confidence=0.5 ) logger.info("✅ FaceDetection ready") return _face_detector # ============================================================================= # ENDPOINTS # ============================================================================= @app.get("/") async def root(): """Health check + информация о сервисе""" return { "status": "ok", "service": "AI Face Detection API", "version": "1.0.0", "mediapipe_version": mp.__version__, "api": "solutions", "purpose": "Face detection for inpainting mask creation" } @app.get("/health") async def health(): """Detailed health check""" try: detector = get_face_detector() return { "status": "healthy", "mediapipe": "ready", "version": mp.__version__, "api": "solutions" } except Exception as e: logger.error(f"❌ Health check failed: {e}") return { "status": "unhealthy", "error": str(e), "version": mp.__version__ } @app.post("/detect") async def detect_face( file: UploadFile = File(...), authorization: Optional[str] = Header(None) ): """ 🔍 Detect face using MediaPipe solutions API (0.10.21) Request: - file: image file (PNG/JPEG/JPG) - Authorization: Bearer YOUR_SECRET (optional) Success Response (200): { "success": true, "face": { "x": int, "y": int, "width": int, "height": int, "score": float }, "image_size": {"width": int, "height": int} } Error Response (404): { "success": false, "error": "No face detected", "image_size": {"width": int, "height": int} } """ # 🔐 Проверка аутентификации if API_SECRET and authorization != f"Bearer {API_SECRET}": logger.warning("⚠️ Unauthorized request") raise HTTPException(status_code=401, detail="Unauthorized") try: # 1. Читаем изображение contents = await file.read() logger.info(f"📥 Received: {len(contents)} bytes") # 2. Конвертируем bytes → numpy → OpenCV BGR img_array = np.frombuffer(contents, np.uint8) img = cv2.imdecode(img_array, cv2.IMREAD_COLOR) if img is None: logger.error("❌ Failed to decode image") raise HTTPException(status_code=400, detail="Failed to decode image") h, w = img.shape[:2] logger.info(f"📐 Image: {w}x{h}px") # 3. 🔑 solutions API требует RGB rgb_image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 4. Детекция detector = get_face_detector() results = detector.process(rgb_image) # 5. Обработка результата if results.detections: detection = results.detections[0] bbox = detection.location_data.relative_bounding_box # Конвертируем relative → absolute координаты x = max(0, int(bbox.xmin * w)) y = max(0, int(bbox.ymin * h)) width = min(w - x, int(bbox.width * w)) height = min(h - y, int(bbox.height * h)) score = detection.score[0] if detection.score else 0 logger.info(f"✅ Face: {width}x{height}px @ ({x},{y}), score={score:.3f}") return JSONResponse({ "success": True, "face": { "x": x, "y": y, "width": width, "height": height, "score": round(score, 3) }, "image_size": { "width": w, "height": h } }) else: logger.warning("⚠️ No face detected") return JSONResponse({ "success": False, "error": "No face detected", "image_size": { "width": w, "height": h } }, status_code=404) except HTTPException: raise except Exception as e: logger.error(f"❌ Error: {type(e).__name__}: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"Internal error: {str(e)}") if __name__ == "__main__": import uvicorn uvicorn.run("app:app", host="0.0.0.0", port=7860, log_level="info")