Mode belajar: tulis kode sendiri. Copy/cut, seleksi teks dan klik kanan dibatasi pada halaman; pembatasan UI bukan jaminan anti-penyalinan mutlak.
1. Tujuan
- Memvalidasi dan memproses video frame demi frame tanpa memuat semua citra ke memori.
- Mengukur FPS sumber, FPS inferensi dan throughput pipeline secara terpisah.
- Menyajikan hitungan per frame tanpa mengklaim jumlah objek unik.
- Membuat output video minimal 30 detik atau demonstrasi webcam browser terbatas dengan cleanup.
Prasyarat: Modul 10–11, loop Python, Results.boxes dan penyimpanan citra.
2. Materi Pembahasan

2.1 Video sebagai urutan citra
Video menyimpan urutan frame dengan informasi waktu. Inferensi deteksi dapat diterapkan pada tiap frame seperti pada gambar, tetapi menghasilkan keluaran berulang sepanjang urutan. Contoh utama membaca file menggunakan VideoCapture, memeriksa apakah capture terbuka, lalu memproses satu frame pada satu waktu. Cara ini membatasi penggunaan memori karena citra tidak dikumpulkan menjadi daftar besar.
Video memiliki batas durasi, ukuran, dimensi dan FPS agar eksperimen Colab tetap terkontrol. Metadata tidak selalu akurat, sehingga program juga membandingkan jumlah frame yang benar-benar terbaca. File rusak atau codec yang tidak didukung harus dilaporkan sebagai kegagalan input, bukan diasumsikan selesai. Writer dan capture dibebaskan dalam finally agar runtime tidak menahan berkas atau sumber daya setelah error.
2.2 Waktu sumber dan waktu komputasi
FPS video sumber menunjukkan berapa frame dimainkan per detik pada timeline input. FPS inferensi adalah jumlah frame dibagi waktu yang dihabiskan dalam pemanggilan model. Throughput pipeline sebelum transcoding juga mencakup decode, resize, anotasi dan penulisan. Ketiga angka tidak dapat dipertukarkan: video 30 FPS tetap dapat membutuhkan beberapa menit untuk diproses di CPU.
Pengukuran menggunakan perf_counter, dan GPU disinkronkan agar operasi asinkron tidak membuat waktu tampak terlalu kecil. Pada contoh ini pemanggilan pertama dapat mencakup warm-up sehingga laporan bukan benchmark perangkat yang ketat. Catat device, ukuran input dan batas pengukuran. Transcoding serta tampilan notebook terjadi sesudah pengukuran pipeline; latensi interaktif total tentu dapat lebih besar dari angka inferensi.
2.3 Counting per frame bukan counting unik
Tabel per frame mencatat timestamp, jumlah bbox, waktu inferensi dan hitungan setiap kelas. Jumlah semua bbox sepanjang video disebut sum frame detections atau jumlah kemunculan deteksi. Rata-rata per frame menggambarkan kepadatan deteksi dalam urutan. Bus yang diam selama seratus frame dapat menyumbang seratus kemunculan walaupun hanya satu bus fisik.
Menghitung objek unik memerlukan tracking, yaitu proses mengaitkan kandidat antarframe dengan identitas track. Bahkan jumlah ID track masih dapat berlebih ketika objek hilang lalu memperoleh ID baru, atau kurang ketika identitas tertukar. Counting melewati garis juga membutuhkan aturan arah dan pencatatan agar track tidak dihitung berkali-kali. Praktikum ini tidak menyamakan deteksi dengan tracking dan tidak mengklaim total unik dari penjumlahan sederhana.
2.4 Ukuran frame dan keluaran yang dapat diputar
Frame diperkecil secara proporsional hingga sisi terpanjang maksimum 960 piksel sebelum inferensi dan penulisan. Langkah ini menurunkan biaya decode lanjutan dan ukuran keluaran, tetapi objek kecil bisa makin sulit terlihat. Koordinat bbox mengacu pada frame hasil resize yang dianalisis, bukan otomatis pada frame asli. Parameter imgsz mengatur skala masukan internal model dan berbeda dari dimensi video keluaran.
Contoh menulis AVI sementara dengan MJPG kemudian mengubahnya menjadi MP4 H.264 melalui ffmpeg untuk playback browser. Konversi dilakukan dengan argumen subprocess, bukan perintah shell dari nama upload. Writer dibuka dengan dimensi genap dan diverifikasi; video final juga dibaca kembali. Output tidak menyertakan audio sehingga tidak identik dengan sumber. Jika codec gagal, notebook harus menampilkan error dan tidak mengklaim video telah dibuat.
2.5 Webcam browser dalam runtime cloud
Colab menjalankan Python di runtime yang terpisah dari laptop. VideoCapture(0) akan merujuk perangkat di server, bukan webcam browser pengguna. Jalur opsional memakai getUserMedia di JavaScript, meminta izin, mengambil maksimal lima frame, dan mengirim data JPEG ke Python melalui output.eval_js. Kamera dihentikan dalam finally, termasuk penanganan permintaan izin yang terlambat selesai.
Batch frame kamera diambil terlebih dahulu dan baru dianalisis. Ini adalah demonstrasi capture browser terbatas, bukan sistem realtime desktop dan bukan pengukuran FPS kamera yang akurat. Izin ditolak, kamera sibuk, konteks browser, atau kebijakan organisasi dapat menghambat capture. Gunakan video sebagai alternatif yang mudah diulang. Hindari merekam wajah orang tanpa persetujuan dan jangan mempertahankan stream kamera ketika sel selesai.
Video pembelajaran sesuai bab
Object Detection and Tracking with Ultralytics YOLOv8 | Episode 7 — Ultralytics.
Fokus saat menonton: Amati deteksi di video dan asosiasi ID tracking. Bandingkan hitungan deteksi per-frame dengan objek unik.
Referensi dan sumber ilustrasi
3. Praktikum 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.
# Materi 12 — Pengolahan Citra
Nama: [Nama lengkap mahasiswa]
NPM: [NPM mahasiswa]
Kelas: [Kelas, jika diperlukan]- Jalankan setup dan upload file video berizin berdurasi 30–120 detik.
- Periksa metadata dan durasi terdecode; semua frame diproses, input yang melampaui batas ditolak.
- Tampilkan video hasil, tabel per frame, grafik deteksi dan laporan tiga jenis laju pemrosesan.
- Audit beberapa frame untuk false positive, false negative dan ketidakstabilan temporal.
- Untuk jalur webcam, lewati upload/pemrosesan video dan jalankan sel opsional setelah setup; izinkan kamera hanya bila diperlukan.
- Simpan output notebook, jelaskan sum frame detections bukan unik dan bagikan satu tautan Viewer.
Sel 1 — Instalasi Ultralytics
Jalankan sebagai sel tersendiri di Colab; tidak perlu meng-upgrade NumPy. Catat versi paket di sel berikutnya.
%pip install -q ultralyticsSel 2 — Model baseline dan direktori keluaran
Bobot yolov8n.pt diunduh saat pemuatan pertama. Ini baseline ringan, bukan pernyataan bahwa model ini yang terbaru. CPU dapat digunakan.
from pathlib import Path
import cv2, numpy as np, matplotlib.pyplot as plt
import torch, ultralytics
from ultralytics import YOLO
from google.colab import files
OUTPUT = Path('/content/hasil_yolo')
OUTPUT.mkdir(parents=True, exist_ok=True)
DEVICE = 0 if torch.cuda.is_available() else 'cpu'
model = YOLO('yolov8n.pt')
print('Ultralytics:', ultralytics.__version__, 'Device:', DEVICE)
print('Kelas model:', model.names)Sel 3 — Upload satu file video
Contoh utama memakai file, bukan kamera lokal runtime. Maksimum 150 MB; video tugas minimal 30 detik, contoh menerima 1–120 detik agar pengujian awal singkat.
video_files = files.upload()
if len(video_files) != 1:
raise ValueError('Upload tepat satu file video.')
video_name, video_bytes = next(iter(video_files.items()))
suffix = Path(video_name).suffix.lower()
if suffix not in {'.mp4', '.avi', '.mov', '.mkv', '.webm'}:
raise ValueError('Gunakan MP4/AVI/MOV/MKV/WebM.')
if not (0 < len(video_bytes) <= 150 * 1024**2):
raise ValueError('Video kosong atau melebihi 150 MB.')
VIDEO_INPUT = Path('/content') / ('input_video' + suffix)
VIDEO_INPUT.write_bytes(video_bytes)
print('Input:', VIDEO_INPUT)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_annotatedSel 5 — Jalankan dan tampilkan laporan video
Gunakan min_seconds=30.0 untuk memastikan video tugas memenuhi durasi. Ubah menjadi 1.0 hanya untuk uji awal yang bukan memenuhi tugas.
from IPython.display import display, Video
mp4_path, frame_table, report, first_frame = process_video(
VIDEO_INPUT, model, conf=0.35, output_dir=OUTPUT, device=DEVICE,
min_seconds=30.0, max_seconds=120.0)
display(pd.DataFrame([report]))
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 video (s)'); plt.ylabel('Deteksi per frame'); plt.grid(); plt.show()
display(Video(str(mp4_path), embed=True, width=720))
print('Video:', mp4_path, 'CSV:', OUTPUT / 'per_frame.csv')
print('Jumlah kemunculan deteksi bukan jumlah objek unik.')Sel 6 — Opsional: ambil lima frame kamera browser
Jalankan hanya jika ingin memilih jalur webcam. Izin kamera diperlukan; maksimum lima frame, stream dihentikan dalam finally. Capture berurutan kemudian inferensi: bukan realtime desktop.
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
webcam_frames = capture_browser_frames()
for i, frame in enumerate(webcam_frames):
result = model.predict(frame, conf=0.35, device=DEVICE, verbose=False)[0]
annotated = result.plot()
if not cv2.imwrite(str(OUTPUT / f'webcam_{i:02}.jpg'), annotated):
raise IOError('Gagal simpan frame kamera.')
plt.figure(figsize=(9,6)); plt.imshow(cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB))
plt.title(f'Frame browser {i}: {len(result.boxes)} deteksi')
plt.axis('off'); plt.show()Jika terjadi error
- Video tidak bisa diputar di browser.
- Periksa log ffmpeg, pastikan MP4 H.264 dan yuv420p terbentuk, lalu verifikasi dengan pembacaan ulang.
- FPS sumber nol atau NaN.
- Input ditolak; ekspor ulang dengan FPS yang jelas. Jangan mengganti metadata diam-diam.
- Webcam tidak tersedia.
- Periksa izin browser dan kamera; gunakan file video. Jangan memakai VideoCapture(0) di runtime.
- Proses terasa lambat.
- Gunakan runtime GPU bila tersedia atau uji klip singkat; tetap laporkan batas pengukuran tanpa menjanjikan realtime.
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 12
Buat deteksi video dengan input minimal 30 detik ATAU demo webcam browser terbatas, menampilkan hasil dan analisis counting serta FPS.
Ketentuan pengerjaan
- Pilihan video: file berizin 30–120 detik, validasi dan pemrosesan seluruh frame.
- Pilihan webcam: minimal 5 frame dari capture browser, izin jelas, batas capture dan cleanup track; tidak diwajibkan video 30 detik untuk jalur ini.
- Tampilkan semua frame webcam atau video anotasi pada notebook, serta tabel hitungan per frame.
- Jalur video laporkan FPS sumber, inferensi dan pipeline; jalur webcam ukur waktu inferensi Python dan jelaskan bukan FPS realtime kamera.
- Jelaskan kemunculan deteksi versus objek unik; tidak memakai sum bbox sebagai unique count.
- Kumpulkan hanya SATU link Colab dengan output dan analisis terlihat, Viewer, serta identitas di Markdown teratas.
Pertanyaan analisis
- Mengapa sumber 30 FPS dapat memiliki throughput inferensi lebih rendah?
- Bagaimana resize dan occlusion memengaruhi konsistensi hasil?
- Apa tambahan yang dibutuhkan untuk menghitung kendaraan unik atau melintasi garis?
# Materi 12 — 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.
- Jalankan sel dengan konfigurasi valid dan simpan notebook beserta output. Pastikan opsi menghilangkan output saat menyimpan tidak aktif untuk versi pengumpulan.
- Bagikan sebagai Viewer/Pelihat kepada dosen. Gunakan akses link jika diizinkan institusi; bila dibatasi, beri akses khusus akun dosen. Jangan memberi izin edit publik.
- Periksa akses dari akun penerima. Kirim URL notebook Colab, bukan folder Drive atau runtime sementara.
| Aspek | Bobot | Indikator |
|---|---|---|
| Alur input dan cleanup | 30% | Video minimal 30 detik atau webcam browser terbatas, validasi dan release benar. |
| Inferensi dan output | 30% | Frame diproses, anotasi serta tabel tampil. |
| Counting dan pengukuran | 30% | FPS dijelaskan sesuai ruang lingkup, tidak mengklaim jumlah unik. |
| Reproduksibilitas dan etika | 10% | Sumber/izin dan notebook Viewer lengkap. |