iBetter Books
수정

첫 프로젝트는 출입 통제 시스템입니다. PART 09의 라이브니스 게이트와 PART 07의 대규모 인식을, 이번에는 웹 API로 감싸 다른 프로그램이나 기기에서 호출할 수 있게 만듭니다. 문 앞의 카메라가 사진을 보내면, 서버가 진짜 등록자인지 판정해 출입 여부를 돌려주는 구조입니다.

왜 API로 만드나

지금까지의 코드는 한 대의 컴퓨터에서 실행하는 스크립트였습니다. 실제 출입 시스템은 여러 출입구(카메라)가 한 서버에 요청을 보내는 형태가 많습니다. 그래서 인식 로직을 웹 API로 감싸면, 카메라는 사진만 보내고 무거운 인식은 서버가 처리하는 깔끔한 분리가 됩니다. 이 API를 만드는 데 가벼운 웹 프레임워크 FastAPI를 씁니다.

pip install fastapi uvicorn python-multipart

출입 판정 API

PART 09 Ch05에서 만든 access_check 로직을, 이미지 업로드를 받는 API 엔드포인트로 감쌉니다.

# 파일: access_api.py"""사진을 받아 라이브니스+인식으로 출입을 판정하는 API."""import pickleimport numpy as npimport cv2import faissfrom fastapi import FastAPI, UploadFilefrom deepface import DeepFacefrom insightface.app import FaceAnalysisapp = FastAPI()# 서버 시작 시 한 번만 모델·인덱스 로드 (요청마다 로드하면 느림)recognizer = FaceAnalysis(name="buffalo_l", providers=["CPUExecutionProvider"])recognizer.prepare(ctx_id=-1, det_size=(640, 640))index = faiss.read_index("faces.index")with open("names.pkl", "rb") as f:    names = pickle.load(f)LIVENESS_MIN, RECOG_MIN = 0.9, 0.4@app.post("/access")async def access(file: UploadFile):    # 업로드된 바이트를 OpenCV 이미지로 디코딩    data = await file.read()    img = cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR)    cv2.imwrite("/tmp/_q.jpg", img)         # DeepFace는 경로 입력이 편함    # 1) 라이브니스 게이트 (PART 09)    try:        faces = DeepFace.extract_faces("/tmp/_q.jpg", detector_backend="yunet",                                       anti_spoofing=True)    except ValueError:        return {"granted": False, "reason": "no_face"}    if not faces or not faces[0]["is_real"] or faces[0]["antispoof_score"] < LIVENESS_MIN:        return {"granted": False, "reason": "spoof"}    # 2) 인식 (PART 07)    got = recognizer.get(img)    if not got:        return {"granted": False, "reason": "recog_fail"}    q = np.array([got[0].normed_embedding], dtype="float32")    faiss.normalize_L2(q)    D, I = index.search(q, k=1)    if float(D[0][0]) < RECOG_MIN:        return {"granted": False, "reason": "unknown"}    return {"granted": True, "name": names[int(I[0][0])],            "score": round(float(D[0][0]), 3)}

서버를 띄우는 명령은 다음과 같습니다.

uvicorn access_api:app --host 0.0.0.0 --port 8000

이제 출입구 카메라는 사진 파일을 POST /access로 보내기만 하면, 서버가 {"granted": true, "name": "홍길동"} 같은 답을 돌려줍니다.

두 가지 핵심 설계

이 코드에는 실무에서 중요한 두 가지가 들어 있습니다.

  • 모델은 시작 시 한 번만 로드: 모델 로딩은 비쌉니다. 요청마다 로드하면 응답이 느려지므로, 서버가 켜질 때 전역으로 한 번만 준비합니다(PART 06의 워밍업과 같은 원리).
  • 게이트 순서: 라이브니스 → 인식. 가짜는 인식 연산까지 가지 않습니다(PART 09).
flowchart TB A[카메라] -->|POST /access| B[FastAPI] B --> C{라이브니스} C -->|가짜| D[granted: false] C -->|진짜| E[InsightFace + faiss] E --> F[granted + name]

운영을 위한 보강

항목 보강
동시 요청 uvicorn 워커 수 조정, 무거운 추론은 큐로
보안 API 키·HTTPS, 출입 로그 기록
거짓 거부 사번·카드 우회 경로(PART 09)
개인정보 임베딩만 저장, 원본 사진 즉시 폐기(PART 11)

실무 팁. 업로드 이미지를 임시 파일로 저장하는 부분(/tmp/_q.jpg)은 동시 요청에서 서로 덮어쓸 수 있습니다. 실제 서비스에서는 요청마다 고유한 임시 파일 이름을 쓰거나(예: 요청 ID 붙이기), DeepFace에 경로 대신 배열을 직접 넘기는 방식으로 바꿔야 합니다. 데모에서는 단순화했지만, 동시성은 서버 코드에서 늘 점검할 지점입니다.

이 장에서 기억할 것

출입 통제 시스템은 FastAPI로 인식 로직을 웹 API로 감싸, 카메라가 사진만 보내고 서버가 판정하는 구조로 만듭니다. 모델은 서버 시작 시 한 번만 로드하고, 라이브니스→인식 게이트 순서를 지킵니다. 동시성·보안·거짓거부 우회·개인정보를 운영 단계에서 보강합니다. 다음 장에서는 개인을 판정하지 않고 전체 경향만 보는 집중도·감정 모니터링을 만듭니다.