Gambar digital pada dasarnya adalah kumpulan angka yang tersusun rapi, sehingga array NumPy menjadi wadah alami untuk menyimpan dan mengolahnya. Topik ini membahas bentuk array gambar grayscale dan RGB, dtype uint8 beserta rentang nilainya, posisi kanal warna sebagai axis ketiga, serta varian float dengan rentang 0.0 sampai 1.0 yang lazim dipakai pada pemrosesan gambar. Seluruh contoh memakai array sintetis berukuran kecil agar setiap keluaran dapat direproduksi, dan topik ditutup dengan uji coba menyimpan serta memuat kembali berkas PNG.
Gambar grayscale sebagai array dua dimensi#
Gambar grayscale, yaitu gambar hitam putih tanpa informasi warna, direpresentasikan sebagai array dua dimensi dengan shape (tinggi, lebar). Setiap elemen array adalah satu piksel (pixel), yaitu titik terkecil penyusun gambar, dan nilainya menyatakan derajat terang titik tersebut. Nilai 0 berarti hitam penuh, nilai 255 berarti putih penuh, dan nilai di antaranya adalah gradasi abu-abu. Tipe data yang lazim adalah uint8 karena tipe itu menampung tepat 256 nilai dengan 1 byte per piksel, sehingga representasinya sama persis dengan cara sebagian besar format berkas menyimpan gambar.
Array gambar tidak harus berasal dari berkas. Untuk pembelajaran, array sintetis berukuran kecil lebih praktis karena isinya dapat dilihat seluruhnya dan keluarannya pasti sama di setiap eksekusi. Kode berikut membuat gambar gradien 5x4, kemudian memeriksa atribut-atribut pokoknya.
import numpy as np
gradasi = np.arange(20, dtype=np.uint8).reshape(5, 4)
print(gradasi)
print(gradasi.shape, gradasi.dtype)
print(gradasi.min(), gradasi.max())
print(gradasi[2, 3])[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]
[16 17 18 19]]
(5, 4) uint8
0 19
11Keluaran memperlihatkan array 5x4 dengan 20 piksel bernilai 0 sampai 19, dtype uint8, serta nilai minimum 0 dan maksimum 19. Ingat bahwa dimensi pertama adalah baris dan dimensi kedua adalah kolom, sehingga gradasi[2, 3] berarti baris indeks 2, kolom indeks 3, dan menghasilkan 11. Urutan ini sama dengan aturan [baris, kolom] pada pengindeksan 2D umum, tetapi sering terbalik dilakukan karena kebiasaan menyebut koordinat x terlebih dahulu.
Kanal warna sebagai axis ketiga#
Gambar berwarna menambahkan satu dimensi lagi pada arraynya. Representasi RGB menyusun setiap piksel sebagai campuran tiga kanal (channel), yaitu merah, hijau, dan biru, sehingga arraynya berdimensi tiga dengan shape (tinggi, lebar, 3). Axis ketiga inilah yang menyimpan kanal warna, dan satu piksel berwarna kini berupa tiga nilai sekaligus, bukan satu nilai. Gambar berikut memetakan hubungan indeks baris, kolom, dan kanal pada kedua jenis array gambar.
Kode berikut membentuk gambar RGB 4x4 dari satu array grayscale yang digandakan menjadi tiga kanal dengan komposisi berbeda.
import numpy as np
abu = np.arange(16, dtype=np.uint8).reshape(4, 4)
rgb = np.dstack((abu, abu * 2, abu * 4))
print(rgb.shape, rgb.dtype)
print(rgb[1, 2])
print(rgb[:, :, 0])(4, 4, 3) uint8
[ 6 12 24]
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]Array rgb memiliki shape (4, 4, 3), dan rgb[1, 2] menghasilkan tiga nilai sekaligus, yaitu [ 6 12 24], karena piksel tersebut dibaca pada ketiga kanalnya. Nilainya bertingkat karena komponen hijau dihitung dua kali lipat dan biru empat kali lipat dari komponen merahnya. Sementara itu, rgb[:, :, 0] mengambil seluruh baris dan kolom pada kanal pertama saja, sehingga hasilnya kembali menjadi array dua dimensi yang mewakili komponen merah. Slice semacam ini merupakan view atas rgb, sebagaimana aturan umum slicing pada NumPy, sehingga perubahan pada hasilnya juga mengubah rgb.
Rentang nilai uint8 dan varian float#
Konvensi 0 sampai 255 pada uint8 mendominasi penyimpanan berkas karena hemat memori dan sesuai dengan spesifikasi format yang umum. Namun, banyak pustaka pemrosesan gambar bekerja dengan konvensi kedua, yaitu float dengan rentang 0.0 sampai 1.0. Konversinya cukup membagi array dengan 255. Alasan konvensi float dipakai pada pemrosesan adalah aman terhadap overflow, karena hasil perkalian dan pembagian antar nilai pecahan tidak akan keluar dari jangkauan float64, sedangkan aritmetika uint8 dapat melipat nilai diam-diam. Kunci bermain aman adalah mengetahui konvensi mana yang sedang dipegang, dan itu selalu bisa diperiksa dari dtype serta nilai maksimalnya.
import numpy as np
abu = np.arange(16, dtype=np.uint8).reshape(4, 4)
abu_float = abu / 255
print(abu_float.dtype)
print(abu_float[0, 0], abu_float[1, 2])
print(abu_float.min(), abu_float.max())float64
0.0 0.023529411764705882
0.0 0.058823529411764705Pembagian dengan 255 menghasilkan array float64, dan piksel bernilai 6 kini menjadi 0.023529411764705882, yaitu 6/255. Rasio antar piksel tidak berubah, hanya skalanya yang bergeser dari 0-255 menjadi 0.0-1.0.
Menyimpan dan memuat kembali gambar kecil#
Alur kerja dengan berkas nyata mencakup dua arah, yaitu menyimpan array menjadi berkas gambar dan memuat berkas kembali menjadi array. Pustaka imageio membaca berkas gambar dan mengembalikan ndarray, sedangkan Pillow menyediakan penyimpanan berkas dari ndarray. Contoh berikut membuat PNG kecil dari array sintetis dengan Image.fromarray, memuatnya kembali dengan imageio, membandingkan isinya, lalu membersihkan berkas sementara melalui tempfile.
import os
import tempfile
import imageio.v3 as iio
import numpy as np
from PIL import Image
pola = np.arange(48, dtype=np.uint8).reshape(6, 8) * 5
print(pola.shape, pola.dtype)
with tempfile.TemporaryDirectory() as folder:
berkas = os.path.join(folder, "pola.png")
Image.fromarray(pola).save(berkas)
muat = iio.imread(berkas)
print(muat.shape, muat.dtype)
print(np.array_equal(pola, muat))(6, 8) uint8
(6, 8) uint8
TrueArray yang disimpan dan array yang dimuat memiliki shape, dtype, dan isi yang identik, ditandai oleh True dari np.array_equal, sehingga dua arah konversinya tidak menghilangkan data. Pemeriksaan kesetaraan semacam ini menjadi kebiasaan yang berguna saat mencoba pustaka pemuatan baru, karena pustaka gambar di ekosistem Python memiliki kebiasaan keluaran yang berbeda. Sebagian pustaka mengembalikan ndarray, sedangkan sebagian lainnya mengembalikan objek gambar pustakanya sendiri, sehingga imageio dipakai di sini karena keluarannya berupa ndarray yang langsung siap diolah.
Untuk berkas foto sungguhan, polanya sama saja, yaitu memanggil fungsi pemuatan dan mendapatkan ndarray.
# tidak-dijalankan
import imageio.v3 as iio
foto = iio.imread("foto.jpg")
print(foto.shape, foto.dtype)Keluaran yang diharapkan dari pola di atas berupa shape bertingkat tiga untuk berkas JPEG berwarna, misalnya (1080, 1920, 3), dengan dtype uint8. Bentuk persisnya bergantung pada resolusi berkas yang dimuat.
Kesalahan umum#
Menganggap setiap gambar berwarna otomatis berdimensi tiga. Gambar grayscale yang dimuat tetap berbentuk (tinggi, lebar), dan kode yang memotong kanal ketiga akan gagal atau salah memproses. Perbaikannya adalah memeriksa foto.ndim lebih dahulu, kemudian menangani kasus dua dimensi dan tiga dimensi secara terpisah.
Mencampur konvensi rentang 0-255 dan 0.0-1.0 dalam satu alur. Menambahkan konstanta kecil ke array float 0.0-1.0 seolah-olah masih 0-255 menghasilkan nilai di atas 1.0 yang tampak putih penuh saat ditampilkan. Perbaikannya adalah mengunci satu konvensi per alur kerja, dan memeriksa dtype beserta max() pada awal maupun akhir pemrosesan.
Membalik urutan indeks menjadi [kolom, baris]. Pada gambar yang lebar dan tingginya berbeda, indeks terbalik dapat menunjuk piksel lain tanpa error apa pun, sehingga hasil olahan tidak terduga. Perbaikannya adalah mengingat bahwa axis 0 selalu baris, sehingga indeksnya ditulis [baris, kolom] atau [baris, kolom, kanal]. Kebiasaan yang membantu adalah menyusun indeks dalam variabel bernama jelas, misalnya b untuk baris dan k untuk kolom, sebelum digabung menjadi satu ekspresi pengindeksan.
Ringkasan#
- Gambar grayscale adalah array dua dimensi dengan shape (tinggi, lebar), sedangkan gambar RGB adalah array tiga dimensi dengan shape (tinggi, lebar, 3) dan kanal warna berada pada axis ketiga.
- dtype
uint8menyimpan satu piksel sebagai 1 byte dengan rentang 0 sampai 255, sedangkan konvensi float memakai rentang 0.0 sampai 1.0 yang diperoleh dengan membagi array dengan 255. - Inspeksi
shape,dtype,min(), danmax()sebelum mengolah menentukan konvensi rentang yang sedang dipakai dan mencegah kesalahan arah indeks. imageiomengembalikan ndarray saat memuat berkas gambar, dan Pillow menyimpan ndarray sebagai berkas, sehingga array dan berkas saling dipertukarkan tanpa kehilangan data.
Referensi#
- imageio — Read the Docs — Dokumentasi pustaka imageio untuk memuat dan menyimpan berkas gambar sebagai array NumPy.
- Pillow documentation — Read the Docs
— Dokumentasi pustaka Pillow, termasuk
Image.fromarrayuntuk membentuk gambar dari ndarray. - NumPy: the absolute basics for beginners — NumPy — Panduan resmi dasar array NumPy, termasuk shape, dtype, dan pengindeksan berdimensi banyak.
- Data types — NumPy
— Rujukan resmi tipe data NumPy, termasuk
uint8dan keluarga tipe float.