Semantic segmentation memberi label kelas pada tiap piksel, tetapi dua kucing yang berdampingan tetap tercetak sebagai satu gumpalan “kucing”. Instance segmentation menuntaskan kekurangan itu: setiap objek individual mendapat mask dan identitas tersendiri, sehingga dua kucing berarti dua mask terpisah yang bisa dihitung, dilacak, dan diukur satu per satu. Pada chapter ini kita mulai dari solusi paling sederhana — connected components pada mask biner — lalu membuktikan lewat eksperimen NumPy di mana solusi itu runtuh, sebelum memahami mengapa Mask R-CNN dan mekanisme RoIAlign diperlukan.
Dari Kelas ke Objek Individual #
Perbedaan semantic dan instance terletak pada pertanyaan yang dijawab. Semantic segmentation menjawab “piksel ini termasuk kelas apa”; instance segmentation menjawab “piksel ini termasuk objek yang mana”. Gabungan keduanya disebut panoptic segmentation: seluruh piksel berlabel kelas (stuff seperti jalan dan langit sekaligus things seperti orang dan mobil) dan objek countable juga terpisah per instance. Tabel berikut memetakan posisi tiap tugas:
| Aspek | Semantic | Instance | Panoptic |
|---|---|---|---|
| Pertanyaan | kelas tiap piksel | objek tiap piksel | kelas + objek tiap piksel |
| Dua objek sekelas | satu mask gabungan | dua mask terpisah | dua mask terpisah + label kelas |
| Keluaran | mask berlabel (H, W) | daftar mask + skor | mask berlabel + id objek |
| Metrik | IoU per kelas, Dice | mask AP | panoptic quality (PQ) |
| Model contoh | U-Net, DeepLab | Mask R-CNN, YOLACT | Panoptic-DeepLab, MaskFormer |
Mask R-CNN dan RoIAlign #
Mask R-CNN memperluas detektor Faster R-CNN — yang kita bahas secara konsep pada chapter Object Detection — dengan satu cabang keluaran tambahan: untuk tiap kotak kandidat yang lolos, sebuah jaringan kecil memprediksi mask biner pada resolusi tetap (misalnya 28x28). Arsitektur menjadi tiga kepala sekaligus: kelas, kotak, dan mask, semuanya belajar dari backbone yang sama. Karena mask diprediksi per region of interest (RoI), dua objek yang saling tumpang tindih tetap menghasilkan dua mask terpisah — inilah jalan keluar dari keterbatasan connected components.
Komponen pentingnya bernama RoIAlign. Kotak kandidat dari proposal network jarang jatuh tepat pada koordinat bulat, sedangkan feature map punya resolusi diskrit. RoIPool versi lama membulatkan koordinat dan kehilangan sub-piksel; RoIAlign mengambil nilai feature map lewat interpolasi bilinear pada posisi persisnya, sehingga penyelarasan kotak ke mask tetap akurat — selisih beberapa piksel terasa besar ketika mask yang dinilai pakai IoU per objek.
Eksperimen: Connected Components Memisahkan Dua “Kucing” #
Deteksi instance paling sederhana pada mask biner adalah connected components: piksel bernilai 1 yang saling bersambung dikelompokkan menjadi satu instance. Berkas connected_components.py menempelkan dua pola “kucing” identik pada satu kanvas, memisahkannya dengan scipy.ndimage.label, lalu mengulang skenario ketika kedua objek saling menempel.
"""Memisahkan instance dengan connected components.
Alur: dua objek kelas sama (dua "kucing") pada satu mask semantik ->
scipy.ndimage.label memisah jadi 2 instance -> statistik per instance ->
skenario objek bersentuhan -> connected components gagal memisah ->
motivasi Mask R-CNN.
"""
import numpy as np
from scipy import ndimage
# Pola "kucing" sederhana 6 baris x 7 kolom (blok dengan telinga)
POLA = np.array(
[
[0, 1, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1, 0],
[0, 0, 1, 1, 1, 0, 0],
],
dtype=int,
)
H, W = 8, 17 # kanvas
def tempel(kanvas, pola, baris, kolom):
kanvas[baris: baris + pola.shape[0], kolom: kolom + pola.shape[1]] |= pola
def tampil(mask, simbol="#"):
return "\n".join(
"".join(simbol if v else "." for v in baris) for baris in mask
)
# ---------- 1. Dua kucing terpisah ----------
semantik = np.zeros((H, W), dtype=int)
tempel(semantik, POLA, 1, 1) # kucing A: baris 1-6, kolom 1-7
tempel(semantik, POLA, 1, 9) # kucing B: baris 1-6, kolom 9-15
print("mask semantik (dua objek kelas sama, satu simbol):")
print(tampil(semantik))
print()
label_objek, jumlah = ndimage.label(semantik)
print(f"connected components menemukan {jumlah} instance:")
print("\n".join(
"".join(str(v) if v else "." for v in baris) for baris in label_objek
))
print()
print("statistik per instance (indeks dimulai dari 1):")
for id_objek in range(1, jumlah + 1):
objek = label_objek == id_objek
ys, xs = np.where(objek)
print(f" instance {id_objek}: luas {int(objek.sum()):2d} piksel, "
f"kotak (y {ys.min()}-{ys.max()}, x {xs.min()}-{xs.max()}), "
f"centroid ({ys.mean():.1f}, {xs.mean():.1f})")
print()
# ---------- 2. Dua kucing bersentuhan ----------
menempel = np.zeros((H, W), dtype=int)
tempel(menempel, POLA, 1, 1) # kucing A: kolom 1-7
tempel(menempel, POLA, 1, 8) # kucing B digeser: kolom 8-14 (menempel)
print("skenario objek bersentuhan:")
print(tampil(menempel))
label2, jumlah2 = ndimage.label(menempel)
print(f"connected components menemukan {jumlah2} instance "
f"(seharusnya 2 kucing)")
print()
print("kesimpulan: komponen yang tersambung piksel demi piksel tidak bisa")
print("dipisah tanpa memahami bentuk objek -- di sinilah model seperti")
print("Mask R-CNN belajar memisahkan instance yang bahkan saling tumpang")
print("tindih.")Output yang Dihasilkan:
mask semantik (dua objek kelas sama, satu simbol):
.................
..#...#...#...#..
..#####...#####..
.#######.#######.
.#######.#######.
..#####...#####..
...###.....###...
.................
connected components menemukan 2 instance:
.................
..1...1...2...2..
..11111...22222..
.1111111.2222222.
.1111111.2222222.
..11111...22222..
...111.....222...
.................
statistik per instance (indeks dimulai dari 1):
instance 1: luas 29 piksel, kotak (y 1-6, x 1-7), centroid (3.6, 4.0)
instance 2: luas 29 piksel, kotak (y 1-6, x 9-15), centroid (3.6, 12.0)
skenario objek bersentuhan:
.................
..#...#..#...#...
..#####..#####...
.##############..
.##############..
..#####..#####...
...###....###....
.................
connected components menemukan 1 instance (seharusnya 2 kucing)
kesimpulan: komponen yang tersambung piksel demi piksel tidak bisa
dipisah tanpa memahami bentuk objek -- di sinilah model seperti
Mask R-CNN belajar memisahkan instance yang bahkan saling tumpang
tindih.Mari kita telaah kode di atas baris per baris:
- Baris 13-23 (
POLA): satu pola kucing 6x7 — telinga di baris pertama, badan melebar di tengah, menyempit di bawah — disimpan sebagai array biner yang bisa ditempel berulang. - Baris 28-29 (
tempel): operator|=menempel pola ke kanvas tanpa merusak tempelan lain; dua kucing sekelas menghasilkan satu mask semantik dengan simbol yang sama. - Baris 39-44: mask semantik menunjukkan dua bentuk kucing, tetapi tidak ada informasi mana kucing pertama dan mana kedua — persis keterbatasan semantic segmentation.
- Baris 47-51 (
ndimage.label): fungsi ini menomori tiap kelompok piksel bersambung. Dua kucing terpisah menghasilkan label 1 dan 2, tercetak jelas pada grid kedua. - Baris 54-60: statistik per instance dihitung dari koordinat pikselnya: luas 29 piksel untuk masing-masing, kotak pembatas, dan centroid — angka-angka yang hanya bermakna setelah instance terpisah.
- Baris 63-66: skenario kedua menggeser kucing B sehingga tepinya menyentuh kucing A — kedua pola kini bersambung pada baris 3-4.
- Baris 70-72:
ndimage.labelhanya menemukan 1 instance — komponen yang tersambung piksel demi piksel mustahil dipisah tanpa memahami bentuk.
Dengan tiap objek kini punya mask sendiri, pertanyaan alami berikutnya: bisakah kita membandingkan isi dua gambar tanpa label kelas sama sekali? Chapter Image Embedding
mengubah gambar menjadi vektor fitur lalu mencari kemiripannya lewat cosine similarity. Detail ndimage.label bisa dirujuk di dokumentasi scipy
, sementara Mask R-CNN siap pakai tersedia di torchvision
.