Image Processing sekarang sudah menjadi salah satu bidang yang sedang banyak dibicarakan dengan berbagai aplikasi yang ditawarkan. Dalam image processing atau pengolahan citra, deep learning digunakan untuk memproses data citra dan mempelajari fitur-fitur penting dari citra atau gambar tersebut untuk melakukan berbagai tugas salah satu contohnya yaitu, segmentasi gambar.
Segmentasi gambar sangat penting dalam ekstraksi fitur dari sebuah gambar. Dengan segmentasi gambar, program dapat mengenal beberapa objek dalam suatu gambar. Segmentasi semantik adalah proses mengenali gambar dengan melabeli masing-masing jenis objek pada gambar. Semisal pada gambar di bawah ini, tanah dilabeli coklat, gedung dilabeli kuning, sedangkan orang dilabeli biru.
Pada artikel kali ini, kita akan membahas salah satu model segmentasi gambar yakni U-Net.
Original Paper : https://arxiv.org/pdf/1505.04597v1.pdf
U-NET
U-Net adalah arsitektur yang dibuat oleh Olaf Ronneberger, Philipp Fischer, dan Thomas Brox. Tujuan utama U-Net dibuat untuk melakukan segementasi gambar Biologi. Arsitektur ini memenangkan ISBI (International Symposium on Biomedical Imaging) pada 2015. U-Net dikhususkan untuk melakukan semantic segmentation.
Semantic Segmentation adalah teknik dalam pengolahan citra digital yang bertujuan untuk mengidentifikasi dan mengklasifikasikan setiap piksel dalam citra ke dalam kategori atau kelas yang berbeda. Tujuan utama dari semantic segmentation adalah memahami dan menganalisis struktur objek dalam citra, dengan menghasilkan sebuah peta label yang menunjukkan area dan batas-batas setiap objek yang ada.
Arsitektur U-NET
Dinamakan U-NET karena memiliki bentuk arsitektur seperti huruf “U”. Arsitektur U-NET memiliki 4 bagian berikut :
Encoder (Contracting Path)
Encoder merupakan bagian pertama dari arsitektur UNET yang bertanggung jawab untuk mengekstraksi fitur-fitur visual dari citra input. Tujuan utama encoder adalah mengurangi dimensi citra secara bertahap dan menghasilkan representasi fitur yang semakin abstrak. Encoder pada U-Net terletak pada bagian kiri arsitektur. Pada tahap encoder, citra input akan menjadi lebih kecil atau disebut downsampling.
Pada setiap lapisan konvolusi, jumlah filter yang digunakan meningkat secara bertahap. Hal ini memungkinkan encoder untuk mengekstraksi fitur-fitur yang semakin kompleks dan semakin abstrak seiring dengan perjalanan citra melalui arsitektur. Setiap tingkatan memiliki jumlah filter yang berbeda (64 , 128, 256, 512). Pada tiap tingkatan terdapat Convolutional Block yang berisikan Convolutional Layer dengan filter 3x3 dengan fungsi aktivasi ReLU (Rectified Linear Unit).
Proses pengurangan dimensi dalam encoder biasanya dilakukan dengan Max Pooling dengan filter 2x2. Tujuan dari operasi pengurangan dimensi ini adalah untuk memperkecil ukuran representasi fitur dan meningkatkan efisiensi komputasi.
Selama proses encoder, informasi spasial dan kontekstual semakin tajam. Fitur-fitur seperti tepi, sudut, tekstur, dan pola yang penting dalam citra semakin diwakili oleh representasi fitur ini. Output dari Max Pooling akan menjadi input pada Convolutional Block tingkatan selanjutnya dan akan disimpan pada Skip Connections.
Decoder (Expansive Path)
Decoder terletak pada bagian kanan arsitektur U-Net dan merupakan komponen penting dalam proses rekonstruksi dan segmentasi objek atau disebut upsampling. Tugas utama decoder adalah mengembalikan ukuran piksel pada peta fitur (feature maps) agar output dari arsitektur memiliki resolusi yang tinggi melalui proses yang disebut upsampling. Upsampling diperlukan karena pada tahap encoder, dimensi citra telah dikurangi secara bertahap.
Decoder pada U-Net memiliki struktur yang mirip tapi tidak sama dengan encoder, dengan tingkatan yang sesuai. Pada setiap tingkatan, decoder menggunakan lapisan Transposed Convolutional Layer dengan filter 2x2 untuk meningkatkan dimensi peta fitur. Pada setiap tingkatan decoder, jumlah filter pada lapisan transposed convolutional berkurang secara bertahap, dengan ukuran filter sebesar 512, 256, 128, dan 64. Input pada tiap tingkatan decoder berasal dari skip connection yang di-crop (dipotong) agar sesuai dengan ukuran peta fitur pada tingkatan decoder yang sama.
Selanjutnya, output decoder pada tingkatan sebelumnya dan hasil crop dari skip connection digabungkan (concatenate). Hal ini memungkinkan penggabungan informasi-fitur dari tingkatan encoder dan decoder, sehingga decoder dapat memanfaatkan informasi-fitur yang lebih banyak dan detail dalam proses rekonstruksi.
Skip Connections
Skip Connections yang menghubungkan lapisan-lapisan encoder dengan lapisan-lapisan decoder pada tingkatan yang sama. Skip connections memungkinkan informasi-fitur yang lebih rinci dan kontekstual dari lapisan-lapisan encoder lebih mudah ditransfer ke lapisan-lapisan decoder, membantu dalam rekonstruksi resolusi spasial yang lebih baik dan mempertahankan informasi-detail yang penting selama proses decode. Skip Connections dibutuhkan karena pada saat encoder/downsampling terdapat informasi yang terbuang.
Bridge
Bridge merupakan bagian tengah pada arsitektur U-Net yang menjadi penghubung antar encoder dan decoder. Bridge berisikan Convolutional Layer dengan filter 3x3 dan jumlah filter 1024.
Code
Setelah kami jelaskan mengenai arsitekturnya, sekarang kami akan memberikan contoh codenya. Code yang akan kami berikan merupakan code untuk Vanilla U-Net atau code U-Net yang sesuai dengan paper.
Jika ingin U-Net melakukan prediksi dengan lebih baik dapat menambahkan BatchNormalization setelah Convolutional Layer. Output dari arsitektur Vannila U-Net ini akan lebih kecil dimensinya dibandingkan dengan inputnya, namun jika ingin menghasilkan output yang sama dengan input maka padding diubah menjadi “same”. Code U-Net dengan menggunakan padding “same” dan BatchNormalization dapat ditemukan disini.
Perlu untuk import framework TensorFlow, jumlah filter yang digunakan pada arsitektur U-Net yaitu [64, 128, 256, 512], dan menginisalisasikan Input dimensi dari gambar. List skip berfungsi untuk menyimpan tensor skip connection.
import tensorflow as tf
num_filters = [64, 128, 256, 512]
skip = []
input = tf.keras.Input(shape=(572, 572, 3))
Encoder Code
Code dibawah ini merupakan implementasi dari :
Vanilla U-Net menggunakan Conv2D dengan padding “valid” , sehingga dimensi outputnya akan berkurang.
conv = tf.keras.layers.Conv2D(num_filters[0], 3, padding="valid", activation='relu')(input)
conv = tf.keras.layers.Conv2D(num_filters[0], 3, padding="valid", activation='relu')(conv)
skip.append(conv) # simpan ke list skip
print("ConvBlock [1] Shape : ", conv.shape)
print("Skip Connection [1] Shape : ", skip[0].shape)
maxpool = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv)
print("Max Pooling [1] Shape : ", maxpool.shape)
ConvBlock [1] Shape : (None, 568, 568, 64)
Skip Connection [1] Shape : (None, 568, 568, 64)
Max Pooling [1] Shape : (None, 284, 284, 64)
Code dibawah ini merupakan implementasi dari :
conv = tf.keras.layers.Conv2D(num_filters[1], 3, padding="valid", activation='relu')(maxpool)
conv = tf.keras.layers.Conv2D(num_filters[1], 3, padding="valid", activation='relu')(conv)
skip.append(conv)
print("ConvBlock [2] Shape : ", conv.shape)
print("Skip Connection [2] Shape : ", skip[1].shape)
maxpool = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv)
print("Max Pooling [2] Shape : ", maxpool.shape)
ConvBlock [2] Shape : (None, 280, 280, 128)
Skip Connection [2] Shape : (None, 280, 280, 128)
Max Pooling [2] Shape : (None, 140, 140, 128)
Code dibawah ini merupakan implementasi dari :
conv = tf.keras.layers.Conv2D(num_filters[2], 3, padding="valid", activation='relu')(maxpool)
conv = tf.keras.layers.Conv2D(num_filters[2], 3, padding="valid", activation='relu')(conv)
skip.append(conv)
print("ConvBlock [3] Shape : ", conv.shape)
print("Skip Connection[3] Shape : ", skip[2].shape)
maxpool = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv)
print("Max Pooling [3] Shape : ", maxpool.shape)
ConvBlock [3] Shape : (None, 136, 136, 256)
Skip Connection[3] Shape : (None, 136, 136, 256)
Max Pooling [3] Shape : (None, 68, 68, 256)
Code dibawah ini merupakan implementasi dari :
conv = tf.keras.layers.Conv2D(num_filters[3], 3, padding="valid", activation='relu')(maxpool)
conv = tf.keras.layers.Conv2D(num_filters[3], 3, padding="valid", activation='relu')(conv)
skip.append(conv)
print("ConvBlock [4] Shape : ", conv.shape)
print("Skip Connection[4] Shape : ", skip[3].shape)
maxpool = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv)
print("Max Pooling [4] Shape : ", maxpool.shape)
ConvBlock [4] Shape : (None, 64, 64, 512)
Skip Connection[4] Shape : (None, 64, 64, 512)
Max Pooling [4] Shape : (None, 32, 32, 512)
Bridge Code
Code dibawah ini merupakan implementasi dari :
bridge = tf.keras.layers.Conv2D(1024, 3, padding="valid", activation='relu')(maxpool)
bridge = tf.keras.layers.Conv2D(1024, 3, padding="valid", activation='relu')(bridge)
print("\\nBridge Shape : ", conv.shape)
Bridge Shape : (None, 64, 64, 512)
Decoder Code
Pada tahap ini, skip connection di-crop dan di-concatenate dengan output dari up-conv. Dimensi crop yang akan digunakan terhadap skip connections disesuaikan dengan output decoder.
Code dibawah ini merupakan implementasi dari :
up_conv = tf.keras.layers.Conv2DTranspose(num_filters[-1], (2, 2), strides=(2, 2), padding='valid')(bridge)
print("\\nUp-Conv [1] Shape: ", up_conv.shape)
concat = tf.keras.layers.concatenate([up_conv, tf.keras.layers.Cropping2D(cropping=((4,4), (4,4)))(skip[-1])])
print("Concat [1] Shape: ", concat.shape)
decoder = tf.keras.layers.Conv2D(num_filters[-1], 3, padding="valid", activation='relu')(concat)
decoder = tf.keras.layers.Conv2D(num_filters[-1], 3, padding="valid", activation='relu')(decoder)
print("Decoder [1] Shape : ", decoder.shape)
Up-Conv [1] Shape: (None, 56, 56, 512)
Concat [1] Shape: (None, 56, 56, 1024)
Decoder [1] Shape : (None, 52, 52, 512)
Code dibawah ini merupakan implementasi dari :
up_conv = tf.keras.layers.Conv2DTranspose(num_filters[-2], (2, 2), strides=(2, 2), padding='valid')(decoder)
print("Up-Conv [2] Shape: ", up_conv.shape)
concat = tf.keras.layers.concatenate([up_conv,tf.keras.layers.Cropping2D(cropping=((16,16), (16,16)))(skip[-2])])
print("Concat [2] Shape: ", concat.shape)
decoder = tf.keras.layers.Conv2D(num_filters[-2], 3, padding="valid", activation='relu')(concat)
decoder = tf.keras.layers.Conv2D(num_filters[-2], 3, padding="valid", activation='relu')(decoder)
print("Decoder [2] Shape : ", decoder.shape)
Up-Conv [2] Shape: (None, 104, 104, 256)
Concat [2] Shape: (None, 104, 104, 512)
Decoder [2] Shape : (None, 100, 100, 256)
Code dibawah ini merupakan implementasi dari :
up_conv = tf.keras.layers.Conv2DTranspose(num_filters[-3], (2, 2), strides=(2, 2), padding='valid')(decoder)
print("Up-Conv [3] Shape: ", up_conv.shape)
concat = tf.keras.layers.concatenate([up_conv,tf.keras.layers.Cropping2D(cropping=((40,40), (40,40)))(skip[-3])])
print("Concat [3] Shape: ", concat.shape)
decoder = tf.keras.layers.Conv2D(num_filters[-3], 3, padding="valid", activation='relu')(concat)
decoder = tf.keras.layers.Conv2D(num_filters[-3], 3, padding="valid", activation='relu')(decoder)
print("Decoder [3] Shape : ", decoder.shape)
Up-Conv [3] Shape: (None, 200, 200, 128)
Concat [3] Shape: (None, 200, 200, 256)
Decoder [3] Shape : (None, 196, 196, 128)
Code dibawah ini merupakan implementasi dari :
up_conv = tf.keras.layers.Conv2DTranspose(num_filters[-4], (2, 2), strides=(2, 2), padding='valid')(decoder)
print("Up-Conv [4] Shape: ", up_conv.shape)
concat = tf.keras.layers.concatenate([up_conv,tf.keras.layers.Cropping2D(cropping=((88,88), (88,88)))(skip[-4])])
print("Concat [4] Shape: ", concat.shape)
decoder = tf.keras.layers.Conv2D(num_filters[-4], 3, padding="valid", activation='relu')(concat)
decoder = tf.keras.layers.Conv2D(num_filters[-4], 3, padding="valid", activation='relu')(decoder)
print("Decoder [4] Shape : ", decoder.shape)
# Fully Connected Layers
outputs = tf.keras.layers.Conv2D(2, 1, activation='sigmoid') (decoder)
print("Output Shape : ", outputs.shape)
Up-Conv [4] Shape: (None, 392, 392, 64)
Concat [4] Shape: (None, 392, 392, 128)
Decoder [4] Shape : (None, 388, 388, 64)
Output Shape : (None, 388, 388, 2)
Terdapat beberapa model deep learning yang dapat digunakan untuk segmentasi gambar, maka kami disini menjelaskan salah satu model yang dapat kalian pelajari yaitu U-Net, model yang efektif menjalani tugas segmentasi gambar.
Semoga penjelasan ini bermanfaat bagi kalian yang sedang mempelajari deep learning dan jangan sungkan memberikan kritik atau saran lewat kolom komentar, stay tune untuk review paper deep learning kita selanjutnya! See you 👋🏼!
Traktir kopi kami di Saweria ☕(tidak harus)