MIF_E31230910_MP-HRIS-ML/services/embedding_service.py

215 lines
6.0 KiB
Python

import cv2
import os
import sys
import logging
import numpy as np
logger = logging.getLogger('flask_ml')
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from utils.lbp_features import extract_lbp_features, preprocess_face, FACE_SIZE
BLUR_THRESHOLD = 30.0
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
profile_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_profileface.xml'
)
def get_blur_score(img):
return cv2.Laplacian(img, cv2.CV_64F).var()
def detect_and_crop_face(gray):
h, w = gray.shape
if max(h, w) > 640:
scale = 640.0 / max(h, w)
gray = cv2.resize(gray, (int(w * scale), int(h * scale)), interpolation=cv2.INTER_AREA)
h, w = gray.shape
min_size = int(min(h, w) * 0.1)
faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(min_size, min_size))
if len(faces) == 0:
faces = face_cascade.detectMultiScale(gray, 1.05, 3, minSize=(min_size, min_size))
if len(faces) == 0:
faces = profile_cascade.detectMultiScale(gray, 1.1, 3, minSize=(min_size, min_size))
if len(faces) == 0:
flipped = cv2.flip(gray, 1)
faces = face_cascade.detectMultiScale(flipped, 1.05, 3, minSize=(min_size, min_size))
if len(faces) > 0:
gray = flipped
if len(faces) == 0:
return None
faces = sorted(faces, key=lambda f: f[2] * f[3], reverse=True)
(x, y, fw, fh) = faces[0]
padding = int(max(fw, fh) * 0.1)
x1 = max(0, x - padding)
y1 = max(0, y - padding)
x2 = min(w, x + fw + padding)
y2 = min(h, y + fh + padding)
face_crop = gray[y1:y2, x1:x2]
if face_crop.shape[0] < 10 or face_crop.shape[1] < 10:
return None
face_resized = cv2.resize(face_crop, FACE_SIZE, interpolation=cv2.INTER_AREA)
return face_resized
def compute_embedding_from_frames(frame_dir):
valid_ext = ('.jpg', '.jpeg', '.png')
features_list = []
files = sorted([
f for f in os.listdir(frame_dir)
if f.lower().endswith(valid_ext) and f.startswith('frame_')
])
if len(files) == 0:
raise Exception("Tidak ada frame wajah di folder dataset.")
for filename in files:
filepath = os.path.join(frame_dir, filename)
img = cv2.imread(filepath, cv2.IMREAD_GRAYSCALE)
if img is None:
continue
proc = preprocess_face(img)
features = extract_lbp_features(proc)
features_list.append(features)
if len(features_list) == 0:
raise Exception("Tidak ada frame valid untuk dihitung embedding-nya.")
avg_embedding = np.mean(features_list, axis=0)
norm = np.linalg.norm(avg_embedding)
if norm > 0:
avg_embedding = avg_embedding / norm
return avg_embedding.tolist()
def compute_embedding_from_image(image_path):
from PIL import Image
try:
pil_img = Image.open(image_path)
exif = pil_img.getexif()
orientation = exif.get(274, 1)
if orientation == 3:
pil_img = pil_img.rotate(180, expand=True)
elif orientation == 6:
pil_img = pil_img.rotate(270, expand=True)
elif orientation == 8:
pil_img = pil_img.rotate(90, expand=True)
img_array = np.array(pil_img)
if len(img_array.shape) == 3 and img_array.shape[2] == 3:
img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
img = img_array
except Exception:
img = cv2.imread(image_path)
if img is None:
raise Exception("Gagal membaca file gambar.")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img
face = detect_and_crop_face(gray)
if face is None:
raise Exception(
"Wajah tidak terdeteksi. Pastikan pencahayaan cukup dan wajah terlihat jelas."
)
blur_score = get_blur_score(face)
if blur_score < BLUR_THRESHOLD:
raise Exception(
f"Foto terlalu buram (Score: {round(blur_score, 1)}). Harap foto ulang."
)
proc = preprocess_face(face)
features = extract_lbp_features(proc)
norm = np.linalg.norm(features)
if norm > 0:
features = features / norm
return features.tolist(), float(blur_score)
def compute_embedding_from_video(video_path, target_frames=10):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise Exception("Gagal membuka file video verifikasi.")
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
if total_frames <= 0:
cap.release()
raise Exception("Video tidak valid (0 frame).")
candidates = []
frame_idx = 0
sample_interval = max(1, total_frames // (target_frames * 3))
while True:
ret, frame = cap.read()
if not ret:
break
if frame_idx % sample_interval != 0:
frame_idx += 1
continue
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
face_raw = detect_and_crop_face(gray)
if face_raw is None:
frame_idx += 1
continue
blur_score = get_blur_score(face_raw)
if blur_score < BLUR_THRESHOLD:
frame_idx += 1
continue
proc = preprocess_face(face_raw)
features = extract_lbp_features(proc)
candidates.append({
'features': features,
'blur_score': blur_score,
})
frame_idx += 1
cap.release()
if len(candidates) == 0:
raise Exception(
"Tidak ada frame wajah yang valid dalam video. "
"Pastikan wajah menghadap depan dengan pencahayaan cukup."
)
candidates.sort(key=lambda c: c['blur_score'], reverse=True)
selected = candidates[:target_frames]
features_list = [c['features'] for c in selected]
avg_embedding = np.mean(features_list, axis=0)
norm = np.linalg.norm(avg_embedding)
if norm > 0:
avg_embedding = avg_embedding / norm
avg_blur = float(np.mean([c['blur_score'] for c in selected]))
return avg_embedding.tolist(), avg_blur