WildOjisan commited on
Commit
6273393
ยท
1 Parent(s): 2734cd5
Files changed (6) hide show
  1. .dockerignore +8 -0
  2. .gitignore +6 -0
  3. Dockerfile +36 -0
  4. README.md +4 -0
  5. app.py +142 -0
  6. 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]