5.32. Menyimpan dan kompresi

Setiap halaman hingga saat ini bekerja dengan citra di kamera: ditangkap ke dalam buffer bingkai atau dialokasikan di heap MicroPython, dimanipulasi melalui metode-metode modul image, dan ditampilkan di pratinjau IDE atau diteruskan ke tahap berikutnya dalam skrip yang sama. Sebagian besar aplikasi pada suatu titik perlu melakukan kebalikannya: mengambil citra yang saat ini ada di RAM dan menyimpannya ke tempat yang persisten -- ke kartu SD, ke host USB, melalui jaringan -- di mana sesuatu selain kamera dapat membacanya.

Modul image mengekspos dua jalur untuk pekerjaan tersebut. Jalur save menulis citra ke file di filesystem, dengan format file dipilih berdasarkan ekstensi dan detail pengkodean ditangani oleh metode tersebut. Jalur to-format mengembalikan objek Image yang berisi aliran byte yang dikodekan, cocok untuk diteruskan ke panggilan streaming atau jaringan tanpa pernah menyentuh filesystem. Masing-masing cocok untuk aplikasi yang berbeda; keduanya dibangun di atas mesin kompresi yang sama di bawahnya.

5.32.1. Menyimpan ke file

save() menulis citra ke filesystem pada path tertentu:

img.save("/sdcard/capture.jpg")
img.save("/sdcard/capture.bmp")
img.save("/sdcard/region.jpg", roi=(40, 60, 200, 150), quality=85)

Format dipilih dari ekstensi file. Lima ekstensi dikenali: .bmp menulis bitmap Windows (lossless, tanpa kompresi, piksel yang ditangkap secara byte-per-byte); .pgm menulis portable graymap (lossless, hanya skala abu-abu); .ppm menulis portable pixmap (lossless, RGB); .jpg dan .jpeg keduanya menulis JPEG (lossy, terkompresi). Citra penerima sudah harus berada dalam format warna yang tepat untuk container yang dipilih -- citra warna yang disimpan sebagai .pgm adalah sebuah kesalahan.

roi membatasi penyimpanan ke sub-persegi panjang dari citra, seperti yang dilakukan kata kunci roi pada setiap metode modul image lainnya. Citra penuh adalah nilai default. Kata kunci ini diabaikan saat menyimpan citra terkompresi JPEG karena bentuk di disk sudah mencakup seluruh bingkai dan pengkodean ulang melalui pemotongan akan mengalahkan tujuan menyimpan byte terkompresi yang ada.

quality adalah kualitas kompresi JPEG dari 0 hingga 100 dan hanya bermakna ketika output adalah JPEG (kata kunci diabaikan untuk format lossless). Default 50 adalah keseimbangan yang tepat untuk sebagian besar aplikasi; 70 hingga 85 adalah kisaran untuk kualitas visual lebih tinggi, 30 hingga 50 adalah kisaran yang tepat untuk thumbnail kecil dan transmisi dengan keterbatasan bandwidth, dan 90 ke atas dicadangkan untuk kasus di mana citra akan diperiksa secara manual atau diproses melalui algoritma downstream yang sensitif terhadap artefak kompresi.

Citra penerima dikembalikan sehingga panggilan dapat dirantai: img.save("/sdcard/x.jpg").draw_string(0, 0, "saved"). Objek yang dikembalikan adalah citra yang sama di memori; penyimpanan adalah efek samping.

Penggunaan umum adalah pola capture-and-log. Sebuah pemicu menyala (sebuah blob terdeteksi, tombol ditekan, timer berakhir); skrip menangkap bingkai; menambahkan timestamp ke nama file; dan memanggil save() untuk mendorong citra ke kartu SD. Pratinjau IDE tetap berjalan, pemicu berikutnya menyala, dan file yang tersimpan terakumulasi.

5.32.2. Pengkodean ke memori

Ketika tujuan bukan filesystem melainkan koneksi jaringan, port serial, atau input modul lain, aplikasi membutuhkan aliran byte yang dikodekan di memori alih-alih di disk. to_jpeg() dan to_png() menghasilkan tepat itu:

encoded = img.to_jpeg(quality=80, copy=True)
bytes_to_send = encoded.bytearray()
sock.send(bytes_to_send)

Perilaku default adalah konversi di tempat: penerima dikonversi menjadi citra JPEG (atau PNG) dan objek yang sama dikembalikan. Dengan copy=True konversi menulis ke objek heap yang baru dialokasikan; dengan copy_to_fb=True output mendarat di buffer bingkai. Pilihannya sama seperti yang ditawarkan metode konversi lainnya -- di tempat secara default, salin ketika original diperlukan setelahnya.

quality dan subsampling adalah kenop penyetelan JPEG yang sama yang diekspos oleh jalur save. subsampling memilih skema chroma-subsampling: image.JPEG_SUBSAMPLING_AUTO memilih yang terbaik untuk kualitas yang dipilih, image.JPEG_SUBSAMPLING_444 menjaga chroma pada resolusi penuh (file terbesar, akurasi warna terbaik), image.JPEG_SUBSAMPLING_422 dan image.JPEG_SUBSAMPLING_420 membagi dua resolusi chroma di sepanjang satu atau kedua sumbu (file lebih kecil, sedikit pelembutan warna yang tidak terlihat pada jarak tampilan normal). Default AUTO adalah pilihan yang tepat kecuali aplikasi memiliki kebutuhan khusus.

PNG melalui to_png() bersifat lossless tetapi lebih lambat untuk dikodekan dan menghasilkan file yang lebih besar dari JPEG untuk konten fotografi (konten fotografi dikompres dengan buruk di bawah skema prediksi PNG). Gunakan PNG ketika citra adalah line art, screenshot, atau berisi grafis berpinggiran tajam yang digambar di atas bingkai yang ditangkap -- pengkodean lossless mempertahankan tepi tajam yang akan dilembutkan oleh JPEG. Sebaliknya JPEG adalah default yang tepat.

Baik to_jpeg() maupun to_png() menerima kata kunci posisional gaya gambar dan skala yang sama seperti metode konversi lainnya -- x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint -- sehingga panggilan yang sama dapat mengkodekan versi sumber yang diskalakan, dipotong, atau dipetakan palet dalam satu langkah. compress() adalah ejaan warisan dari to_jpeg(); keduanya menerima argumen yang sama dan menghasilkan hasil yang sama.

5.32.3. Apa yang diperoleh dari kompresi

Angka-angka di balik pertukaran JPEG-versus-raw layak untuk ditelusuri sekali.

Sebuah bingkai RGB565 320x240 adalah 153.600 byte (satu bingkai yang ditangkap pada QVGA). Sebuah bingkai 640x480 adalah 614.400 byte; sebuah bingkai 1280x960 adalah 2.457.600 byte. Tidak ada yang terlalu besar dibandingkan tampilan desktop atau ponsel, tetapi cukup besar dalam konteks kamera yang hanya memiliki beberapa MB RAM total, kartu SD dengan bandwidth penulisan terbatas, dan tautan host yang biasanya berjalan melalui USB CDC, UART, atau modul nirkabel pada kecepatan sedang.

JPEG pada quality=50 biasanya mengompresi bingkai yang ditangkap secara fotografis sebesar 10x hingga 20x: bingkai 614 KB 640x480 itu menjadi aliran byte yang dikodekan sebesar 30 hingga 60 KB. Pada quality=85 kompresi turun menjadi 5x hingga 10x (60 hingga 120 KB untuk bingkai yang sama). Pada quality=10 -- penuh artefak tetapi masih dapat dikenali -- kompresi mencapai 30x hingga 50x (12 hingga 20 KB).

Angka-angka tersebut menentukan apa yang praktis untuk dilakukan dengan bingkai yang tersimpan. Jalur kartu SD yang mempertahankan 10 MB/s menangani 30 bingkai per detik konten VGA yang dikodekan JPEG quality=50 dengan ruang tersisa (sekitar 1 hingga 2 MB/s); menyimpan konten yang sama tanpa kompresi membutuhkan lebih dari 18 MB/s, melampaui apa yang dapat dipertahankan jalur filesystem kamera ke kartu. Host USB yang menarik bingkai yang dikodekan JPEG melalui CDC pada 1 MB/s menerima bingkai 30 hingga 60 KB pada sekitar 15 hingga 30 bingkai per detik; menarik bingkai mentah pada kecepatan yang sama hanya mendapatkan satu atau dua bingkai per detik.

Singkatnya: metode kompresi bukan sekadar kemudahan untuk menyimpan. Metode inilah yang membuat bingkai yang ditangkap dapat digunakan di luar kamera pada laju bingkai yang dipedulikan aplikasi. Memilih kompresi yang tepat -- kualitas JPEG 50 untuk pencatatan umum, 80 untuk pekerjaan berkualitas, PNG untuk penangkapan line-art -- adalah bagian dari pekerjaan rutin setiap aplikasi kamera non-trivial.