Pengantar Pemrosesan Gambar Python dalam Fotografi Komputasi

Diterbitkan: 2022-03-11

Fotografi komputasional adalah tentang meningkatkan proses fotografi dengan komputasi. Meskipun kita biasanya cenderung berpikir bahwa ini hanya berlaku untuk pasca-pemrosesan hasil akhir (mirip dengan pengeditan foto), kemungkinannya jauh lebih kaya karena komputasi dapat diaktifkan pada setiap langkah proses fotografi—mulai dengan pencahayaan pemandangan, dilanjutkan dengan lensa, dan akhirnya bahkan pada tampilan gambar yang diambil.

Ini penting karena memungkinkan untuk melakukan lebih banyak dan dengan cara yang berbeda dari apa yang dapat dicapai dengan kamera biasa. Hal ini juga penting karena jenis kamera yang paling umum saat ini—kamera ponsel—tidak terlalu kuat dibandingkan dengan saudara kandungnya yang lebih besar (DSLR), namun ia berhasil melakukan pekerjaan dengan baik dengan memanfaatkan daya komputasi yang tersedia di perangkat. .

Kita akan melihat dua contoh di mana komputasi dapat meningkatkan fotografi—lebih tepatnya, kita akan melihat bagaimana mengambil lebih banyak bidikan dan menggunakan sedikit Python untuk menggabungkannya dapat menciptakan hasil yang bagus dalam dua situasi di mana perangkat keras kamera seluler tidak benar-benar bersinar—cahaya rendah dan rentang dinamis tinggi.

Fotografi cahaya rendah

Katakanlah kita ingin mengambil foto pemandangan dengan cahaya rendah, tetapi kamera memiliki aperture (lensa) kecil dan waktu eksposur terbatas. Ini situasi khas untuk kamera ponsel yang, dengan pemandangan cahaya redup, dapat menghasilkan gambar seperti ini (diambil dengan kamera iPhone 6):

Gambar mainan pasangan di lingkungan dengan cahaya redup

Jika kita mencoba untuk meningkatkan kontras, hasilnya adalah sebagai berikut, yang juga cukup buruk:

Gambar yang sama seperti di atas, jauh lebih terang tetapi dengan gangguan visual yang mengganggu

Apa yang terjadi? Dari mana semua kebisingan ini berasal?

Jawabannya adalah kebisingan berasal dari sensor—perangkat yang mencoba menentukan kapan cahaya mengenainya dan seberapa kuat cahaya itu. Dalam cahaya rendah, bagaimanapun, ia harus meningkatkan sensitivitasnya dengan banyak untuk mendaftarkan apa pun, dan sensitivitas tinggi itu berarti ia juga mulai mendeteksi positif palsu—foton yang sebenarnya tidak ada. (Sebagai catatan, masalah ini tidak hanya memengaruhi perangkat, tetapi juga kita manusia: Lain kali Anda berada di ruangan gelap, luangkan waktu sejenak untuk memperhatikan kebisingan yang ada di bidang visual Anda.)

Sejumlah noise akan selalu ada dalam perangkat pencitraan; namun, jika sinyal (informasi yang berguna) memiliki intensitas tinggi, noise akan diabaikan (rasio sinyal terhadap noise tinggi). Saat sinyal rendah—seperti dalam cahaya redup—noise akan menonjol (sinyal rendah hingga noise).

Namun, kami dapat mengatasi masalah noise, bahkan dengan semua keterbatasan kamera, untuk mendapatkan bidikan yang lebih baik daripada yang di atas.

Untuk melakukan itu, kita perlu memperhitungkan apa yang terjadi dari waktu ke waktu: Sinyal akan tetap sama (adegan yang sama dan kita menganggapnya statis) sedangkan noise akan benar-benar acak. Artinya, jika kita mengambil banyak bidikan pemandangan, mereka akan memiliki versi noise yang berbeda, tetapi informasi berguna yang sama.

Jadi, jika kita rata-rata banyak gambar yang diambil dari waktu ke waktu, noise akan hilang sementara sinyal tidak terpengaruh.

Ilustrasi berikut menunjukkan contoh yang disederhanakan: Kami memiliki sinyal (segitiga) yang terpengaruh oleh noise, dan kami mencoba memulihkan sinyal dengan rata-rata beberapa contoh dari sinyal yang sama yang terpengaruh oleh noise yang berbeda.

Demonstrasi empat panel segitiga, gambar tersebar yang mewakili segitiga dengan noise tambahan, semacam segitiga bergerigi yang mewakili rata-rata 50 instance, dan rata-rata 1000 instance, yang terlihat hampir identik dengan segitiga aslinya.

Kami melihat bahwa, meskipun kebisingan cukup kuat untuk sepenuhnya mendistorsi sinyal dalam satu contoh, rata-rata secara bertahap mengurangi kebisingan dan kami memulihkan sinyal asli.

Mari kita lihat bagaimana prinsip ini berlaku untuk gambar: Pertama, kita perlu mengambil beberapa bidikan subjek dengan eksposur maksimum yang dimungkinkan oleh kamera. Untuk hasil terbaik, gunakan aplikasi yang memungkinkan pengambilan gambar manual. Penting bahwa bidikan diambil dari lokasi yang sama, jadi tripod (improvisasi) akan membantu.

Lebih banyak bidikan biasanya berarti kualitas yang lebih baik, tetapi jumlah pastinya tergantung pada situasinya: seberapa banyak cahaya yang ada, seberapa sensitif kamera, dll. Rentang yang bagus bisa berkisar antara 10 dan 100.

Setelah kita memiliki gambar-gambar ini (dalam format mentah jika memungkinkan), kita dapat membaca dan memprosesnya dengan Python.

Bagi mereka yang tidak terbiasa dengan pemrosesan gambar dengan Python, kita harus menyebutkan bahwa gambar direpresentasikan sebagai array 2D nilai byte (0-255)—yaitu, untuk gambar monokrom atau skala abu-abu. Gambar berwarna dapat dianggap sebagai satu set tiga gambar seperti itu, satu untuk setiap saluran warna (R, G, B), atau secara efektif array 3D yang diindeks oleh posisi vertikal, posisi horizontal, dan saluran warna (0, 1, 2) .

Kami akan menggunakan dua perpustakaan: NumPy (http://www.numpy.org/) dan OpenCV (https://opencv.org/). Yang pertama memungkinkan kita untuk melakukan perhitungan pada array dengan sangat efektif (dengan kode yang sangat pendek), sementara OpenCV menangani pembacaan/penulisan file gambar dalam kasus ini, tetapi jauh lebih mampu, menyediakan banyak prosedur grafik tingkat lanjut—beberapa di antaranya akan kita gunakan nanti di artikel.

 import os import numpy as np import cv2 folder = 'source_folder' # We get all the image files from the source folder files = list([os.path.join(folder, f) for f in os.listdir(folder)]) # We compute the average by adding up the images # Start from an explicitly set as floating point, in order to force the # conversion of the 8-bit values from the images, which would otherwise overflow average = cv2.imread(files[0]).astype(np.float) for file in files[1:]: image = cv2.imread(file) # NumPy adds two images element wise, so pixel by pixel / channel by channel average += image # Divide by count (again each pixel/channel is divided) average /= len(files) # Normalize the image, to spread the pixel intensities across 0..255 # This will brighten the image without losing information output = cv2.normalize(average, None, 0, 255, cv2.NORM_MINMAX) # Save the output cv2.imwrite('output.png', output)

Hasilnya (dengan kontras otomatis yang diterapkan) menunjukkan bahwa noise hilang, peningkatan yang sangat besar dari gambar aslinya.

Foto asli mainan, kali ini lebih cerah dan lebih jernih, dengan sedikit noise yang terlihat

Namun, kami masih melihat beberapa artefak aneh, seperti bingkai kehijauan dan pola seperti kisi. Kali ini, bukan suara acak, tapi suara pola tetap. Apa yang terjadi?

Sebuah close-up dari sudut kiri atas gambar di atas

Tampilan jarak dekat dari sudut kiri atas, menunjukkan bingkai hijau dan pola kisi

Sekali lagi, kita bisa menyalahkan sensor. Dalam hal ini, kita melihat bahwa bagian yang berbeda dari sensor bereaksi secara berbeda terhadap cahaya, menghasilkan pola yang terlihat. Beberapa elemen pola ini teratur dan kemungkinan besar terkait dengan substrat sensor (logam/silikon) dan bagaimana ia memantulkan/menyerap foton yang masuk. Elemen lain, seperti piksel putih, hanyalah piksel sensor yang rusak, yang dapat menjadi terlalu sensitif atau terlalu tidak sensitif terhadap cahaya.

Untungnya, ada cara untuk menghilangkan jenis kebisingan ini juga. Ini disebut pengurangan bingkai gelap.

Untuk melakukan itu, kita membutuhkan gambar dari noise pola itu sendiri, dan ini bisa diperoleh jika kita memotret kegelapan. Ya, benar—tutup saja lubang kamera dan ambil banyak gambar (katakanlah 100) dengan waktu pencahayaan dan nilai ISO maksimum, lalu proses seperti yang dijelaskan di atas.

Saat merata-ratakan banyak bingkai hitam (yang sebenarnya bukan hitam, karena noise acak) kita akan berakhir dengan noise pola tetap. Kita dapat menganggap noise tetap ini akan tetap konstan, jadi langkah ini hanya diperlukan sekali: Gambar yang dihasilkan dapat digunakan kembali untuk semua bidikan cahaya rendah di masa mendatang.

Berikut adalah bagaimana bagian kanan atas dari pola noise (kontras disesuaikan) terlihat seperti untuk iPhone 6:

Kebisingan pola untuk bagian bingkai yang ditampilkan pada gambar sebelumnya

Sekali lagi, kami melihat tekstur seperti kisi-kisi, dan bahkan apa yang tampak seperti piksel putih yang macet.

Setelah kita memiliki nilai noise bingkai gelap ini (dalam variabel average_noise ), kita cukup menguranginya dari bidikan kita sejauh ini, sebelum menormalkan:

 average -= average_noise output = cv2.normalize(average, None, 0, 255, cv2.NORM_MINMAX) cv2.imwrite('output.png', output)

Ini foto terakhir kami:

Satu lagi gambar foto, kali ini sama sekali tidak ada bukti diambil dalam cahaya redup

Rentang Dinamis Tinggi

Keterbatasan lain yang dimiliki kamera kecil (seluler) adalah rentang dinamisnya yang kecil, yang berarti rentang intensitas cahaya yang dapat menangkap detailnya agak kecil.

Dengan kata lain, kamera hanya mampu menangkap pita sempit dari intensitas cahaya dari suatu pemandangan; intensitas di bawah pita itu tampak sebagai hitam murni, sedangkan intensitas di atasnya tampak putih murni, dan detail apa pun hilang dari wilayah itu.

Namun, ada trik yang dapat digunakan kamera (atau fotografer)—yaitu menyesuaikan waktu pencahayaan (waktu sensor terpapar cahaya) untuk mengontrol jumlah total cahaya yang masuk ke sensor, secara efektif menggeser rentang ke atas atau ke bawah untuk menangkap rentang yang paling tepat untuk adegan tertentu.

Tapi ini adalah kompromi. Banyak detail yang gagal masuk ke dalam foto final. Dalam dua gambar di bawah ini, kita melihat pemandangan yang sama yang diambil dengan waktu pencahayaan berbeda: pencahayaan sangat singkat (1/1000 detik), pencahayaan sedang (1/50 detik), dan pencahayaan lama (1/4 detik).

Tiga versi dari gambar bunga yang sama, yang satu sangat gelap sehingga sebagian besar foto berwarna hitam, satu tampak normal, meskipun dengan pencahayaan yang sedikit kurang baik, dan yang ketiga dengan cahaya yang terlalu tinggi sehingga sulit untuk melihat bunga di latar depan

Seperti yang Anda lihat, tidak satu pun dari ketiga gambar yang dapat menangkap semua detail yang tersedia: Filamen lampu hanya terlihat pada bidikan pertama, dan beberapa detail bunga terlihat baik di tengah atau bidikan terakhir tetapi tidak keduanya.

Kabar baiknya adalah ada sesuatu yang bisa kita lakukan untuk itu, dan sekali lagi ini melibatkan pembuatan beberapa bidikan dengan sedikit kode Python.

Pendekatan yang akan kita ambil didasarkan pada karya Paul Debevec et al., yang menjelaskan metode dalam makalahnya di sini. Cara kerjanya seperti ini:

Pertama, memerlukan beberapa bidikan dari pemandangan yang sama (stasioner) tetapi dengan waktu pencahayaan yang berbeda. Sekali lagi, seperti pada kasus sebelumnya, kita membutuhkan tripod atau penyangga untuk memastikan kamera tidak bergerak sama sekali. Kami juga memerlukan aplikasi pemotretan manual (jika menggunakan ponsel) sehingga kami dapat mengontrol waktu pencahayaan dan mencegah penyesuaian otomatis kamera. Jumlah bidikan yang diperlukan bergantung pada rentang intensitas yang ada dalam gambar (dari tiga ke atas), dan waktu pencahayaan harus diberi jarak melintasi rentang tersebut sehingga detail yang ingin kami pertahankan terlihat jelas dalam setidaknya satu bidikan.

Selanjutnya, algoritma digunakan untuk merekonstruksi kurva respons kamera berdasarkan warna piksel yang sama pada waktu pencahayaan yang berbeda. Ini pada dasarnya memungkinkan kita membuat peta antara kecerahan pemandangan sebenarnya dari suatu titik, waktu pencahayaan, dan nilai yang akan dimiliki piksel terkait dalam gambar yang diambil. Kami akan menggunakan implementasi metode Debevec dari perpustakaan OpenCV.

 # Read all the files with OpenCV files = ['1.jpg', '2.jpg', '3.jpg', '4.jpg', '5.jpg'] images = list([cv2.imread(f) for f in files]) # Compute the exposure times in seconds exposures = np.float32([1. / t for t in [1000, 500, 100, 50, 10]]) # Compute the response curve calibration = cv2.createCalibrateDebevec() response = calibration.process(images, exposures)

Kurva respons terlihat seperti ini:

Grafik yang menampilkan kurva respons sebagai paparan piksel (log) di atas nilai piksel

Pada sumbu vertikal, kami memiliki efek kumulatif dari kecerahan pemandangan suatu titik dan waktu pencahayaan, sedangkan pada sumbu horizontal kami memiliki nilai (0 hingga 255 per saluran) piksel yang sesuai.

Kurva ini kemudian memungkinkan kita untuk melakukan operasi sebaliknya (yang merupakan langkah selanjutnya dalam proses)—dengan nilai piksel dan waktu pencahayaan, kita dapat menghitung kecerahan sebenarnya dari setiap titik dalam pemandangan. Nilai kecerahan ini disebut irradiance, dan mengukur jumlah energi cahaya yang jatuh pada satu unit area sensor. Berbeda dengan data gambar, ini diwakili menggunakan angka floating point karena mencerminkan rentang nilai yang jauh lebih luas (karenanya, rentang dinamis tinggi). Setelah kita memiliki gambar irradiance (gambar HDR), kita cukup menyimpannya:

 # Compute the HDR image merge = cv2.createMergeDebevec() hdr = merge.process(images, exposures, response) # Save it to disk cv2.imwrite('hdr_image.hdr', hdr)

Bagi kita yang cukup beruntung memiliki tampilan HDR (yang semakin umum), dimungkinkan untuk memvisualisasikan gambar ini secara langsung dengan segala kemegahannya. Sayangnya, standar HDR masih dalam tahap awal, sehingga proses untuk melakukannya mungkin agak berbeda untuk tampilan yang berbeda.

Bagi kita semua, kabar baiknya adalah kita masih dapat memanfaatkan data ini, meskipun tampilan normal mengharuskan gambar memiliki nilai byte (0-255) saluran. Meskipun kita perlu menyerahkan sebagian dari kekayaan peta penyinaran, setidaknya kita memiliki kendali atas bagaimana melakukannya.

Proses ini disebut pemetaan nada dan melibatkan pengubahan peta radiasi titik mengambang (dengan rentang nilai yang tinggi) ke gambar nilai byte standar. Ada teknik untuk melakukan itu sehingga banyak detail tambahan dipertahankan. Sekedar memberikan contoh bagaimana ini bisa bekerja, bayangkan bahwa sebelum kita menekan rentang floating point menjadi nilai byte, kita meningkatkan (mempertajam) tepi yang ada dalam gambar HDR. Meningkatkan tepi ini akan membantu melestarikannya (dan secara implisit detail yang mereka berikan) juga dalam gambar rentang dinamis rendah.

OpenCV menyediakan satu set operator pemetaan nada ini, seperti Drago, Durand, Mantiuk atau Reinhardt. Berikut adalah contoh bagaimana salah satu dari operator ini (Durand) dapat digunakan dan hasil yang dihasilkannya.

 durand = cv2.createTonemapDurand(gamma=2.5) ldr = durand.process(hdr) # Tonemap operators create floating point images with values in the 0..1 range # This is why we multiply the image with 255 before saving cv2.imwrite('durand_image.png', ldr * 255) 

Hasil perhitungan di atas ditampilkan sebagai gambar

Menggunakan Python, Anda juga dapat membuat operator sendiri jika Anda memerlukan kontrol lebih besar atas prosesnya. Misalnya, ini adalah hasil yang diperoleh dengan operator khusus yang menghilangkan intensitas yang direpresentasikan dalam piksel yang sangat sedikit sebelum mengecilkan rentang nilai menjadi 8 bit (diikuti dengan langkah kontras otomatis):

Gambar yang dihasilkan dari mengikuti proses di atas

Dan berikut adalah kode untuk operator di atas:

 def countTonemap(hdr, min_fraction=0.0005): counts, ranges = np.histogram(hdr, 256) min_count = min_fraction * hdr.size delta_range = ranges[1] - ranges[0] image = hdr.copy() for i in range(len(counts)): if counts[i] < min_count: image[image >= ranges[i + 1]] -= delta_range ranges -= delta_range return cv2.normalize(image, None, 0, 1, cv2.NORM_MINMAX)

Kesimpulan

Kami telah melihat bagaimana dengan sedikit Python dan beberapa pustaka pendukung, kami dapat mendorong batas kamera fisik untuk meningkatkan hasil akhirnya. Kedua contoh yang telah kita bahas menggunakan beberapa bidikan berkualitas rendah untuk menciptakan sesuatu yang lebih baik, tetapi ada banyak pendekatan lain untuk masalah dan batasan yang berbeda.

Sementara banyak ponsel kamera memiliki aplikasi toko atau built-in yang menangani contoh-contoh khusus ini, jelas tidak sulit sama sekali untuk memprogram ini dengan tangan dan untuk menikmati tingkat kontrol dan pemahaman yang lebih tinggi yang dapat diperoleh.

Jika Anda tertarik dengan perhitungan gambar pada perangkat seluler, lihat Tutorial OpenCV: Deteksi Objek Real-time Menggunakan MSER di iOS oleh sesama Toptaler dan pengembang elit OpenCV Altaibayar Tseveenbayar.