WildOjisan's picture
.
6273393
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)