WildOjisan's picture
.
6273393
Raw History Blame Contribute Delete
5.11 kB
import os
import cv2
import numpy as np
import pickle
import faiss
from insightface.app import FaceAnalysis
from fastapi import FastAPI, UploadFile, File, HTTPException
from io import BytesIO
# 1. FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
app = FastAPI(
title="InsightFace Face Recognition API",
description="InsightFace (buffalo_l)๋ฅผ ์‚ฌ์šฉํ•œ ์–ผ๊ตด ์ธ์‹ ๋ฐ FAISS ์ธ๋ฑ์Šค ๊ฒ€์ƒ‰ API"
)
# ๋ชจ๋ธ ๋ฐ ์ธ๋ฑ์Šค ์ „์—ญ ๋ณ€์ˆ˜
model = None
index = None
labels = None
# ๐Ÿš€ ํ™˜๊ฒฝ ์„ค์ •: Docker ํ™˜๊ฒฝ์—์„œ๋Š” ๋ชจ๋ธ/์ธ๋ฑ์Šค ํŒŒ์ผ์„ ํ”„๋กœ์ ํŠธ ๋””๋ ‰ํ† ๋ฆฌ์— ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.
# ์ธ๋ฑ์Šค ํŒŒ์ผ์€ /app/data ํด๋”์— ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.
LOAD_DIR = "data"
FAISS_INDEX_FILE = os.path.join(LOAD_DIR, "faiss_index_v2.index")
LABELS_FILE = os.path.join(LOAD_DIR, "faiss_labels_v2.pkl")
# โœ… 1. ๋ชจ๋ธ ๋ฐ ์ธ๋ฑ์Šค ์ค€๋น„ (์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰)
@app.on_event("startup")
async def startup_event():
global model, index, labels
print("๐Ÿš€ ์„œ๋ฒ„ ์‹œ์ž‘: InsightFace ๋ชจ๋ธ ๋ฐ FAISS ์ธ๋ฑ์Šค ๋กœ๋”ฉ ์ค‘...")
# InsightFace ArcFace ๋ชจ๋ธ ์ค€๋น„
try:
# CPUExecutionProvider ์‚ฌ์šฉ (GPU๊ฐ€ ์—†๋Š” ํ™˜๊ฒฝ/Docker์— ์ ํ•ฉ)
model = FaceAnalysis(name='buffalo_l', providers=['CPUExecutionProvider'])
model.prepare(ctx_id=0)
print("โœ… InsightFace model (buffalo_l) ๋กœ๋”ฉ ์™„๋ฃŒ.")
except Exception as e:
print(f"โŒ InsightFace ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ: {e}")
raise HTTPException(status_code=500, detail=f"๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ: {e}")
# ์ธ๋ฑ์Šค & ๋ผ๋ฒจ ๋กœ๋”ฉ
try:
if not os.path.exists(FAISS_INDEX_FILE) or not os.path.exists(LABELS_FILE):
print(f"โŒ FAISS ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค. ๊ฒฝ๋กœ ํ™•์ธ: {LOAD_DIR}")
raise FileNotFoundError(f"ํ•„์š”ํ•œ FAISS ์ธ๋ฑ์Šค ํŒŒ์ผ ํ˜น์€ ๋ผ๋ฒจ ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค.")
index = faiss.read_index(FAISS_INDEX_FILE)
with open(LABELS_FILE, "rb") as f:
labels = pickle.load(f)
print(f"โœ… FAISS ์ธ๋ฑ์Šค ๋กœ๋”ฉ ์™„๋ฃŒ. ์ด {index.ntotal}๊ฐœ์˜ ์ž„๋ฒ ๋”ฉ ๋กœ๋“œ.")
except Exception as e:
print(f"โŒ FAISS ์ธ๋ฑ์Šค ๋กœ๋”ฉ ์‹คํŒจ: {e}")
raise HTTPException(status_code=500, detail=f"FAISS ๋กœ๋”ฉ ์‹คํŒจ: {e}")
# ๐Ÿš€ ์–ผ๊ตด ์ž„๋ฒ ๋”ฉ ์ถ”์ถœ ํ•จ์ˆ˜ (์›๋ณธ ์ž„๋ฒ ๋”ฉ๋งŒ ์ถ”์ถœ)
def get_face_embedding(img_np):
"""
Numpy ๋ฐฐ์—ด ํ˜•ํƒœ์˜ ์ด๋ฏธ์ง€์—์„œ ์–ผ๊ตด ์ž„๋ฒ ๋”ฉ์„ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.
"""
global model
if model is None:
raise HTTPException(status_code=500, detail="๋ชจ๋ธ์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
# BGR์„ RGB๋กœ ๋ณ€ํ™˜ (InsightFace ๋ชจ๋ธ์€ RGB๋ฅผ ์„ ํ˜ธ)
img = cv2.cvtColor(img_np, cv2.COLOR_BGR2RGB)
faces = model.get(img)
if faces:
return faces[0].embedding
else:
return None
# --- API ์—”๋“œํฌ์ธํŠธ ์ •์˜ ---
# 3. ๋ฃจํŠธ ์—”๋“œํฌ์ธํŠธ (GET /)
@app.get("/")
def read_root():
result={"success":True,"data":None,"msg":""}
try:
result["data"]="ok"
return result
except Exception as e:
result["success"] = False
result["msg"]=f"server error. {e!r}"
return result
# ๐Ÿš€ ์–ผ๊ตด ์˜ˆ์ธก API ์—”๋“œํฌ์ธํŠธ
@app.post("/predict_person/")
async def predict_person(
image: UploadFile = File(..., description="๋ถ„์„ํ•  ์–ผ๊ตด ์ด๋ฏธ์ง€ ํŒŒ์ผ"),
top_k: int = 1
):
"""
์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€์—์„œ ์–ผ๊ตด์„ ์ธ์‹ํ•˜๊ณ , FAISS ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€์žฅ ์œ ์‚ฌํ•œ ์ธ๋ฌผ์„ ์˜ˆ์ธกํ•ฉ๋‹ˆ๋‹ค.
"""
global index, labels
# 1. ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ฝ๊ธฐ
content = await image.read()
np_array = np.frombuffer(content, np.uint8)
img_np = cv2.imdecode(np_array, cv2.IMREAD_COLOR)
if img_np is None:
raise HTTPException(status_code=400, detail="์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
# 2. ์–ผ๊ตด ์ž„๋ฒ ๋”ฉ ์ถ”์ถœ
embedding = get_face_embedding(img_np)
if embedding is None:
raise HTTPException(status_code=404, detail="์ด๋ฏธ์ง€์—์„œ ์–ผ๊ตด์„ ์ธ์‹ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
# 3. ์ž„๋ฒ ๋”ฉ ์ •๊ทœํ™”
embedding = embedding.astype('float32')
embedding /= np.linalg.norm(embedding)
# ์ฟผ๋ฆฌ ํ˜•์‹์— ๋งž๊ฒŒ [1, D] ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜
query_vector = np.array([embedding])
# 4. ์œ ์‚ฌ๋„ ๊ฒ€์ƒ‰
# top_k๋Š” ์ตœ๋Œ€ ์ธ๋ฑ์Šค ํฌ๊ธฐ(index.ntotal)๋ฅผ ์ดˆ๊ณผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
k = min(top_k, index.ntotal)
scores, indices = index.search(query_vector, k)
# 5. ๊ฒฐ๊ณผ ํฌ๋งทํŒ…
results = []
for idx, score in zip(indices[0], scores[0]):
# labels[idx]๋Š” ์ธ๋ฑ์Šค์— ์ €์žฅ๋œ ์ž„๋ฒ ๋”ฉ ์ค‘ ๊ฐ€์žฅ ์œ ์‚ฌํ•œ ์ž„๋ฒ ๋”ฉ์˜ ๋ผ๋ฒจ
results.append({
"rank": len(results) + 1,
"person_id": labels[idx],
"similarity_score": float(f"{score:.4f}")
})
return {"filename": image.filename, "predictions": results}
if __name__ == "__main__":
# --reload ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ž๋™ ์žฌ์‹œ์ž‘๋˜๊ฒŒ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)