import base64 import cv2 import numpy as np import requests from fastapi import FastAPI from pydantic import BaseModel import uvicorn import insightface import gradio as gr import threading # ---------- Load Face Detector + Recognition Model ---------- model = insightface.app.FaceAnalysis(name="buffalo_l") model.prepare(ctx_id=0, det_size=(640, 640)) # ---------- FastAPI App ---------- app = FastAPI() # ---------- API Request Schema ---------- class CompareRequest(BaseModel): image1: str | None = None # base64 image2: str | None = None # base64 image1_url: str | None = None # URL image2_url: str | None = None # URL # ---------- Helper: Convert base64 to CV2 image ---------- def b64_to_img(b64_string): try: img_data = base64.b64decode(b64_string) np_arr = np.frombuffer(img_data, np.uint8) img = cv2.imdecode(np_arr, cv2.IMREAD_COLOR) return img except: return None # ---------- Helper: Convert URL to CV2 image ---------- def url_to_img(url): try: resp = requests.get(url, timeout=5) np_arr = np.frombuffer(resp.content, np.uint8) img = cv2.imdecode(np_arr, cv2.IMREAD_COLOR) return img except: return None # ---------- Helper: Extract face embedding ---------- def get_embedding(img): faces = model.get(img) if len(faces) == 0: return None return faces[0].embedding # first detected face # ---------- POST /compare API ---------- @app.post("/compare") async def compare_faces(req: CompareRequest): # ---- Load Image 1 ---- if req.image1: img1 = b64_to_img(req.image1) elif req.image1_url: img1 = url_to_img(req.image1_url) else: img1 = None # ---- Load Image 2 ---- if req.image2: img2 = b64_to_img(req.image2) elif req.image2_url: img2 = url_to_img(req.image2_url) else: img2 = None if img1 is None or img2 is None: return {"error": "Invalid image data or URL."} emb1 = get_embedding(img1) emb2 = get_embedding(img2) if emb1 is None or emb2 is None: return {"error": "No face detected in one or both images."} # Cosine similarity similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2)) matched = similarity > 0.55 # threshold return { "similarity": float(similarity), "match": matched } # ---------- Gradio UI ---------- def gradio_ui(img1_text, img2_text): def load_any(input_str): if input_str.startswith("http://") or input_str.startswith("https://"): return url_to_img(input_str) else: return b64_to_img(input_str) img1 = load_any(img1_text) img2 = load_any(img2_text) if img1 is None or img2 is None: return "Invalid image data or URL." emb1 = get_embedding(img1) emb2 = get_embedding(img2) if emb1 is None or emb2 is None: return "Face not detected." similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2)) matched = similarity > 0.55 return f"Similarity: {similarity:.3f} | Match: {matched}" # ---------- Run Gradio in Background Thread ---------- def launch_gradio(): gr.Interface( fn=gradio_ui, inputs=[ gr.Textbox(label="Image 1 (base64 or URL)"), gr.Textbox(label="Image 2 (base64 or URL)") ], outputs="text", title="Face Match API (Text Input)" ).launch(server_name="0.0.0.0", server_port=7860) # ---------- MAIN ---------- if __name__ == "__main__": # Start Gradio in a separate thread threading.Thread(target=launch_gradio, daemon=True).start() # Start FastAPI server uvicorn.run(app, host="0.0.0.0", port=8000)