Spaces:
Build error
Build error
Commit ยท
6273393
1
Parent(s): 2734cd5
- .dockerignore +8 -0
- .gitignore +6 -0
- Dockerfile +36 -0
- README.md +4 -0
- app.py +142 -0
- requirements.txt +2 -0
.dockerignore
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
*.pyo
|
| 4 |
+
*.pyd
|
| 5 |
+
*.log
|
| 6 |
+
.git
|
| 7 |
+
.gitignore
|
| 8 |
+
.venv/
|
.gitignore
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
*.pyo
|
| 4 |
+
*.pyd
|
| 5 |
+
*.log
|
| 6 |
+
.venv/
|
Dockerfile
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dockerfile
|
| 2 |
+
|
| 3 |
+
# Python slim ์ด๋ฏธ์ง๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉ (์ฉ๋ ์ ๊ฐ)
|
| 4 |
+
FROM python:3.10-slim
|
| 5 |
+
|
| 6 |
+
# ํ์ํ ํ๊ฒฝ ๋ณ์ ์ค์
|
| 7 |
+
ENV PYTHONUNBUFFERED 1
|
| 8 |
+
ENV APP_HOME /app
|
| 9 |
+
WORKDIR $APP_HOME
|
| 10 |
+
|
| 11 |
+
# ์์คํ
ํจํค์ง ์ค์น: OpenCV๊ฐ ํ์๋ก ํ๋ ํจํค์ง
|
| 12 |
+
RUN apt-get update && \
|
| 13 |
+
apt-get install -y --no-install-recommends \
|
| 14 |
+
libgl1-mesa-glx \
|
| 15 |
+
libsm6 \
|
| 16 |
+
libxext6 \
|
| 17 |
+
libxrender1 && \
|
| 18 |
+
rm -rf /var/lib/apt/lists/*
|
| 19 |
+
|
| 20 |
+
# requirements.txt ๋ณต์ฌ ๋ฐ ์ค์น
|
| 21 |
+
COPY requirements.txt .
|
| 22 |
+
RUN pip install --no-cache-dir --upgrade pip
|
| 23 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 24 |
+
|
| 25 |
+
# ์ ํ๋ฆฌ์ผ์ด์
์ฝ๋ ๋ณต์ฌ
|
| 26 |
+
COPY app.py .
|
| 27 |
+
|
| 28 |
+
# ์ธ๋ฑ์ค ํ์ผ๋ค์ด ์์นํ 'data' ํด๋ ์์ฑ ๋ฐ ํ์ผ ๋ณต์ฌ
|
| 29 |
+
# FAISS ์ธ๋ฑ์ค ํ์ผ๊ณผ pickle ํ์ผ์ ๋ฏธ๋ฆฌ ์ด ํด๋์ ๋ฃ์ด์ผ ํฉ๋๋ค.
|
| 30 |
+
RUN mkdir -p data
|
| 31 |
+
# COPY ./data/faiss_index_v2.index data/
|
| 32 |
+
# COPY ./data/faiss_labels_v2.pkl data/
|
| 33 |
+
# (์ฃผ: ์์ ๋ ์ค์ ์ค์ ๋ก ์ธ๋ฑ์ค ํ์ผ์ repo์ ์ถ๊ฐํ ํ ์ฃผ์์ ํด์ ํด์ผ ํฉ๋๋ค.)
|
| 34 |
+
|
| 35 |
+
# FastAPI ์ฑ ์คํ (Hugging Face Spaces ๊ธฐ๋ณธ ํฌํธ 7860 ์ฌ์ฉ)
|
| 36 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -8,3 +8,7 @@ pinned: false
|
|
| 8 |
---
|
| 9 |
|
| 10 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 11 |
+
|
| 12 |
+
py -3.10 -m uv venv venv
|
| 13 |
+
|
| 14 |
+
.\venv\Scripts\Activate.ps1
|
app.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import cv2
|
| 3 |
+
import numpy as np
|
| 4 |
+
import pickle
|
| 5 |
+
import faiss
|
| 6 |
+
from insightface.app import FaceAnalysis
|
| 7 |
+
from fastapi import FastAPI, UploadFile, File, HTTPException
|
| 8 |
+
from io import BytesIO
|
| 9 |
+
|
| 10 |
+
# 1. FastAPI ์ ํ๋ฆฌ์ผ์ด์
์ธ์คํด์ค ์์ฑ
|
| 11 |
+
app = FastAPI(
|
| 12 |
+
title="InsightFace Face Recognition API",
|
| 13 |
+
description="InsightFace (buffalo_l)๋ฅผ ์ฌ์ฉํ ์ผ๊ตด ์ธ์ ๋ฐ FAISS ์ธ๋ฑ์ค ๊ฒ์ API"
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
# ๋ชจ๋ธ ๋ฐ ์ธ๋ฑ์ค ์ ์ญ ๋ณ์
|
| 17 |
+
model = None
|
| 18 |
+
index = None
|
| 19 |
+
labels = None
|
| 20 |
+
|
| 21 |
+
# ๐ ํ๊ฒฝ ์ค์ : Docker ํ๊ฒฝ์์๋ ๋ชจ๋ธ/์ธ๋ฑ์ค ํ์ผ์ ํ๋ก์ ํธ ๋๋ ํ ๋ฆฌ์ ๋ฐฐ์นํฉ๋๋ค.
|
| 22 |
+
# ์ธ๋ฑ์ค ํ์ผ์ /app/data ํด๋์ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
|
| 23 |
+
LOAD_DIR = "data"
|
| 24 |
+
FAISS_INDEX_FILE = os.path.join(LOAD_DIR, "faiss_index_v2.index")
|
| 25 |
+
LABELS_FILE = os.path.join(LOAD_DIR, "faiss_labels_v2.pkl")
|
| 26 |
+
|
| 27 |
+
# โ
1. ๋ชจ๋ธ ๋ฐ ์ธ๋ฑ์ค ์ค๋น (์๋ฒ ์์ ์ ํ ๋ฒ๋ง ์คํ)
|
| 28 |
+
@app.on_event("startup")
|
| 29 |
+
async def startup_event():
|
| 30 |
+
global model, index, labels
|
| 31 |
+
print("๐ ์๋ฒ ์์: InsightFace ๋ชจ๋ธ ๋ฐ FAISS ์ธ๋ฑ์ค ๋ก๋ฉ ์ค...")
|
| 32 |
+
|
| 33 |
+
# InsightFace ArcFace ๋ชจ๋ธ ์ค๋น
|
| 34 |
+
try:
|
| 35 |
+
# CPUExecutionProvider ์ฌ์ฉ (GPU๊ฐ ์๋ ํ๊ฒฝ/Docker์ ์ ํฉ)
|
| 36 |
+
model = FaceAnalysis(name='buffalo_l', providers=['CPUExecutionProvider'])
|
| 37 |
+
model.prepare(ctx_id=0)
|
| 38 |
+
print("โ
InsightFace model (buffalo_l) ๋ก๋ฉ ์๋ฃ.")
|
| 39 |
+
except Exception as e:
|
| 40 |
+
print(f"โ InsightFace ๋ชจ๋ธ ๋ก๋ฉ ์คํจ: {e}")
|
| 41 |
+
raise HTTPException(status_code=500, detail=f"๋ชจ๋ธ ๋ก๋ฉ ์คํจ: {e}")
|
| 42 |
+
|
| 43 |
+
# ์ธ๋ฑ์ค & ๋ผ๋ฒจ ๋ก๋ฉ
|
| 44 |
+
try:
|
| 45 |
+
if not os.path.exists(FAISS_INDEX_FILE) or not os.path.exists(LABELS_FILE):
|
| 46 |
+
print(f"โ FAISS ํ์ผ์ด ์์ต๋๋ค. ๊ฒฝ๋ก ํ์ธ: {LOAD_DIR}")
|
| 47 |
+
raise FileNotFoundError(f"ํ์ํ FAISS ์ธ๋ฑ์ค ํ์ผ ํน์ ๋ผ๋ฒจ ํ์ผ์ด ์์ต๋๋ค.")
|
| 48 |
+
|
| 49 |
+
index = faiss.read_index(FAISS_INDEX_FILE)
|
| 50 |
+
with open(LABELS_FILE, "rb") as f:
|
| 51 |
+
labels = pickle.load(f)
|
| 52 |
+
print(f"โ
FAISS ์ธ๋ฑ์ค ๋ก๋ฉ ์๋ฃ. ์ด {index.ntotal}๊ฐ์ ์๋ฒ ๋ฉ ๋ก๋.")
|
| 53 |
+
except Exception as e:
|
| 54 |
+
print(f"โ FAISS ์ธ๋ฑ์ค ๋ก๋ฉ ์คํจ: {e}")
|
| 55 |
+
raise HTTPException(status_code=500, detail=f"FAISS ๋ก๋ฉ ์คํจ: {e}")
|
| 56 |
+
|
| 57 |
+
# ๐ ์ผ๊ตด ์๋ฒ ๋ฉ ์ถ์ถ ํจ์ (์๋ณธ ์๋ฒ ๋ฉ๋ง ์ถ์ถ)
|
| 58 |
+
def get_face_embedding(img_np):
|
| 59 |
+
"""
|
| 60 |
+
Numpy ๋ฐฐ์ด ํํ์ ์ด๋ฏธ์ง์์ ์ผ๊ตด ์๋ฒ ๋ฉ์ ์ถ์ถํฉ๋๋ค.
|
| 61 |
+
"""
|
| 62 |
+
global model
|
| 63 |
+
if model is None:
|
| 64 |
+
raise HTTPException(status_code=500, detail="๋ชจ๋ธ์ด ์ด๊ธฐํ๋์ง ์์์ต๋๋ค.")
|
| 65 |
+
|
| 66 |
+
# BGR์ RGB๋ก ๋ณํ (InsightFace ๋ชจ๋ธ์ RGB๋ฅผ ์ ํธ)
|
| 67 |
+
img = cv2.cvtColor(img_np, cv2.COLOR_BGR2RGB)
|
| 68 |
+
faces = model.get(img)
|
| 69 |
+
|
| 70 |
+
if faces:
|
| 71 |
+
return faces[0].embedding
|
| 72 |
+
else:
|
| 73 |
+
return None
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
# --- API ์๋ํฌ์ธํธ ์ ์ ---
|
| 78 |
+
# 3. ๋ฃจํธ ์๋ํฌ์ธํธ (GET /)
|
| 79 |
+
@app.get("/")
|
| 80 |
+
def read_root():
|
| 81 |
+
result={"success":True,"data":None,"msg":""}
|
| 82 |
+
try:
|
| 83 |
+
result["data"]="ok"
|
| 84 |
+
return result
|
| 85 |
+
except Exception as e:
|
| 86 |
+
result["success"] = False
|
| 87 |
+
result["msg"]=f"server error. {e!r}"
|
| 88 |
+
return result
|
| 89 |
+
|
| 90 |
+
# ๐ ์ผ๊ตด ์์ธก API ์๋ํฌ์ธํธ
|
| 91 |
+
@app.post("/predict_person/")
|
| 92 |
+
async def predict_person(
|
| 93 |
+
image: UploadFile = File(..., description="๋ถ์ํ ์ผ๊ตด ์ด๋ฏธ์ง ํ์ผ"),
|
| 94 |
+
top_k: int = 1
|
| 95 |
+
):
|
| 96 |
+
"""
|
| 97 |
+
์
๋ก๋๋ ์ด๋ฏธ์ง์์ ์ผ๊ตด์ ์ธ์ํ๊ณ , FAISS ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ฅ ์ ์ฌํ ์ธ๋ฌผ์ ์์ธกํฉ๋๋ค.
|
| 98 |
+
"""
|
| 99 |
+
global index, labels
|
| 100 |
+
|
| 101 |
+
# 1. ์ด๋ฏธ์ง ํ์ผ ์ฝ๊ธฐ
|
| 102 |
+
content = await image.read()
|
| 103 |
+
np_array = np.frombuffer(content, np.uint8)
|
| 104 |
+
img_np = cv2.imdecode(np_array, cv2.IMREAD_COLOR)
|
| 105 |
+
|
| 106 |
+
if img_np is None:
|
| 107 |
+
raise HTTPException(status_code=400, detail="์ด๋ฏธ์ง ํ์ผ์ ์ฝ์ ์ ์์ต๋๋ค.")
|
| 108 |
+
|
| 109 |
+
# 2. ์ผ๊ตด ์๋ฒ ๋ฉ ์ถ์ถ
|
| 110 |
+
embedding = get_face_embedding(img_np)
|
| 111 |
+
|
| 112 |
+
if embedding is None:
|
| 113 |
+
raise HTTPException(status_code=404, detail="์ด๋ฏธ์ง์์ ์ผ๊ตด์ ์ธ์ํ ์ ์์ต๋๋ค.")
|
| 114 |
+
|
| 115 |
+
# 3. ์๋ฒ ๋ฉ ์ ๊ทํ
|
| 116 |
+
embedding = embedding.astype('float32')
|
| 117 |
+
embedding /= np.linalg.norm(embedding)
|
| 118 |
+
|
| 119 |
+
# ์ฟผ๋ฆฌ ํ์์ ๋ง๊ฒ [1, D] ํํ๋ก ๋ณํ
|
| 120 |
+
query_vector = np.array([embedding])
|
| 121 |
+
|
| 122 |
+
# 4. ์ ์ฌ๋ ๊ฒ์
|
| 123 |
+
# top_k๋ ์ต๋ ์ธ๋ฑ์ค ํฌ๊ธฐ(index.ntotal)๋ฅผ ์ด๊ณผํ ์ ์์ต๋๋ค.
|
| 124 |
+
k = min(top_k, index.ntotal)
|
| 125 |
+
scores, indices = index.search(query_vector, k)
|
| 126 |
+
|
| 127 |
+
# 5. ๊ฒฐ๊ณผ ํฌ๋งทํ
|
| 128 |
+
results = []
|
| 129 |
+
for idx, score in zip(indices[0], scores[0]):
|
| 130 |
+
# labels[idx]๋ ์ธ๋ฑ์ค์ ์ ์ฅ๋ ์๋ฒ ๋ฉ ์ค ๊ฐ์ฅ ์ ์ฌํ ์๋ฒ ๋ฉ์ ๋ผ๋ฒจ
|
| 131 |
+
results.append({
|
| 132 |
+
"rank": len(results) + 1,
|
| 133 |
+
"person_id": labels[idx],
|
| 134 |
+
"similarity_score": float(f"{score:.4f}")
|
| 135 |
+
})
|
| 136 |
+
|
| 137 |
+
return {"filename": image.filename, "predictions": results}
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
if __name__ == "__main__":
|
| 141 |
+
# --reload ์ต์
์ ๏ฟฝ๏ฟฝ๏ฟฝ๊ฐํ์ฌ ์ฝ๋๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์๋ ์ฌ์์๋๊ฒ ์ค์ ํฉ๋๋ค.
|
| 142 |
+
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn[standard]
|