Spaces:
Running
Running
| #!/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 | |
| # ============================================================================= | |
| 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" | |
| } | |
| 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__ | |
| } | |
| 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") |