subhan971 commited on
Commit
2e5d6c0
·
verified ·
1 Parent(s): e7176ff

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +495 -341
app.py CHANGED
@@ -1,341 +1,495 @@
1
- """
2
- Face Verification System - Main Application
3
- Supports user registration with profile picture upload and live face verification
4
- """
5
-
6
- import os
7
- import cv2
8
- import numpy as np
9
- import base64
10
- import logging
11
- from datetime import datetime
12
- from typing import Optional, Dict, Any
13
- from pathlib import Path
14
-
15
- from fastapi import FastAPI, File, UploadFile, HTTPException, Form
16
- from fastapi.middleware.cors import CORSMiddleware
17
- from fastapi.responses import JSONResponse
18
- from pydantic import BaseModel
19
- import uvicorn
20
-
21
- # Import our modules
22
- from face_detector import FaceDetector
23
- from face_verifier import FaceVerifier
24
- from liveness_detector import LivenessDetector
25
- from database_manager import DatabaseManager
26
-
27
- # Setup logging
28
- logging.basicConfig(
29
- level=logging.INFO,
30
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
31
- )
32
- logger = logging.getLogger(__name__)
33
-
34
- # Initialize FastAPI app
35
- app = FastAPI(
36
- title="Face Verification System",
37
- version="1.0.0",
38
- description="Real-time face verification with anti-spoofing"
39
- )
40
-
41
- # CORS middleware
42
- app.add_middleware(
43
- CORSMiddleware,
44
- allow_origins=["*"],
45
- allow_credentials=True,
46
- allow_methods=["*"],
47
- allow_headers=["*"],
48
- )
49
-
50
- # Initialize components
51
- face_detector = FaceDetector()
52
- face_verifier = FaceVerifier()
53
- liveness_detector = LivenessDetector()
54
- db_manager = DatabaseManager()
55
-
56
- # Pydantic models
57
- class VerificationRequest(BaseModel):
58
- user_id: str
59
- live_image_base64: str
60
- check_liveness: bool = True
61
-
62
- class VerificationResponse(BaseModel):
63
- success: bool
64
- match: bool
65
- confidence: float
66
- is_live: Optional[bool] = None
67
- message: str
68
- timestamp: str
69
-
70
- class RegistrationResponse(BaseModel):
71
- success: bool
72
- user_id: str
73
- message: str
74
- face_detected: bool
75
- face_quality_score: float
76
-
77
-
78
- @app.on_event("startup")
79
- async def startup_event():
80
- """Initialize system on startup"""
81
- logger.info("=" * 60)
82
- logger.info("🚀 Face Verification System Starting")
83
- logger.info("=" * 60)
84
-
85
- # Create necessary directories
86
- Path("uploads").mkdir(exist_ok=True)
87
- Path("temp").mkdir(exist_ok=True)
88
-
89
- # Initialize database
90
- db_manager.initialize()
91
-
92
- logger.info("✓ System initialized successfully")
93
-
94
-
95
- @app.get("/")
96
- async def root():
97
- """API root endpoint with documentation links"""
98
- return {
99
- "service": "Face Verification API",
100
- "version": "1.0.0",
101
- "status": "running",
102
- "documentation": "/docs",
103
- "endpoints": {
104
- "register": "POST /register - Register a new user with profile picture",
105
- "verify": "POST /verify - Verify face against registered profile",
106
- "stats": "GET /stats - Get system statistics",
107
- "health": "GET /health - Health check"
108
- },
109
- "usage": {
110
- "register": {
111
- "method": "POST",
112
- "content_type": "multipart/form-data",
113
- "fields": {
114
- "user_id": "string (required)",
115
- "profile_picture": "file (required)"
116
- }
117
- },
118
- "verify": {
119
- "method": "POST",
120
- "content_type": "application/json",
121
- "body": {
122
- "user_id": "string (required)",
123
- "live_image_base64": "string (required, base64 encoded image)",
124
- "check_liveness": "boolean (optional, default: true)"
125
- }
126
- }
127
- }
128
- }
129
-
130
-
131
- @app.post("/register", response_model=RegistrationResponse)
132
- async def register_user(
133
- user_id: str = Form(...),
134
- profile_picture: UploadFile = File(...)
135
- ):
136
- """
137
- Register a new user with their profile picture
138
- """
139
- try:
140
- logger.info(f"Registration request for user: {user_id}")
141
-
142
- # Check if user already exists
143
- if db_manager.user_exists(user_id):
144
- raise HTTPException(400, f"User {user_id} already registered")
145
-
146
- # Read image
147
- image_bytes = await profile_picture.read()
148
- nparr = np.frombuffer(image_bytes, np.uint8)
149
- image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
150
-
151
- if image is None:
152
- raise HTTPException(400, "Invalid image format")
153
-
154
- # Detect face
155
- faces = face_detector.detect_faces(image)
156
-
157
- if len(faces) == 0:
158
- return RegistrationResponse(
159
- success=False,
160
- user_id=user_id,
161
- message="No face detected in the image",
162
- face_detected=False,
163
- face_quality_score=0.0
164
- )
165
-
166
- if len(faces) > 1:
167
- return RegistrationResponse(
168
- success=False,
169
- user_id=user_id,
170
- message="Multiple faces detected. Please upload image with single face",
171
- face_detected=True,
172
- face_quality_score=0.0
173
- )
174
-
175
- # Get face quality score
176
- face = faces[0]
177
- quality_score = face_detector.assess_face_quality(image, face)
178
-
179
- if quality_score < 0.5:
180
- return RegistrationResponse(
181
- success=False,
182
- user_id=user_id,
183
- message=f"Face quality too low ({quality_score:.2f}). Please use a clearer image",
184
- face_detected=True,
185
- face_quality_score=quality_score
186
- )
187
-
188
- # Extract face embedding
189
- embedding = face_verifier.extract_embedding(image, face)
190
-
191
- if embedding is None:
192
- raise HTTPException(500, "Failed to extract face embedding")
193
-
194
- # Save to database
195
- image_path = f"uploads/{user_id}.jpg"
196
- cv2.imwrite(image_path, image)
197
-
198
- db_manager.register_user(user_id, embedding, image_path)
199
-
200
- logger.info(f"✓ User {user_id} registered successfully")
201
-
202
- return RegistrationResponse(
203
- success=True,
204
- user_id=user_id,
205
- message="User registered successfully",
206
- face_detected=True,
207
- face_quality_score=quality_score
208
- )
209
-
210
- except HTTPException:
211
- raise
212
- except Exception as e:
213
- logger.error(f"Registration error: {e}")
214
- raise HTTPException(500, f"Registration failed: {str(e)}")
215
-
216
-
217
- @app.post("/verify", response_model=VerificationResponse)
218
- async def verify_face(request: VerificationRequest):
219
- """
220
- Verify a live face capture against registered profile
221
- """
222
- try:
223
- logger.info(f"Verification request for user: {request.user_id}")
224
-
225
- # Check if user exists
226
- user_data = db_manager.get_user(request.user_id)
227
- if user_data is None:
228
- return VerificationResponse(
229
- success=False,
230
- match=False,
231
- confidence=0.0,
232
- message=f"User {request.user_id} not found",
233
- timestamp=datetime.now().isoformat()
234
- )
235
-
236
- # Decode live image
237
- image_bytes = base64.b64decode(request.live_image_base64)
238
- nparr = np.frombuffer(image_bytes, np.uint8)
239
- live_image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
240
-
241
- if live_image is None:
242
- raise HTTPException(400, "Invalid image format")
243
-
244
- # Detect face in live image
245
- faces = face_detector.detect_faces(live_image)
246
-
247
- if len(faces) == 0:
248
- return VerificationResponse(
249
- success=True,
250
- match=False,
251
- confidence=0.0,
252
- message="No face detected in live image",
253
- timestamp=datetime.now().isoformat()
254
- )
255
-
256
- if len(faces) > 1:
257
- return VerificationResponse(
258
- success=True,
259
- match=False,
260
- confidence=0.0,
261
- message="Multiple faces detected. Please ensure only one face is visible",
262
- timestamp=datetime.now().isoformat()
263
- )
264
-
265
- face = faces[0]
266
-
267
- # Check liveness if requested
268
- is_live = None
269
- if request.check_liveness:
270
- is_live = liveness_detector.detect_liveness(live_image, face)
271
- if not is_live:
272
- logger.warning(f"Liveness check failed for user {request.user_id}")
273
- # Continue with verification but flag the result
274
-
275
- # Extract embedding from live image
276
- live_embedding = face_verifier.extract_embedding(live_image, face)
277
-
278
- if live_embedding is None:
279
- raise HTTPException(500, "Failed to extract face embedding from live image")
280
-
281
- # Compare with stored embedding
282
- stored_embedding = np.array(user_data['embedding'])
283
- similarity = face_verifier.compare_embeddings(stored_embedding, live_embedding)
284
-
285
- # Determine match (threshold: 0.6)
286
- threshold = 0.6
287
- is_match = similarity >= threshold
288
-
289
- # Record verification attempt
290
- db_manager.record_verification(request.user_id, is_match, similarity, is_live)
291
-
292
- message = "Face verified successfully" if is_match else "Face does not match"
293
- if is_live is False:
294
- message += " (Warning: Possible spoofing attempt detected)"
295
-
296
- logger.info(f"✓ Verification complete for {request.user_id}: match={is_match}, confidence={similarity:.3f}")
297
-
298
- return VerificationResponse(
299
- success=True,
300
- match=is_match,
301
- confidence=similarity,
302
- is_live=is_live,
303
- message=message,
304
- timestamp=datetime.now().isoformat()
305
- )
306
-
307
- except HTTPException:
308
- raise
309
- except Exception as e:
310
- logger.error(f"Verification error: {e}")
311
- raise HTTPException(500, f"Verification failed: {str(e)}")
312
-
313
-
314
- @app.get("/stats")
315
- async def get_statistics():
316
- """Get system statistics"""
317
- try:
318
- stats = db_manager.get_statistics()
319
- return stats
320
- except Exception as e:
321
- logger.error(f"Stats error: {e}")
322
- raise HTTPException(500, f"Failed to get statistics: {str(e)}")
323
-
324
-
325
- @app.get("/health")
326
- async def health_check():
327
- """Health check endpoint"""
328
- return {
329
- "status": "healthy",
330
- "service": "Face Verification System",
331
- "version": "1.0.0",
332
- "timestamp": datetime.now().isoformat()
333
- }
334
-
335
-
336
- if __name__ == "__main__":
337
- port = int(os.getenv("PORT", 7860))
338
- host = os.getenv("HOST", "0.0.0.0")
339
-
340
- logger.info(f"Starting Face Verification System on {host}:{port}")
341
- uvicorn.run(app, host=host, port=port, log_level="info")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Face Verification System - Main Application
3
+ Supports user registration with profile picture upload and live face verification
4
+ """
5
+
6
+ import os
7
+ import cv2
8
+ import numpy as np
9
+ import base64
10
+ import logging
11
+ from datetime import datetime
12
+ from typing import Optional, Dict, Any
13
+ from pathlib import Path
14
+
15
+ from fastapi import FastAPI, File, UploadFile, HTTPException, Form
16
+ from fastapi.middleware.cors import CORSMiddleware
17
+ from fastapi.responses import JSONResponse
18
+ from pydantic import BaseModel
19
+ import uvicorn
20
+
21
+ # Import our modules
22
+ from face_detector import FaceDetector
23
+ from face_verifier import FaceVerifier
24
+ from liveness_detector import LivenessDetector
25
+ from database_manager import DatabaseManager
26
+
27
+ # Setup logging
28
+ logging.basicConfig(
29
+ level=logging.INFO,
30
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
31
+ )
32
+ logger = logging.getLogger(__name__)
33
+
34
+ # Initialize FastAPI app
35
+ app = FastAPI(
36
+ title="Face Verification System",
37
+ version="1.0.0",
38
+ description="Real-time face verification with anti-spoofing"
39
+ )
40
+
41
+ # CORS middleware
42
+ app.add_middleware(
43
+ CORSMiddleware,
44
+ allow_origins=["*"],
45
+ allow_credentials=True,
46
+ allow_methods=["*"],
47
+ allow_headers=["*"],
48
+ )
49
+
50
+ # Initialize components
51
+ face_detector = FaceDetector()
52
+ face_verifier = FaceVerifier()
53
+ liveness_detector = LivenessDetector()
54
+ db_manager = DatabaseManager()
55
+
56
+ # Pydantic models
57
+ class VerificationRequest(BaseModel):
58
+ user_id: str
59
+ live_image_base64: str
60
+ check_liveness: bool = True
61
+
62
+ class VerificationResponse(BaseModel):
63
+ success: bool
64
+ match: bool
65
+ confidence: float
66
+ is_live: Optional[bool] = None
67
+ message: str
68
+ timestamp: str
69
+
70
+ class RegistrationResponse(BaseModel):
71
+ success: bool
72
+ user_id: str
73
+ message: str
74
+ face_detected: bool
75
+ face_quality_score: float
76
+
77
+
78
+ @app.on_event("startup")
79
+ async def startup_event():
80
+ """Initialize system on startup"""
81
+ logger.info("=" * 60)
82
+ logger.info("🚀 Face Verification System Starting")
83
+ logger.info("=" * 60)
84
+
85
+ # Create necessary directories
86
+ Path("uploads").mkdir(exist_ok=True)
87
+ Path("temp").mkdir(exist_ok=True)
88
+
89
+ # Initialize database
90
+ db_manager.initialize()
91
+
92
+ logger.info("✓ System initialized successfully")
93
+
94
+
95
+ @app.get("/")
96
+ async def root():
97
+ """API root endpoint with documentation links"""
98
+ return {
99
+ "service": "Face Verification API",
100
+ "version": "1.0.0",
101
+ "status": "running",
102
+ "documentation": "/docs",
103
+ "endpoints": {
104
+ "register": "POST /register - Register a new user with profile picture",
105
+ "verify": "POST /verify - Verify face against registered profile",
106
+ "stats": "GET /stats - Get system statistics",
107
+ "health": "GET /health - Health check"
108
+ },
109
+ "usage": {
110
+ "register": {
111
+ "method": "POST",
112
+ "content_type": "multipart/form-data",
113
+ "fields": {
114
+ "user_id": "string (required)",
115
+ "profile_picture": "file (required)"
116
+ }
117
+ },
118
+ "verify": {
119
+ "method": "POST",
120
+ "content_type": "application/json",
121
+ "body": {
122
+ "user_id": "string (required)",
123
+ "live_image_base64": "string (required, base64 encoded image)",
124
+ "check_liveness": "boolean (optional, default: true)"
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+
131
+ @app.post("/register", response_model=RegistrationResponse)
132
+ async def register_user(
133
+ user_id: str = Form(...),
134
+ profile_picture: UploadFile = File(...)
135
+ ):
136
+ """
137
+ Register a new user with their profile picture
138
+ """
139
+ try:
140
+ logger.info(f"Registration request for user: {user_id}")
141
+
142
+ # Check if user already exists
143
+ if db_manager.user_exists(user_id):
144
+ raise HTTPException(400, f"User {user_id} already registered")
145
+
146
+ # Read image
147
+ image_bytes = await profile_picture.read()
148
+ nparr = np.frombuffer(image_bytes, np.uint8)
149
+ image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
150
+
151
+ if image is None:
152
+ raise HTTPException(400, "Invalid image format")
153
+
154
+ # Detect face
155
+ faces = face_detector.detect_faces(image)
156
+
157
+ if len(faces) == 0:
158
+ return RegistrationResponse(
159
+ success=False,
160
+ user_id=user_id,
161
+ message="No face detected in the image",
162
+ face_detected=False,
163
+ face_quality_score=0.0
164
+ )
165
+
166
+ if len(faces) > 1:
167
+ return RegistrationResponse(
168
+ success=False,
169
+ user_id=user_id,
170
+ message="Multiple faces detected. Please upload image with single face",
171
+ face_detected=True,
172
+ face_quality_score=0.0
173
+ )
174
+
175
+ # Get face quality score
176
+ face = faces[0]
177
+ quality_score = face_detector.assess_face_quality(image, face)
178
+
179
+ if quality_score < 0.5:
180
+ return RegistrationResponse(
181
+ success=False,
182
+ user_id=user_id,
183
+ message=f"Face quality too low ({quality_score:.2f}). Please use a clearer image",
184
+ face_detected=True,
185
+ face_quality_score=quality_score
186
+ )
187
+
188
+ # Extract face embedding
189
+ embedding = face_verifier.extract_embedding(image, face)
190
+
191
+ if embedding is None:
192
+ raise HTTPException(500, "Failed to extract face embedding")
193
+
194
+ # Save to database
195
+ image_path = f"uploads/{user_id}.jpg"
196
+ cv2.imwrite(image_path, image)
197
+
198
+ db_manager.register_user(user_id, embedding, image_path)
199
+
200
+ logger.info(f"✓ User {user_id} registered successfully")
201
+
202
+ return RegistrationResponse(
203
+ success=True,
204
+ user_id=user_id,
205
+ message="User registered successfully",
206
+ face_detected=True,
207
+ face_quality_score=quality_score
208
+ )
209
+
210
+ except HTTPException:
211
+ raise
212
+ except Exception as e:
213
+ logger.error(f"Registration error: {e}")
214
+ raise HTTPException(500, f"Registration failed: {str(e)}")
215
+
216
+
217
+ @app.post("/verify", response_model=VerificationResponse)
218
+ async def verify_face(request: VerificationRequest):
219
+ """
220
+ Verify a live face capture against registered profile
221
+ """
222
+ try:
223
+ logger.info(f"Verification request for user: {request.user_id}")
224
+
225
+ # Check if user exists
226
+ user_data = db_manager.get_user(request.user_id)
227
+ if user_data is None:
228
+ return VerificationResponse(
229
+ success=False,
230
+ match=False,
231
+ confidence=0.0,
232
+ message=f"User {request.user_id} not found",
233
+ timestamp=datetime.now().isoformat()
234
+ )
235
+
236
+ # Decode live image
237
+ image_bytes = base64.b64decode(request.live_image_base64)
238
+ nparr = np.frombuffer(image_bytes, np.uint8)
239
+ live_image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
240
+
241
+ if live_image is None:
242
+ raise HTTPException(400, "Invalid image format")
243
+
244
+ # Detect face in live image
245
+ faces = face_detector.detect_faces(live_image)
246
+
247
+ if len(faces) == 0:
248
+ return VerificationResponse(
249
+ success=True,
250
+ match=False,
251
+ confidence=0.0,
252
+ message="No face detected in live image",
253
+ timestamp=datetime.now().isoformat()
254
+ )
255
+
256
+ if len(faces) > 1:
257
+ return VerificationResponse(
258
+ success=True,
259
+ match=False,
260
+ confidence=0.0,
261
+ message="Multiple faces detected. Please ensure only one face is visible",
262
+ timestamp=datetime.now().isoformat()
263
+ )
264
+
265
+ face = faces[0]
266
+
267
+ # Check liveness if requested
268
+ is_live = None
269
+ if request.check_liveness:
270
+ is_live = liveness_detector.detect_liveness(live_image, face)
271
+ if not is_live:
272
+ logger.warning(f"Liveness check failed for user {request.user_id}")
273
+ # Continue with verification but flag the result
274
+
275
+ # Extract embedding from live image
276
+ live_embedding = face_verifier.extract_embedding(live_image, face)
277
+
278
+ if live_embedding is None:
279
+ raise HTTPException(500, "Failed to extract face embedding from live image")
280
+
281
+ # Compare with stored embedding
282
+ stored_embedding = np.array(user_data['embedding'])
283
+ similarity = face_verifier.compare_embeddings(stored_embedding, live_embedding)
284
+
285
+ # Determine match (threshold: 0.6)
286
+ threshold = 0.6
287
+ is_match = similarity >= threshold
288
+
289
+ # Record verification attempt
290
+ db_manager.record_verification(request.user_id, is_match, similarity, is_live)
291
+
292
+ message = "Face verified successfully" if is_match else "Face does not match"
293
+ if is_live is False:
294
+ message += " (Warning: Possible spoofing attempt detected)"
295
+
296
+ logger.info(f"✓ Verification complete for {request.user_id}: match={is_match}, confidence={similarity:.3f}")
297
+
298
+ return VerificationResponse(
299
+ success=True,
300
+ match=is_match,
301
+ confidence=similarity,
302
+ is_live=is_live,
303
+ message=message,
304
+ timestamp=datetime.now().isoformat()
305
+ )
306
+
307
+ except HTTPException:
308
+ raise
309
+ except Exception as e:
310
+ logger.error(f"Verification error: {e}")
311
+ raise HTTPException(500, f"Verification failed: {str(e)}")
312
+
313
+
314
+ # Pydantic model for direct comparison
315
+ class CompareRequest(BaseModel):
316
+ image1_base64: str
317
+ image2_base64: str
318
+ check_liveness: bool = False
319
+
320
+ class CompareResponse(BaseModel):
321
+ success: bool
322
+ match: bool
323
+ similarity: float
324
+ confidence: float
325
+ is_live_image1: Optional[bool] = None
326
+ is_live_image2: Optional[bool] = None
327
+ message: str
328
+ details: Optional[Dict[str, Any]] = None
329
+
330
+
331
+ @app.post("/compare", response_model=CompareResponse)
332
+ async def compare_faces(request: CompareRequest):
333
+ """
334
+ Direct face comparison - Compare two images to check if they are the same person
335
+ No registration required - just send two images
336
+
337
+ This is the main endpoint for Flutter app integration
338
+ """
339
+ try:
340
+ logger.info("Direct face comparison request received")
341
+
342
+ # Decode first image
343
+ image1_bytes = base64.b64decode(request.image1_base64)
344
+ nparr1 = np.frombuffer(image1_bytes, np.uint8)
345
+ image1 = cv2.imdecode(nparr1, cv2.IMREAD_COLOR)
346
+
347
+ if image1 is None:
348
+ raise HTTPException(400, "Invalid format for first image")
349
+
350
+ # Decode second image
351
+ image2_bytes = base64.b64decode(request.image2_base64)
352
+ nparr2 = np.frombuffer(image2_bytes, np.uint8)
353
+ image2 = cv2.imdecode(nparr2, cv2.IMREAD_COLOR)
354
+
355
+ if image2 is None:
356
+ raise HTTPException(400, "Invalid format for second image")
357
+
358
+ # Detect faces in first image
359
+ faces1 = face_detector.detect_faces(image1)
360
+ if len(faces1) == 0:
361
+ return CompareResponse(
362
+ success=True,
363
+ match=False,
364
+ similarity=0.0,
365
+ confidence=0.0,
366
+ message="No face detected in first image",
367
+ details={"faces_in_image1": 0, "faces_in_image2": "not_checked"}
368
+ )
369
+
370
+ if len(faces1) > 1:
371
+ return CompareResponse(
372
+ success=True,
373
+ match=False,
374
+ similarity=0.0,
375
+ confidence=0.0,
376
+ message="Multiple faces detected in first image. Please use image with single face",
377
+ details={"faces_in_image1": len(faces1), "faces_in_image2": "not_checked"}
378
+ )
379
+
380
+ # Detect faces in second image
381
+ faces2 = face_detector.detect_faces(image2)
382
+ if len(faces2) == 0:
383
+ return CompareResponse(
384
+ success=True,
385
+ match=False,
386
+ similarity=0.0,
387
+ confidence=0.0,
388
+ message="No face detected in second image",
389
+ details={"faces_in_image1": 1, "faces_in_image2": 0}
390
+ )
391
+
392
+ if len(faces2) > 1:
393
+ return CompareResponse(
394
+ success=True,
395
+ match=False,
396
+ similarity=0.0,
397
+ confidence=0.0,
398
+ message="Multiple faces detected in second image. Please use image with single face",
399
+ details={"faces_in_image1": 1, "faces_in_image2": len(faces2)}
400
+ )
401
+
402
+ face1 = faces1[0]
403
+ face2 = faces2[0]
404
+
405
+ # Check liveness if requested
406
+ is_live1 = None
407
+ is_live2 = None
408
+ if request.check_liveness:
409
+ is_live1 = liveness_detector.detect_liveness(image1, face1)
410
+ is_live2 = liveness_detector.detect_liveness(image2, face2)
411
+
412
+ if not is_live1 or not is_live2:
413
+ logger.warning("Liveness check failed for one or both images")
414
+
415
+ # Extract embeddings
416
+ embedding1 = face_verifier.extract_embedding(image1, face1)
417
+ if embedding1 is None:
418
+ raise HTTPException(500, "Failed to extract face embedding from first image")
419
+
420
+ embedding2 = face_verifier.extract_embedding(image2, face2)
421
+ if embedding2 is None:
422
+ raise HTTPException(500, "Failed to extract face embedding from second image")
423
+
424
+ # Compare embeddings
425
+ similarity = face_verifier.compare_embeddings(embedding1, embedding2)
426
+
427
+ # Determine match (threshold: 0.6)
428
+ threshold = 0.6
429
+ is_match = similarity >= threshold
430
+
431
+ # Build message
432
+ if is_match:
433
+ message = "✓ MATCH - Both images are of the same person"
434
+ else:
435
+ message = "✗ NOT MATCH - Images are of different persons"
436
+
437
+ if request.check_liveness:
438
+ if not is_live1:
439
+ message += " (Warning: First image may be a spoof)"
440
+ if not is_live2:
441
+ message += " (Warning: Second image may be a spoof)"
442
+
443
+ logger.info(f"✓ Comparison complete: match={is_match}, similarity={similarity:.3f}")
444
+
445
+ return CompareResponse(
446
+ success=True,
447
+ match=is_match,
448
+ similarity=similarity,
449
+ confidence=similarity,
450
+ is_live_image1=is_live1,
451
+ is_live_image2=is_live2,
452
+ message=message,
453
+ details={
454
+ "faces_in_image1": 1,
455
+ "faces_in_image2": 1,
456
+ "threshold_used": threshold,
457
+ "similarity_percentage": round(similarity * 100, 2)
458
+ }
459
+ )
460
+
461
+ except HTTPException:
462
+ raise
463
+ except Exception as e:
464
+ logger.error(f"Comparison error: {e}")
465
+ raise HTTPException(500, f"Face comparison failed: {str(e)}")
466
+
467
+
468
+ @app.get("/stats")
469
+ async def get_statistics():
470
+ """Get system statistics"""
471
+ try:
472
+ stats = db_manager.get_statistics()
473
+ return stats
474
+ except Exception as e:
475
+ logger.error(f"Stats error: {e}")
476
+ raise HTTPException(500, f"Failed to get statistics: {str(e)}")
477
+
478
+
479
+ @app.get("/health")
480
+ async def health_check():
481
+ """Health check endpoint"""
482
+ return {
483
+ "status": "healthy",
484
+ "service": "Face Verification System",
485
+ "version": "1.0.0",
486
+ "timestamp": datetime.now().isoformat()
487
+ }
488
+
489
+
490
+ if __name__ == "__main__":
491
+ port = int(os.getenv("PORT", 7860))
492
+ host = os.getenv("HOST", "0.0.0.0")
493
+
494
+ logger.info(f"Starting Face Verification System on {host}:{port}")
495
+ uvicorn.run(app, host=host, port=port, log_level="info")