Pengolahan Citra · Materi 08

Pengenalan Video dengan OpenCV

Membaca frame, memahami FPS, mengubah ukuran, menambahkan waktu relatif dan nomor frame, serta menampilkan video hasil di Colab.

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. Membuka berkas video dan membaca metadata serta frame secara aman.
  2. Menghitung waktu relatif frame menggunakan FPS dengan menyatakan asumsi ketika metadata tidak valid.
  3. Mengubah ukuran frame dan menambahkan timestamp serta nomor frame secara konsisten.
  4. Menyimpan, memeriksa ulang, dan menampilkan video hasil berformat ramah browser dalam notebook.

Prasyarat: Memahami array citra BGR/RGB, resize, putText, perulangan, dan unggah berkas ke Colab.

2. Materi Pembahasan

Ilustrasi frame dibuat dari satu foto; bukan rekaman perjalanan bus nyata.
Ilustrasi frame dibuat dari satu foto; bukan rekaman perjalanan bus nyata.

2.1 Video sebagai urutan citra dan informasi waktu

Secara praktis, video dapat dipahami sebagai rangkaian citra yang dipresentasikan menurut informasi waktu. Sebuah frame adalah satu citra; resolusi menyatakan lebar dan tinggi frame. FPS menjelaskan banyak frame yang dipresentasikan per detik pada alur frame rate konstan. Mengubah ukuran frame mengubah resolusi, sedangkan mengganti FPS keluaran mengubah kecepatan pemutaran jika jumlah frame tetap. Kedua operasi ini memiliki akibat berbeda dan sebaiknya tidak diperlakukan sebagai hal yang sama.

Contoh pengolahan pada modul ini adalah menambahkan tulisan pada setiap frame. Proses tersebut menggunakan keterampilan pengolahan citra yang sudah dipelajari, tetapi diulang secara berurutan. Video bukan hanya citra: sumber dapat memiliki audio, rotasi, dan waktu presentasi yang tidak seragam. Jalur OpenCV yang menulis ulang frame di sini hanya menghasilkan video tanpa audio. Catat kehilangan audio tersebut agar keluaran tidak dianggap salinan lengkap sumber.

2.2 Membuka video dan memeriksa metadata

VideoCapture pada modul ini menerima nama berkas, bukan angka kamera. Pemeriksaan isOpened() mendeteksi kegagalan awal pembukaan. Selanjutnya read() memberikan penanda keberhasilan dan frame; penanda gagal dapat berarti akhir video maupun masalah decode. Karena itu kita tidak boleh langsung memakai frame tanpa memeriksa hasil. CAP_PROP_FPS dan CAP_PROP_FRAME_COUNT membantu memperkirakan karakteristik input, tetapi bukan jaminan bahwa semua frame selalu dapat dibaca.

Input praktikum dibatasi satu klip pendek agar waktu, memori, dan ukuran output terkendali. Bila jumlah frame dan FPS tersedia, durasi nominal dibandingkan dengan batas dua puluh detik. Metadata yang tidak tersedia tidak boleh dianggap durasi nol; loop pemrosesan tetap dibatasi oleh jumlah frame dan waktu kerja. Resolusi besar ditolak untuk demo, sedangkan output dibuat lebih kecil agar ringan ditampilkan dalam notebook.

2.3 Timestamp relatif dan batas ketelitiannya

Kita menggunakan indeks internal mulai nol, sehingga waktu frame pertama adalah nol detik. Nomor yang ditampilkan kepada pembaca mulai satu untuk memudahkan pembacaan. Dengan asumsi FPS konstan, waktu relatif dihitung dari indeks dibagi FPS. Ini bukan tanggal, waktu jam perekaman, atau bukti kapan suatu peristiwa benar-benar terjadi. Tulisan t=2.00s menjelaskan posisi dalam alur video yang diproses, bukan pukul dua pada jam dunia nyata.

Jika metadata FPS tidak valid atau di luar batas demo, kode menggunakan 25 FPS dan menandainya sebagai estimasi. Waktu relatif serta durasi keluaran yang dihitung dari nilai itu ikut menjadi estimasi. Video variable frame rate juga dapat menyimpang dari hitungan indeks/FPS meskipun metadata terlihat masuk akal. Untuk analisis waktu yang presisi diperlukan timestamp presentasi sumber; praktikum pengantar ini sengaja membatasi klaim pada pendekatan frame rate konstan.

2.4 Resize, anotasi, dan penulisan frame

VideoWriter dibuat dengan FPS dan ukuran output yang jelas. Setiap frame diubah ke ukuran yang sama sebelum diberi tulisan, kemudian diserahkan ke writer dalam BGR. Karena codec tertentu memerlukan dimensi genap, lebar dan tinggi output dibulatkan ke bilangan genap terdekat dengan batas minimum. Resize dilakukan proporsional untuk menghindari bentuk objek berubah, meskipun pembulatan beberapa piksel dapat menimbulkan selisih rasio yang sangat kecil.

Kita menggambar latar tulisan agar nomor dan waktu terbaca pada berbagai pemandangan. Pemilihan ukuran teks mempertimbangkan lebar output, bukan semata resolusi asli. Frame pertama yang sudah dianotasi disimpan sebagai cuplikan pemeriksaan dalam notebook, lalu ditampilkan setelah konversi BGR ke RGB. cap dan writer dibebaskan dalam finally, sehingga proses yang berhenti karena kesalahan tidak meninggalkan sumber daya terbuka atau header video belum ditutup.

2.5 Berkas hasil, codec, dan playback notebook

Ekstensi MP4 menyatakan kontainer, bukan jaminan codec yang dapat diputar semua browser. Demo menulis file kerja dengan codec mp4v kemudian menggunakan FFmpeg untuk transkode ke H.264 dengan format piksel yuv420p. File kerja adalah artefak sementara di runtime, bukan dokumen pengumpulan. Transkode juga dibatasi waktu eksekusi; jika FFmpeg atau encoder tidak tersedia, kode memberi kegagalan jelas alih-alih mengklaim hasil bisa dimainkan.

Setelah penulisan dan transkode, video dibuka ulang untuk memastikan frame pertama dapat didekode. Ukuran file diperiksa sebelum ditanamkan ke notebook agar tidak menghasilkan keluaran sangat besar. Mahasiswa tetap perlu menjalankan tombol play untuk memverifikasi playback browser, karena keberhasilan decode OpenCV belum membuktikan kompatibilitas semua browser. Yang dikumpulkan adalah satu link Colab berisi player video, metadata, dan analisis; berkas video tidak dikumpulkan secara terpisah.

Video pembelajaran sesuai bab

OpenCV Python Tutorial #3 - Cameras and VideoCapture — Tech With Tim.

Fokus saat menonton: Ikuti pembacaan frame dan loop VideoCapture. Pahami FPS, resize per-frame serta pelepasan resource.

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 08 — Pengolahan Citra
Nama: [Nama lengkap mahasiswa]
NPM: [NPM mahasiswa]
Kelas: [Kelas, jika diperlukan]
  1. Jalankan setup 0 sebelum import, lalu ketik sel 1–2 setelah identitas Nama/NPM, unggah klip pendek, dan baca metadata yang dicetak.
  2. Pastikan durasi maksimal 20 detik serta izin perekaman/penggunaan klip.
  3. Jalankan sel 3; catat FPS yang dipakai, ukuran output, jumlah frame, dan alasan berhenti.
  4. Jalankan transkode dan pemeriksaan hasil pada sel 4–5.
  5. Putar video pada player notebook dan periksa nomor frame serta timestamp relatif di awal dan akhir.
  6. Tulis analisis FPS/estimasi, perubahan resolusi, audio yang tidak dipertahankan, dan satu link Colab Viewer.

Sel 1 — 0. Setup baseline OpenCV 4.x

Jalankan sebelum import cv2 untuk menyamakan baseline dengan modul sebelumnya. Jika cv2 sudah diimport dan versi berubah, restart runtime lalu jalankan dari awal. Tidak perlu menaikkan NumPy secara terpisah.

%pip install "opencv-python-headless>=4.10,<5"

Sel 2 — 1. Import dan batas demo

Gunakan klip maksimum 20 detik, 50 MB, maksimal 1920×1920 piksel. FFmpeg diperiksa pada sel transkode.

import cv2
import numpy as np
import matplotlib.pyplot as plt
import math, time, pathlib, shutil, subprocess
from google.colab import files
from IPython.display import display, Video
MAX_SECONDS = 20
MAX_BYTES = 50 * 1024 * 1024
MAX_FRAMES = 1200
MAX_WALL_SECONDS = 60

Sel 3 — 2. Unggah dan inspeksi

Unggah tepat satu MP4/AVI/MOV/WebM pendek berizin. Durasi nominal yang tidak diketahui tetap dibatasi loop pada sel berikutnya.

uploaded = files.upload()
if len(uploaded) != 1:
    raise ValueError('Unggah tepat satu video pendek.')
nama, data = next(iter(uploaded.items()))
if len(data) > MAX_BYTES or not nama.lower().endswith(('.mp4', '.avi', '.mov', '.webm')):
    raise ValueError('Video harus MP4/AVI/MOV/WebM, maksimal 50 MB.')
input_path = pathlib.Path('input_clip' + pathlib.Path(nama).suffix.lower())
input_path.write_bytes(data)
cap = cv2.VideoCapture(str(input_path))
try:
    if not cap.isOpened():
        raise RuntimeError('Video tidak dapat dibuka.')
    fps_meta = float(cap.get(cv2.CAP_PROP_FPS))
    count_meta = float(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    fps_estimate = not (math.isfinite(fps_meta) and 1 <= fps_meta <= 60)
    fps = 25.0 if fps_estimate else fps_meta
    ok, first = cap.read()
    if not ok or first is None:
        raise RuntimeError('Frame pertama tidak dapat didekode.')
    in_h, in_w = first.shape[:2]
    if max(in_h, in_w) > 1920:
        raise ValueError('Resolusi input maksimal 1920 piksel per sisi untuk demo.')
    duration_nominal = (count_meta / fps_meta if math.isfinite(count_meta)
                        and count_meta > 0 and math.isfinite(fps_meta)
                        and fps_meta > 0 else None)
    if duration_nominal is not None and duration_nominal > MAX_SECONDS + 0.1:
        raise ValueError('Potong klip menjadi maksimal 20 detik sebelum unggah.')
finally:
    cap.release()
print('Resolusi input:', (in_w, in_h))
print('FPS metadata:', fps_meta, '| FPS dipakai:', fps)
print('FPS fallback/estimasi:', fps_estimate)
print('Durasi nominal metadata:', duration_nominal)
plt.figure(figsize=(8, 5))
plt.imshow(cv2.cvtColor(first, cv2.COLOR_BGR2RGB))
plt.axis('off'); plt.show()

Sel 4 — 3. Resize, teks, dan simpan video

Timestamp berasal dari indeks/FPS. Pemrosesan dibatasi 20 detik nominal, 1200 frame, dan 60 detik kerja; output ini tidak menyimpan audio.

scale = min(1.0, 640 / in_w, 640 / in_h)
out_w = max(2, int(in_w * scale) // 2 * 2)
out_h = max(2, int(in_h * scale) // 2 * 2)
raw_path = pathlib.Path('video_anotasi_mp4v.mp4')
cap = cv2.VideoCapture(str(input_path))
writer = None
processed = 0
preview = None
frame_limit = min(MAX_FRAMES, max(1, int(math.floor(MAX_SECONDS * fps))))
start = time.monotonic()
stop_reason = 'batas frame/durasi nominal'
try:
    if not cap.isOpened():
        raise RuntimeError('Video gagal dibuka untuk pemrosesan.')
    writer = cv2.VideoWriter(str(raw_path), cv2.VideoWriter_fourcc(*'mp4v'),
                             fps, (out_w, out_h))
    if not writer.isOpened():
        raise RuntimeError('VideoWriter tidak tersedia untuk codec mp4v.')
    while processed < frame_limit:
        if time.monotonic() - start > MAX_WALL_SECONDS:
            stop_reason = 'batas waktu kerja; hasil hanya cuplikan'
            break
        ok, frame = cap.read()
        if not ok or frame is None:
            stop_reason = 'akhir input atau read/decode gagal'
            break
        frame = cv2.resize(frame, (out_w, out_h))
        label = f'Frame {processed+1} | t={processed/fps:.2f}s'
        if fps_estimate:
            label += ' (est.)'
        font_scale = max(0.25, min(0.6, out_w / 700))
        text_size, baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, font_scale, 1)
        cv2.rectangle(frame, (0, 0), (min(out_w-1, text_size[0]+12),
                      min(out_h-1, text_size[1]+baseline+12)), (0, 0, 0), -1)
        cv2.putText(frame, label, (6, text_size[1]+6), cv2.FONT_HERSHEY_SIMPLEX,
                    font_scale, (255, 255, 255), 1, cv2.LINE_AA)
        writer.write(frame)
        if preview is None:
            preview = frame.copy()
        processed += 1
finally:
    cap.release()
    if writer is not None:
        writer.release()
if processed == 0 or not raw_path.exists() or raw_path.stat().st_size == 0:
    raise RuntimeError('Tidak ada video hasil yang valid.')
print('Frame ditulis:', processed, '| ukuran:', (out_w, out_h))
print('Durasi keluaran nominal:', processed/fps, 'detik; estimasi:', fps_estimate)
print('Alasan berhenti:', stop_reason)
plt.figure(figsize=(8, 5))
plt.imshow(cv2.cvtColor(preview, cv2.COLOR_BGR2RGB))
plt.axis('off'); plt.show()

Sel 5 — 4. Transkode H.264 untuk browser

Gunakan FFmpeg yang tersedia di runtime Colab. Sel ini tidak memasang paket lokal dan tidak mengubah NumPy.

ffmpeg = shutil.which('ffmpeg')
if ffmpeg is None:
    raise RuntimeError('FFmpeg tidak tersedia di runtime; gunakan runtime Colab standar dan jalankan ulang.')
browser_path = pathlib.Path('video_anotasi_browser.mp4')
command = [ffmpeg, '-y', '-i', str(raw_path), '-an', '-c:v', 'libx264',
           '-preset', 'veryfast', '-crf', '23', '-pix_fmt', 'yuv420p',
           '-movflags', '+faststart', str(browser_path)]
result = subprocess.run(command, capture_output=True, text=True, timeout=120)
if result.returncode != 0:
    raise RuntimeError('Transkode gagal: ' + result.stderr[-1500:])
if not browser_path.exists() or browser_path.stat().st_size == 0:
    raise RuntimeError('Berkas H.264 tidak terbentuk.')
print('Ukuran video browser (MB):', round(browser_path.stat().st_size / (1024*1024), 2))

Sel 6 — 5. Verifikasi dan tampilkan hasil

Player ditanam dalam output notebook. Jalankan play dan pastikan tulisan terbaca; video ini bukan berkas pengumpulan terpisah.

check = cv2.VideoCapture(str(browser_path))
try:
    if not check.isOpened():
        raise RuntimeError('Video hasil tidak dapat dibuka ulang.')
    ok, check_frame = check.read()
    if not ok or check_frame is None:
        raise RuntimeError('Frame video hasil gagal didekode.')
    if check_frame.shape[:2] != (out_h, out_w):
        raise RuntimeError('Ukuran video hasil tidak sesuai.')
    print('Frame pertama output berhasil didekode.')
    print('FPS output:', check.get(cv2.CAP_PROP_FPS))
    print('Jumlah frame output menurut metadata:', check.get(cv2.CAP_PROP_FRAME_COUNT))
finally:
    check.release()
if browser_path.stat().st_size > 20 * 1024 * 1024:
    raise RuntimeError('Output terlalu besar untuk embed demo; gunakan klip yang lebih pendek.')
display(Video(str(browser_path), embed=True, mimetype='video/mp4'))

Jika terjadi error

cap.isOpened() bernilai False.
Periksa berkas dan format; gunakan MP4 pendek yang dapat diputar, bukan hanya mengganti ekstensi.
Metadata FPS nol/NaN atau melampaui batas demo.
Kode memakai 25 FPS sebagai estimasi. Gunakan klip lain jika tugas membutuhkan waktu akurat dan jelaskan fallback.
VideoWriter gagal atau video kosong.
Periksa isOpened(), frame count, ukuran genap, dan kesesuaian setiap frame dengan ukuran writer.
MP4 tidak diputar browser.
Jalankan transkode H.264/yuv420p dan cek pesan FFmpeg; ekstensi MP4 saja tidak menentukan codec.
Video hasil tanpa suara.
Ini batas jalur pengolahan frame OpenCV dan opsi -an, bukan kerusakan tak terduga. Jelaskan bahwa audio tidak dipertahankan.

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 8

Buat video baru dengan timestamp relatif dan nomor frame, lalu tampilkan hasil video dalam notebook. Kumpulkan hanya satu link Google Colab dengan output serta analisis terlihat dan akses Viewer.

Ketentuan pengerjaan

  1. Nama/NPM pada sel Markdown teratas.
  2. Gunakan satu klip berizin berdurasi maksimal 20 detik, maksimal 50 MB; tanpa data sensitif.
  3. Cetak metadata FPS/resolusi dan nyatakan fallback sebagai estimasi bila dipakai.
  4. Resize proporsional dan tulis nomor frame serta timestamp relatif pada seluruh frame yang diproses.
  5. Simpan video hasil, bebaskan cap/writer, periksa ulang frame, serta tampilkan player hasil di notebook.
  6. Laporkan jumlah frame dan alasan penghentian; jika berhenti karena batas kerja, gunakan klip lebih pendek agar tugas memproses klip sampai selesai.
  7. Analisis harus terlihat di notebook; tidak mengumpulkan berkas MP4/.ipynb/ZIP/PDF/screenshot terpisah.

Pertanyaan analisis

  1. Bagaimana FPS menentukan timestamp dan durasi nominal output?
  2. Apakah FPS sumber valid? Jika tidak, bagian laporan mana yang merupakan estimasi?
  3. Apa perubahan resolusi dan apa dampaknya pada keterbacaan tulisan?
  4. Mengapa perlu transkode, apa audio yang hilang, dan apakah seluruh klip selesai diproses?
Identitas di bagian paling atas notebook: tambahkan sel Text/Markdown sebelum kode dan isi Nama serta NPM.
# Materi 08 — 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
Pipeline baca dan tulis35%Input dibaca, batas dicek, frame diproses konsisten, dan sumber daya dibebaskan.
Anotasi dan playback30%Nomor frame/timestamp terbaca dan player video hasil berfungsi dalam notebook.
Analisis waktu dan batasan25%FPS, estimasi, resolusi, penghentian, dan kehilangan audio dijelaskan sesuai output.
Keterbacaan dan akses10%Identitas, output, Markdown, dan satu link Viewer lengkap.