5.11. การทำ Frame Differencing

การทำ frame differencing เปรียบเทียบแต่ละเฟรมใหม่กับเฟรมอ้างอิงที่จัดเก็บไว้เพื่อค้นหาส่วนของฉากที่เปลี่ยนแปลงไป เป็นหัวใจหลักของแอปพลิเคชันกล้องที่คอยเฝ้าดูสิ่งที่เกิดขึ้น เช่น การบันทึกเมื่อตรวจพบการเคลื่อนไหว การแจ้งเตือนการบุกรุก หรือ "บันทึกวิดีโอเมื่อมีอะไรเคลื่อนที่" โดยสร้างขึ้นทั้งหมดจากการดำเนินการระดับพิกเซลที่กล่าวถึงก่อนหน้า ได้แก่ ความแตกต่างสัมบูรณ์ ค่าขีดแบ่ง และการค้นหาบริเวณ ซึ่งรันในทุกเฟรม

5.11.1. ไปป์ไลน์พื้นฐาน

ขั้นตอนแรกคือการ รับเฟรมอ้างอิง ในช่วงเริ่มต้นของโปรแกรม โดยเฉพาะเมื่อฉากอยู่ในสถานะที่หมายถึง "ไม่มีการเปลี่ยนแปลง" แอปพลิเคชันจะบันทึกเฟรมหนึ่งไว้ เฟรมนั้นจะกลายเป็นเส้นฐานที่ใช้เปรียบเทียบกับเฟรมที่บันทึกตามมาทุกเฟรม

reference = csi0.snapshot().copy()

การใช้ .copy() มีความสำคัญ csi0.snapshot() เพียงอย่างเดียวจะคืนค่า Image ที่บัฟเฟอร์อยู่ในบัฟเฟอร์เฟรม ซึ่งการเรียก snapshot ครั้งต่อไป จะเขียนทับมัน .copy() จัดสรรบัฟเฟอร์แยกต่างหากสำหรับเฟรมอ้างอิงและให้พิกเซลของเฟรม นี้ คงอยู่ได้แม้หลังจากการบันทึกครั้งถัดไป

ขั้นตอนที่สองรันในทุกเฟรม: บันทึกภาพใหม่ จากนั้นคำนวณความแตกต่างสัมบูรณ์ระหว่างภาพนั้นกับเฟรมอ้างอิง นั่นคือสิ่งที่ difference() ทำ:

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

หลังจากการเรียกนี้ current จะมีภาพที่พิกเซลที่ไม่ใช่ศูนย์ทำเครื่องหมายทุกตำแหน่งที่ฉากเปลี่ยนแปลงนับตั้งแต่บันทึกเฟรมอ้างอิง โดยขนาดของแต่ละพิกเซลจะสัดส่วนกับขนาดการเปลี่ยนแปลงในตำแหน่งนั้น

ขั้นตอนที่สาม กำหนดค่าขีดแบ่ง ให้กับภาพความแตกต่าง ความแตกต่างดิบมักมีสัญญาณรบกวนอยู่เสมอ: ความผันแปรความสว่างเล็กน้อยจากสัญญาณรบกวนของเซนเซอร์ การเปลี่ยนแปลงค่าเกรเดียนต์จากการเปลี่ยนแปลงของแสง และการสั่นของพิกเซลย่อยจากการเคลื่อนที่เล็กน้อยของกล้อง การกำหนดค่าขีดแบ่ง ซึ่งใช้ binary() ที่กำหนดค่าขีดแบ่งสูงกว่าระดับสัญญาณรบกวน จะเก็บเฉพาะการเปลี่ยนแปลงที่ใหญ่พอที่จะนับเป็นการเคลื่อนไหวจริงและละทิ้งส่วนที่เหลือ ผลลัพธ์เป็นภาพไบนารีที่พิกเซลที่ไม่ใช่ศูนย์คือตำแหน่งที่เปลี่ยนแปลงจริง

ขั้นตอนที่สี่แยก บริเวณที่เชื่อมต่อกัน ของมาสก์ไบนารีนั้น ซึ่งเป็นกลุ่มของพิกเซลที่ไม่ใช่ศูนย์ที่อยู่ติดกันซึ่งก่อตัวเป็นกลุ่มที่ต่อเนื่องกัน find_blobs() ทำสิ่งนั้นในการเรียกครั้งเดียว และคืนรายการของบริเวณการเคลื่อนไหว แต่ละบริเวณมีกรอบล้อมรอบและจำนวนพิกเซล ที่แอปพลิเคชันส่วนที่เหลือสามารถนำไปใช้ได้

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.

ไปป์ไลน์ frame-differencing: เฟรมอ้างอิงรวมกับเฟรมปัจจุบันกลายเป็นภาพความแตกต่าง การกำหนดค่าขีดแบ่งเปลี่ยนความแตกต่างเป็นมาสก์ไบนารีของตำแหน่งที่เปลี่ยนแปลง ขั้นตอนบริเวณที่เชื่อมต่อกันเปลี่ยนมาสก์เป็นรายการของบริเวณการเคลื่อนไหว

5.11.2. เฟรมอ้างอิงในหน่วยความจำและบนดิสก์

ไปป์ไลน์พื้นฐานเก็บเฟรมอ้างอิงไว้ใน RAM นั่นคือคำตอบที่ถูกต้องเมื่อเฟรมอ้างอิงถูกบันทึกในการรันสคริปต์ ครั้งนี้ และต้องอยู่รอดได้เพียงตราบเท่าที่สคริปต์ยังคงทำงานอยู่

สำหรับแอปพลิเคชันที่ทำงานต่อเนื่องยาวนาน เช่น กล้องที่ควรกลับมาตรวจจับการเปลี่ยนแปลงหลังจากรีสตาร์ทไฟ หรือสคริปต์ที่ทำงานเป็นช่วงๆ ซึ่งต้องตรวจจับ การเปลี่ยนแปลงใดๆ นับตั้งแต่ช่วงเวลาก่อนหน้า เฟรมอ้างอิงต้องอยู่ได้นานกว่าสคริปต์ที่กำลังทำงาน รูปแบบคือการ บันทึก เฟรมอ้างอิงไปยังดิสก์:

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

และโหลดกลับมาเมื่อเริ่มต้นการรันแต่ละครั้ง:

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

ลอจิกการทำ differencing ไม่เปลี่ยนแปลง มีเพียงที่ที่เฟรมอ้างอิงอยู่ระหว่างการบันทึกเท่านั้นที่เปลี่ยน การปรับปรุงบางอย่างสามารถขยายรูปแบบบนดิสก์นี้ได้อย่างเป็นธรรมชาติ เช่น การบันทึกเฟรมอ้างอิงใหม่โดยอัตโนมัติตามตัวจับเวลา หรือค่าเฉลี่ยแบบกลิ้งเพื่อติดตามการเปลี่ยนแปลงของแสงช้าๆ แต่การแทนที่ที่ศูนย์กลางก็เหมือนกัน

5.11.3. การแยกแหล่งกำเนิดแสง

รูปแบบการลบเดียวกันนี้ปรากฏขึ้นในบริบทที่แตกต่างออกไปเล็กน้อย: การแยก แหล่งกำเนิดแสง ออกจากส่วนที่เหลือของฉาก เคล็ดลับคือการบันทึกเฟรมอ้างอิง "ปิดไฟ" ซึ่งเป็นเฟรมที่ถ่ายเมื่อสิ่งที่กำลังตรวจจับ (สัญญาณ IR, พิกเซลบนหน้าจอ, ตัวบ่งบอกสถานะ) ยัง ไม่ สว่าง และลบเฟรมอ้างอิงนั้นออกจากแต่ละเฟรมถัดไป ผลลัพธ์มีความสว่างเป็นศูนย์ทุกที่ที่ฉากเหมือนกันในทั้งสองการบันทึก และมีความสว่างที่ไม่ใช่ศูนย์เฉพาะที่ที่แหล่งกำเนิดแสงสว่างจริงๆ

5.11.4. การเลือกระหว่าง difference หรือ sub

หมายเหตุทางปฏิบัติเกี่ยวกับการเลือกการดำเนินการทางเลขคณิต difference() คืนค่าสัมบูรณ์ของการเปลี่ยนแปลงโดยไม่มีเครื่องหมาย ซึ่งทำให้ไวต่อการเปลี่ยนแปลงในทั้งสองทิศทาง (สว่างขึ้นหรือมืดลง) โดยแลกกับการที่ไม่บอกแอปพลิเคชัน ว่า การเปลี่ยนแปลงนั้นไปในทิศทางใด สำหรับการตรวจจับการเคลื่อนไหวล้วนๆ นั่นคือคำตอบที่ถูกต้อง: ทุกอย่างที่เคลื่อนที่น่าสนใจทั้งนั้น ไม่ว่าความสว่างจะเปลี่ยนในทิศทางใด

สำหรับการตรวจจับแหล่งกำเนิดแสง พิกเซลที่สว่างมักจะ สว่างกว่า เฟรมอ้างอิงที่ปิดไฟเสมอ ดังนั้น sub() (ที่มีการตัดที่ศูนย์) จึงเป็นตัวเลือกที่เหมาะสมกว่า ทุกที่ที่เฟรมปัจจุบันมืดกว่าเฟรมอ้างอิง (ซึ่งจะเป็นสัญญาณรบกวนของเซนเซอร์รอบค่าที่ไม่สว่าง) จะถูกตัดเป็นศูนย์แทนที่จะรายงานสัญญาณเท็จว่า "ไฟกำลังติดอยู่"