Membedah source code ekstraksi ciri warna, bentuk (kontur), geometri, dan tekstur GLCM

Membedah source code ekstraksi ciri warna, bentuk (kontur), geometri, dan tekstur GLCM

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

→

255
255
250
255
255
255
120
90
130
255
250
85
40
95
255
255
125
80
120
255
255
255
255
250
255

💻 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 variabel cv_img.
  • setup_ui membangun tampilan.
  • load_image membuka file lalu memanggil ekstraksi.
  • extract_features adalah jantung program.

GAMBAR 2 • Alur data dari file gambar sampai hasil

📁 Pilih file
load_image
→
🖼 imread
format BGR
→
🎨 Rata-rata B,G,R
📐 Threshold → Kontur
🧱 Grayscale → GLCM
→
📊 Hasil
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)

P09: EKSTRAKSI FITUR BENTUK (KONTUR) & CIRI TEKSTUR (GLCM)
Buka Citra Uji

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]

B (indeks 0)
40
G (indeks 1)
90
R (indeks 2)
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)

0
0
1
1
0
0
1
1
0
2
2
2
2
2
3
3

→

Matriks GLCM (baris i, kolom j)

 
j=0
j=1
j=2
j=3
i=0
4
2
1
0
i=1
2
4
0
0
i=2
1
0
6
1
i=3
0
0
1
2

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.