CoolFace
Apppublic

keyfarel/svm-expression-api

sourceHugging Faceapache-2.0updated 10mo agoView on Hugging Face
0likes
main.py156 linesDownload Raw Back to root
1from fastapi import FastAPI, UploadFile, File, HTTPException2import joblib3import numpy as np4import cv25import mediapipe as mp6import math7import os8from skimage.feature import hog  # <-- LIBRARY BARU UNTUK HOG9 10app = FastAPI(title="API SVM Expression Recognition")11 12# --- 1. SETUP MODEL & MEDIAPIPE ---13model_svm = None14scaler_data = None15pca_transform = None16label_encoder = None17 18mp_face_mesh = mp.solutions.face_mesh19 20@app.on_event("startup")21async def load_models():22    global model_svm, scaler_data, pca_transform, label_encoder23    current_dir = os.path.dirname(os.path.abspath(__file__))24    try:25        model_svm = joblib.load(os.path.join(current_dir, 'svm_gridsearch_best.pkl'))26        scaler_data = joblib.load(os.path.join(current_dir, 'scaler_fixed.pkl'))27        pca_transform = joblib.load(os.path.join(current_dir, 'pca_model.pkl'))28        label_encoder = joblib.load(os.path.join(current_dir, 'label_encoder_fixed.pkl'))29        print("--- SEMUA MODEL SUKSES DIMUAT ---")30    except Exception as e:31        print(f"--- ERROR LOAD MODEL: {e} ---")32 33# --- 2. FUNGSI PREPROCESSING & HOG ---34 35def get_face_landmarks_bgr(image_bgr):36    h, w = image_bgr.shape[:2]37    with mp_face_mesh.FaceMesh(static_image_mode=True, max_num_faces=1, refine_landmarks=True) as face_mesh:38        results = face_mesh.process(cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB))39    40    if not results.multi_face_landmarks:41        return None42    lms = results.multi_face_landmarks[0]43    coords = []44    for lm in lms.landmark:45        coords.append((int(lm.x * w), int(lm.y * h)))46    return coords47 48def align_face_by_eyes(image_bgr, landmarks):49    left_idx, right_idx = 33, 26350    (lx, ly) = landmarks[left_idx]51    (rx, ry) = landmarks[right_idx]52    eyes_center = ((lx + rx) // 2, (ly + ry) // 2)53    dy = ry - ly54    dx = rx - lx55    angle = math.degrees(math.atan2(dy, dx))56    M = cv2.getRotationMatrix2D(eyes_center, angle, 1.0)57    h, w = image_bgr.shape[:2]58    rotated = cv2.warpAffine(image_bgr, M, (w, h), flags=cv2.INTER_LINEAR)59    return rotated, M60 61def crop_face_from_landmarks(rotated_bgr, landmarks, M, margin=0.25):62    pts = np.array([[x, y, 1] for (x, y) in landmarks]).T63    transformed = M.dot(pts).T64    xs = transformed[:,0]65    ys = transformed[:,1]66    x1, x2 = int(xs.min()), int(xs.max())67    y1, y2 = int(ys.min()), int(ys.max())68    w_box = x2 - x169    h_box = y2 - y170    x1m = max(0, int(x1 - margin * w_box))71    y1m = max(0, int(y1 - margin * h_box))72    x2m = min(rotated_bgr.shape[1], int(x2 + margin * w_box))73    y2m = min(rotated_bgr.shape[0], int(y2 + margin * h_box))74    x1m, y1m = max(0, x1m), max(0, y1m)75    x2m, y2m = min(rotated_bgr.shape[1], x2m), min(rotated_bgr.shape[0], y2m)76    face_crop = rotated_bgr[y1m:y2m, x1m:x2m]77    return face_crop78 79def preprocess_pipeline(image_bytes):80    nparr = np.frombuffer(image_bytes, np.uint8)81    img_bgr = cv2.imdecode(nparr, cv2.IMREAD_COLOR)82    if img_bgr is None: raise ValueError("Gambar rusak")83 84    landmarks = get_face_landmarks_bgr(img_bgr)85    if landmarks is None: raise ValueError("Wajah tidak terdeteksi")86 87    rotated, M = align_face_by_eyes(img_bgr, landmarks)88    face_crop = crop_face_from_landmarks(rotated, landmarks, M, margin=0.25)89    90    if face_crop is None or face_crop.size == 0: raise ValueError("Crop gagal")91 92    gray = cv2.cvtColor(face_crop, cv2.COLOR_BGR2GRAY)93    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))94    img_clahe = clahe.apply(gray)95    img_resized = cv2.resize(img_clahe, (128, 128))96    97    return img_resized98 99# --- FUNGSI BARU: EKSTRAKSI FITUR HOG ---100def extract_features_hog(image_gray):101    # Parameter ini HARUS SAMA dengan training di Colab102    # Default umum: orientations=9, pixels_per_cell=(8, 8), cells_per_block=(2, 2)103    fd = hog(image_gray, 104             orientations=9, 105             pixels_per_cell=(8, 8), 106             cells_per_block=(2, 2), 107             block_norm='L2-Hys')108    return fd109 110# --- 3. ENDPOINT API ---111 112@app.get("/")113def home():114    return {"status": "ready"}115 116@app.post("/predict_image")117async def predict_image(file: UploadFile = File(...)):118    if model_svm is None:119        raise HTTPException(status_code=500, detail="Model belum siap")120    121    try:122        contents = await file.read()123        124        # 1. Preprocessing (Crop & Grayscale)125        try:126            processed_img = preprocess_pipeline(contents)127        except ValueError as e:128            return {"status": "error", "message": str(e)}129 130        # 2. HOG Feature Extraction (GANTI DARI FLATTEN KE HOG)131        # Input: Gambar (128, 128) -> Output: Vektor HOG (contoh: 8100 fitur)132        features_hog = extract_features_hog(processed_img)133        features = features_hog.reshape(1, -1)134        135        # 3. Scaling & PCA136        features_scaled = scaler_data.transform(features)137        features_pca = pca_transform.transform(features_scaled)138        139        # 4. Predict SVM140        prediction_index = model_svm.predict(features_pca)141        result_label = label_encoder.inverse_transform(prediction_index)142        143        return {144            "status": "success",145            "prediction": result_label[0]146        }147        148    except Exception as e:149        print(f"Error: {e}")150        error_msg = str(e)151        if "X has" in error_msg or "mismatch" in error_msg:152             detail_msg = f"Error Dimensi: Jumlah fitur HOG tidak cocok. Training vs Server berbeda. Error asli: {error_msg}"153        else:154             detail_msg = error_msg155             156        raise HTTPException(status_code=500, detail=detail_msg)