Menghitung bakteri pada foto mikroskop, memeriksa cacat cetakan dari kamera inline, mengukur luas tumor pada hasil scan — semuanya bermuara pada pekerjaan yang sama: citra digital hanyalah matriks angka, dan MATLAB adalah mesin matriks. Satu piksel grayscale menyimpan satu bilangan 0-255; satu citra 64x64 adalah 4096 bilangan yang siap dijumlah, dibandingkan, dan difilter dengan operasi array yang sudah kita kuasai. Chapter ini membangun citra sintetis, menyimpan dan memuatnya lewat imwrite/imread, lalu menjalankan empat operasi inti: konversi grayscale, thresholding, histogram, dan deteksi tepi Sobel.

  • Citra grayscale sebagai matriks uint8
  • Roundtrip berkas PNG dengan imwrite/imread
  • Konversi rgb2gray, threshold, dan histogram
  • Sobel manual dengan conv2 dan morfologi biner

Citra Digital adalah Matriks #

Aturan dasarnya: citra grayscale adalah matriks dua dimensi — img(i,j) menyimpan intensitas piksel baris i, kolom j. Kelas uint8 membatasi nilai ke 0-255 sehingga tiap piksel hanya memakan satu byte. Kita peragakan dengan citra sintetis 64x64 berisi dua kotak di atas latar gelap:

matlab
img = uint8(40*ones(64, 64));      % latar gelap
img(16:32, 40:56) = 200;           % kotak terang kanan-atas
img(40:56,  8:24) = 120;           % kotak abu kiri-bawah
fprintf('citra %dx%d, kelas %s, rentang %d s.d. %d\n', ...
        size(img,1), size(img,2), class(img), min(img(:)), max(img(:)));

Output yang Dihasilkan:

text
citra 64x64, kelas uint8, rentang 40 s.d. 200

Mari kita bedah baris-baris kode di atas:

  1. Baris 1 (uint8(40*ones(64,64))): ones membangun matriks double, 40* mengisi intensitas, uint8() memotong ke 0-255 — konversi dilakukan terakhir agar perkalian tidak terpotong pembulatan.
  2. Baris 2-3 (img(range) = nilai): indexing rentang menyalakan blok piksel — pengolahan citra di MATLAB tidak butuh loop, cukup slicing.
  3. Baris 4-5 (fprintf): min(img(:))/max(img(:)) meratakan citra jadi vektor lalu mencari ekstremanya — pola dari bab operasi array.
Kelas penyimpananRentangUkuran per pikselKegunaan umum
uint80 s.d. 2551 bytecitra standar, hemat memori
uint160 s.d. 655352 bytecitra medis, rentang dinamis lebar
doublebebas8 bytehasil perhitungan (bisa negatif)
logical0 / 11 bytemask biner hasil threshold

Siklus kerja nyata: simpan hasil antara sebagai berkas, muat kembali esok hari. Fungsi imwrite menulis matriks ke PNG, imread memuatnya kembali:

matlab
imwrite(img, 'citra_uji.png');
imgBaca = imread('citra_uji.png');
fprintf('roundtrip PNG identik: %d\n', isequal(imgBaca, img));

Output yang Dihasilkan:

text
roundtrip PNG identik: 1

Mekanisme di balik layar: PNG lossless, sehingga isequal mengembalikan 1 — 4096 piksel kembali tanpa satu bit berubah, kelas uint8 tetap. Di sesi interaktif, imshow(img) menampilkan citra; pada mode headless seperti contoh.m, hasil disimpan ke PNG lewat imwrite dan statistiknya dicatat lewat fprintf.

Konversi Grayscale #

Citra berwarna adalah matriks tiga dimensi M x N x 3 — satu bidang per kanal merah, hijau, biru. Konversi ke grayscale tidak memakai rata-rata polos, melainkan bobot luma sesuai kepekaan mata manusia:

matlab
rgb = zeros(64, 64, 3, 'uint8');
rgb(:,:,1) = 200;                  % kanal merah
rgb(:,:,2) = 100;                  % kanal hijau
rgb(:,:,3) = 50;                   % kanal biru
abu = rgb2gray(rgb);
fprintf('rgb2gray(200,100,50) = %d\n', abu(1,1));
fprintf('rata-rata kanal      = %d\n', round(mean(double(rgb(1,1,:)))));

Output yang Dihasilkan:

text
rgb2gray(200,100,50) = 124
rata-rata kanal      = 117

rgb2gray menghasilkan 124, tujuh poin di atas rata-rata kanal 117. Penyebabnya: bobot resmi 0.2989*R + 0.5870*G + 0.1140*B memberi kanal hijau porsi terbesar, sehingga piksel oranye-kemerahan jatuh sedikit lebih terang daripada rata-rata. Pilihan rumus konversi mengubah hasil — saat mereplikasi pipeline pihak lain, pastikan bobotnya sama.

Thresholding dan Histogram #

Ambang membagi citra menjadi dua kelas piksel dengan satu perbandingan — hasilnya mask logical yang bisa dihitung luasnya:

matlab
bw = img > 128;
fprintf('piksel > 128: %d dari %d (%.1f persen)\n', ...
        sum(bw(:)), numel(bw), 100*sum(bw(:))/numel(bw));

Output yang Dihasilkan:

text
piksel > 128: 289 dari 4096 (7.1 persen)

Mask 289 piksel persis menutup kotak terang 17x17 — sum(bw(:)) menggantikan penghitungan manual. Sebelum memilih ambang, kita lihat dulu sebaran intensitasnya lewat histogram:

matlab
pusatBin = [31.5 95.5 159.5 223.5];
n = hist(double(img(:)), pusatBin);
fprintf('histogram 4 bin: ');
fprintf('%d ', n);
fprintf('\n');

Output yang Dihasilkan:

text
histogram 4 bin: 3518 289 0 289 

Histogram tiga puncak menceritakan seluruhnya: 3518 piksel latar (intensitas 40), 289 kotak abu (120), 289 kotak terang (200). Ambang 128 jatuh di lembah antara dua kelompok terakhir — posisi ideal untuk pemisahan bersih.

Deteksi Tepi Sobel Manual #

Tepi adalah lokasi perubahan intensitas tajam — dalam bahasa matriks, gradien besar. Operator Sobel menghitungnya dengan dua kernel konvolusi; besaran gradien sqrt(gx.^2 + gy.^2) menjadi peta kekuatan tepi, dan konvolusi 2D ditangani conv2:

matlab
Gx = [-1 0 1; -2 0 2; -1 0 1];
Gy = Gx';
gx  = conv2(double(img), Gx, 'same');
gy  = conv2(double(img), Gy, 'same');
mag = sqrt(gx.^2 + gy.^2);
tepi = mag > 100;
tepi([1 end], :) = false;          % buang artefak tepi citra
tepi(:, [1 end]) = false;          % akibat padding nol conv2
fprintf('piksel tepi Sobel: %d\n', sum(tepi(:)));
imwrite(uint8(255*tepi), 'tepi_sobel.png');

Output yang Dihasilkan:

text
piksel tepi Sobel: 272
peta tepi tersimpan: tepi_sobel.png

Mari kita bedah baris-baris kode di atas:

  1. Baris 1-2 (Gx, Gy): kernel 3x3 horizontal dan vertikal; Gy = Gx' memutarnya 90 derajat tanpa menulis ulang.
  2. Baris 3-4 (conv2(..., 'same')): konvolusi citra-kernel dengan double(img) — uint8 akan melimpah pada penjumlahan berbobot, jadi konversi ke double lebih dulu.
  3. Baris 5 (mag): gradien dua arah digabung dengan Pythagoras sqrt(gx.^2 + gy.^2).
  4. Baris 7-8 (tepi([1 end], :) = false): dua baris terluar dinolkan — lihat peringatan di bawah.

Garis putih tipis mengelilingi kedua kotak pada peta tersimpan — kernel 3x3 menghasilkan tepi setebal dua piksel.

Morfologi Biner: Erosi dan Dilasi #

Mask biner dibersihkan dengan morfologi: erosi mengecilkan wilayah terang, dilasi membesarkannya, keduanya dikendalikan structuring element. Untuk elemen 3x3 penuh, keduanya menjadi penghitungan tetangga — dan conv2 kembali bekerja:

matlab
se = ones(3);
erosi  = conv2(double(bw), se, 'same') == 9;
dilasi = conv2(double(bw), se, 'same') >= 1;
fprintf('biner %d, erosi %d, dilasi %d piksel\n', ...
        sum(bw(:)), sum(erosi(:)), sum(dilasi(:)));

Output yang Dihasilkan:

text
biner 289, erosi 225, dilasi 361 piksel

Mekanismenya transparan: conv2 menghitung jumlah tetangga terang tiap piksel. Erosi menuntut seluruh sembilan tetangga terang (== 9) — kotak 17x17 menyusut menjadi 15x15 = 225; dilasi puas dengan satu tetangga (>= 1) — kotak mengembang menjadi 19x19 = 361. Aritmetika blok rapi ini adalah pintu masuk terbaik sebelum memakai imerode/imdilate di Image Processing Toolbox.

Berkas contoh.m menjalankan seluruh rantai itu, lalu membersihkan berkas demo.

Ringkasan #

Citra grayscale adalah matriks uint8 0-255; citra berwarna menambah dimensi ketiga untuk tiga kanal. imwrite/imread menutup siklus PNG lossless yang terverifikasi identik. rgb2gray memakai bobot luma (124, bukan rata-rata 117) — pilihan rumus mengubah hasil. Thresholding memanfaatkan lembah histogram untuk memisahkan objek dari latar, dan Sobel via conv2 menghasilkan 272 piksel tepi setelah artefak pinggir dibuang. Morfologi erosi-dilasi turun ke penghitungan tetangga: 289 → 225 saat tererosi, 361 saat terdilasi. Seluruh operasi hanya memakai alat inti — array, conv2, logika — tanpa toolbox.