187 lines
5.7 KiB
Python
187 lines
5.7 KiB
Python
import cv2
|
|
import os
|
|
import sys
|
|
import json
|
|
import numpy as np
|
|
import joblib
|
|
from sklearn.svm import SVC
|
|
from sklearn.preprocessing import StandardScaler
|
|
from sklearn.pipeline import Pipeline
|
|
from sklearn.model_selection import train_test_split, GridSearchCV
|
|
from skimage.feature import local_binary_pattern
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
|
|
from utils.lbp_features import (
|
|
extract_lbp_features, extract_lbp_from_augmented,
|
|
augmentasi_lbp, preprocess_face, FACE_SIZE, LBP_SCALES
|
|
)
|
|
|
|
|
|
def load_images(dataset_path):
|
|
valid_ext = ('.jpg', '.jpeg', '.png')
|
|
images = []
|
|
|
|
if not os.path.exists(dataset_path):
|
|
return images
|
|
|
|
files = sorted([
|
|
f for f in os.listdir(dataset_path)
|
|
if f.lower().endswith(valid_ext) and (
|
|
f.startswith('frame_') or f.startswith('selfie_')
|
|
)
|
|
])
|
|
|
|
for filename in files:
|
|
filepath = os.path.join(dataset_path, filename)
|
|
img = cv2.imread(filepath, cv2.IMREAD_GRAYSCALE)
|
|
if img is not None:
|
|
if img.shape != FACE_SIZE:
|
|
img = cv2.resize(img, FACE_SIZE, interpolation=cv2.INTER_AREA)
|
|
images.append(img)
|
|
|
|
return images
|
|
|
|
|
|
def train_model(base_datasets_path, model_output_path, approved_user_ids=None):
|
|
if not os.path.exists(base_datasets_path):
|
|
raise Exception(f"Base datasets path tidak ditemukan: {base_datasets_path}")
|
|
|
|
user_images_dict = {}
|
|
label_map = {}
|
|
user_stats = {}
|
|
|
|
for folder_name in sorted(os.listdir(base_datasets_path)):
|
|
folder_path = os.path.join(base_datasets_path, folder_name)
|
|
if not os.path.isdir(folder_path):
|
|
continue
|
|
|
|
user_id = folder_name
|
|
|
|
if approved_user_ids and user_id not in approved_user_ids:
|
|
continue
|
|
|
|
images = load_images(folder_path)
|
|
if len(images) < 10:
|
|
continue
|
|
|
|
label_map[user_id] = user_id
|
|
user_images_dict[user_id] = images
|
|
|
|
if len(label_map) == 0:
|
|
raise Exception("Tidak ada user dengan dataset valid (minimal 10 gambar per user).")
|
|
|
|
if len(label_map) < 2:
|
|
raise Exception(
|
|
"Minimal 2 user yang sudah di-approve diperlukan untuk melatih model SVM. "
|
|
f"Saat ini baru {len(label_map)} user."
|
|
)
|
|
|
|
X_ori = []
|
|
y_ori = []
|
|
X_aug = []
|
|
y_aug = []
|
|
|
|
for user_id, images in user_images_dict.items():
|
|
ori_count = 0
|
|
aug_count = 0
|
|
|
|
for img in images:
|
|
proc_img = preprocess_face(img)
|
|
features = extract_lbp_features(proc_img)
|
|
X_ori.append(features)
|
|
y_ori.append(user_id)
|
|
ori_count += 1
|
|
|
|
pola = local_binary_pattern(proc_img, P=8, R=1, method='uniform')
|
|
lbp_uint8 = (pola * 255.0 / 9).astype(np.uint8)
|
|
variasi = augmentasi_lbp(lbp_uint8)
|
|
for aug_img in variasi:
|
|
aug_features = extract_lbp_from_augmented(aug_img)
|
|
X_aug.append(aug_features)
|
|
y_aug.append(user_id)
|
|
aug_count += 1
|
|
|
|
user_stats[user_id] = ori_count + aug_count
|
|
|
|
X = np.array(X_ori + X_aug)
|
|
y = np.array(y_ori + y_aug)
|
|
|
|
X_train, X_test, y_train, y_test = train_test_split(
|
|
X, y, test_size=0.2, random_state=42, stratify=y
|
|
)
|
|
|
|
param_grid = {
|
|
'svm__C': [0.1, 1, 10, 20],
|
|
'svm__gamma': ['scale', 'auto', 0.01, 0.001, 0.1]
|
|
}
|
|
|
|
pipe_search = Pipeline([
|
|
('scaler', StandardScaler()),
|
|
('svm', SVC(kernel='rbf', probability=False))
|
|
])
|
|
|
|
grid = GridSearchCV(
|
|
pipe_search, param_grid,
|
|
cv=5, scoring='accuracy',
|
|
refit=False, return_train_score=True, verbose=0
|
|
)
|
|
grid.fit(X_train, y_train)
|
|
|
|
best_C = grid.best_params_['svm__C']
|
|
best_gamma = grid.best_params_['svm__gamma']
|
|
cv_score = grid.best_score_
|
|
|
|
model_final = Pipeline([
|
|
('scaler', StandardScaler()),
|
|
('svm', SVC(kernel='rbf', C=best_C, gamma=best_gamma, probability=True))
|
|
])
|
|
model_final.fit(X_train, y_train)
|
|
test_acc = model_final.score(X_test, y_test)
|
|
|
|
if not os.path.exists(model_output_path):
|
|
os.makedirs(model_output_path)
|
|
|
|
model_file = os.path.join(model_output_path, "face_model.pkl")
|
|
scaler_file = os.path.join(model_output_path, "face_scaler.pkl")
|
|
labels_file = os.path.join(model_output_path, "face_labels.json")
|
|
|
|
temp_model_file = os.path.join(model_output_path, "face_model_temp.pkl")
|
|
temp_scaler_file = os.path.join(model_output_path, "face_scaler_temp.pkl")
|
|
temp_labels_file = os.path.join(model_output_path, "face_labels_temp.json")
|
|
|
|
joblib.dump(model_final.named_steps['svm'], temp_model_file)
|
|
joblib.dump(model_final.named_steps['scaler'], temp_scaler_file)
|
|
|
|
labels_data = {
|
|
"classes": list(model_final.named_steps['svm'].classes_),
|
|
"user_ids": list(label_map.keys()),
|
|
"best_C": best_C,
|
|
"best_gamma": str(best_gamma),
|
|
"cv_score": round(cv_score, 4),
|
|
"test_accuracy": round(test_acc, 4),
|
|
}
|
|
with open(temp_labels_file, 'w') as f:
|
|
json.dump(labels_data, f, indent=2)
|
|
|
|
os.replace(temp_model_file, model_file)
|
|
os.replace(temp_scaler_file, scaler_file)
|
|
os.replace(temp_labels_file, labels_file)
|
|
|
|
return {
|
|
"total_users": len(label_map),
|
|
"user_stats": user_stats,
|
|
"total_samples": len(X),
|
|
"train_samples": len(X_train),
|
|
"test_samples": len(X_test),
|
|
"feature_dimension": int(X.shape[1]),
|
|
"best_C": best_C,
|
|
"best_gamma": str(best_gamma),
|
|
"cv_score": round(cv_score, 4),
|
|
"test_accuracy": round(test_acc, 4),
|
|
"classes": list(model_final.named_steps['svm'].classes_),
|
|
"model_path": model_file,
|
|
"scaler_path": scaler_file,
|
|
"labels_path": labels_file
|
|
}
|