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
conv2dan 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:
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:
citra 64x64, kelas uint8, rentang 40 s.d. 200Mari kita bedah baris-baris kode di atas:
- Baris 1 (
uint8(40*ones(64,64))):onesmembangun matriks double,40*mengisi intensitas,uint8()memotong ke 0-255 — konversi dilakukan terakhir agar perkalian tidak terpotong pembulatan. - Baris 2-3 (
img(range) = nilai): indexing rentang menyalakan blok piksel — pengolahan citra di MATLAB tidak butuh loop, cukup slicing. - Baris 4-5 (
fprintf):min(img(:))/max(img(:))meratakan citra jadi vektor lalu mencari ekstremanya — pola dari bab operasi array.
| Kelas penyimpanan | Rentang | Ukuran per piksel | Kegunaan umum |
|---|---|---|---|
uint8 | 0 s.d. 255 | 1 byte | citra standar, hemat memori |
uint16 | 0 s.d. 65535 | 2 byte | citra medis, rentang dinamis lebar |
double | bebas | 8 byte | hasil perhitungan (bisa negatif) |
logical | 0 / 1 | 1 byte | mask biner hasil threshold |
Menulis dan Membaca Citra #
Siklus kerja nyata: simpan hasil antara sebagai berkas, muat kembali esok hari. Fungsi imwrite menulis matriks ke PNG, imread memuatnya kembali:
imwrite(img, 'citra_uji.png');
imgBaca = imread('citra_uji.png');
fprintf('roundtrip PNG identik: %d\n', isequal(imgBaca, img));Output yang Dihasilkan:
roundtrip PNG identik: 1Mekanisme 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:
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:
rgb2gray(200,100,50) = 124
rata-rata kanal = 117rgb2gray 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:
bw = img > 128;
fprintf('piksel > 128: %d dari %d (%.1f persen)\n', ...
sum(bw(:)), numel(bw), 100*sum(bw(:))/numel(bw));Output yang Dihasilkan:
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:
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:
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:
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:
piksel tepi Sobel: 272
peta tepi tersimpan: tepi_sobel.pngMari kita bedah baris-baris kode di atas:
- Baris 1-2 (
Gx,Gy): kernel 3x3 horizontal dan vertikal;Gy = Gx'memutarnya 90 derajat tanpa menulis ulang. - Baris 3-4 (
conv2(..., 'same')): konvolusi citra-kernel dengandouble(img)—uint8akan melimpah pada penjumlahan berbobot, jadi konversi kedoublelebih dulu. - Baris 5 (
mag): gradien dua arah digabung dengan Pythagorassqrt(gx.^2 + gy.^2). - 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:
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:
biner 289, erosi 225, dilasi 361 pikselMekanismenya 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.