5.4. Membaca dan menulis piksel¶
Sebagian besar operasi pada sebuah citra menyembunyikan pekerjaan per-pikselnya di dalam satu panggilan metode tunggal, di mana perulangan yang menyentuh setiap piksel berjalan pada kecepatan native. Namun ada kalanya kode aplikasi ingin menyentuh satu piksel tertentu secara langsung: untuk membaca apa yang ada di posisi tertentu, menulis nilai baru ke dalamnya, mengambil sampel satu titik untuk langkah kalibrasi, atau melakukan debug nilai di lokasi yang diketahui. Modul image mengekspos tingkat akses tersebut melalui dua bentuk pengalamatan, masing-masing cocok untuk cara berpikir yang berbeda tentang di mana sebuah piksel berada.
5.4.1. Pengalamatan berdasarkan koordinat¶
Bentuk yang paling alami adalah yang sudah dikembangkan kosakatanya oleh Koordinat: namai piksel berdasarkan Kartesius (x, y). get_pixel() menerima (x, y) dan mengembalikan nilai pada posisi tersebut; set_pixel() menerima (x, y) yang sama beserta nilai dan menulisnya.
Apa yang dikembalikan atau diterima oleh panggilan tersebut bergantung pada format citra. Citra skala abu-abu, biner, dan Bayer membawa satu nilai per piksel -- kecerahan untuk skala abu-abu, 0 atau 1 untuk biner, satu sampel saluran warna untuk Bayer -- sehingga get_pixel() mengembalikan satu integer. RGB565 membawa tiga saluran warna yang dikemas ke dalam 16 bit, dan get_pixel membongkar ketiganya menjadi tuple (r, g, b) secara default, dengan setiap saluran dipetakan ke rentang 0 -- 255.
Perilaku default dapat dibalik di salah satu ujungnya. Memberikan rgbtuple=False ke get_pixel pada citra RGB565 akan kembali ke kata 16-bit yang dikemas mentah -- bentuk yang sama yang dikembalikan oleh indeks linier, dan bentuk yang efisien ketika aplikasi akan menulis kembali nilai yang dikemas tersebut. Memberikan rgbtuple=True pada citra satu saluran melakukan kebalikannya: nilai yang tersimpan dikonversi menjadi tuple RGB888 sebelum dikembalikan, dengan citra Bayer melalui langkah debayer di tempat. Argumen ini ada agar kode pemanggil dapat meminta piksel dalam ruang warna yang seragam terlepas dari bagaimana citra yang mendasarinya menyimpannya.
Citra terkompresi -- JPEG dan PNG -- tidak didukung oleh get_pixel atau set_pixel. Byte-nya tidak merepresentasikan piksel pada posisi yang diketahui, dan metode ini akan memunculkan error daripada mengembalikan nilai yang tidak akan bermakna.
Dalam praktiknya pola-pola tersebut terlihat seperti:
v = img.get_pixel(40, 30) # grayscale: int 0..255
img.set_pixel(40, 30, 255) # write white
r, g, b = img.get_pixel(40, 30) # RGB565: defaults to (r, g, b) tuple
img.set_pixel(40, 30, (255, 0, 0)) # write red
Jika (x, y) yang diminta berada di luar citra, get_pixel mengembalikan None dan set_pixel tidak melakukan apa pun. Hal ini dirancang agar toleran: banyak algoritme berjalan mendekati tepi citra dan sesekali mengindeks posisi di luar jangkauan, dan no-op yang diam lebih tidak mengganggu daripada exception setiap kali hal itu terjadi.
5.4.2. Pengalamatan berdasarkan indeks linier¶
Bentuk lainnya adalah mengalamati piksel berdasarkan posisinya di dalam buffer yang mendasarinya. Ingat tata letak buffer: piksel disimpan baris demi baris, semua piksel baris atas terlebih dahulu, lalu semua piksel baris berikutnya, dan seterusnya hingga ke bawah. Susunan tersebut berarti setiap piksel memiliki indeks integer tunggal yang dihitung mulai dari 0 di kiri atas dan bertambah sepanjang setiap baris. Piksel pada koordinat (x, y) memiliki indeks linier y * width + x.
Piksel dialamatkan baik berdasarkan (x, y) Kartesius maupun berdasarkan indeks linier yang menelusuri buffer baris demi baris, dari kiri ke kanan.¶
Modul image mengekspos indeks tersebut melalui notasi subscript Python biasa: img[i] membaca piksel pada indeks linier i, img[i] = value menulisnya. Apa yang dikembalikan oleh bentuk indeks adalah nilai mentah yang tersimpan untuk format tersebut, bukan tuple yang dibongkar yang dikembalikan get_pixel() secara default. Perbedaan tersebut penting karena format yang dipilih sebelumnya menentukan tampilan nilai mentahnya:
Piksel skala abu-abu dan Bayer dikembalikan sebagai integer 8-bit.
Piksel RGB565 dan YUV422 dikembalikan sebagai integer 16-bit -- kata yang dikemas.
Piksel biner dikembalikan sebagai
0atau1.Piksel JPEG dan PNG dikembalikan sebagai integer 8-bit, satu byte per waktu dari aliran terkompresi. Nilai-nilai tersebut bersifat buram -- merupakan bagian dari encoding terkompresi bukan piksel dalam arti biasa.
Bentuk indeks cocok untuk kode yang sudah berpikir dalam istilah offset buffer: perulangan yang menelusuri setiap piksel sekali, algoritme yang perlu melompat satu baris sekaligus, atau sepotong kode yang menerjemahkan antara tata letak buffer. Kode yang berpikir dalam istilah koordinat x dan y lebih baik dilayani oleh get_pixel dan set_pixel; kedua bentuk ini mengalamati piksel yang sama melalui model mental yang berbeda.
Kelas Image juga dapat diiterasi. for v in img: menelusuri buffer dalam urutan row-major yang sama, menghasilkan nilai mentah satu piksel per waktu, dan len(img) adalah jumlah piksel untuk format tidak terkompresi atau jumlah byte untuk aliran terkompresi.
5.4.3. Mengapa Python per-piksel adalah jalur lambat¶
Catatan praktis yang perlu diungkapkan dengan jujur. Menelusuri sebuah citra satu piksel per waktu dari Python itu lambat. Sebuah citra skala abu-abu 320 × 240 memiliki 76.800 piksel; memanggil get_pixel() pada masing-masingnya dalam perulangan for menjalankan jutaan instruksi bytecode MicroPython untuk melakukan pekerjaan yang bisa diselesaikan oleh metode native yang setara dalam beberapa ratus mikrodetik. Itu bukan faktor kecil. Itulah perbedaan antara skrip yang memproses bingkai secara real time dan skrip yang merangkak jauh di bawah frame rate kamera.
Hampir setiap metode pada antarmuka Image ada karena terdapat versi native yang lebih cepat dari pola per-piksel yang umum. Perulangan yang menjumlahkan dua citra menjadi satu panggilan native tunggal. Perulangan yang menghaluskan setiap piksel dengan merata-ratakannya dengan tetangganya menjadi yang lainnya. Perulangan yang mengklasifikasikan setiap piksel terhadap ambang batas menjadi yang ketiga. Tugas aplikasi, sebagian besar waktu, adalah mengenali metode citra-utuh mana yang cocok dengan pekerjaan yang akan dilakukan perulangan tersebut, dan menggunakannya daripada menulis perulangan secara manual.
Baca dan tulis pada tingkat piksel masih merupakan alat yang tepat ketika tidak ada cara lain yang cocok -- menambal pengukuran tertentu kembali ke buffer, mengambil sampel satu posisi untuk langkah kalibrasi, melakukan debug nilai di lokasi yang diketahui. Intinya adalah bahwa keduanya merupakan jalur lambat, digunakan ketika metode citra-utuh tidak memiliki bentuk yang dibutuhkan aplikasi, bukan sebagai cara default untuk beroperasi pada piksel.