5.11. Pembedaan bingkai

Pembedaan bingkai membandingkan setiap bingkai baru terhadap bingkai referensi yang tersimpan untuk menemukan bagian-bagian adegan yang telah berubah. Teknik ini merupakan tulang punggung aplikasi kamera yang memantau sesuatu -- pengambilan gambar yang dipicu gerakan, peringatan penyusupan, "simpan video ketika ada yang bergerak" -- dan dibangun sepenuhnya dari operasi piksel-per-piksel yang telah dibahas sebelumnya: selisih mutlak, ambang batas, dan pencarian wilayah, yang dijalankan pada setiap bingkai.

5.11.1. Pipeline dasar

Tahap pertama adalah mendapatkan referensi. Di suatu titik mendekati startup -- idealnya ketika adegan berada dalam keadaan yang berarti "tidak ada perubahan" -- aplikasi mengambil sebuah bingkai dan menyimpannya. Bingkai tersebut menjadi garis dasar yang akan dibandingkan dengan setiap pengambilan gambar berikutnya.

reference = csi0.snapshot().copy()

Metode .copy() sangat penting. csi0.snapshot() sendiri mengembalikan sebuah Image yang buffernya berada di buffer bingkai, di mana panggilan berikutnya ke snapshot akan menimpanya. .copy() mengalokasikan buffer terpisah untuk referensi dan membiarkan piksel dari bingkai ini bertahan melewati pengambilan gambar berikutnya.

Tahap kedua berjalan pada setiap bingkai: ambil citra segar, lalu hitung selisih mutlak antara citra tersebut dan referensi. Itulah tepatnya yang dilakukan oleh difference():

current = csi0.snapshot()
current.difference(reference)

Setelah panggilan ini, current menyimpan sebuah citra yang piksel-piksel bukan-nolnya menandai setiap posisi di mana adegan berubah sejak referensi diambil, dengan besaran setiap piksel proporsional terhadap seberapa besar perubahannya di posisi tersebut.

Tahap ketiga mengambang citra perbedaan. Perbedaan mentah selalu mengandung sedikit noise: variasi kecerahan kecil dari noise tembakan sensor, perubahan gradien dari pergeseran pencahayaan, jitter sub-piksel dari sedikit gerakan kamera. Proses ambang batas -- binary() dengan ambang batas yang ditetapkan di atas lantai noise tersebut -- hanya menyimpan perubahan yang cukup besar untuk dihitung sebagai gerakan nyata dan membuang sisanya, menghasilkan citra biner yang piksel-piksel bukan-nolnya adalah posisi yang benar-benar berubah.

Tahap keempat mengekstrak wilayah-wilayah yang terhubung dari masker biner tersebut -- kelompok piksel bukan-nol yang berdekatan yang membentuk tambalan berkesinambungan. find_blobs() melakukan hal itu dalam satu panggilan, mengembalikan daftar wilayah gerakan, masing-masing dengan kotak pembatas dan jumlah piksel, yang dapat ditindaklanjuti oleh aplikasi.

A horizontal pipeline diagram. The leftmost two panels are a reference frame and a current frame side by side, with a plus mark between them. An arrow leads from the pair to a third panel labelled difference, in which a few patches are bright against a dark background. An arrow leads from there to a fourth panel showing a binary thresholded version of the difference, with the same patches now solid white. A final arrow leads to a fifth panel showing the binary mask annotated with rectangular bounding boxes drawn around each patch.

Pipeline frame-differencing: bingkai referensi ditambah bingkai saat ini menjadi citra perbedaan; ambang batas mengubah perbedaan menjadi masker biner posisi yang berubah; langkah wilayah terhubung mengubah masker menjadi daftar wilayah gerakan.

5.11.2. Referensi di memori dan di disk

Pipeline dasar menyimpan bingkai referensi di RAM. Itu adalah jawaban yang tepat ketika referensi diambil pada jalannya skrip ini dan hanya perlu bertahan selama skrip tetap berjalan.

Untuk aplikasi yang berjalan lama -- kamera yang harus melanjutkan deteksi perubahan setelah siklus daya, skrip intermiten yang perlu mendeteksi setiap perubahan sejak suatu momen sebelumnya -- bingkai referensi harus bertahan lebih lama dari skrip yang berjalan. Polanya adalah menyimpan referensi ke disk:

csi0.snapshot().save("/sdcard/reference.bmp")

dan memuatnya kembali di awal setiap run:

reference = image.Image("/sdcard/reference.bmp")

Logika pembedaan tidak berubah; hanya di mana referensi disimpan di antara pengambilan gambar yang berbeda. Beberapa penyempurnaan secara alami memperluas varian on-disk ini -- pengambilan ulang referensi secara otomatis pada timer, rata-rata bergulir opsional untuk melacak pergeseran pencahayaan lambat -- tetapi penggantian di pusatnya tetap sama.

5.11.3. Isolasi sumber cahaya

Pola pengurangan yang sama muncul dalam pengaturan yang sedikit berbeda: mengisolasi sumber cahaya terhadap sisa adegan. Triknya adalah mengambil referensi "lampu-mati" -- bingkai yang diambil ketika apa pun yang sedang dideteksi (suar IR, piksel layar, indikator status) tidak diterangi -- dan mengurangi referensi tersebut dari setiap bingkai berikutnya. Hasilnya memiliki kecerahan nol di mana-mana adegan sama dalam kedua pengambilan gambar, dan kecerahan bukan-nol hanya di mana sumber cahaya benar-benar menyala.

5.11.4. Memilih difference atau sub

Catatan praktis tentang operasi aritmetika mana yang harus dipilih. difference() mengembalikan nilai mutlak dari perubahan -- bebas tanda -- yang membuatnya sensitif terhadap perubahan di kedua arah (menjadi lebih terang atau lebih gelap) dengan biaya tidak memberi tahu aplikasi arah mana perubahan itu terjadi. Untuk deteksi gerakan murni, itulah jawaban yang tepat: apa pun yang bergerak itu menarik, terlepas dari arah pergeseran kecerahan.

Untuk deteksi sumber cahaya, piksel yang diterangi selalu lebih terang dari referensi lampu-mati, sehingga sub() (dengan pemotongannya di nol) adalah pilihan yang lebih tepat. Di mana pun bingkai saat ini lebih gelap dari referensi (yang akan menjadi noise sensor sekitar nilai tidak diterangi) terpotong ke nol daripada melaporkan sinyal "lampu menyala" yang palsu.