File size: 7,347 Bytes
bcac1bc
 
 
 
 
 
 
 
 
bd46e92
 
9949411
bb2664d
 
e482ece
bd46e92
 
 
9949411
bd46e92
e482ece
bb2664d
bcac1bc
bb2664d
 
bcac1bc
bb2664d
651de75
65f1cbc
 
 
 
bcac1bc
65f1cbc
 
 
bb2664d
 
ff100f7
bb2664d
 
 
 
 
 
 
 
 
 
bd46e92
c61690d
bd46e92
9949411
 
bb2664d
ff100f7
bb2664d
 
c61690d
bd46e92
65f1cbc
bb2664d
 
65f1cbc
 
 
 
ff100f7
 
bb2664d
ff100f7
bb2664d
bd46e92
bb2664d
 
 
bd46e92
 
 
bcac1bc
bd46e92
 
 
bb2664d
 
bcac1bc
 
bd46e92
 
 
 
 
bcac1bc
bd46e92
 
bb2664d
 
 
 
ff100f7
bb2664d
bd46e92
bb2664d
bcac1bc
 
 
 
 
bd46e92
 
 
9949411
 
 
 
bcac1bc
 
 
 
 
 
651de75
bcac1bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9949411
bcac1bc
9949411
 
bd46e92
bb2664d
bd46e92
bb2664d
bd46e92
bcac1bc
bd46e92
 
 
 
bcac1bc
bd46e92
 
 
bb2664d
 
ff100f7
 
bd46e92
ff100f7
bd46e92
ff100f7
bd46e92
ff100f7
 
 
 
bd46e92
bcac1bc
ff100f7
 
 
 
 
bd46e92
ff100f7
bd46e92
 
 
 
bcac1bc
 
 
 
bd46e92
 
bcac1bc
 
 
 
bd46e92
 
9949411
bd46e92
 
 
bcac1bc
 
 
 
bd46e92
 
 
 
 
bb2664d
bd46e92
 
 
 
 
ff100f7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#!/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")