Spaces:
Sleeping
Sleeping
# -*- coding: utf-8 -*- | |
""" | |
Aplikasi Gradio untuk Analisis Komparatif Deteksi Helm Keselamatan | |
================================================================== | |
Deskripsi: | |
Aplikasi ini memungkinkan pengguna untuk mengunggah video dari lingkungan konstruksi | |
dan membandingkan kinerja dua model AI (YOLOv5m dan YOLOv8m) dalam mendeteksi | |
helm keselamatan secara real-time. | |
""" | |
# --- 1. IMPORT LIBRARY --- | |
import gradio as gr | |
from ultralyticsplus import YOLO, render_result | |
import cv2 | |
import numpy as np | |
import os | |
import tempfile | |
import time | |
import logging | |
import subprocess | |
# --- 2. SETUP & KONFIGURASI AWAL --- | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
logger = logging.getLogger(__name__) | |
# Set YOLO_CONFIG_DIR untuk mengatasi masalah izin | |
os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics" | |
# --- 3. PEMUATAN MODEL AI --- | |
try: | |
logger.info("Memulai pemuatan model AI...") | |
model_spesialis = YOLO('keremberke/yolov8m-hard-hat-detection') | |
model_spesialis.overrides['conf'] = 0.25 | |
model_spesialis.overrides['iou'] = 0.45 | |
model_spesialis.overrides['agnostic_nms'] = False | |
model_spesialis.overrides['max_det'] = 1000 | |
logger.info("β Model Spesialis (YOLOv8m-HH) berhasil dimuat.") | |
model_generalis = YOLO('keremberke/yolov5m-construction-safety') | |
model_generalis.overrides['conf'] = 0.25 | |
model_generalis.overrides['iou'] = 0.45 | |
model_generalis.overrides['agnostic_nms'] = False | |
model_generalis.overrides['max_det'] = 1000 | |
logger.info("β Model Generalis (YOLOv5m-CS) berhasil dimuat.") | |
models = { | |
"YOLOv8m (Spesialis Helm)": model_spesialis, | |
"YOLOv5m (Generalis Konstruksi)": model_generalis | |
} | |
logger.info("Semua model siap digunakan.") | |
except Exception as e: | |
logger.error(f"Gagal memuat model AI: {e}") | |
raise RuntimeError(f"Tidak dapat memuat model AI. Error: {e}") | |
# --- 4. FUNGSI KONVERSI VIDEO --- | |
def convert_video_to_mp4(input_path): | |
"""Konversi video input ke MP4 jika format tidak didukung.""" | |
temp_mp4_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name | |
ffmpeg_command = [ | |
"ffmpeg", | |
"-y", | |
"-i", input_path, | |
"-c:v", "libx264", | |
"-preset", "veryfast", | |
"-pix_fmt", "yuv420p", | |
"-t", "30", | |
temp_mp4_path | |
] | |
result = subprocess.run(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) | |
if result.returncode == 0 and os.path.exists(temp_mp4_path) and os.path.getsize(temp_mp4_path) > 0: | |
logger.info("Video dikonversi ke MP4: %s", temp_mp4_path) | |
if input_path != temp_mp4_path: | |
os.remove(input_path) | |
return temp_mp4_path | |
else: | |
logger.error("Konversi video gagal: %s", result.stderr) | |
return None | |
# --- 5. FUNGSI UTAMA PEMROSESAN VIDEO --- | |
def process_video_and_analyze(video_path, selected_model_name, progress=gr.Progress(track_tqdm=True)): | |
"""Fungsi utama untuk memproses video, deteksi objek, dan analisis.""" | |
if video_path is None: | |
return None, "Status: Silakan unggah video terlebih dahulu.", "" | |
try: | |
logger.info(f"Memulai pemrosesan video: {video_path} menggunakan model: {selected_model_name}") | |
start_time = time.time() | |
# Pilih model | |
model = models[selected_model_name] | |
# Buka video dan coba konversi jika gagal | |
cap = cv2.VideoCapture(video_path) | |
if not cap.isOpened(): | |
logger.warning("Format video tidak didukung, mencoba konversi ke MP4...") | |
converted_path = convert_video_to_mp4(video_path) | |
if converted_path: | |
video_path = converted_path | |
cap = cv2.VideoCapture(video_path) | |
if not cap.isOpened(): | |
logger.error("Gagal membuka video setelah konversi") | |
return None, "Error: Video tidak dapat dibuka meskipun dikonversi.", "" | |
# Dapatkan properti video | |
fps = cap.get(cv2.CAP_PROP_FPS) or 15 | |
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
max_duration = 30 | |
max_frames = int(fps * max_duration) | |
if total_frames > max_frames: | |
logger.warning("Video lebih dari 30 detik, dipotong ke %d frame", max_frames) | |
total_frames = max_frames | |
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
target_width = 640 | |
target_height = int(height * (target_width / width)) if width > 0 else 480 | |
# Konfigurasi video output | |
temp_output_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name | |
fourcc = cv2.VideoWriter_fourcc(*"mp4v") | |
out = cv2.VideoWriter(temp_output_path, fourcc, fps, (target_width, target_height)) | |
if not out.isOpened(): | |
logger.error("Gagal membuka VideoWriter") | |
cap.release() | |
return None, "Error: Gagal membuat file video sementara.", "" | |
# Variabel untuk analisis | |
detection_count = 0 | |
helm_detected_count = 0 | |
frame_count = 0 | |
progress(0, desc="Memulai Pemrosesan...") | |
while cap.isOpened() and frame_count < max_frames: | |
ret, frame = cap.read() | |
if not ret: | |
break | |
frame_count += 1 | |
# Update progress | |
progress(frame_count / total_frames, desc=f"Memproses Frame {frame_count}/{total_frames}") | |
# Resize frame | |
frame_resized = cv2.resize(frame, (target_width, target_height)) | |
# Proses frame (skip setiap frame ke-3 untuk performa) | |
if frame_count % 3 != 0: | |
out.write(frame_resized) | |
continue | |
# Prediksi | |
results = model.predict(frame_resized) | |
annotated_frame = render_result(model=model, image=frame_resized, result=results[0]) | |
annotated_frame = np.array(annotated_frame) # Konversi PIL ke NumPy | |
annotated_frame = cv2.cvtColor(annotated_frame, cv2.COLOR_RGB2BGR) # Konversi RGB ke BGR | |
# Hitung deteksi | |
detection_count += len(results[0].boxes) | |
for box in results[0].boxes: | |
class_id = int(box.cls) | |
class_name = model.names[class_id].lower() | |
if class_name in ['hardhat', 'helmet']: | |
helm_detected_count += 1 | |
out.write(annotated_frame) | |
end_time = time.time() | |
processing_time = end_time - start_time | |
cap.release() | |
out.release() | |
# Verifikasi file output | |
if not os.path.exists(temp_output_path) or os.path.getsize(temp_output_path) == 0: | |
logger.error("File video sementara tidak valid atau kosong") | |
return None, "Error: File video sementara tidak valid.", "" | |
logger.info(f"Video berhasil diproses dalam {processing_time:.2f} detik.") | |
# Analisis | |
analysis_text = f""" | |
### Analisis Kinerja Model: {selected_model_name} | |
- **Waktu Proses Total:** {processing_time:.2f} detik | |
- **Total Frame Diproses:** {frame_count} | |
- **Jumlah Deteksi Keseluruhan:** {detection_count} objek | |
- **Jumlah Deteksi Helm:** {helm_detected_count} objek | |
**Catatan:** | |
- **Model Spesialis (YOLOv8m):** Fokus pada helm, akurasi tinggi untuk 'Hardhat'/'Helmet'. | |
- **Model Generalis (YOLOv5m):** Deteksi berbagai objek, akurasi helm mungkin lebih rendah. | |
""" | |
return temp_output_path, f"Status: Video berhasil diproses! ({processing_time:.2f} detik)", analysis_text | |
except Exception as e: | |
logger.error(f"Terjadi error saat memproses video: {e}", exc_info=True) | |
return None, f"Error: Terjadi kesalahan - {e}", "" | |
finally: | |
if 'cap' in locals() and cap.isOpened(): | |
cap.release() | |
if 'out' in locals() and out.isOpened(): | |
out.release() | |
# --- 6. PEMBUATAN ANTARMUKA PENGGUNA (GRADIO UI) --- | |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky")) as iface: | |
gr.Markdown( | |
""" | |
# π‘οΈ Analisis Komparatif Deteksi Helm Keselamatan | |
Aplikasi ini membandingkan kinerja dua model AI dalam mendeteksi helm keselamatan pada video konstruksi. | |
""" | |
) | |
with gr.Row(): | |
with gr.Column(scale=1): | |
gr.Markdown("### **Langkah 1: Konfigurasi & Unggah**") | |
selected_model_ui = gr.Radio( | |
choices=list(models.keys()), | |
label="Pilih Model AI", | |
value="YOLOv8m (Spesialis Helm)" | |
) | |
video_input = gr.Video( | |
label="Unggah Video" | |
) | |
detect_btn = gr.Button("π Mulai Deteksi", variant="primary") | |
gr.Markdown("### **Cara Penggunaan**") | |
gr.Markdown( | |
""" | |
1. Pilih model AI. | |
2. Unggah video atau gunakan contoh. | |
3. Klik "Mulai Deteksi". | |
4. Lihat hasil dan analisis. | |
""" | |
) | |
with gr.Column(scale=1): | |
gr.Markdown("### **Langkah 2: Hasil Deteksi**") | |
video_output = gr.Video(label="Video Hasil Deteksi") | |
status_text = gr.Textbox(label="Status Proses", interactive=False) | |
gr.Markdown("### **Ringkasan Analisis**") | |
analysis_output = gr.Markdown(label="Analisis Kinerja") | |
# Contoh video | |
gr.Examples( | |
examples=[ | |
["video.mp4", "YOLOv8m (Spesialis Helm)"] | |
], | |
inputs=[video_input, selected_model_ui], | |
outputs=[video_output, status_text, analysis_output], | |
fn=process_video_and_analyze, | |
cache_examples=False, | |
label="Contoh Video" | |
) | |
# Informasi dan kredit | |
with gr.Accordion("βΉοΈ Informasi & Kredit", open=False): | |
gr.Markdown( | |
""" | |
### **Identitas Prototipe** | |
- **Pengembang:** Faisal Fahmi Yuliawan | |
- **Tujuan:** Membandingkan deteksi helm dengan model AI. | |
### **Kredit** | |
- **Model AI:** keremberke/yolov8m-hard-hat-detection, keremberke/yolov5m-construction-safety | |
- **Teknologi:** Ultralytics YOLO, Gradio, OpenCV, FFmpeg | |
""" | |
) | |
detect_btn.click( | |
fn=process_video_and_analyze, | |
inputs=[video_input, selected_model_ui], | |
outputs=[video_output, status_text, analysis_output] | |
) | |
# --- 7. LUNCURKAN APLIKASI --- | |
if __name__ == "__main__": | |
iface.launch(debug=True) |