|
|
|
|
|
import io, json, numpy as np, os |
|
|
from typing import Optional |
|
|
from fastapi import FastAPI, UploadFile, File, Form |
|
|
from fastapi.middleware.cors import CORSMiddleware |
|
|
from fastapi.responses import JSONResponse |
|
|
from PIL import Image |
|
|
import insightface |
|
|
from insightface.app import FaceAnalysis |
|
|
|
|
|
|
|
|
LABELS = json.load(open("models/labels.json")) |
|
|
GALLERY = np.load("models/gallery_mean.npy").astype("float32") |
|
|
THRESH = json.load(open("models/threshold.json")).get("cosine_threshold", 0.35) |
|
|
|
|
|
|
|
|
FA = FaceAnalysis(name="buffalo_l", providers=["CPUExecutionProvider"]) |
|
|
FA.prepare(ctx_id=-1, det_size=(640,640)) |
|
|
|
|
|
def embed_pil(img: Image.Image) -> Optional[np.ndarray]: |
|
|
arr = np.array(img.convert("RGB")) |
|
|
faces = FA.get(arr) |
|
|
if not faces: return None |
|
|
f = max(faces, key=lambda z:(z.bbox[2]-z.bbox[0])*(z.bbox[3]-z.bbox[1])) |
|
|
return f.normed_embedding.astype("float32") |
|
|
|
|
|
def cosine_sim(a: np.ndarray, B: np.ndarray) -> np.ndarray: |
|
|
return (B @ a) / (np.linalg.norm(a)*np.linalg.norm(B,axis=1) + 1e-12) |
|
|
|
|
|
app = FastAPI(title="FaceVerify API") |
|
|
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) |
|
|
|
|
|
@app.post("/verify") |
|
|
async def verify(name: str = Form(...), image: UploadFile = File(...)): |
|
|
try: |
|
|
img = Image.open(io.BytesIO(await image.read())).convert("RGB") |
|
|
except Exception as e: |
|
|
return JSONResponse({"status":"error","reason":f"bad_image:{e}"}, 400) |
|
|
|
|
|
emb = embed_pil(img) |
|
|
if emb is None: |
|
|
return JSONResponse({"status":"denied","reason":"no_face_detected"}, 200) |
|
|
|
|
|
sims = cosine_sim(emb, GALLERY) |
|
|
i = int(np.argmax(sims)) |
|
|
score = float(sims[i]) |
|
|
matched = LABELS[i] if 0 <= i < len(LABELS) else None |
|
|
ok = score >= THRESH |
|
|
return JSONResponse({ |
|
|
"status": "accepted" if ok else "denied", |
|
|
"score": score, "threshold": THRESH, |
|
|
"claimed_name": name, "matched_name": matched |
|
|
}) |
|
|
|