130 lines
3.6 KiB
Python
130 lines
3.6 KiB
Python
import cv2
|
|
import os
|
|
import numpy as np
|
|
|
|
import sys
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
|
|
from utils.lbp_features import FACE_SIZE
|
|
|
|
face_cascade = cv2.CascadeClassifier(
|
|
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
|
|
)
|
|
|
|
|
|
def crop_wajah(gray):
|
|
"""Crop wajah persis seperti notebook ablasi jalur 2."""
|
|
wajah = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(50, 50))
|
|
if len(wajah) == 0:
|
|
return None, None
|
|
|
|
(x, y, w, h) = max(wajah, key=lambda r: r[2] * r[3])
|
|
sisi = max(w, h)
|
|
cx, cy = x + w // 2, y + h // 2
|
|
half = sisi // 2
|
|
y1 = max(0, cy - half)
|
|
y2 = min(gray.shape[0], cy + half)
|
|
x1 = max(0, cx - half)
|
|
x2 = min(gray.shape[1], cx + half)
|
|
|
|
face_crop_gray = cv2.resize(gray[y1:y2, x1:x2], FACE_SIZE)
|
|
face_crop_color = None
|
|
return face_crop_gray, (x1, y1, x2, y2)
|
|
|
|
|
|
def extract_frames(video_path, output_dir, target_frames=200):
|
|
"""
|
|
Ekstraksi frame sesuai ablasi jalur 2:
|
|
1. Baca SEMUA frame
|
|
2. Crop SEMUA frame (Haar Cascade)
|
|
3. Sampling merata (np.linspace) dari yang berhasil crop
|
|
"""
|
|
if not os.path.exists(video_path):
|
|
raise Exception(f"Video tidak ditemukan: {video_path}")
|
|
|
|
if not os.path.exists(output_dir):
|
|
os.makedirs(output_dir)
|
|
else:
|
|
for f in os.listdir(output_dir):
|
|
if (f.startswith("frame_") or f.startswith("raw_frame_")) and f.endswith(".jpg"):
|
|
os.remove(os.path.join(output_dir, f))
|
|
|
|
cap = cv2.VideoCapture(video_path)
|
|
if not cap.isOpened():
|
|
raise Exception("Gagal membuka file video.")
|
|
|
|
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
fps = cap.get(cv2.CAP_PROP_FPS)
|
|
|
|
if total_frames <= 0:
|
|
raise Exception("Video tidak valid (0 frame).")
|
|
|
|
semua_crop = []
|
|
skipped_no_face = 0
|
|
frame_idx = 0
|
|
|
|
while True:
|
|
ret, frame = cap.read()
|
|
if not ret:
|
|
break
|
|
|
|
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
|
face_gray, bbox = crop_wajah(gray)
|
|
|
|
if face_gray is None:
|
|
skipped_no_face += 1
|
|
frame_idx += 1
|
|
continue
|
|
|
|
(x1, y1, x2, y2) = bbox
|
|
face_color = frame[y1:y2, x1:x2]
|
|
|
|
semua_crop.append({
|
|
'frame_idx': frame_idx,
|
|
'face_gray': face_gray,
|
|
'face_color': face_color,
|
|
})
|
|
|
|
frame_idx += 1
|
|
|
|
cap.release()
|
|
|
|
total_crop = len(semua_crop)
|
|
|
|
if total_crop == 0:
|
|
raise Exception(
|
|
"Tidak ada frame wajah yang berhasil diekstrak. "
|
|
"Pastikan wajah terlihat jelas di video."
|
|
)
|
|
|
|
if total_crop <= target_frames:
|
|
hasil = semua_crop
|
|
else:
|
|
indices = np.linspace(0, total_crop - 1, target_frames, dtype=int)
|
|
hasil = [semua_crop[i] for i in indices]
|
|
|
|
saved_count = 0
|
|
for cand in hasil:
|
|
filename = f"frame_{saved_count:03d}.jpg"
|
|
filename_raw = f"raw_frame_{saved_count:03d}.jpg"
|
|
cv2.imwrite(
|
|
os.path.join(output_dir, filename),
|
|
cand['face_gray'],
|
|
[int(cv2.IMWRITE_JPEG_QUALITY), 95]
|
|
)
|
|
cv2.imwrite(os.path.join(output_dir, filename_raw), cand['face_color'])
|
|
saved_count += 1
|
|
|
|
interval = total_crop / saved_count if saved_count > 0 else 0
|
|
|
|
return {
|
|
"total_video_frames": total_frames,
|
|
"video_fps": round(fps, 1),
|
|
"total_crop_detected": total_crop,
|
|
"total_extracted": saved_count,
|
|
"target_frames": target_frames,
|
|
"skipped_no_face": skipped_no_face,
|
|
"sampling_interval": round(interval, 1),
|
|
"output_dir": output_dir
|
|
}
|