Pengolahan Citra · Materi 14

Project Akhir — Sistem Computer Vision berbasis YOLO

Aplikasi Colab end-to-end dengan pilihan input, validasi, inferensi, anotasi, counting, keluaran dan demo.

Google Colab · OpenCV · Pemahaman konsep → pengamatan → praktikum → analisis

Mode belajar: tulis kode sendiri. Copy/cut, seleksi teks dan klik kanan dibatasi pada halaman; pembatasan UI bukan jaminan anti-penyalinan mutlak.

1. Tujuan

  1. Merancang sistem deteksi gambar dan video dengan alur input sampai output yang lengkap.
  2. Menyediakan antarmuka form Colab untuk mode, model, kelas dan confidence.
  3. Memvalidasi input serta menangani kegagalan dan cleanup tanpa keluaran palsu.
  4. Mendemonstrasikan aplikasi dan menilai batasan berdasarkan pengujian nyata.

Prasyarat: Modul 10–13; inferensi gambar/video, counting, Colab serta model custom jika dipilih.

2. Materi Pembahasan

Contoh komponen project: inference YOLOv8n benar-benar dijalankan; UI ada di praktikum.
Contoh komponen project: inference YOLOv8n benar-benar dijalankan; UI ada di praktikum.

2.1 Mendefinisikan masalah dan pengguna

Project akhir menggabungkan konsep deteksi menjadi sistem yang dapat dijalankan orang lain. Tetapkan tujuan yang terbatas, misalnya mendeteksi kendaraan dalam foto dan klip pendek atau kategori custom yang telah dilatih. Tentukan siapa pengguna, format input yang diterima, jenis keluaran dan kondisi penggunaan. Sistem tidak boleh menjanjikan semua objek dikenali jika kategori tersebut tidak ada pada model.

Syarat dasar aplikasi adalah menerima input, memvalidasinya, menjalankan inferensi, menampilkan anotasi dan menyimpan hasil. Notebook bukan hanya laporan training atau kumpulan screenshot. Contoh memilih antarmuka form parameter Colab agar fokus pada alur yang dapat diperiksa tanpa membangun server. Form juga dapat diganti dengan mengedit nilai variabel langsung, sehingga contoh tetap mudah dibaca ketika renderer tidak menampilkan kontrol interaktif.

2.2 Input, validasi dan preprocessing

Mode gambar menerima satu JPG, JPEG atau PNG. Mode video menerima satu klip dengan format, ukuran, FPS, dimensi dan durasi yang diperiksa. Nama upload tidak dipakai sebagai perintah shell ataupun lokasi keluaran bebas; aplikasi menyimpan input pada path yang dikontrol. Error diperlakukan sebagai kondisi yang perlu pesan, bukan alasan melewati validasi agar demo tampak berhasil.

Preprocessing memakai resize proporsional hingga sisi maksimum 960 piksel tanpa mengubah warna atau menambahkan enhancement yang tidak diperlukan. Objek kecil dapat kehilangan detail setelah resize sehingga perubahan ini harus didokumentasikan. Koordinat bbox mengacu pada citra hasil preprocessing. Urutan BGR dipertahankan untuk OpenCV dan masukan array Ultralytics, lalu diubah ke RGB pada tampilan Matplotlib agar warna tidak salah.

2.3 Model dan parameter yang dapat ditelusuri

Pilihan default adalah yolov8n.pt sebagai baseline ringan. Model custom dapat memakai best.pt dari pekerjaan kelompok yang dipercaya. Pemuatan file bobot bukan operasi aman untuk sumber sembarang; contoh tidak menjadikannya input publik tanpa batas. Kategori diperoleh dari model.names, sehingga daftar kelas benar-benar berasal dari model yang aktif.

Confidence dan class filter disediakan sebagai parameter antarmuka. Filter kosong berarti seluruh kelas model, sedangkan nama yang tidak dikenal dihentikan sebelum inferensi. Catat versi paket, device, model dan preprocessing pada konfigurasi run. Perubahan parameter dapat memengaruhi kualitas dan kecepatan; keputusan sebaiknya berdasarkan kebutuhan serta hasil validation atau audit terencana, bukan memilih pengaturan yang hanya bagus pada satu gambar demo.

2.4 Output gambar, video dan browser webcam

Mode gambar memproduksi citra anotasi, tabel bbox, confidence dan counting. Mode video memproses tiap frame, menyimpan tabel temporal, mengukur laju pemrosesan dan membuat MP4 yang dapat ditampilkan di notebook. Capture dan writer dilepas bahkan ketika proses gagal. Keluaran video tidak membawa audio sumber, dan waktu transcoding tidak disamakan dengan waktu inferensi model.

Mode webcam browser bersifat opsional. JavaScript mengambil lima frame dengan izin pengguna dan menghentikan track dalam finally; Python melakukan inferensi sesudah capture. Ini bukan realtime desktop atau akses VideoCapture(0) di laptop. Total deteksi dalam beberapa frame bukan jumlah objek unik. Jika proyek membutuhkan tracking, identitas dan aturan counting temporal harus ditambahkan dan diuji, bukan diasumsikan dari tabel bbox biasa.

2.5 Pengujian, presentasi dan batasan

Uji aplikasi dengan input valid, input salah format, gambar tanpa target dan situasi sulit seperti objek kecil atau terhalang. Periksa bahwa hasil yang disimpan dapat dibaca dan tabel nol deteksi tetap memiliki struktur yang benar. Demo harus menjalankan jalur input ke output, bukan hanya memutar video anotasi yang sumber prosesnya tidak jelas. Jika runtime atau jaringan gagal, laporkan kegagalan dan batas verifikasi secara jujur.

Presentasi mencakup masalah, diagram pipeline, alasan model, sumber data, hasil nyata, kegagalan dan rencana perbaikan. Untuk tugas, aplikasi wajib mendukung gambar dan video; webcam boleh menjadi fitur tambahan. Dokumentasi serta ringkasan presentasi diletakkan di notebook yang sama. Satu tautan Viewer dengan output terlihat memudahkan penilaian tanpa meminta bobot, ZIP atau laporan terpisah, tetapi tim harus memastikan output tidak terhapus sebelum notebook dibagikan.

Video pembelajaran sesuai bab

How to Perform Real-Time Object Counting with Ultralytics YOLO11 | Apples on a Moving Conveyor Belt🍏 — Ultralytics.

Fokus saat menonton: Amati penerapan penghitung objek sebagai contoh project. Fokus pada alur input–deteksi–tracking–keluaran dan evaluasi; video menggunakan YOLO11 sedangkan contoh kuliah memakai YOLOv8.

Buka video di YouTube jika pemutar tidak tersedia

Video referensi berbahasa Inggris. Bila ada setup lokal, API atau versi model berbeda, pelajari konsepnya dan tetap gunakan alur Google Colab pada praktikum bab ini. Ketersediaan pemutar mengikuti YouTube, pemilik video dan jaringan.

Referensi dan sumber ilustrasi

Foto daun: Ανώνυμος Βικιπαιδιστής, Wikimedia Commons, CC BY 3.0; diubah ukuran/intensitas untuk ilustrasi. Foto bus: contoh aset Ultralytics. Foto astronaut: NASA melalui scikit-image. Panel diagram/simulasi ditandai pada caption; bukan hasil eksperimen yang dikarang. Hak penggunaan sumber tetap mengikuti sumber asli.

3. Praktikum Google Colab

Buka Google Colab

Buat notebook baru, simpan pada Drive milik sendiri, lalu isi identitas. Gunakan sel kode terpisah untuk setiap langkah dan jalankan berurutan dengan Shift + Enter. Tidak perlu mengunduh notebook contoh atau memasang Python lokal.

Identitas di bagian paling atas notebook: tambahkan sel Text/Markdown sebelum kode dan isi Nama serta NPM.
# Materi 14 — Pengolahan Citra
Nama: [Nama lengkap mahasiswa]
NPM: [NPM mahasiswa]
Kelas: [Kelas, jika diperlukan]
  1. Tentukan masalah, kelas, pengguna dan batasan; buat Markdown identitas anggota serta desain pipeline di notebook.
  2. Jalankan instalasi, isi form mode gambar, baseline atau custom terpercaya, confidence dan filter.
  3. Jalankan semua helper, upload gambar pada sel aplikasi dan periksa anotasi, tabel serta laporan run.
  4. Ganti mode menjadi video, ulangi setup serta sel helper/aplikasi, upload klip dan tampilkan video hasil.
  5. Jika menggunakan webcam, pilih webcam_browser dan izinkan kamera; uji bahwa kamera mati setelah capture atau error.
  6. Uji input salah format, target tidak ada dan objek sulit; catat pesan error serta batasan.
  7. Lakukan demo end-to-end, tambahkan ringkasan presentasi pada notebook dan simpan output kedua mode.
  8. Bagikan satu tautan Google Colab Viewer dan periksa dari sesi lain bahwa output serta analisis terlihat.

Sel 1 — Instalasi Ultralytics

Jalankan sebagai sel tersendiri di Colab; tidak perlu meng-upgrade NumPy. Catat versi paket di sel berikutnya.

%pip install -q ultralytics

Sel 2 — Antarmuka parameter Colab dan model

Parameter #@param menjadi form di Colab (atau edit nilainya langsung). Pilih mode dan confidence, jalankan setup lalu sel berikutnya. Custom hanya bobot .pt milik kelompok dari training sendiri; pemuatan bobot tak tepercaya berisiko, bukan input publik sembarang.

#@title Parameter aplikasi
INPUT_MODE = 'gambar' #@param ['gambar', 'video', 'webcam_browser']
CONFIDENCE = 0.35 #@param {type:'number'}
MODEL_SOURCE = 'baseline' #@param ['baseline', 'custom_terpercaya']
TARGET_CLASSES = '' #@param {type:'string'}

from pathlib import Path
from collections import Counter
import io, time, uuid, json
import cv2, numpy as np, matplotlib.pyplot as plt, pandas as pd
from PIL import Image
import torch, ultralytics
from ultralytics import YOLO
from google.colab import files, output
from IPython.display import display, Video
if INPUT_MODE not in {'gambar','video','webcam_browser'}:
    raise ValueError('Mode input tidak valid.')
if not 0 < CONFIDENCE <= 1:
    raise ValueError('Confidence harus di rentang (0,1].')
DEVICE = 0 if torch.cuda.is_available() else 'cpu'
OUTPUT = Path('/content/hasil_project') / uuid.uuid4().hex[:8]
OUTPUT.mkdir(parents=True, exist_ok=False)
if MODEL_SOURCE == 'baseline':
    MODEL_LABEL = 'yolov8n.pt'
elif MODEL_SOURCE == 'custom_terpercaya':
    trusted = files.upload()
    if len(trusted) != 1: raise ValueError('Upload satu best.pt milik kelompok.')
    weight_name, weight_bytes = next(iter(trusted.items()))
    if Path(weight_name).suffix.lower() != '.pt' or not 0 < len(weight_bytes) <= 100*1024**2:
        raise ValueError('Bobot harus .pt, maksimum 100 MB.')
    MODEL_LABEL = str(OUTPUT/'custom_best.pt')
    Path(MODEL_LABEL).write_bytes(weight_bytes)
else:
    raise ValueError('Sumber model tidak valid.')
model = YOLO(MODEL_LABEL)
if model.task != 'detect': raise ValueError('Gunakan model object detection, bukan task lain.')
requested = [name.strip() for name in TARGET_CLASSES.split(',') if name.strip()]
name_to_id = {name:int(idx) for idx,name in model.names.items()}
if set(requested)-set(name_to_id):
    raise ValueError(f'Kelas tidak tersedia: {set(requested)-set(name_to_id)}')
CLASS_FILTER = [name_to_id[name] for name in requested] if requested else None
print('Model:', MODEL_LABEL, 'device:', DEVICE, 'Ultralytics:', ultralytics.__version__)
print('Kelas model:',model.names,'filter:', requested or 'semua')
print('Output:', OUTPUT)

Sel 3 — Validasi, preprocessing dan deteksi gambar

Helper memeriksa ukuran sebelum decode, resize proporsional maksimum 960 piksel, inferensi, counting, menyimpan dan menampilkan. Koordinat tabel merujuk gambar setelah resize.

def decode_image(name, content):
    if Path(name).suffix.lower() not in {'.jpg','.jpeg','.png'}:
        raise ValueError('Gambar wajib JPG/JPEG/PNG.')
    if not 0 < len(content) <= 20*1024**2:
        raise ValueError('Gambar kosong atau melebihi 20 MB.')
    with Image.open(io.BytesIO(content)) as header:
        if header.width*header.height > 20_000_000:
            raise ValueError('Gambar melebihi 20 megapiksel.')
        header.verify()
    image = cv2.imdecode(np.frombuffer(content,np.uint8),cv2.IMREAD_COLOR)
    if image is None: raise ValueError('Decode gambar gagal.')
    return image

def detect_and_show(image, tag):
    h,w = image.shape[:2]
    scale = min(1.0,960/max(h,w))
    if scale < 1:
        image = cv2.resize(image,(max(1,round(w*scale)),max(1,round(h*scale))))
    if DEVICE != 'cpu': torch.cuda.synchronize()
    t0 = time.perf_counter()
    result = model.predict(image, conf=CONFIDENCE, classes=CLASS_FILTER,
                           imgsz=640, device=DEVICE, verbose=False)[0]
    if DEVICE != 'cpu': torch.cuda.synchronize()
    elapsed = time.perf_counter()-t0
    annotated = result.plot()
    destination = OUTPUT/f'{tag}_annotated.jpg'
    if not cv2.imwrite(str(destination),annotated): raise IOError('Simpan gambar gagal.')
    rows=[]; counts=Counter()
    for box in result.boxes:
        idx=int(box.cls.item()); name=model.names[idx]; counts[name]+=1
        x1,y1,x2,y2=box.xyxy[0].cpu().tolist()
        rows.append({'class':name,'confidence':float(box.conf.item()),
                     'x1':x1,'y1':y1,'x2':x2,'y2':y2})
    table=pd.DataFrame(rows,columns=['class','confidence','x1','y1','x2','y2'])
    table.to_csv(OUTPUT/f'{tag}_bbox.csv',index=False)
    plt.figure(figsize=(10,6)); plt.imshow(cv2.cvtColor(annotated,cv2.COLOR_BGR2RGB))
    plt.title(f'{tag}: {len(result.boxes)} deteksi'); plt.axis('off'); plt.show()
    display(table)
    print('Hitungan per kelas:',dict(counts))
    return {'input':tag,'detections':len(result.boxes),'inference_seconds':elapsed,
            'output_image':str(destination),'coordinate_space':'resized image pixels'}

Sel 4 — Pemrosesan frame, hitungan, FPS, dan encoding

Fungsi ini memeriksa capture, metadata, writer dan durasi aktual. Semua frame diproses; keluaran tidak memuat audio. FPS inferensi berbeda dari FPS video sumber.

import math, time, shutil, subprocess
import pandas as pd
from collections import Counter

def process_video(source, model, conf, output_dir, device, classes=None,
                  min_seconds=1.0, max_seconds=120.0):
    if not (0 < conf <= 1):
        raise ValueError('Confidence harus (0,1].')
    if shutil.which('ffmpeg') is None:
        raise RuntimeError('ffmpeg tidak ditemukan pada runtime.')
    output_dir = Path(output_dir); output_dir.mkdir(parents=True, exist_ok=True)
    cap = cv2.VideoCapture(str(source))
    writer = None
    rows, first_annotated = [], None
    avi = output_dir / 'anotasi_mjpg.avi'
    mp4 = output_dir / 'anotasi.mp4'
    start = time.perf_counter()
    infer_seconds = 0.0
    try:
        if not cap.isOpened():
            raise ValueError('Video gagal dibuka; cek codec atau konversi input.')
        fps = float(cap.get(cv2.CAP_PROP_FPS))
        width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        frame_count = float(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        if not math.isfinite(fps) or not 1 <= fps <= 60:
            raise ValueError('FPS metadata harus valid (1–60); jangan menebak FPS.')
        if width <= 0 or height <= 0 or width * height > 8_300_000:
            raise ValueError('Dimensi video tidak valid atau melebihi batas.')
        if not math.isfinite(frame_count) or frame_count <= 0:
            raise ValueError('Jumlah frame metadata tidak valid.')
        estimated_duration = frame_count / fps
        if not min_seconds <= estimated_duration <= max_seconds + 1/fps:
            raise ValueError(f'Durasi metadata {estimated_duration:.2f}s di luar batas.')
        scale = min(1.0, 960 / max(width, height))
        out_w = max(2, int(width * scale) // 2 * 2)
        out_h = max(2, int(height * scale) // 2 * 2)
        writer = cv2.VideoWriter(str(avi), cv2.VideoWriter_fourcc(*'MJPG'),
                                 fps, (out_w, out_h))
        if not writer.isOpened():
            raise RuntimeError('VideoWriter tidak dapat dibuka.')
        index = 0
        limit = math.ceil(max_seconds * fps)
        while True:
            ok, frame = cap.read()
            if not ok:
                break
            if index >= limit:
                raise ValueError('Frame aktual melebihi batas durasi; input ditolak.')
            frame = cv2.resize(frame, (out_w, out_h))
            if device != 'cpu': torch.cuda.synchronize()
            t0 = time.perf_counter()
            result = model.predict(frame, conf=conf, classes=classes,
                                   device=device, imgsz=640, verbose=False)[0]
            if device != 'cpu': torch.cuda.synchronize()
            elapsed = time.perf_counter() - t0
            infer_seconds += elapsed
            counts = Counter(model.names[int(c)] for c in result.boxes.cls.cpu().tolist())
            annotated = result.plot()
            text = f'frame {index} | detections {len(result.boxes)}'
            cv2.putText(annotated, text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX,
                        0.6, (0, 255, 255), 2)
            writer.write(annotated)
            if first_annotated is None: first_annotated = annotated.copy()
            rows.append({'frame': index, 'time_s': index/fps,
                         'detections': len(result.boxes), 'inference_s': elapsed,
                         **{f'count_{name}': counts.get(name, 0)
                            for name in model.names.values()}})
            index += 1
        actual_duration = index / fps
        if index == 0 or actual_duration + 1/fps < min_seconds:
            raise ValueError('Frame terbaca kosong atau durasi aktual terlalu pendek.')
        if abs(index - frame_count) > max(2, 0.01 * frame_count):
            raise ValueError('Decode tidak lengkap atau metadata tidak konsisten; cek file.')
    finally:
        cap.release()
        if writer is not None: writer.release()
    processing_seconds = time.perf_counter() - start
    if not avi.exists() or avi.stat().st_size == 0:
        raise RuntimeError('Video hasil kosong.')
    subprocess.run(['ffmpeg', '-y', '-loglevel', 'error', '-i', str(avi),
                    '-an', '-c:v', 'libx264', '-pix_fmt', 'yuv420p',
                    '-movflags', '+faststart', str(mp4)], check=True, timeout=240)
    check = cv2.VideoCapture(str(mp4))
    try:
        valid, _ = check.read()
        output_frames = check.get(cv2.CAP_PROP_FRAME_COUNT)
        if not check.isOpened() or not valid or abs(output_frames - index) > 2:
            raise RuntimeError('Verifikasi video hasil gagal.')
    finally:
        check.release()
    table = pd.DataFrame(rows)
    table.to_csv(output_dir / 'per_frame.csv', index=False)
    report = {'frames': index, 'source_fps': fps, 'duration_decoded_s': actual_duration,
              'inference_fps': index / infer_seconds,
              'processing_fps_before_transcode': index / processing_seconds,
              'sum_frame_detections_NOT_unique': int(table['detections'].sum()),
              'mean_detections_per_frame': float(table['detections'].mean())}
    return mp4, table, report, first_annotated

Sel 5 — Helper webcam browser opsional

Hanya definisi fungsi pada sel ini. Sel aplikasi memanggilnya bila mode webcam_browser dipilih. Lima frame, izin browser, timeout dan finally stop track; tidak menggunakan VideoCapture(0).

from google.colab import output
from base64 import b64decode

CAMERA_JS = r"""
async function captureBoundedFrames() {
  let stream = null, expired = false, timer = null;
  const video = document.createElement('video');
  video.setAttribute('playsinline', ''); video.muted = true;
  document.body.appendChild(video);
  const stop = s => s.getTracks().forEach(t => t.stop());
  try {
    const request = navigator.mediaDevices.getUserMedia({video: {width:640, height:480}, audio:false});
    request.then(s => { if (expired) stop(s); }).catch(() => {});
    const timeout = new Promise((_, reject) => {
      timer = setTimeout(() => { expired = true; reject(new Error('Izin kamera timeout')); }, 20000);
    });
    stream = await Promise.race([request, timeout]);
    clearTimeout(timer);
    video.srcObject = stream;
    await Promise.race([video.play(), new Promise((_,reject) =>
      setTimeout(() => reject(new Error('Kamera tidak siap')), 10000))]);
    const canvas = document.createElement('canvas');
    canvas.width = 640; canvas.height = 480;
    const context = canvas.getContext('2d');
    const frames = [];
    for (let i = 0; i < 5; i++) {
      await new Promise(resolve => setTimeout(resolve, 400));
      if (!video.videoWidth) throw new Error('Tidak ada frame kamera');
      context.drawImage(video, 0, 0, 640, 480);
      frames.push(canvas.toDataURL('image/jpeg', 0.8));
    }
    return frames;
  } finally {
    expired = true; clearTimeout(timer);
    if (stream) stop(stream);
    video.srcObject = null; video.remove();
  }
}
captureBoundedFrames();
"""
def capture_browser_frames():
    encoded_frames = output.eval_js(CAMERA_JS)
    if not isinstance(encoded_frames, list) or not 1 <= len(encoded_frames) <= 5:
        raise ValueError('Respons kamera tidak valid.')
    frames = []
    for value in encoded_frames:
        if not value.startswith('data:image/jpeg;base64,') or len(value) > 2_000_000:
            raise ValueError('Data kamera tidak valid.')
        frame = cv2.imdecode(np.frombuffer(b64decode(value.split(',',1)[1]),
                                         np.uint8), cv2.IMREAD_COLOR)
        if frame is None: raise ValueError('Decode kamera gagal.')
        frames.append(frame)
    return frames

Sel 6 — Jalankan aplikasi end-to-end

Input upload muncul sesuai mode. Gambar dan video ditampilkan otomatis; webcam browser adalah batch terbatas. Kesalahan dihentikan dengan pesan, bukan hasil yang direkayasa.

reports = []
if INPUT_MODE == 'gambar':
    inputs = files.upload()
    if len(inputs) != 1: raise ValueError('Upload tepat satu gambar.')
    input_name, content = next(iter(inputs.items()))
    image = decode_image(input_name,content)
    reports.append(detect_and_show(image,'gambar'))
elif INPUT_MODE == 'video':
    inputs = files.upload()
    if len(inputs) != 1: raise ValueError('Upload tepat satu video.')
    input_name, content = next(iter(inputs.items()))
    suffix = Path(input_name).suffix.lower()
    if suffix not in {'.mp4','.mov','.avi','.mkv','.webm'}:
        raise ValueError('Format video tidak didukung.')
    if not 0 < len(content) <= 150*1024**2:
        raise ValueError('Video kosong atau melebihi 150 MB.')
    source = OUTPUT/('input'+suffix); source.write_bytes(content)
    video_path, frame_table, report, first_frame = process_video(
        source,model,conf=CONFIDENCE,output_dir=OUTPUT,device=DEVICE,
        classes=CLASS_FILTER,min_seconds=1.0,max_seconds=120.0)
    display(frame_table.head())
    plt.figure(figsize=(10,6)); plt.imshow(cv2.cvtColor(first_frame,cv2.COLOR_BGR2RGB))
    plt.axis('off'); plt.show()
    plt.figure(figsize=(12,4)); plt.plot(frame_table['time_s'],frame_table['detections'])
    plt.xlabel('Waktu (s)'); plt.ylabel('Deteksi per frame'); plt.grid(); plt.show()
    display(Video(str(video_path),embed=True,width=720))
    reports.append({'input':'video','output_video':str(video_path),**report})
else:
    browser_frames = capture_browser_frames()
    for index, frame in enumerate(browser_frames):
        reports.append(detect_and_show(frame,f'webcam_{index:02}'))
    print('Capture batch selesai; stream kamera dihentikan. Ini bukan realtime desktop.')

Sel 7 — Ringkasan, konfigurasi dan verifikasi output

Ringkasan ini berasal dari proses nyata. Run mode gambar dan video untuk demonstrasi; saat mengganti parameter ulangi setup serta sel aplikasi dan simpan kedua output run pada notebook.

report_table = pd.DataFrame(reports)
if report_table.empty: raise RuntimeError('Aplikasi tidak menghasilkan laporan.')
display(report_table)
config = {'mode':INPUT_MODE,'model':MODEL_LABEL,'confidence':CONFIDENCE,
          'classes':requested or 'semua','device':str(DEVICE),
          'ultralytics_version':ultralytics.__version__,
          'preprocessing':'resize proporsional sisi maksimum 960, tanpa enhancement',
          'counting_note':'jumlah bbox per gambar/frame, bukan objek unik antarframe'}
(OUTPUT/'config.json').write_text(json.dumps(config,indent=2,ensure_ascii=False),encoding='utf-8')
report_table.to_csv(OUTPUT/'report.csv',index=False)
output_files=[p for p in OUTPUT.iterdir() if p.is_file()]
if not output_files or any(p.stat().st_size == 0 for p in output_files):
    raise RuntimeError('Terdapat output kosong; periksa proses.')
display(pd.DataFrame([{'file':p.name,'bytes':p.stat().st_size} for p in output_files]))
print('Run tersimpan:',OUTPUT)
print('Tampilkan output dan analisis pada notebook; pengumpulan hanya satu link Colab Viewer.')

Jika terjadi error

Model custom tidak dimuat atau task bukan detect.
Pakai best.pt dari training deteksi sendiri yang dipercaya; periksa file dan task. Jangan memuat bobot tak dikenal.
Mode berubah tetapi input lama tetap dipakai.
Ulangi setup parameter dan sel aplikasi; setiap run mempunyai direktori unik.
Video belum memenuhi batas atau codec tidak terbaca.
Ekspor ulang klip 1–120 detik dengan FPS 1–60 dan format yang terbaca; aplikasi tidak menebak metadata.
Notebook tampak selesai tetapi output kosong.
Periksa exception dan direktori run; jangan menyerahkan hanya kode tanpa keluaran atau mengganti dengan hasil buatan.

Runtime Colab dan file upload bersifat sementara. Simpan output penting pada notebook/Drive sebelum sesi berakhir. Jika runtime direset, jalankan ulang sel dari awal. Gunakan media berizin dan hindari unggahan data sensitif tanpa izin.

4. Tugas 14

Buat aplikasi deteksi gambar/video otomatis berbasis YOLO, lengkap dengan input, validasi, antarmuka sederhana, hasil dan presentasi/demo.

Ketentuan pengerjaan

  1. Aplikasi wajib mendukung gambar DAN video; webcam browser opsional sebagai tambahan, bukan pengganti kedua mode.
  2. Gunakan yolov8n.pt baseline atau best.pt custom kelompok yang dipercaya; jelaskan kelas yang dapat dikenali.
  3. Antarmuka form parameter Colab atau ipywidgets yang bekerja; pilih mode, confidence dan kelas tanpa mengubah algoritme inti.
  4. Alur lengkap upload/input → validasi → preprocessing → model → annotate → counting/tabel → tampilkan dan simpan.
  5. Demo aktual minimal dua gambar berbeda dan satu video; jelaskan FPS serta counting bukan unique objects.
  6. Uji minimal tiga kondisi gagal/sulit, dokumentasikan pesan dan batasan; sertakan privasi serta izin sumber input.
  7. Tuliskan ringkasan presentasi, pipeline dan pembagian kontribusi pada notebook, lalu lakukan demo sesuai arahan dosen.
  8. Kumpulkan hanya SATU link Google Colab dengan kode, output gambar/video dan analisis terlihat, akses Viewer; identitas seluruh anggota pada Markdown paling atas.

Pertanyaan analisis

  1. Kebutuhan pengguna apa yang dipenuhi dan tidak dipenuhi aplikasi?
  2. Apa perubahan input yang paling memengaruhi kualitas atau waktu pemrosesan?
  3. Bagaimana membuktikan output berasal dari run aktual?
  4. Apa risiko jika counting frame dianggap sebagai objek unik?
Identitas di bagian paling atas notebook: tambahkan sel Text/Markdown sebelum kode dan isi Nama serta NPM.
# Materi 14 — Pengolahan Citra
Nama: [Nama lengkap mahasiswa]
NPM: [NPM mahasiswa]
Kelas: [Kelas, jika diperlukan]

Pengumpulan

Kumpulkan satu link Google Colab saja. Nama dan NPM wajib berada pada sel Markdown paling atas. Sertakan kode, output yang tersimpan/terlihat serta analisis di notebook; tidak perlu mengumpulkan file .ipynb, ZIP, PDF atau screenshot terpisah.

  1. Jalankan sel dengan konfigurasi valid dan simpan notebook beserta output. Pastikan opsi menghilangkan output saat menyimpan tidak aktif untuk versi pengumpulan.
  2. Bagikan sebagai Viewer/Pelihat kepada dosen. Gunakan akses link jika diizinkan institusi; bila dibatasi, beri akses khusus akun dosen. Jangan memberi izin edit publik.
  3. Periksa akses dari akun penerima. Kirim URL notebook Colab, bukan folder Drive atau runtime sementara.
AspekBobotIndikator
Aplikasi end-to-end40%Dua mode gambar/video runnable, validasi dan keluaran lengkap.
Antarmuka dan ketahanan20%Parameter mudah diubah, error jelas dan sumber daya dibebaskan.
Pengujian dan analisis25%Demo nyata, kondisi sulit, FPS dan batasan counting tepat.
Presentasi dan reproduksibilitas15%Pipeline, kontribusi, izin dan satu notebook Viewer lengkap.