Langsung ke konten utama

Implementasi Arsitektur U-Net untuk Segmentasi Citra Presisi Tinggi pada Domain Medis dan Visual

U-Net adalah arsitektur encoder–decoder dengan skip connections yang sangat efektif untuk segmentasi piksel-tingkat. Di domain medis, U-Net unggul pada data terbatas dan ketidakseimbangan kelas. Di domain visual umum (jalan, bangunan, tanaman, bagian produk), U-Net memberikan baseline yang kuat dan mudah diproduksikan. Artikel ini memandu Anda dari konsep, pilihan loss, metrik, augmentasi, hingga contoh kode PyTorch yang siap dimodifikasi.

Bayangkan Anda sedang melihat sebuah foto hitam-putih organ tubuh dari hasil CT scan. Bagi mata manusia, gambar itu terlihat rumit: ada jaringan, pembuluh darah, dan mungkin ada lesi kecil yang hampir tak terlihat. Segmentasi citra membantu “mewarnai” bagian-bagian penting itu secara otomatis—menandai mana yang organ sehat, mana yang perlu diawasi, hingga batas lesi yang presisi. Di ruang klinik, ini sangat krusial: dokter bisa mendiagnosis lebih cepat, merencanakan tindakan dengan lebih akurat, dan memantau perkembangan pasien dari waktu ke waktu, tanpa harus menelusuri tiap piksel secara manual.

Di luar dunia medis, manfaatnya sama nyata. Dari langit, citra satelit menangkap pola jalan, atap bangunan, dan hamparan lahan. Segmentasi citra memetakan semuanya secara piksel-per-piksel: jalan dipisahkan dari trotoar, sawah dari permukiman, tanaman sehat dari yang terserang hama. Di konstruksi, retakan mikro pada beton bisa disorot jelas meski tersembunyi dalam tekstur permukaan. Di pabrik, sistem inspeksi kualitas dapat “mengerti” mana bagian produk yang cacat hanya dari bentuk dan tepinya.

Intinya, segmentasi bukan sekadar mengenali objek “apa”, tetapi juga menunjukkan “di mana” tepatnya objek itu berada dan seberapa jauh batasnya. Presisi di level piksel inilah yang tidak diberikan oleh klasifikasi biasa (yang hanya mengatakan “ada kucing di gambar”) atau deteksi objek (yang memberi kotak kasar). Dengan segmentasi, kita mendapatkan peta detail yang siap dipakai untuk keputusan penting—dari meja operasi hingga dashboard kota pintar.

 

Gambar 1. Segmentasi semantik citra satelit (jalan, bangunan, vegetasi, air).

 

 Inti Arsitektur U-Net

Encoder itu seperti saat kita melihat sebuah kota dari peta skala besar. Kita bisa mengenali pola besar: garis jalan utama, area permukiman, kawasan industri, sungai yang membelah kota. Detail kecil—seperti lebar gang, posisi pagar, atau nomor rumah—belum terlihat. Di U-Net, tahap ini dilakukan lewat rangkaian konvolusi + aktivasi + normalisasi lalu pengecilan ukuran (downsampling). Tujuannya adalah merangkum informasi penting menjadi ringkas agar model “mengerti tema besarnya”: ini area organ hati, itu jaringan paru, yang ini latar belakang. Semakin ke bawah (semakin dalam lapisan), gambaran makin abstrak, tapi konteks makin kuat.

Setelah paham konteks, Decoder bekerja kebalikannya: kembali ke skala yang makin detail, mirip membuka denah. Kini kita bicara lokasi presisi: di mana pintu berada, bagaimana lebar koridor, di mana letak jendela. Secara teknis, ini dilakukan dengan upsampling (misalnya transposed convolution atau interpolasi) lalu konvolusi agar fitur yang tadinya “dipadatkan” bisa dikembalikan ke resolusi tinggi. Hasilnya, model bukan cuma tahu “ada organ A di gambar,” tetapi juga “tepatnya organ A berada di sini, garis tepinya di sini.”

Agar detail halus tidak hilang selama proses “mengecilkan lalu membesarkan” tadi, U-Net punya jembatan cepat bernama skip connections. Anggap ini sebagai catatan detail yang diambil dari peta skala menengah (encoder bagian awal) lalu diserahkan langsung ke tahap denah (decoder) yang selevel. Dengan begitu, ketika decoder menata ulang piksel beresolusi tinggi, ia punya “contekan” tepi, tekstur, dan pola halus yang mungkin sempat memudar. Inilah alasan U-Net terkenal tajam dalam menjaga batas objek—misalnya kontur tumor, tepi pembuluh darah, retakan tipis pada beton, atau garis jalan kecil di citra satelit.

Di ujung proses, Output U-Net bertugas menerjemahkan fitur menjadi label per piksel. Secara praktis, ada konvolusi 1×1 yang memetakan setiap piksel ke jumlah kelas yang diinginkan. Jika kasusnya biner (objek vs latar), aktivasi yang dipakai biasanya sigmoid; jika multi-kelas (organ berbeda, jenis infrastruktur berbeda), dipakai softmax agar tiap piksel memilih kelas yang paling cocok. Hasil akhirnya adalah peta segmentasi: peta yang menandai piksel mana termasuk organ/lesi/jalan/bangunan dan mana yang bukan.

Diringkas: encoder menguasai “apa” (konteks global), decoder menentukan “di mana” (lokasi presisi), skip connections memastikan detail halus tetap terbawa, dan output mengubah semua pemahaman itu menjadi label piksel yang siap dipakai—untuk diagnosis di klinik, pemetaan kota, inspeksi manufaktur, hingga pertanian presisi.

 

Gambar 2 (Header/Hero): Diagram arsitektur U-Net klasik

 

 

Variasi Populer U-Net — Kapan Dipakai & Tips Praktis

A.    U-Net++

Kapan dipakai:

  • Target berukuran kecil/halus (pembuluh retina, bronkus halus, retakan sempit).
  • Perlu batas objek lebih rapih dan stabil terhadap noise.

Kelebihan: Nested skip connections memperkaya konteks lintas resolusi → kontur lebih halus.
Kekurangan: Lebih berat (memori & waktu).
Tips:

  • Aktifkan deep supervision untuk konvergensi lebih cepat.
  • Gunakan Dice+CE atau Focal Tversky saat kelas minoritas.

B. Attention U-Net

Kapan dipakai:

  • Objek kecil di area besar (polip kecil, nodul paru, jalan tipis pada citra satelit).
  • Banyak latar belakang yang “mengganggu”.

Kelebihan: Attention gates menekan latar, memperkuat sinyal objek target.
Kekurangan: Overhead komputasi ringan–sedang.
Tips:

  • Tambahkan weight decay kecil (AdamW) untuk stabilitas.
  • Coba threshold tuning saat inferensi (0.3–0.7) untuk F1/Dice optimal.

C. Residual / Dense U-Net

Kapan dipakai:

  • Jaringan ingin dibuat lebih dalam tanpa kehilangan gradien.
  • Data beragam/kompleks (multi-pusat, variasi alat pemindai, pencahayaan berbeda).

Kelebihan: Gradien stabil, fitur lebih kaya (variasi tekstur/struktur).
Kekurangan: Parameter bertambah, perlu regularisasi baik.
Tips:

  • Residual untuk kedalaman moderat–tinggi; Dense jika ingin reuse fitur kuat.
  • Gunakan GroupNorm untuk mini-batch kecil (medis sering batch kecil).

D. nnU-Net (framework)

Kapan dipakai:

  • Starting point terkuat di medis tanpa banyak trial-and-error.
  • Ingin auto-config (patch size, spacing, augment, loss) berdasar data.

Kelebihan: Baseline sangat kuat; praktik terbaik sudah dibundel.
Kekurangan: Opsi kustom kadang lebih terbatas, pipeline cukup opiniated.
Tips:

  • Mulai dengan nnU-Net untuk baseline, lalu fine-tune loss/augment spesifik masalah.
  • Jaga konsistensi voxel spacing untuk kasus 3D.

E. UNet-Lite / Mobile U-Net

Kapan dipakai:

  • Perangkat terbatas (edge device, bedside ultrasound, drone).
  • Kebutuhan latensi rendah dengan RAM/GPU kecil.

Kelebihan: Jejak memori kecil, cepat.
Kekurangan: Akurasi bisa turun pada kontur rumit.
Tips:

  • Kombinasikan dengan tiling + overlap untuk citra besar.
  • Pertimbangkan post-process morfologi (open/close) untuk perbaiki tepi.

 Contoh Studi Kasus 

Studi kasus segmentasi citra medis dengan U-Net++ di mana kita mensimulasikan segmentasi struktur kecil/halus, seperti pembuluh darah retina atau retakan pada beton.

Rincian Studi Kasus:

  • Masalah yang diselesaikan:
    Segmentasi objek kecil dan halus, seperti garis tipis (misalnya pembuluh darah atau retakan mikro), yang memerlukan model untuk menangkap detail halus dengan presisi tinggi.
  • Dataset yang digunakan:
    Dataset sintetis, yang terdiri dari gambar hitam-putih dengan garis melengkung acak (disimulasikan untuk objek kecil seperti pembuluh darah atau retakan).
  • Arsitektur yang digunakan:
    U-Net++, yang merupakan pengembangan dari U-Net klasik dengan nested skip connections untuk memperbaiki detail objek kecil dan tepi.
  • Loss Function:
    Kombinasi Binary Cross-Entropy (BCE) dan Dice Loss untuk meningkatkan ketepatan segmen di area objek yang lebih kecil.

 

Dataset Sintetis

Untuk membuat data sintetis, kita akan membuat gambar 256x256 dengan beberapa garis acak (misalnya, untuk mensimulasikan pembuluh darah atau retakan) dan memberi label biner (0 untuk latar belakang, 1 untuk objek).

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import random
from torch.utils.data import Dataset, DataLoader
import matplotlib.pyplot as plt
from PIL import Image, ImageDraw
# Dataset Sintetis
class ThinVesselDataset(Dataset):
    def __init__(self, num_samples=100, img_size=256):
        self.num_samples = num_samples
        self.img_size = img_size
    def __len__(self):
        return self.num_samples
    def _random_curve(self, draw):
        x = random.randint(20, self.img_size - 20)
        y = random.randint(20, self.img_size - 20)
        dx = random.randint(10, 30)
        for i in range(random.randint(3, 7)):
            x = min(self.img_size-1, x + dx + random.randint(-5, 5))
            y = min(self.img_size-1, max(0, y + random.randint(-10, 10)))
            draw.line([x-5, y, x+5, y], fill=255, width=2)
    def __getitem__(self, idx):
        img = np.zeros((self.img_size, self.img_size), dtype=np.float32)  # background
        mask = np.zeros((self.img_size, self.img_size), dtype=np.uint8)  # mask kosong
        # Create random curves (simulating thin vessels or cracks)
        img_pil = Image.fromarray(img)
        mask_pil = Image.fromarray(mask)
        draw_img = ImageDraw.Draw(img_pil)
        draw_mask = ImageDraw.Draw(mask_pil)
        # Drawing 3-7 random curves (vessels)
        for _ in range(random.randint(3, 7)):
            self._random_curve(draw_img)
            self._random_curve(draw_mask)
        img = np.array(img_pil)
        mask = np.array(mask_pil)
        img = torch.tensor(img).unsqueeze(0).float() / 255.0  # Normalize to [0, 1]
        mask = torch.tensor(mask).unsqueeze(0).float()
        return img, mask
# Loader dataset
train_dataset = ThinVesselDataset(num_samples=100)
train_loader = DataLoader(train_dataset, batch_size=4, shuffle=True)
# Visualisasi sampel data
img, msk = train_dataset[0]
plt.subplot(1, 2, 1); plt.imshow(img[0], cmap='gray'); plt.title("Image")
plt.subplot(1, 2, 2); plt.imshow(msk[0], cmap='gray'); plt.title("Mask")
plt.show()


 

Arsitektur U-Net++ yang Sederhana

Berikut adalah implementasi U-Net++ yang lebih sederhana. Kita akan menggunakan 2 level saja untuk memudahkan pemahaman, dengan skip connections dan up-sampling.

class SimpleUNetPlusPlus(nn.Module):
    def __init__(self, in_channels=1, out_channels=1, base_filters=16):
        super(SimpleUNetPlusPlus, self).__init__()
        
        # Encoder (down-sampling)
        self.enc1 = self.conv_block(in_channels, base_filters)
        self.enc2 = self.conv_block(base_filters, base_filters * 2)
        self.enc3 = self.conv_block(base_filters * 2, base_filters * 4)
        
        # Decoder (up-sampling)
        self.up2 = nn.ConvTranspose2d(base_filters * 2, base_filters, kernel_size=2, stride=2)
        self.up3 = nn.ConvTranspose2d(base_filters * 4, base_filters * 2, kernel_size=2, stride=2)
        
        # Skip Connections
        self.skip2 = self.conv_block(base_filters + base_filters * 2, base_filters)
        self.skip3 = self.conv_block(base_filters * 2 + base_filters * 4, base_filters * 2)
        
        # Output layer
        self.output = nn.Conv2d(base_filters, out_channels, kernel_size=1)
    
    def conv_block(self, in_channels, out_channels):
        return nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
            nn.ReLU(inplace=True)
        )
    
    def forward(self, x):
        # Encoder
        enc1 = self.enc1(x)
        enc2 = self.enc2(enc1)
        enc3 = self.enc3(enc2)
        
        # Decoder + skip connections
        up2 = self.up2(enc2)
        skip2 = self.skip2(torch.cat([enc1, up2], dim=1))  # Concatenate skip2
        
        up3 = self.up3(enc3)
        skip3 = self.skip3(torch.cat([enc2, up3], dim=1))  # Concatenate skip3
        
        # Final output
        out = self.output(skip3)
        
        return out

# Model instance
model = SimpleUNetPlusPlus(in_channels=1, out_channels=1, base_filters=16).to(device) 

 

Loss Function dan Optimizer

Untuk training, kita akan menggunakan Binary Cross-Entropy Loss yang digabung dengan Dice Loss untuk lebih memperhatikan ketidakseimbangan kelas.

 

 def dice_loss(pred, target, eps=1e-6):
    intersection = (pred * target).sum()
    return 1 - (2. * intersection + eps) / (pred.sum() + target.sum() + eps)

def combo_loss(pred, target):
    bce_loss = nn.BCEWithLogitsLoss()(pred, target)
    dce_loss = dice_loss(torch.sigmoid(pred), target)
    return bce_loss + dce_loss

# Optimizer
optimizer = optim.Adam(model.parameters(), lr=1e-3)

Training dan Evaluasi 

# Training loop
def train_one_epoch(model, dataloader, optimizer):
    model.train()
    running_loss = 0.0
    for img, msk in dataloader:
        img, msk = img.to(device), msk.to(device)
        
        optimizer.zero_grad()
        
        # Forward pass
        output = model(img)
        
        # Loss calculation
        loss = combo_loss(output, msk)
        
        # Backward pass
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
    
    return running_loss / len(dataloader)
# Training loop example
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
for epoch in range(10):
    train_loss = train_one_epoch(model, train_loader, optimizer)
    print(f'Epoch [{epoch+1}/10], Loss: {train_loss:.4f}')

Visualisasi Hasil Prediksi

Setelah training, kita dapat melakukan inferensi pada data dan menampilkan hasilnya.

 # Inferensi dan visualisasi
def infer_and_plot(model, dataloader):
    model.eval()
    with torch.no_grad():
        img, msk = next(iter(dataloader))
        img = img.to(device)
        
        # Prediksi
        output = model(img)
        output = torch.sigmoid(output)
        
        # Visualisasi hasil
        plt.figure(figsize=(12, 6))
        plt.subplot(1, 3, 1); plt.imshow(img[0].cpu().numpy()[0], cmap='gray'); plt.title("Input Image")
        plt.subplot(1, 3, 2); plt.imshow(msk[0].cpu().numpy()[0], cmap='gray'); plt.title("Ground Truth")
        plt.subplot(1, 3, 3); plt.imshow(output[0].cpu().numpy()[0], cmap='gray'); plt.title("Predicted Output")
        plt.show()

# Menampilkan hasil inferensi
infer_and_plot(model, train_loader)


Hasilnya

  

 Penjelasan

  • Dataset Sintetis: Dataset dibuat menggunakan gambar sederhana dengan garis melengkung untuk mensimulasikan pembuluh darah atau retakan.

  •  U-Net++ (sederhana): Hanya menggunakan dua level dan

  •  Loss Function: Menggunakan kombinasi antara

  •  Training & Evaluasi: Proses pelatihan dilakukan dengan optimasi menggunakan Adam dan menggabungkan kedua loss function di atas.

Komentar

Postingan populer dari blog ini

Komputasi Modern

Definisi Komputasi Komputasi sebetulnya bisa diartikan sebagai cara untuk menemukan pemecahan masalah dari data input dengan menggunakan suatu algoritma. Hal ini ialah apa yang disebut dengan teori komputasi, suatu sub-bidang dari ilmu komputer dan matematika. Selama ribuan tahun, perhitungan dan komputasi umumnya dilakukan dengan menggunakan pena dan kertas, atau kapur dan batu tulis, atau dikerjakan secara mental, kadang-kadang dengan bantuan suatu tabel. Namun sekarang, kebanyakan komputasi telah dilakukan dengan menggunakan komputer. Secara umum iIlmu komputasi adalah bidang ilmu yang mempunyai perhatian pada penyusunan model matematika dan teknik penyelesaian numerik serta penggunaan komputer untuk menganalisis dan memecahkan masalah-masalah ilmu (sains). Dalam penggunaan praktis, biasanya berupa penerapan simulasi komputer atau berbagai bentuk komputasi lainnya untuk menyelesaikan masalah-masalah dalam berbagai bidang keilmuan, tetapi dalam perkembangannya digunakan juga u...

Komputasi Dan Parallel Processing

Pengertian Komputasi Komputasi adalah algoritma yang digunakan untuk menemukan suatu cara dalam memecahkan masalah dari sebuah data input. Data input disini adalah sebuah masukan yang berasal dari luar lingkungan sistem. Komputasi ini merupakan bagian dari ilmu komputer berpadu dengan ilmu matematika. Secara umum ilmu komputasi adalah bidang ilmu yang mempunyai perhatian pada penyusunan model matematika dan teknik penyelesaian numerik serta penggunaan komputer untuk menganalisis dan memecahkan masalah-masalah ilmu (sains). Dalam penggunaan secara umum, biasanya berupa penerapan simulasi komputer atau berbagai bidang keilmuan, tetapi dalam perkembangannya digunakan juga untuk menemukan prinsip-prinsip baru yang mendasar terhadap bidang ilmu yang mendasari teori ini. Bidang ini berbeda dengan ilmu komputer (computer science), yang mengkaji komputasi, komputer dan pemrosesan informasi. Bidang ini juga berbeda dengan teori dan percobaan sebagai bentuk tradisional dari ilmu dan kerja ke...

DESAIN SKENARIO GAME

DEFINISI DESAIN Desain biasa diterjemahkan sebagai seni terapan, arsitektur, dan berbagai pencapaian kreatif lainnya. Dalam sebuah kalimat, kata "desain" bisa digunakan, baik sebagai kata benda maupun kata kerja. Sebagai kata kerja, "desain" memiliki arti "proses untuk membuat dan menciptakan obyek baru". Sebagai kata benda, "desain" digunakan untuk menyebut hasil akhir dari sebuah proses kreatif, baik itu berwujud sebuah rencana, proposal, atau berbentuk benda nyata.Proses desain pada umumnya memperhitungkan aspek fungsi, estetika, dan berbagai macam aspek lainnya dengan sumber data yang didapatkan dari riset, pemikiran, brainstorming, maupun dari desain yang sudah ada sebelumnya. Akhir-akhir ini, proses (secara umum) juga dianggap sebagai produk dari desain, sehingga muncul istilah "perancangan proses". Salah satu contoh dari perancangan proses adalah perancangan proses dalam industri kimia. Penggunaan istilah design atau desain ...