firgiawann commited on
Commit
fb193eb
·
verified ·
1 Parent(s): 570e176

Enable training function with dataset/train-test and optional pseudo-labeling

Browse files
Files changed (2) hide show
  1. README.md +5 -1
  2. app.py +189 -0
README.md CHANGED
@@ -37,10 +37,14 @@ Versi ini difokuskan untuk tahap awal:
37
  - hasil gambar deteksi
38
  - ringkasan klasifikasi
39
  - grafik jumlah person terdeteksi
 
 
 
 
40
 
41
  Catatan:
42
  - input evaluasi batch dihapus.
43
- - menu training dipisahkan dari halaman ini.
44
  - input jumlah presensi belum digunakan pada versi ini.
45
 
46
  ## File Penting
 
37
  - hasil gambar deteksi
38
  - ringkasan klasifikasi
39
  - grafik jumlah person terdeteksi
40
+ - **Menu Training** (accordion)
41
+ - membaca dataset dari `dataset/train` dan `dataset/test`
42
+ - jika label `.txt` YOLO belum ada, bisa aktifkan pseudo-label
43
+ - output model terbaik disimpan sebagai `best.pt`
44
 
45
  Catatan:
46
  - input evaluasi batch dihapus.
47
+ - training tersedia sebagai menu terpisah (accordion) di halaman yang sama.
48
  - input jumlah presensi belum digunakan pada versi ini.
49
 
50
  ## File Penting
app.py CHANGED
@@ -1,6 +1,9 @@
1
  import io
2
  import os
 
 
3
  from functools import lru_cache
 
4
  from typing import Any, Tuple
5
 
6
  import cv2
@@ -128,6 +131,161 @@ def run_check_and_classify(
128
  return image, summary, blank
129
 
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  CUSTOM_CSS = """
132
  .container {max-width: 1120px !important; margin: auto;}
133
  .section-box {padding: 14px; border: 1px solid #d7dee8; border-radius: 10px; background: #ffffff;}
@@ -161,12 +319,43 @@ with gr.Blocks(css=CUSTOM_CSS, title="Pengecekan dan Klasifikasi Mahasiswa - YOL
161
  class_result_md = gr.Markdown("Hasil klasifikasi akan tampil di sini.")
162
  comparison_chart = gr.Image(label="Grafik Klasifikasi", type="numpy")
163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  run_btn.click(
165
  fn=run_check_and_classify,
166
  inputs=[image_input, conf_input],
167
  outputs=[output_image, class_result_md, comparison_chart],
168
  )
169
 
 
 
 
 
 
 
170
 
171
  if __name__ == "__main__":
172
  demo.launch()
 
1
  import io
2
  import os
3
+ import shutil
4
+ from datetime import datetime
5
  from functools import lru_cache
6
+ from pathlib import Path
7
  from typing import Any, Tuple
8
 
9
  import cv2
 
131
  return image, summary, blank
132
 
133
 
134
+ def _iter_images(folder: Path) -> list[Path]:
135
+ exts = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
136
+ if not folder.exists():
137
+ return []
138
+ return sorted([p for p in folder.rglob("*") if p.is_file() and p.suffix.lower() in exts])
139
+
140
+
141
+ def _label_candidates(img_path: Path) -> list[Path]:
142
+ stem = img_path.stem
143
+ return [
144
+ img_path.with_suffix(".txt"),
145
+ img_path.parent.parent / "labels" / img_path.parent.name / f"{stem}.txt",
146
+ img_path.parent / "labels" / f"{stem}.txt",
147
+ ]
148
+
149
+
150
+ def _write_pseudo_label(img_rgb: np.ndarray, label_path: Path, conf_threshold: float) -> int:
151
+ model = load_model()
152
+ h, w = img_rgb.shape[:2]
153
+ bgr = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)
154
+ result = model.predict(source=bgr, conf=conf_threshold, verbose=False)[0]
155
+ person_id = _resolve_person_class_id(result)
156
+ boxes_xyxy = result.boxes.xyxy.cpu().numpy() if result.boxes is not None else np.empty((0, 4))
157
+ classes = result.boxes.cls.cpu().numpy() if result.boxes is not None else np.array([])
158
+
159
+ lines = []
160
+ for box, cls_id in zip(boxes_xyxy, classes):
161
+ if int(cls_id) != person_id:
162
+ continue
163
+ x1, y1, x2, y2 = box.tolist()
164
+ xc = ((x1 + x2) / 2) / w
165
+ yc = ((y1 + y2) / 2) / h
166
+ bw = (x2 - x1) / w
167
+ bh = (y2 - y1) / h
168
+ lines.append(f"0 {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}")
169
+
170
+ label_path.parent.mkdir(parents=True, exist_ok=True)
171
+ label_path.write_text("\n".join(lines), encoding="utf-8")
172
+ return len(lines)
173
+
174
+
175
+ def _prepare_training_dataset(dataset_root: Path, use_pseudo_if_missing: bool, pseudo_conf: float) -> Tuple[Path, str]:
176
+ train_src = dataset_root / "train"
177
+ test_src = dataset_root / "test"
178
+ if not train_src.exists() or not test_src.exists():
179
+ raise FileNotFoundError("Folder dataset wajib berisi `dataset/train` dan `dataset/test`.")
180
+
181
+ ts = datetime.now().strftime("%Y%m%d_%H%M%S")
182
+ prepared_root = dataset_root.parent / "prepared_dataset" / ts
183
+ img_train = prepared_root / "images" / "train"
184
+ img_val = prepared_root / "images" / "val"
185
+ lbl_train = prepared_root / "labels" / "train"
186
+ lbl_val = prepared_root / "labels" / "val"
187
+ for p in (img_train, img_val, lbl_train, lbl_val):
188
+ p.mkdir(parents=True, exist_ok=True)
189
+
190
+ def process_split(src: Path, out_img: Path, out_lbl: Path) -> Tuple[int, int]:
191
+ images = _iter_images(src)
192
+ if not images:
193
+ raise ValueError(f"Tidak ada gambar di {src}")
194
+ copied = 0
195
+ labeled = 0
196
+ for img in images:
197
+ target_img = out_img / img.name
198
+ if target_img.exists():
199
+ target_img = out_img / f"{img.stem}_{copied}{img.suffix}"
200
+ shutil.copy2(img, target_img)
201
+
202
+ label_target = out_lbl / f"{target_img.stem}.txt"
203
+ found_label = None
204
+ for cand in _label_candidates(img):
205
+ if cand.exists():
206
+ found_label = cand
207
+ break
208
+
209
+ if found_label is not None:
210
+ shutil.copy2(found_label, label_target)
211
+ labeled += 1
212
+ elif use_pseudo_if_missing:
213
+ rgb = cv2.cvtColor(cv2.imread(str(target_img)), cv2.COLOR_BGR2RGB)
214
+ labeled += 1 if _write_pseudo_label(rgb, label_target, pseudo_conf) >= 0 else 0
215
+ copied += 1
216
+ return copied, labeled
217
+
218
+ c_train, l_train = process_split(train_src, img_train, lbl_train)
219
+ c_val, l_val = process_split(test_src, img_val, lbl_val)
220
+ if l_train == 0 or l_val == 0:
221
+ raise ValueError(
222
+ "Label tidak ditemukan. Aktifkan pseudo-label atau sediakan file .txt YOLO untuk train/test."
223
+ )
224
+
225
+ yaml_path = prepared_root / "data.yaml"
226
+ yaml_path.write_text(
227
+ f"path: {prepared_root.as_posix()}\n"
228
+ "train: images/train\n"
229
+ "val: images/val\n"
230
+ "names:\n"
231
+ " 0: person\n",
232
+ encoding="utf-8",
233
+ )
234
+ info = (
235
+ f"Dataset siap: train={c_train} (labeled={l_train}), "
236
+ f"val={c_val} (labeled={l_val}), yaml={yaml_path}"
237
+ )
238
+ return yaml_path, info
239
+
240
+
241
+ def run_training(
242
+ epochs: float,
243
+ imgsz: float,
244
+ batch: float,
245
+ use_pseudo_if_missing: bool,
246
+ pseudo_conf: float,
247
+ ) -> Tuple[str, str]:
248
+ try:
249
+ base_dir = Path(__file__).resolve().parent
250
+ dataset_root = base_dir / "dataset"
251
+ data_yaml, prep_info = _prepare_training_dataset(
252
+ dataset_root=dataset_root,
253
+ use_pseudo_if_missing=use_pseudo_if_missing,
254
+ pseudo_conf=float(pseudo_conf),
255
+ )
256
+
257
+ model_path = str(base_dir / "yolov8n.pt") if (base_dir / "yolov8n.pt").exists() else resolve_model_path()
258
+ model = YOLO(model_path)
259
+ run_name = f"classroom_train_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
260
+ results = model.train(
261
+ data=str(data_yaml),
262
+ epochs=int(epochs),
263
+ imgsz=int(imgsz),
264
+ batch=int(batch),
265
+ device="cpu",
266
+ project=str(base_dir / "runs_train"),
267
+ name=run_name,
268
+ exist_ok=False,
269
+ verbose=True,
270
+ )
271
+
272
+ save_dir = Path(str(results.save_dir))
273
+ best_pt = save_dir / "weights" / "best.pt"
274
+ best_out = base_dir / "best.pt"
275
+ if best_pt.exists():
276
+ shutil.copy2(best_pt, best_out)
277
+
278
+ msg = (
279
+ "Training selesai.\n"
280
+ f"- {prep_info}\n"
281
+ f"- Run dir: {save_dir}\n"
282
+ f"- best.pt: {best_out if best_out.exists() else best_pt}"
283
+ )
284
+ return msg, str(best_out) if best_out.exists() else ""
285
+ except Exception as err:
286
+ return f"Training gagal: {err}", ""
287
+
288
+
289
  CUSTOM_CSS = """
290
  .container {max-width: 1120px !important; margin: auto;}
291
  .section-box {padding: 14px; border: 1px solid #d7dee8; border-radius: 10px; background: #ffffff;}
 
319
  class_result_md = gr.Markdown("Hasil klasifikasi akan tampil di sini.")
320
  comparison_chart = gr.Image(label="Grafik Klasifikasi", type="numpy")
321
 
322
+ with gr.Column(elem_classes=["section-box"]):
323
+ with gr.Accordion("Menu Training", open=False):
324
+ gr.Markdown(
325
+ "Training akan membaca dataset dari `dataset/train` dan `dataset/test`.\n"
326
+ "Jika label `.txt` belum ada, aktifkan pseudo-label agar proses tetap jalan."
327
+ )
328
+ with gr.Row():
329
+ train_epochs = gr.Number(label="Epochs", value=10, precision=0, minimum=1)
330
+ train_imgsz = gr.Number(label="Image Size", value=640, precision=0, minimum=64)
331
+ train_batch = gr.Number(label="Batch", value=8, precision=0, minimum=1)
332
+ use_pseudo = gr.Checkbox(
333
+ label="Gunakan pseudo-label jika label YOLO tidak tersedia",
334
+ value=True,
335
+ )
336
+ pseudo_conf = gr.Slider(
337
+ 0.1,
338
+ 0.9,
339
+ value=0.30,
340
+ step=0.05,
341
+ label="Confidence pseudo-label",
342
+ )
343
+ train_btn = gr.Button("Mulai Training", variant="primary")
344
+ train_log = gr.Textbox(label="Log Training", lines=8, interactive=False)
345
+ best_model_file = gr.File(label="best.pt hasil training")
346
+
347
  run_btn.click(
348
  fn=run_check_and_classify,
349
  inputs=[image_input, conf_input],
350
  outputs=[output_image, class_result_md, comparison_chart],
351
  )
352
 
353
+ train_btn.click(
354
+ fn=run_training,
355
+ inputs=[train_epochs, train_imgsz, train_batch, use_pseudo, pseudo_conf],
356
+ outputs=[train_log, best_model_file],
357
+ )
358
+
359
 
360
  if __name__ == "__main__":
361
  demo.launch()