File size: 3,787 Bytes
78cdb93
 
 
 
 
 
5059f37
78cdb93
b136c7f
057f178
78cdb93
5059f37
78cdb93
b136c7f
78cdb93
18c23ed
b136c7f
78cdb93
5059f37
78cdb93
5059f37
 
 
 
5f2c279
5059f37
b136c7f
 
 
5f2c279
 
 
b136c7f
 
 
5059f37
b136c7f
78cdb93
5f2c279
 
 
 
78cdb93
 
 
5059f37
b136c7f
 
5f2c279
b136c7f
5059f37
b136c7f
5059f37
78cdb93
 
b136c7f
5059f37
5f2c279
 
 
 
 
 
 
5059f37
5f2c279
 
 
 
 
 
78cdb93
 
18c23ed
78cdb93
 
 
 
 
5059f37
b136c7f
5059f37
 
 
 
b136c7f
18c23ed
 
 
 
b136c7f
057f178
193f77c
057f178
5059f37
 
 
 
 
 
 
 
b136c7f
 
5059f37
b136c7f
aece0c4
 
b136c7f
 
193f77c
b136c7f
5059f37
 
78cdb93
5059f37
78cdb93
057f178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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)