PENGOLAHAN CITRA DIGITAL • PERTEMUAN 9
Bagaimana Komputer "Mengenali" Sebuah Benda?
Membedah source code ekstraksi ciri warna, bentuk (kontur), geometri, dan tekstur GLCM
ALIMIN, M.Kom
🎨
CIRI WARNA
Rata-rata kanal B, G, R
"Seberapa merah?"
📐
CIRI BENTUK
Luas, keliling, kebundaran
"Seberapa bulat?"
🧱
CIRI TEKSTUR
5 properti GLCM
"Seberapa kasar?"
🎯 Tujuan Pembelajaran
Setelah sesi ini, Anda mampu:
- Menjelaskan mengapa citra harus diubah menjadi vektor ciri sebelum diklasifikasi.
- Membaca alur program Tkinter + OpenCV + scikit-image baris demi baris.
- Menghitung dan menafsirkan luas, keliling, kebundaran, serta 5 properti GLCM.
- Mengkritisi keterbatasan kode dan merancang perbaikannya.
Klik setiap babak untuk membuka. Perhatikan gambar ilustrasi di tiap babak, karena gambar membantu memahami angka.
Babak 1 — Pembuka: Komputer Itu Buta Makna
Bayangkan saya menaruh apel dan jeruk di meja. Anda langsung tahu mana yang mana. Bagi komputer, keduanya hanyalah tabel angka 0 sampai 255. Lihat sendiri:
GAMBAR 1 • Citra 5x5 piksel: apa yang Anda lihat vs apa yang komputer lihat
👁 Yang manusia lihat
→
💻 Yang komputer lihat
Objek gelap di tengah dengan latar putih. Nilai kecil = gelap, nilai besar = terang.
Karena itu kita tidak bertanya "ini apa", melainkan mengukur: seberapa merah? seberapa bulat? seberapa kasar?
Perumpamaan: ekstraksi ciri seperti petugas yang membuat deskripsi buronan: tinggi, warna rambut, bentuk wajah. Deskripsi itulah yang dicocokkan, bukan foto utuhnya.
Babak 2 — Peta Besar: Anatomi Program
Program ini hanya punya satu class dengan empat method:
__init__menyiapkan jendela dan variabelcv_img.setup_uimembangun tampilan.load_imagemembuka file lalu memanggil ekstraksi.extract_featuresadalah jantung program.
GAMBAR 2 • Alur data dari file gambar sampai hasil
load_image
format BGR
teks + gambar kontur
Tiga jalur ekstraksi berjalan dari citra yang sama, lalu hasilnya digabung dalam satu tampilan.
Babak 3 — Bedah Import: Lima Spesialis dalam Satu Tim
import tkinter as tk from tkinter import filedialog import cv2 import numpy as np from skimage.feature import graycomatrix, graycoprops from PIL import Image, ImageTk
🖥 tkinter
Pembuat antarmuka dan jendela pilih file.
📷 cv2
Membaca citra, threshold, mencari kontur.
𝛑 numpy
Di sini dipakai untuk konstanta np.pi.
🧱 skimage
graycomatrix membangun matriks, graycoprops menghitung statistiknya.
🖼 PIL
Jembatan agar array NumPy tampil di Tkinter.
Catatan: di scikit-image versi lama fungsinya bernama greycomatrix.
Insight: tidak ada satu library yang mengerjakan semuanya. Skill penting seorang engineer citra adalah tahu siapa mengerjakan apa.
Babak 4 — Antarmuka: Panggung Tempat Semua Ditampilkan
left = tk.LabelFrame(body, text="Citra Objek & Deteksi Kontur")
right = tk.LabelFrame(body, text="Vektor Ciri yang Diekstraksi", width=420)
right.pack_propagate(False)
self.txt_features = tk.Text(right, font=("Consolas", 10), ...)
GAMBAR 3 • Denah jendela aplikasi (ukuran 950x640)
Citra Objek & Deteksi Kontur
Vektor Ciri yang Diekstraksi
=== 1. FITUR WARNA ===
Mean Blue : 87.42
=== 2. GEOMETRI ===
Kebundaran: 0.8123
=== 3. TEKSTUR ===
Contrast : 12.3456
Kiri untuk "apa yang dilihat", kanan untuk "apa yang diukur".
- Dua panel: mata kita membandingkan gambar dengan angka secara langsung.
pack_propagate(False)mengunci lebar panel kanan 420 piksel; tanpa ini panel mengecil mengikuti isinya.- Font Consolas (monospace) membuat titik dua dan angka sejajar rapi.
Babak 5 — Pintu Masuk: load_image()
p = filedialog.askopenfilename(filetypes=[("Image Files", "*.png *.jpg *.jpeg *.bmp")])
if not p:
return
self.cv_img = cv2.imread(p)
self.extract_features()
if not p: return adalah guard clause: bila pengguna menekan Cancel, fungsi berhenti dengan sopan.
Fakta penting: cv2.imread membaca dalam urutan kanal B-G-R, bukan RGB. Dampaknya terlihat jelas di Babak 6 dan Babak 10.
Babak 6 — Ciri Warna: Rata-rata yang Sederhana tapi Bermakna
self.cv_img[:, :, 0].mean() # Blue self.cv_img[:, :, 1].mean() # Green self.cv_img[:, :, 2].mean() # Red
Citra berwarna adalah array 3 dimensi [tinggi, lebar, kanal]. Potongan [:, :, 0] berarti "semua baris, semua kolom, kanal ke-0" yaitu Blue.
GAMBAR 4 • Satu piksel jeruk tersimpan sebagai [B, G, R] = [40, 90, 200]
✔ Dibaca sebagai BGR
(oranye, benar)
✘ Salah dibaca sebagai RGB
(kebiruan)
Inilah mengapa Mean Blue di program sebenarnya adalah kanal indeks 0, dan mengapa konversi BGR ke RGB wajib sebelum ditampilkan.
Perumpamaan: seperti mengaduk semua cat di kanvas menjadi satu warna dominan. Apel merah punya Mean Red tinggi; daun punya Mean Green tinggi.
Keterbatasan: rata-rata mencakup seluruh gambar termasuk latar. Latar putih menaikkan ketiga nilai sekaligus.
Babak 7 — Ciri Tekstur: GLCM, Siapa Duduk di Sebelah Siapa?
gray = cv2.cvtColor(self.cv_img, cv2.COLOR_BGR2GRAY)
glcm = graycomatrix(gray, distances=[1], angles=[0],
levels=256, symmetric=True, normed=True)
contrast = graycoprops(glcm, 'contrast')[0, 0]
Perumpamaan kelas: setiap piksel adalah mahasiswa dengan "tingkat keabuan" tertentu. GLCM mencatat berapa kali mahasiswa bernilai i duduk tepat di sebelah kanan mahasiswa bernilai j. Kelas yang duduknya acak dan kelas yang berkelompok punya catatan sangat berbeda. Itulah tekstur.
GAMBAR 5 • Membangun GLCM dari citra mini 4x4 dengan 4 level abu (jarak 1, sudut 0°, symmetric)
Citra (level 0-3)
→
Matriks GLCM (baris i, kolom j)
Contoh: pasangan (0,1) muncul 2 kali dari kiri ke kanan; karena symmetric, (1,0) juga bernilai 2. Total 24 pasangan. Setelah dibagi 24 (normed), setiap sel menjadi probabilitas.
🧮 Hitung manual dari matriks di atas
Contrast = Σ p x (i-j)² = (4 + 8 + 2) / 24 ≈ 0,583
Homogeneity = Σ p / (1+(i-j)²) = (16 + 3 + 0,4) / 24 ≈ 0,808
Energy = √(Σ p²) = √(84/576) ≈ 0,382
Membaca parameternya:
distances=[1]: tetangga berjarak 1 piksel.angles=[0]: arah horizontal; satuannya radian (np.pi/2= 90°).levels=256: matriks 256 x 256.symmetric=True: (i,j) dan (j,i) dihitung sama.normed=True: jumlah diubah menjadi probabilitas (total = 1).
GAMBAR 6 • Tiga jenis tekstur dan kecenderungan GLCM sudut 0°
Halus
Homogeneity tinggi, contrast rendah
Garis vertikal rapat
Berganti terang-gelap ke arah kanan: contrast tinggi
Garis horizontal rapat
Tetangga kanan sama: contrast rendah di 0°, tinggi di 90°
Ilustrasi konseptual. Kecenderungan berlaku bila lebar garis mendekati 1 piksel. Inilah alasan perlunya banyak sudut.
Lima properti yang diambil:
Contrast: selisih intensitas antar tetangga, dikuadratkan. Tinggi = tepi tajam, tekstur kasar.
Dissimilarity: mirip contrast tetapi selisihnya linear, lebih "lembut" terhadap pencilan.
Homogeneity: kebalikan contrast, 0 sampai 1. Mendekati 1 = tetangga hampir sama (halus).
Energy: akar dari ASM, 0 sampai 1. Tinggi = pola seragam dan berulang.
Correlation: -1 sampai 1, seberapa linear ketergantungan intensitas antar tetangga.
[0, 0] di akhir memilih elemen pertama hasil, karena hanya satu jarak dan satu sudut yang dipakai.
Babak 8 — Ciri Bentuk: Dari Threshold ke Kontur
_, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(annotated, contours, -1, (0, 255, 0), 2)
Langkah 1, memisahkan objek dari latar. Dengan THRESH_BINARY_INV dan ambang 200, piksel yang lebih gelap atau sama dengan 200 menjadi putih (objek), sisanya hitam (latar). Program ini mengasumsikan latar belakang terang.
GAMBAR 7 • Skala keabuan 0-255 dan ambang 200
0 sampai 200 → 255 (PUTIH = objek)
>200 → 0 (hitam)
Citra gray: objek gelap, latar terang
Hasil threshold INV: objek jadi putih
drawContours: garis hijau
Langkah 2, menelusuri tepinya.
RETR_EXTERNAL: hanya kontur terluar; lubang di dalam objek diabaikan.CHAIN_APPROX_SIMPLE: titik yang segaris dibuang. Persegi panjang cukup disimpan 4 titik sudutnya.
Langkah 3, menggambar. -1 = semua kontur, (0, 255, 0) = hijau dalam BGR, tebal 2 piksel. Digambar pada salinan (.copy()) agar citra asli tidak rusak.
Babak 9 — Geometri: Luas, Keliling, dan Kebundaran
c = max(contours, key=cv2.contourArea) area = cv2.contourArea(c) perimeter = cv2.arcLength(c, True) circularity = 4 * np.pi * (area / (perimeter ** 2))
- max(..., key=contourArea): ambil kontur terbesar, strategi sederhana untuk mengabaikan noise kecil.
- contourArea: luas dalam piksel persegi.
- arcLength(c, True): panjang keliling;
True= kontur tertutup.
GAMBAR 8 • Kebundaran = 4πA / P² untuk berbagai bentuk
Lingkaran
1,000
Persegi
0,785
Persegi panjang 4:1
0,503
Batang 10:1 (mirip pensil)
0,260
Semakin kurus dan memanjang bentuknya, semakin kecil nilainya. Pada citra digital, nilai bisa sedikit menyimpang karena keliling dihitung dari piksel.
Mengapa begitu? Lingkaran menutup luas terbesar untuk kelilingnya, jadi nilainya tepat 1. Satu angka ini sudah mampu membedakan bola dari pensil.
Pengaman if perimeter > 0 mencegah pembagian dengan nol.
Babak 10 — Menampilkan Hasil: BGR ke RGB dan Jebakan Garbage Collector
rgb = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) scale = min(420 / w, 450 / h) resized = cv2.resize(rgb, (disp_w, disp_h)) img_tk = ImageTk.PhotoImage(image=Image.fromarray(resized)) self.lbl_img.config(image=img_tk, text="") self.lbl_img.image = img_tk
GAMBAR 9 • Skala: satu faktor untuk lebar dan tinggi menjaga rasio aspek
Gambar asli (lebar 2x tinggi)
✔ Satu skala: rasio tetap
✘ Skala beda: gambar gepeng
- BGR ke RGB: PIL dan Tkinter mengharapkan RGB. Tanpa konversi, jeruk tampil kebiruan (lihat Gambar 4).
- min(420/w, 450/h): satu faktor skala;
max(1, ...)menjamin ukuran tidak pernah 0. - self.lbl_img.image = img_tk: baris yang tampak aneh ini krusial. Tkinter tidak menyimpan referensi gambar; tanpa atribut ini Python membuangnya dan label menjadi kosong.
Babak 11 — Bedah Kritis: Apa yang Perlu Diperbaiki?
Dosen yang baik tidak hanya menjelaskan kode yang benar, tetapi juga menunjukkan celahnya.
GAMBAR 10 • Masalah #1: GLCM dihitung di seluruh gambar, bukan hanya objek
Kode sekarang
area merah (seluruh gambar) dihitung
Perbaikan
hitung hanya di dalam kontur hijau
1. GLCM dihitung di seluruh gambar, latar ikut memengaruhi contrast dan energy. Solusi: gunakan mask kontur atau crop bounding box.
2. Threshold tetap 200 hanya cocok untuk latar terang. Solusi: Otsu (cv2.THRESH_OTSU) atau adaptive threshold.
3. "Jumlah Objek" ikut menghitung noise: bintik kecil dianggap objek. Solusi: saring kontur dengan batas luas minimum.
4. Satu sudut saja (0°). Serat vertikal akan terlewat. Solusi: angles=[0, np.pi/4, np.pi/2, 3*np.pi/4] lalu rata-ratakan.
5. levels=256 itu berat: matriks 256x256 per sudut. Praktik umum: kuantisasi ke 16 atau 32 level.
6. Tidak ada penanganan error: bila cv2.imread gagal (misalnya path non-ASCII di Windows), hasilnya None dan program crash di cvtColor. Tambahkan if self.cv_img is None.
Babak 12 — Eksperimen: Uji Sendiri Hipotesis Anda
Jalankan program dengan beberapa citra, catat angkanya, lalu jawab berdasarkan data, bukan firasat.
Eksperimen 1: bola, kotak, pensil di atas kertas putih. Urutkan berdasarkan kebundaran. Sesuai dugaan?
Eksperimen 2: bandingkan kain polos dan kain batik. Ciri GLCM mana yang paling berbeda? Mengapa?
Eksperimen 3: ganti ambang 200 menjadi 120 dan 240. Apa yang terjadi pada jumlah objek dan luas?
Eksperimen 4: putar gambar bertekstur garis 90°. Apakah nilai GLCM berubah? Kaitkan dengan Gambar 6 dan parameter angles.
Tantangan lanjut: terapkan perbaikan nomor 1 sampai 4 pada Babak 11, lalu bandingkan hasil sebelum dan sesudah.
🎉 Penutup
Hari ini kita belajar bahwa "melihat" bagi komputer adalah mengukur. Warna, bentuk, dan tekstur adalah tiga kalimat dalam deskripsi sebuah benda, dan vektor ciri itulah bahasa yang dipahami algoritma klasifikasi.
Tantangan Anda: jangan hanya menjalankan kode ini. Rusak, ubah, dan perbaiki, sampai Anda bisa menjelaskan setiap angka di layar.