Files changed (1) hide show
  1. app.py +252 -149
app.py CHANGED
@@ -1,180 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
- from ultralyticsplus import YOLO, render_result
3
  import cv2
4
  import numpy as np
5
  import os
6
- import logging
7
  import tempfile
8
- import subprocess
9
-
10
- # Setup logging
11
- logging.basicConfig(level=logging.INFO)
12
- logger = logging.getLogger(__name__)
13
-
14
- # Muat model YOLOv8 dari Hugging Face
15
- logger.info("Memuat model YOLOv8 dari keremberke/yolov8m-hard-hat-detection...")
16
- model = YOLO('keremberke/yolov8m-hard-hat-detection')
17
-
18
- # Set parameter model
19
- model.overrides['conf'] = 0.25 # NMS confidence threshold
20
- model.overrides['iou'] = 0.45 # NMS IoU threshold
21
- model.overrides['agnostic_nms'] = False # NMS class-agnostic
22
- model.overrides['max_det'] = 1000 # Maksimum deteksi per frame
23
- logger.info("Model berhasil dimuat")
24
-
25
- def convert_video_to_mp4(input_path):
26
- """Konversi video input ke MP4 jika format tidak didukung."""
27
- temp_mp4_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
28
- ffmpeg_command = [
29
- "ffmpeg",
30
- "-y", # Overwrite jika ada
31
- "-i", input_path,
32
- "-c:v", "libx264",
33
- "-preset", "veryfast",
34
- "-pix_fmt", "yuv420p",
35
- "-t", "30", # Batasi durasi maksimal 30 detik
36
- temp_mp4_path
37
- ]
38
- result = subprocess.run(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
39
- if result.returncode == 0 and os.path.exists(temp_mp4_path) and os.path.getsize(temp_mp4_path) > 0:
40
- logger.info("Video dikonversi ke MP4: %s", temp_mp4_path)
41
- os.remove(input_path) # Hapus file asli setelah konversi
42
- return temp_mp4_path
43
- else:
44
- logger.error("Konversi video gagal: %s", result.stderr)
45
- return None
46
-
47
- def process_video(video_path):
 
 
 
 
 
 
 
 
 
 
48
  try:
49
- logger.info("Memulai pemrosesan video: %s", video_path)
 
 
 
 
50
 
51
- # Buka video
52
  cap = cv2.VideoCapture(video_path)
53
  if not cap.isOpened():
54
- logger.warning("Format video tidak didukung, mencoba konversi ke MP4...")
55
- converted_path = convert_video_to_mp4(video_path)
56
- if converted_path:
57
- video_path = converted_path
58
- cap = cv2.VideoCapture(video_path)
59
- if not cap.isOpened():
60
- logger.error("Gagal membuka video setelah konversi")
61
- return None, "Error: Video tidak dapat dibuka meskipun dikonversi."
62
-
63
- # Dapatkan FPS dan batasi durasi
64
- fps = cap.get(cv2.CAP_PROP_FPS) or 15
65
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
66
- max_duration = 30 # Detik
67
- max_frames = int(fps * max_duration)
68
- if total_frames > max_frames:
69
- logger.warning("Video lebih dari 30 detik, dipotong ke %d frame", max_frames)
70
- cap.set(cv2.CAP_PROP_POS_FRAMES, 0) # Reset ke frame awal
71
-
72
- # Batasi resolusi ke 640x480
73
- width, height = 640, 480
74
 
75
- # Buat file video sementara (.avi)
76
- temp_video_path = tempfile.NamedTemporaryFile(suffix=".avi", delete=False).name
77
- fourcc = cv2.VideoWriter_fourcc(*"XVID")
78
- out = cv2.VideoWriter(temp_video_path, fourcc, fps, (width, height))
79
- if not out.isOpened():
80
- logger.error("Gagal membuka VideoWriter")
81
- cap.release()
82
- return None, "Error: Gagal membuat file video sementara."
 
 
 
 
 
83
 
84
- # Proses frame
85
- frame_count = 0
86
- while cap.isOpened() and frame_count < max_frames:
87
  ret, frame = cap.read()
88
  if not ret:
89
  break
90
- frame_count += 1
91
 
92
- # Resize frame ke 640x480
93
- frame = cv2.resize(frame, (width, height))
 
 
 
 
 
 
 
 
 
94
 
95
- # Skip frame untuk mengurangi beban
96
- if frame_count % 3 != 0: # Tulis frame asli untuk frame yang dilewati
97
- out.write(frame)
98
- continue
 
 
 
 
 
 
 
 
 
 
99
 
100
- # Proses frame untuk deteksi
101
- results = model.predict(frame)
102
- annotated_frame = render_result(model=model, image=frame, result=results[0])
103
- annotated_frame = np.array(annotated_frame) # Konversi PIL ke NumPy
104
- annotated_frame = cv2.cvtColor(annotated_frame, cv2.COLOR_RGB2BGR) # Konversi RGB ke BGR
105
- logger.debug("Menulis frame %d dengan ukuran %s", frame_count, annotated_frame.shape)
106
  out.write(annotated_frame)
107
-
108
- # Bersihkan
 
 
 
109
  cap.release()
110
  out.release()
111
 
112
- # Periksa apakah file sementara ada dan tidak kosong
113
- if not os.path.exists(temp_video_path) or os.path.getsize(temp_video_path) == 0:
114
- logger.error("File video sementara tidak valid atau kosong")
115
- return None, "Error: File video sementara tidak valid."
116
-
117
- # Konversi .avi ke .mp4 dengan FFmpeg
118
- final_video_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
119
- ffmpeg_command = [
120
- "ffmpeg",
121
- "-y", # Overwrite jika ada
122
- "-i", temp_video_path,
123
- "-c:v", "libx264",
124
- "-preset", "veryfast",
125
- "-pix_fmt", "yuv420p",
126
- final_video_path
127
- ]
128
- result = subprocess.run(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
129
- if result.returncode != 0:
130
- logger.error("FFmpeg gagal: %s", result.stderr)
131
- os.remove(temp_video_path)
132
- return None, f"Error: Konversi video gagal - {result.stderr}"
133
-
134
- # Bersihkan file sementara
135
- os.remove(temp_video_path)
136
 
137
- # Periksa apakah file final ada dan valid
138
- if not os.path.exists(final_video_path) or os.path.getsize(final_video_path) == 0:
139
- logger.error("File video final tidak valid atau kosong")
140
- return None, "Error: File video final tidak valid."
141
-
142
- # Verifikasi file dengan FFmpeg (opsional untuk memastikan playable)
143
- verify_command = ["ffprobe", "-v", "error", "-show_format", "-show_streams", final_video_path]
144
- verify_result = subprocess.run(verify_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
145
- if verify_result.returncode != 0:
146
- logger.warning("File video mungkin tidak playable: %s", verify_result.stderr)
147
-
148
- logger.info("Video selesai diproses: %s", final_video_path)
149
- return final_video_path, "Video berhasil diproses! (Unduh jika tidak playable)"
 
 
 
150
  except Exception as e:
151
- logger.error("Error saat memproses video: %s", str(e))
152
- return None, f"Error: {str(e)}"
153
- finally:
154
- if 'cap' in locals() and cap.isOpened():
155
- cap.release()
156
- if 'out' in locals() and out.isOpened():
157
- out.release()
158
-
159
- # Setup antarmuka Gradio
160
- with gr.Blocks() as iface:
161
- gr.Markdown("## Deteksi Helm Keselamatan")
 
 
 
 
 
 
162
  with gr.Row():
163
  with gr.Column(scale=1):
164
- video_input = gr.Video(label="Unggah Video (maks. 30 detik)", interactive=True)
165
- detect_vid_btn = gr.Button("Detect Objects")
166
- video_examples = gr.Examples(
167
- examples=[["video.mp4"]], # Sesuaikan dengan file di repositori
168
- inputs=[video_input],
169
- label="Contoh Video",
 
 
170
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  with gr.Column(scale=1):
172
- video_output = gr.Video(label="Hasil Deteksi Helm Keselamatan")
173
- status_text = gr.Textbox(label="Status", interactive=False)
174
- detect_vid_btn.click(fn=process_video, inputs=video_input, outputs=[video_output, status_text])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
- iface.launch()
177
 
178
- # Luncurkan aplikasi
179
  if __name__ == "__main__":
180
- iface.launch()
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Aplikasi Gradio untuk Analisis Komparatif Deteksi Helm Keselamatan
4
+ ==================================================================
5
+
6
+ Deskripsi:
7
+ Aplikasi ini memungkinkan pengguna untuk mengunggah video dari lingkungan konstruksi
8
+ dan membandingkan kinerja dua model AI (YOLOv5m dan YOLOv8m) dalam mendeteksi
9
+ helm keselamatan secara real-time. Aplikasi ini dirancang untuk menjadi alat
10
+ evaluasi yang praktis dan mudah digunakan.
11
+
12
+ Fitur Utama:
13
+ - Perbandingan langsung antara model Generalis (YOLOv5) dan Spesialis (YOLOv8).
14
+ - Antarmuka pengguna (GUI) yang interaktif dan mudah dipahami.
15
+ - Pemrosesan video dengan batasan durasi dan resolusi untuk stabilitas.
16
+ - Menampilkan video hasil dengan anotasi deteksi (bounding box).
17
+ - Memberikan ringkasan hasil analisis kualitatif dan kuantitatif.
18
+ - Menyediakan dokumentasi, cara penggunaan, dan kredit yang jelas.
19
+
20
+ Pengembang: Faisal Fahmi Yuliawan
21
+ Berdasarkan Analisis pada Artikel Ilmiah.
22
+ """
23
+
24
+ # --- 1. IMPORT LIBRARY ---
25
  import gradio as gr
26
+ from ultralytics import YOLO
27
  import cv2
28
  import numpy as np
29
  import os
 
30
  import tempfile
31
+ import time
32
+ import logging
33
+
34
+ # --- 2. SETUP & KONFIGURASI AWAL ---
35
+
36
+ # Konfigurasi logging untuk memantau proses
37
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
38
+
39
+ # --- 3. PEMUATAN MODEL AI ---
40
+ # Memuat kedua model saat aplikasi dimulai untuk respons yang lebih cepat.
41
+ # Ini menggunakan memori lebih, tetapi meningkatkan pengalaman pengguna.
42
+
43
+ try:
44
+ logging.info("Memulai pemuatan model AI...")
45
+ # Model 1: Spesialis (Fokus pada Helm)
46
+ model_spesialis = YOLO('keremberke/yolov8m-hard-hat-detection')
47
+ model_spesialis.overrides['conf'] = 0.25
48
+ model_spesialis.overrides['iou'] = 0.45
49
+ logging.info("โœ… Model Spesialis (YOLOv8m-HH) berhasil dimuat.")
50
+
51
+ # Model 2: Generalis (Mendeteksi banyak objek konstruksi)
52
+ model_generalis = YOLO('keremberke/yolov5m-construction-safety')
53
+ model_generalis.overrides['conf'] = 0.25
54
+ model_generalis.overrides['iou'] = 0.45
55
+ logging.info("โœ… Model Generalis (YOLOv5m-CS) berhasil dimuat.")
56
+
57
+ # Membuat dictionary untuk kemudahan akses model
58
+ models = {
59
+ "YOLOv8m (Spesialis Helm)": model_spesialis,
60
+ "YOLOv5m (Generalis Konstruksi)": model_generalis
61
+ }
62
+ logging.info("Semua model siap digunakan.")
63
+
64
+ except Exception as e:
65
+ logging.error(f"Gagal memuat salah satu model AI: {e}")
66
+ # Jika model gagal dimuat, aplikasi tidak dapat berjalan.
67
+ # Gradio akan menangani error ini dan menampilkannya di UI.
68
+ raise RuntimeError(f"Tidak dapat memuat model AI. Aplikasi tidak dapat dijalankan. Error: {e}")
69
+
70
+
71
+ # --- 4. FUNGSI UTAMA PEMROSESAN VIDEO ---
72
+
73
+ def process_video_and_analyze(video_path, selected_model_name, progress=gr.Progress(track_tqdm=True)):
74
+ """
75
+ Fungsi utama untuk memproses video, melakukan deteksi objek,
76
+ dan mengembalikan video hasil beserta analisisnya.
77
+ """
78
+ if video_path is None:
79
+ return None, "Status: Silakan unggah video terlebih dahulu.", ""
80
+
81
  try:
82
+ logging.info(f"Memulai pemrosesan video: {video_path} menggunakan model: {selected_model_name}")
83
+ start_time = time.time()
84
+
85
+ # Pilih model berdasarkan input dari UI
86
+ model = models[selected_model_name]
87
 
88
+ # Buka file video
89
  cap = cv2.VideoCapture(video_path)
90
  if not cap.isOpened():
91
+ logging.error(f"Gagal membuka file video: {video_path}")
92
+ return None, "Error: Gagal membuka file video. Format mungkin tidak didukung.", ""
93
+
94
+ # Dapatkan properti video
95
+ fps = cap.get(cv2.CAP_PROP_FPS) or 30
 
 
 
 
 
 
96
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
97
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
98
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
 
 
 
 
 
 
99
 
100
+ # Batasi resolusi untuk performa yang lebih baik
101
+ target_width = 640
102
+ target_height = int(height * (target_width / width)) if width > 0 else 480
103
+
104
+ # Konfigurasi video output
105
+ # Menggunakan tempfile untuk file output sementara
106
+ temp_output_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
107
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
108
+ out = cv2.VideoWriter(temp_output_path, fourcc, fps, (target_width, target_height))
109
+
110
+ # Variabel untuk analisis
111
+ detection_count = 0
112
+ helm_detected_count = 0
113
 
114
+ progress(0, desc="Memulai Pemrosesan...")
115
+ for frame_idx in range(total_frames):
 
116
  ret, frame = cap.read()
117
  if not ret:
118
  break
 
119
 
120
+ # Update progress bar
121
+ progress(frame_idx / total_frames, desc=f"Memproses Frame {frame_idx+1}/{total_frames}")
122
+
123
+ # Resize frame
124
+ frame_resized = cv2.resize(frame, (target_width, target_height))
125
+
126
+ # Lakukan prediksi (inferensi)
127
+ results = model.predict(frame_resized, verbose=False)
128
+
129
+ # Render hasil deteksi ke frame
130
+ annotated_frame = results[0].plot()
131
 
132
+ # Hitung deteksi
133
+ detection_count += len(results[0].boxes)
134
+ # Hitung deteksi helm secara spesifik
135
+ if selected_model_name == "YOLOv8m (Spesialis Helm)":
136
+ for box in results[0].boxes:
137
+ class_id = int(box.cls)
138
+ # Kelas '0' biasanya 'Hardhat' di model ini
139
+ if model.names[class_id].lower() in ['hardhat', 'helmet']:
140
+ helm_detected_count += 1
141
+ elif selected_model_name == "YOLOv5m (Generalis Konstruksi)":
142
+ for box in results[0].boxes:
143
+ class_id = int(box.cls)
144
+ if model.names[class_id].lower() == 'helmet':
145
+ helm_detected_count += 1
146
 
 
 
 
 
 
 
147
  out.write(annotated_frame)
148
+
149
+ end_time = time.time()
150
+ processing_time = end_time - start_time
151
+
152
+ # Tutup file video
153
  cap.release()
154
  out.release()
155
 
156
+ logging.info(f"Video berhasil diproses dalam {processing_time:.2f} detik.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
+ # Siapkan teks analisis
159
+ analysis_text = f"""
160
+ ### Analisis Kinerja Model: {selected_model_name}
161
+
162
+ - **Waktu Proses Total:** {processing_time:.2f} detik
163
+ - **Total Frame Diproses:** {total_frames}
164
+ - **Jumlah Deteksi Keseluruhan:** {detection_count} objek
165
+ - **Jumlah Deteksi Helm:** {helm_detected_count} objek
166
+
167
+ **Catatan:**
168
+ - **Model Spesialis (YOLOv8m):** Diharapkan memiliki akurasi deteksi helm yang tinggi, namun hanya mendeteksi kelas terkait helm ('Hardhat', 'NO-Hardhat').
169
+ - **Model Generalis (YOLOv5m):** Mampu mendeteksi berbagai objek (11 kelas termasuk 'person', 'vest'), namun akurasi untuk 'helmet' mungkin lebih rendah karena fokusnya terbagi.
170
+ """
171
+
172
+ return temp_output_path, f"Status: Video berhasil diproses! ({processing_time:.2f} detik)", analysis_text
173
+
174
  except Exception as e:
175
+ logging.error(f"Terjadi error saat memproses video: {e}", exc_info=True)
176
+ return None, f"Error: Terjadi kesalahan internal - {e}", ""
177
+
178
+
179
+ # --- 5. PEMBUATAN ANTARMUKA PENGGUNA (GRADIO UI) ---
180
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky")) as iface:
181
+
182
+ # --- Bagian Judul dan Deskripsi ---
183
+ gr.Markdown(
184
+ """
185
+ # ๐Ÿ›ก๏ธ Analisis Komparatif Deteksi Helm Keselamatan
186
+ Selamat datang di prototipe aplikasi untuk analisis perbandingan model AI dalam mendeteksi helm keselamatan.
187
+ Aplikasi ini memungkinkan Anda untuk melihat perbedaan kinerja antara model AI yang **spesialis** (hanya untuk helm)
188
+ dan model AI yang **generalis** (untuk berbagai objek konstruksi).
189
+ """
190
+ )
191
+
192
  with gr.Row():
193
  with gr.Column(scale=1):
194
+ # --- Bagian Input Pengguna ---
195
+ gr.Markdown("### **Langkah 1: Konfigurasi & Unggah**")
196
+
197
+ selected_model_ui = gr.Radio(
198
+ choices=list(models.keys()),
199
+ label="Pilih Model AI untuk Perbandingan",
200
+ value="YOLOv8m (Spesialis Helm)",
201
+ info="Pilih algoritma yang ingin Anda gunakan untuk mendeteksi objek."
202
  )
203
+
204
+ video_input = gr.Video(
205
+ label="Unggah Video Anda",
206
+ info="Format video yang direkomendasikan adalah .mp4. Durasi maksimal ~30 detik."
207
+ )
208
+
209
+ detect_btn = gr.Button("๐Ÿš€ Mulai Deteksi", variant="primary")
210
+
211
+ gr.Markdown("---")
212
+ gr.Markdown("### **Cara Penggunaan**")
213
+ gr.Markdown(
214
+ """
215
+ 1. **Pilih Model AI** yang ingin Anda uji dari opsi di atas.
216
+ 2. **Unggah file video** dari perangkat Anda atau **pilih contoh** di bawah.
217
+ 3. Klik tombol **"Mulai Deteksi"** dan tunggu proses selesai.
218
+ 4. Lihat **video hasil** dan **analisis kinerja** di sebelah kanan.
219
+ 5. Ulangi dengan model lain untuk melihat perbandingannya.
220
+ """
221
+ )
222
+
223
  with gr.Column(scale=1):
224
+ # --- Bagian Output Hasil ---
225
+ gr.Markdown("### **Langkah 2: Lihat Hasil Deteksi**")
226
+
227
+ video_output = gr.Video(label="Video Hasil Deteksi")
228
+ status_text = gr.Textbox(label="Status Proses", interactive=False)
229
+
230
+ gr.Markdown("---")
231
+ gr.Markdown("### **Ringkasan Analisis**")
232
+ analysis_output = gr.Markdown(label="Analisis Kinerja")
233
+
234
+ # --- Bagian Contoh Video ---
235
+ gr.Examples(
236
+ examples=[
237
+ [os.path.join(os.path.dirname(__file__), "video_contoh_1.mp4"), "YOLOv8m (Spesialis Helm)"],
238
+ [os.path.join(os.path.dirname(__file__), "video_contoh_2.mp4"), "YOLOv5m (Generalis Konstruksi)"]
239
+ ],
240
+ inputs=[video_input, selected_model_ui],
241
+ outputs=[video_output, status_text, analysis_output],
242
+ fn=process_video_and_analyze,
243
+ cache_examples=True,
244
+ label="Contoh Video (Klik untuk mencoba)"
245
+ )
246
+
247
+ # --- Bagian Informasi dan Kredit ---
248
+ with gr.Accordion("โ„น๏ธ Informasi, Kredit, dan Potensi Pengembangan", open=False):
249
+ gr.Markdown(
250
+ """
251
+ ### **Identitas Prototipe**
252
+ - **Pengembang:** Faisal Fahmi Yuliawan (sebagai bagian dari studi ilmiah).
253
+ - **Tujuan:** Menyediakan alat bantu visual untuk membandingkan kinerja model AI dalam konteks keselamatan kerja (K3), khususnya deteksi helm.
254
+ - **Relevansi:** Masalah kepatuhan penggunaan APD (Alat Pelindung Diri) seperti helm adalah kasus nyata dan krusial di industri konstruksi untuk mencegah kecelakaan fatal.
255
+
256
+ ### **Kredit Pihak Ketiga**
257
+ - **Model AI:** - `keremberke/yolov8m-hard-hat-detection` (Model Spesialis) dari Hugging Face.
258
+ - `keremberke/yolov5m-construction-safety` (Model Generalis) dari Hugging Face.
259
+ - **Teknologi:** - **Ultralytics YOLO:** Framework utama untuk model AI.
260
+ - **Gradio:** Framework untuk membangun antarmuka web interaktif ini.
261
+ - **OpenCV & FFmpeg:** Untuk pemrosesan dan manipulasi video.
262
+
263
+ ### **Potensi Pengembangan & Komersialisasi**
264
+ - **Skalabilitas:** Arsitektur aplikasi ini dapat dengan mudah diperluas untuk mencakup lebih banyak model AI atau jenis APD lain (misalnya, rompi, sepatu bot, sarung tangan).
265
+ - **Integrasi:** Dapat diintegrasikan dengan sistem CCTV di lokasi konstruksi untuk pemantauan otomatis dan sistem peringatan *real-time*.
266
+ - **Potensi Bisnis:** Memiliki potensi komersial sebagai produk SaaS (*Software as a Service*) untuk perusahaan konstruksi yang ingin meningkatkan standar K3 dan melakukan audit kepatuhan secara digital.
267
+ """
268
+ )
269
+
270
+ # --- Hubungkan Aksi Tombol ke Fungsi ---
271
+ detect_btn.click(
272
+ fn=process_video_and_analyze,
273
+ inputs=[video_input, selected_model_ui],
274
+ outputs=[video_output, status_text, analysis_output]
275
+ )
276
 
 
277
 
278
+ # --- 6. LUNCURKAN APLIKASI ---
279
  if __name__ == "__main__":
280
+ # Untuk menjalankan, simpan kode ini sebagai file .py (misal: app.py)
281
+ # Buat 2 file video contoh: video_contoh_1.mp4 dan video_contoh_2.mp4 di folder yang sama.
282
+ # Jalankan dari terminal dengan: python app.py
283
+ iface.launch(debug=True, share=True)