Files
presensi_face_service/main.py
2026-03-05 14:48:43 +07:00

111 lines
3.1 KiB
Python

import io
import math
import os
from typing import Any, Dict
import cv2
import numpy as np
from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.responses import JSONResponse
from insightface.app import FaceAnalysis
app = FastAPI(title="SMAN1 Face Embedding Service", version="1.0.0")
MODEL_NAME = os.getenv("FACE_MODEL_NAME", "buffalo_l")
DETECT_SIZE = int(os.getenv("FACE_DET_SIZE", "640"))
app_insight: FaceAnalysis | None = None
def get_app() -> FaceAnalysis:
global app_insight
if app_insight is None:
app_insight = FaceAnalysis(name=MODEL_NAME)
# ctx_id = 0 -> GPU, -1 -> CPU
ctx_id = int(os.getenv("FACE_CTX_ID", "-1"))
app_insight.prepare(ctx_id=ctx_id, det_size=(DETECT_SIZE, DETECT_SIZE))
return app_insight
def compute_blur_score(gray: np.ndarray) -> float:
# Variance of Laplacian
return float(cv2.Laplacian(gray, cv2.CV_64F).var())
def compute_brightness(img: np.ndarray) -> float:
# Normalize brightness to 0..1 (approx)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
v = hsv[:, :, 2]
return float(v.mean() / 255.0)
@app.post("/embed")
async def embed(image: UploadFile = File(...)) -> JSONResponse:
"""
Terima satu file gambar, kembalikan embedding + metrik kualitas.
Response JSON:
{
"embedding": [...],
"quality_score": float,
"faces_count": int,
"face_size": float,
"blur": float,
"brightness": float
}
"""
if not image.filename:
raise HTTPException(status_code=400, detail="File gambar wajib diisi")
content = await image.read()
img_array = np.frombuffer(content, dtype=np.uint8)
img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
if img is None:
raise HTTPException(status_code=400, detail="Gagal membaca gambar")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
app_face = get_app()
faces = app_face.get(img)
faces_count = len(faces)
if faces_count == 0:
raise HTTPException(status_code=422, detail="Wajah tidak ditemukan")
# Ambil face terbesar
main_face = max(faces, key=lambda f: (f.bbox[2] - f.bbox[0]) * (f.bbox[3] - f.bbox[1]))
x1, y1, x2, y2 = map(int, main_face.bbox)
w = max(0, x2 - x1)
h = max(0, y2 - y1)
face_size = float(max(w, h))
blur_score = compute_blur_score(gray[y1:y2, x1:x2]) if w > 0 and h > 0 else 0.0
brightness = compute_brightness(img)
# InsightFace sudah memberi embedding ter-normalisasi
embedding_vec = main_face.embedding.astype(float).tolist()
# Quality score sederhana: kombinasi face_size, blur, brightness (bisa disesuaikan)
quality_score = float(
math.tanh((face_size / 100.0))
* math.tanh(blur_score / 50.0)
* math.tanh(brightness * 2.0)
)
return JSONResponse(
{
"embedding": embedding_vec,
"quality_score": quality_score,
"faces_count": faces_count,
"face_size": face_size,
"blur": blur_score,
"brightness": brightness,
}
)
@app.get("/health")
async def health() -> Dict[str, Any]:
return {"status": "ok", "model": MODEL_NAME}