5.16. เคอร์เนลคอนโวลูชันแบบกำหนดเอง

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

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

5.16.1. เมธอด morph

ลายเซ็นของเมธอดมีลักษณะเหมือนกับฟิลเตอร์บริเวณใกล้เคียงตัวอื่นๆ โดยมีอาร์กิวเมนต์เพิ่มเติมหนึ่งตัว:

img.morph(size, kernel, mul=1.0, add=0.0)

size คือรัศมีในแบบเดียวกับที่ใช้ในทุกที่ ดังนั้นเคอร์เนลจะต้องมีจำนวนแถวเป็น (2 * size + 1) คูณจำนวนคอลัมน์ (2 * size + 1) พอดี ตัวเคอร์เนลเองเป็นรายการ Python แบบแบนของตัวเลขเหล่านั้น เรียงลำดับตาม แถวหลัก นั่นคือ รายการ (2 * size + 1) ตัวแรกคือแถวบน รายการ (2 * size + 1) ถัดไปคือแถวที่สอง และต่อๆ ไปจนถึงแถวล่างสุด mul จะปรับขนาดผลรวมของผลคูณก่อนที่จะเขียนลงในพิกเซลเอาต์พุต และ add จะเพิ่มค่าคงที่ ค่าเริ่มต้น mul=1.0 และ add=0.0 จะปล่อยให้เอาต์พุตของคอนโวลูชันไม่เปลี่ยนแปลง

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

คู่ threshold=True / offset=N จากส่วนค่าขีดแบ่งแบบปรับตัวยังใช้งานได้กับ morph() ด้วย ดังนั้นเฟรมเวิร์กเคอร์เนลแบบกำหนดเองเดียวกันสามารถสร้างค่าขีดแบ่งแบบไบนารีที่มีค่าตัดออกคำนวณจากสถิติแบบกำหนดเองได้

5.16.2. โครงร่างของเคอร์เนล

เคอร์เนล 3x3 (size=1) คือรายการแบบแบนของตัวเลขเก้าตัวที่เรียงจากซ้ายไปขวา จากบนลงล่าง แบบแผนนี้อ่านได้ตามธรรมชาติหากแบ่งรายการออกเป็นสาม Python บรรทัด:

sobel_x = [-1,  0,  1,
           -2,  0,  2,
           -1,  0,  1]

นี่คือตัวดำเนินการไล่ระดับ Sobel-x ซึ่งเป็นเคอร์เนลมาตรฐานตัวแรกที่แอปพลิเคชันใดๆ จะต้องการและมีประโยชน์ในการอธิบายตั้งแต่ต้นจนจบ รูปแบบนั้นตรงไปตรงมา: น้ำหนักลบในคอลัมน์ซ้าย น้ำหนักบวกในคอลัมน์ขวา โดยคอลัมน์กลางเป็นศูนย์ น้ำหนักของแถว -1, -2, -1 (หรือ 1, 2, 1 ทางด้านขวา) สูงกว่าในตรงกลางมากกว่าที่มุม ซึ่งทำให้แถวตรงกลางมีอิทธิพลต่อผลลัพธ์มากกว่าแถวมุม

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

การรันเคอร์เนล:

img.morph(1, sobel_x, mul=0.25)

เคอร์เนล Sobel มีผลรวมเป็นศูนย์ เนื่องจากน้ำหนักลบทุกตัวทางด้านซ้ายจะจับคู่กับน้ำหนักบวกที่เท่ากันทางด้านขวา ดังนั้นการหารอัตโนมัติจึงไม่หารด้วยสิ่งใด และ mul เป็นสเกลเดียวของผลรวมของผลคูณ mul=0.25 จะรักษาการตอบสนองให้อยู่ในช่วง: ผลรวมสัมบูรณ์สูงสุดที่ Sobel-x สามารถสร้างได้จากบริเวณ 3x3 คือประมาณ 4 * 255 = 1020 (พิกเซลสว่างแปดตัวถ่วงน้ำหนักสูงสุดที่ 2) และการหารลงด้วยสี่จะทำให้กรณีสุดขั้วอยู่ที่ 255 ซึ่งรูปแบบจะตัดค่าอย่างสะอาด

เคอร์เนล Sobel-y ที่ตรงกันจะตรวจจับขอบในแนวนอนโดยการหมุนรูปแบบน้ำหนักเดียวกัน 90 องศา:

sobel_y = [-1, -2, -1,
            0,  0,  0,
            1,  2,  1]

แอปพลิเคชันที่ต้องการตรวจจับขอบ ใดๆ โดยไม่คำนึงถึงทิศทาง มักจะรันทั้ง Sobel ทั้งสองและรวมการตอบสนองเข้าด้วยกัน

5.16.3. การชดเชยเอาต์พุต

add คืออีกครึ่งหนึ่งของเรื่องสเกล การตอบสนองของเคอร์เนลที่มีผลรวมเป็นศูนย์เป็น เครื่องหมาย ซึ่งบวกทางด้านหนึ่งของขอบและลบทางอีกด้านหนึ่ง และครึ่งลบจะถูกตัดเป็นศูนย์เมื่อเขียนลงในพิกเซลแบบไม่มีเครื่องหมาย add=128 จะเลื่อนการตอบสนองให้อยู่กึ่งกลางที่สีเทากลาง ดังนั้นการตอบสนองลบจะยังคงอยู่เป็นค่าต่ำกว่า 128 และค่าบวกจะอยู่สูงกว่า: การตอบสนองต่อขอบหรือเอฟเฟกต์นูนจะมองเห็นได้ในทั้งสองทิศทาง โดยแลกกับครึ่งหนึ่งของช่วงในแต่ละทิศทาง

การรวมกันของ mul และ add ที่เคอร์เนลคาดหวังเป็นส่วนหนึ่งของการออกแบบเคอร์เนล โดย แคตตาล็อกเคอร์เนลมาตรฐาน จะแสดงรายการการตั้งค่าที่ถูกต้องสำหรับเคอร์เนลทั่วไปแต่ละตัว

5.16.4. เคอร์เนลขนาดใหญ่กว่า

ทุกอย่างในหน้านี้ได้อธิบายด้วยเคอร์เนล 3x3 (size=1) เนื่องจากนั้นคือขนาดที่แคตตาล็อกมาตรฐานใช้และเนื่องจากโครงร่างแถวหลักนั้นเขียนด้วยมือได้ง่ายที่ขนาดนั้น อย่างไรก็ตาม ไม่มีสิ่งใดในกลไกที่จำกัดเคอร์เนลให้เป็น 3x3 size=2 จะรันเคอร์เนล 5x5 โดยมีรายการ 25 ตัวในรายการแบบแบน size=3 จะรัน 7x7 โดยมี 49 ตัว และต่อๆ ไป จนถึงรัศมีใดก็ตามที่แอปพลิเคชันยินดีจ่ายราคา เฟรมเวิร์กรองรับทั้งโครงร่างรายการแบบแบนและแบบแถวซ้อนกันที่ขนาดคี่ใดก็ได้

เหตุผลที่ต้องใช้เคอร์เนลขนาดใหญ่กว่าเหมือนกับเหตุผลที่ต้องใช้บริเวณใกล้เคียงขนาดใหญ่กว่าในฟิลเตอร์ในตัวใดๆ ได้แก่ การเฉลี่ยมากขึ้น การตรวจจับลักษณะเด่นที่กว้างขึ้น และความไวต่อสัญญาณรบกวนระดับพิกเซลเดียวน้อยลง ต้นทุนเติบโตตามกำลังสองของรัศมี เคอร์เนล 5x5 ทำงานต่อพิกเซลประมาณ 2.8 เท่าของ 3x3 และ 7x7 ประมาณ 5.4 เท่า และตัวคูณนั้นมาตัดโดยตรงจากอัตราเฟรม

รูปแบบที่ใช้จริงคือให้คงที่ที่ size=1 สำหรับแคตตาล็อกมาตรฐาน และใช้ขนาดใหญ่กว่าเฉพาะเมื่ออัลกอริทึมต้องการบริเวณใกล้เคียงที่ใหญ่กว่า ตัวตรวจจับขอบแทบจะไม่ได้ประโยชน์จากการเกิน 3x3 ฟิลเตอร์การทำให้เรียบบางครั้งทำ ขนาดที่เหมาะสมขึ้นอยู่กับขนาดของลักษณะเด่นที่แอปพลิเคชันพยายามเน้นหรือลดลง

5.16.5. เมื่อไรจึงควรใช้ morph

สำหรับการทำให้เรียบในชีวิตประจำวัน mean(), gaussian() และ bilateral() เร็วกว่าและสะอาดกว่า สำหรับการตรวจจับขอบ laplacian() และ find_edges() ถูกสร้างมาเพื่อจุดประสงค์นี้โดยเฉพาะ กรณีที่ควรใช้ morph() โดยตรงคือเมื่อแอปพลิเคชันต้องการคอนโวลูชัน เฉพาะ ที่ฟิลเตอร์ในตัวไม่ได้เปิดเผย ไม่ว่าจะเป็น Sobel แบบทิศทาง เทมเพลตขอบแบบกำหนดเอง เคอร์เนลที่ปรับแต่งให้เหมาะกับพื้นผิวเฉพาะที่ส่วนที่เหลือของไปป์ไลน์จะค้นหา หรือเคอร์เนลที่มีประโยชน์จากแคตตาล็อกมาตรฐานที่การประมวลผลภาพแบบคลาสสิกได้สร้างสมมาตลอดหลายทศวรรษ ความยืดหยุ่นเต็มรูปแบบของเคอร์เนลแบบกำหนดเองนั้นมีให้ใช้ แต่ราคาที่ต้องจ่ายคือแอปพลิเคชันต้องรับผิดชอบในการเลือกค่าเคอร์เนลที่สร้างผลลัพธ์ที่ต้องการ