5.17. แคตาล็อกของเคอร์เนลมาตรฐาน¶
การประมวลผลภาพแบบดั้งเดิมได้สะสมแคตาล็อกของรูปแบบน้ำหนักเคอร์เนลที่ปรากฏซ้ำแล้วซ้ำเล่า ไม่ว่าจะเป็นตัวตรวจจับขอบ ตัวทำให้คมชัด การทำนูน การทำให้เรียบ การเบลอจากการเคลื่อนไหว และทั้งหมดนั้นทำงานผ่าน morph() แต่ละอันสั้น ทำหน้าที่เดียว และส่วนใหญ่อ่านได้ง่ายเมื่อเข้าใจตรรกะพื้นฐานของน้ำหนัก
เคอร์เนลด้านล่างทั้งหมดมีขนาด 3x3 หากไม่ได้ระบุไว้ จึงใช้ size=1 ในการเรียกใช้ โครงสร้างน้ำหนักของแต่ละเคอร์เนลได้รับการอธิบายควบคู่กัน เนื่องจากการอ่านน้ำหนักเป็นสิ่งที่สร้างความเข้าใจว่าทำไมเคอร์เนลหนึ่งถึงทำให้นูนในขณะที่อีกอันทำให้คมชัด
5.17.1. เคอร์เนลเอกลักษณ์¶
เคอร์เนลที่ง่ายที่สุดที่เป็นไปได้คือ เอกลักษณ์ -- หนึ่งที่ศูนย์กลาง ศูนย์ทุกที่:
identity = [0, 0, 0,
0, 1, 0,
0, 0, 0]
img.morph(1, identity)
พิกเซลเอาต์พุตแต่ละพิกเซลรับค่าจากศูนย์กลางของพื้นที่ใกล้เคียง ซึ่งคือพิกเซลอินพุตที่ตำแหน่งเดียวกัน ภาพผ่านไปโดยไม่เปลี่ยนแปลง เคอร์เนลเอกลักษณ์ไม่มีประโยชน์ในทางปฏิบัติในฐานะตัวกรอง แต่เป็นพื้นฐานที่มีประโยชน์สำหรับการทำความเข้าใจเคอร์เนลอื่นๆ ทุกตัว: เคอร์เนลที่ไม่ใช่เอกลักษณ์ใดๆ คือเอกลักษณ์บวกกับการปรับเปลี่ยนบางอย่าง
เคอร์เนลที่มีน้ำหนักตรงกลางขนาดใหญ่พร้อมน้ำหนักลบขนาดเล็กรอบๆ ลบ บริเวณรอบข้างออกจากศูนย์กลาง เคอร์เนลที่มีน้ำหนักตรงกลางเป็นศูนย์จะเพิกเฉยต่อพิกเซลนั้นเองและตอบสนองเฉพาะความแตกต่างระหว่างพิกเซลใกล้เคียงเท่านั้น การอ่านเคอร์เนลด้วยวิธีนี้ -- น้ำหนักตรงกลางทำอะไรกับพิกเซล น้ำหนักรอบข้างเพิ่มหรือลดอะไร -- เป็นวิธีที่เร็วที่สุดในการทำนายผลกระทบ
5.17.2. การตรวจจับขอบ¶
เคอร์เนล ตรวจจับขอบ ตอบสนองอย่างแรงต่อตำแหน่งที่ความสว่างเปลี่ยนแปลงอย่างรวดเร็วในทิศทางหนึ่ง และให้ผลลัพธ์ใกล้ศูนย์เมื่อความสว่างสม่ำเสมอ เป็นกลุ่มที่ผลรวมของน้ำหนักเท่ากับศูนย์: พื้นที่แบน (ทุกพิกเซลมีค่าเท่ากัน) ให้ผลลัพธ์เป็นศูนย์ เพราะน้ำหนักบวกทุกค่าถูกหักล้างอย่างแม่นยำด้วยน้ำหนักลบขนาดเท่ากัน
Sobel-x คือตัวอย่างมาตรฐาน มันตรวจจับขอบ แนวตั้ง (การเปลี่ยนแปลงความสว่างซ้าย/ขวา):
sobel_x = [-1, 0, 1,
-2, 0, 2,
-1, 0, 1]
img.morph(1, sobel_x, mul=0.25, add=128)
Sobel-y ที่สอดคล้องกันคือรูปแบบเดียวกันที่หมุน 90 องศา มันตรวจจับขอบแนวนอน (การเปลี่ยนแปลงความสว่างบน/ล่าง):
sobel_y = [-1, -2, -1,
0, 0, 0,
1, 2, 1]
แถวกลางของ Sobel-x มีน้ำหนัก -2 และ 2 แทนที่จะเป็น -1 และ 1 น้ำหนักพิเศษบนแถวกลางทำให้เคอร์เนลมีการทำให้เรียบในทิศทาง ตามแนว ขอบในตัว ซึ่งทำให้มีความทนทานต่อสัญญาณรบกวนมากกว่า Prewitt ที่ตัดขนาดพิเศษเหล่านั้นออก:
prewitt_x = [-1, 0, 1,
-1, 0, 1,
-1, 0, 1]
prewitt_y = [-1, -1, -1,
0, 0, 0,
1, 1, 1]
Prewitt ให้น้ำหนักทุกแถวเท่ากัน ดังนั้นการตอบสนองจึงคมชัดกว่า Sobel เล็กน้อย แต่แลกมาด้วยความอ่อนไหวต่อสัญญาณรบกวนพิกเซลเดี่ยวมากกว่า (ต้นทุนในการเรียกใช้เคอร์เนลเหมือนกัน -- การคอนโวลูชันทำงานเดิมไม่ว่าน้ำหนักจะเป็นอะไร) บนภาพที่สะอาดที่มีขอบชัดเจน มันเป็นตัวแทนที่ใช้ได้ดีสำหรับ Sobel
Scharr ไปในทิศทางตรงกันข้าม น้ำหนักของมันมีขนาดใหญ่กว่าและปรับแต่งเพื่อการตรวจจับทิศทางขอบที่แม่นยำในมุมละเอียดกว่า:
scharr_x = [-3, 0, 3,
-10, 0, 10,
-3, 0, 3]
img.morph(1, scharr_x, mul=0.0625, add=128)
ตัวหาร mul=0.0625 (1/16) นำผลลัพธ์กลับมาอยู่ในช่วง 0 -- 255 หลังจากผลรวมของผลิตภัณฑ์ที่มีค่ามากกว่า Scharr เป็นคำตอบที่ถูกต้องเมื่อแอปพลิเคชันต้องการการตอบสนองกราเดียนต์ที่ถูกต้องเรขาคณิตที่สุด และยินดีจ่ายค่าคำนวณเพิ่มขึ้นเล็กน้อยสำหรับมัน
5.17.3. Laplacian¶
เคอร์เนล Laplacian ตอบสนองต่อขอบใน ทุก ทิศทางพร้อมกัน ในขณะที่ Sobel แต่ละตัวตรวจจับการเปลี่ยนแปลงความสว่างตามแกนหนึ่ง รูปแบบน้ำหนักสมมาตรของ Laplacian ตอบสนองในลักษณะเดียวกันโดยไม่คำนึงถึงทิศทางของขอบ:
laplacian_4 = [ 0, -1, 0,
-1, 4, -1,
0, -1, 0]
img.morph(1, laplacian_4, add=128)
โครงสร้าง: น้ำหนักตรงกลาง 4 เพื่อนบ้านแนวนอน/แนวตั้งสี่ตัวมีน้ำหนัก -1 เส้นทแยงมุมสี่ตัวมีน้ำหนักศูนย์ เคอร์เนลรวมเป็นศูนย์ ดังนั้นพื้นที่แบนให้ผลลัพธ์เป็นศูนย์ เมื่อความสว่างเปลี่ยนแปลง ค่าตรงกลางต่างจากค่าเฉลี่ยของเพื่อนบ้านสี่ทิศหลัก และผลลัพธ์คือขนาดของความแตกต่างนั้น
ตัวแปร 8 ทิศทางรวมถึงเพื่อนบ้านแนวทแยงมุม:
laplacian_8 = [-1, -1, -1,
-1, 8, -1,
-1, -1, -1]
เคอร์เนลแต่ละตัวตรวจจับสิ่งที่แตกต่างกันเล็กน้อย เวอร์ชัน 4 ทิศทางให้ผลลัพธ์ที่สะอาดกว่าบนขอบแนวนอนและแนวตั้ง เวอร์ชัน 8 ทิศทางมีความ isotropic มากกว่า -- ตอบสนองได้เท่าเทียมกันในทุกทิศทาง -- แต่ให้ผลลัพธ์ที่มีสัญญาณรบกวนเล็กน้อยมากกว่า เคอร์เนล 8 ทิศทางยังหมุนเวียนใช้ภายใต้ชื่อ outline ตามการใช้งานในการแสดงภาพขอบ
5.17.5. การทำนูน¶
เคอร์เนล ทำนูน ให้เอฟเฟกต์แสงจากด้านข้างที่พบในโปรแกรมแก้ไขภาพแบบดั้งเดิม ผลลัพธ์ดูเหมือนภาพถูกอัดออกมาเป็นรูปนูนแล้วส่องแสงจากมุมหนึ่ง:
emboss = [-2, -1, 0,
-1, 1, 1,
0, 1, 2]
img.morph(1, emboss, add=128)
เคล็ดลับคือ ความไม่สมมาตรตามแนวทแยงมุม มุมซ้ายบนมีน้ำหนักลบมากที่สุด มุมขวาล่างมีน้ำหนักบวกมากที่สุด และแนวทแยงมุมจากมุมหนึ่งไปอีกมุมหนึ่งดำเนินไปจากลบผ่านหนึ่งถึงบวก ที่แต่ละพิกเซล เคอร์เนลโดยพื้นฐานแล้วคำนวณ "ความสว่างที่ขวาล่างของฉันลบความสว่างที่ซ้ายบนของฉัน" ซึ่งเป็นบวกเมื่อภาพสว่างขึ้นในทิศทางนั้นและเป็นลบเมื่อมืดลง การเพิ่ม 128 จัดศูนย์กลางผลลัพธ์ที่มีเครื่องหมายใหม่ให้เป็นสีเทากลางเพื่อให้มองเห็นเอฟเฟกต์
การหมุนความไม่สมมาตรไปตามแนวทแยงมุมอีกอัน จะทำนูนจากทิศทางตรงกันข้าม:
emboss_alt = [ 0, 1, 2,
-1, 1, 1,
-2, -1, 0]
img.morph(1, emboss_alt, add=128)
ทิศทางการทำนูนสองแบบมีประโยชน์เมื่อใช้ร่วมกัน -- ลบอันหนึ่งออกจากอีกอัน หรือเรียกใช้แต่ละอันบนภาพเดียวกันแล้วเปรียบเทียบการตอบสนอง -- เมื่อแอปพลิเคชันต้องการตรวจจับการวางแนว
5.17.6. การทำให้เรียบ¶
เคอร์เนลทำให้เรียบเป็นกลุ่มที่ผลรวมของน้ำหนักเท่ากับหนึ่ง (และทั้งหมดไม่เป็นลบ) พื้นที่แบนผ่านเคอร์เนลดังกล่าวให้ความสว่างแบบแบนเหมือนเดิม เพราะเคอร์เนลเฉลี่ยค่าพิกเซลเข้าด้วยกันแทนที่จะขยายความแตกต่าง
ที่ง่ายที่สุดคือ box blur ซึ่งคือสิ่งที่ mean() คำนวณ:
box_blur = [1, 1, 1,
1, 1, 1,
1, 1, 1]
img.morph(1, box_blur)
เคอร์เนลรวมเป็น 9 ดังนั้นการหารอัตโนมัติด้วยผลรวมของเคอร์เนลจะเปลี่ยนผลรวมของผลิตภัณฑ์เป็นค่าเฉลี่ยที่แท้จริงบนพิกเซลในพื้นที่ใกล้เคียงทั้งเก้า ในทางปฏิบัติ mean() เป็นวิธีที่ดีกว่าในการเรียกใช้เคอร์เนลนี้ -- ให้ผลลัพธ์เดียวกันได้เร็วกว่า ผ่านเส้นทางที่ปรับให้เหมาะสมสำหรับการคำนวณค่าเฉลี่ยโดยเฉพาะ ในขณะที่ morph เรียกใช้เครื่องจักรคอนโวลูชันทั่วไป box blur อยู่ในแคตาล็อกเพราะเป็นพื้นฐานที่ถูกต้องสำหรับการทำความเข้าใจเคอร์เนลทำให้เรียบอื่นๆ ทุกตัว
การประมาณ Gaussian 3x3 ให้น้ำหนักกับศูนย์กลางและเพื่อนบ้านหลักมากกว่ามุม:
gaussian = [1, 2, 1,
2, 4, 2,
1, 2, 1]
img.morph(1, gaussian)
น้ำหนักคือแถว Pascal-triangle 1, 2, 1 ที่คูณกับตัวเองแบบ outer product น้ำหนักตรงกลาง 4 มากที่สุดเพราะพิกเซลตรงกลางมีส่วนร่วมมากที่สุดในผลลัพธ์ของตัวเอง มุมเป็น 1 เพราะอยู่ไกลจากศูนย์กลางมากที่สุด เคอร์เนลรวมเป็น 16 และการหารอัตโนมัติด้วยผลรวมของเคอร์เนลจัดการการทำให้ปกติ -- ไม่จำเป็นต้องใช้อาร์กิวเมนต์ mul รูปแบบ 3x3 เป็นการประมาณหยาบของ Gaussian จริงและแยกไม่ออกจาก gaussian() ที่ size=1; รูปแบบ morph มีประโยชน์ส่วนใหญ่เมื่อแอปพลิเคชันต้องการรวมการทำให้เรียบกับการดำเนินการอื่นในการผ่านเดียวกัน
5.17.7. การเบลอจากการเคลื่อนไหว¶
เคอร์เนล motion-blur เฉลี่ยพิกเซลตาม ทิศทางหนึ่ง โดยปล่อยให้ทิศทางตั้งฉากไม่เบลอ กรณีง่ายที่สุดคือแนวนอน:
motion_h = [0, 0, 0,
1, 1, 1,
0, 0, 0]
img.morph(1, motion_h)
แถวกลางเฉลี่ยพิกเซลสามพิกเซลตามแกนนอน แถวบนและล่างเป็นศูนย์ เคอร์เนลรวมเป็น 3 ดังนั้นการหารอัตโนมัติด้วยผลรวมของเคอร์เนลให้ค่าเฉลี่ยสามพิกเซลที่แท้จริงโดยไม่ต้องใช้ mul ผลลัพธ์คือสำเนาของอินพุตที่เบลอในแนวนอน -- เอฟเฟกต์ที่กล้องจับภาพเมื่อวัตถุเคลื่อนที่ด้านข้างในระหว่างการรับแสง การเบลอจากการเคลื่อนไหวแนวตั้งคือรูปแบบเดียวกันที่หมุน:
motion_v = [0, 1, 0,
0, 1, 0,
0, 1, 0]
การเบลอจากการเคลื่อนไหวแนวทแยงใช้แนวทแยงหลัก:
motion_diag = [1, 0, 0,
0, 1, 0,
0, 0, 1]
img.morph(1, motion_diag)
เคอร์เนล motion blur มีประโยชน์ทั้งในฐานะ เอฟเฟกต์ (การเบลอเฟรมโดยเจตนาเพื่อวัตถุประสงค์ทางภาพ) และเป็น รูปแบบทดสอบ สำหรับอัลกอริทึมที่ต้องมีความทนทานต่ออาร์ติแฟกต์จากการเคลื่อนไหว (เรียกใช้อัลกอริทึมบนอินพุตที่เบลอจากการเคลื่อนไหวและตรวจสอบว่ายังให้คำตอบที่ถูกต้อง)
5.17.8. การอ่านเคอร์เนลอย่างรวดเร็ว¶
กฎง่ายๆ บางข้อทำให้อ่านเคอร์เนลใหม่ได้ง่ายขึ้น:
รวมเป็นหนึ่ง พร้อมน้ำหนักที่ไม่เป็นลบ ⇒ การทำให้เรียบ (รักษาความสว่างเฉลี่ย)
รวมเป็นศูนย์ พร้อมน้ำหนักทั้งบวกและลบ ⇒ การตอบสนองขอบ (ศูนย์บนพื้นที่แบน)
รวมเป็นหนึ่ง พร้อมตรงกลางบวกขนาดใหญ่และรอบข้างลบขนาดเล็ก ⇒ การทำให้คมชัด (เอกลักษณ์บวกการตอบสนองขอบ)
ไม่สมมาตรตามแนวทแยงมุม พร้อมผลรวมเป็นหนึ่ง ⇒ การทำนูน (เน้นด้านหนึ่งของทุกการเปลี่ยนแปลงความสว่าง)
รวมตัวตามแกนหนึ่ง พร้อมผลรวมเป็นหนึ่ง ⇒ การเบลอตามทิศทาง
ข้อแรกที่เคอร์เนลตรงกันนั้นมักเป็นการคาดเดาที่ถูกต้องว่ามันทำอะไร เคอร์เนลที่มีประโยชน์ส่วนใหญ่สามารถจดจำได้จากการจัดวางรูปแบบน้ำหนักเพียงอย่างเดียว
เมื่อ ไม่มี เคอร์เนลมาตรฐานตัวใดทำสิ่งที่แอปพลิเคชันต้องการ ขั้นตอนถัดไปคือการปรับแต่งด้วยมือ การผสมผสานของกฎข้างต้นและตัวควบคุม mul / add ครอบคลุมการผ่านเชิงเส้นเกือบทุกอย่างที่ไปป์ไลน์ machine vision แบบดั้งเดิมเคยต้องการ จากนั้นก็เป็นเรื่องของการลองน้ำหนัก ดูผลลัพธ์ และทำซ้ำ