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.4. การทำให้คมชัด

เคอร์เนล ทำให้คมชัด คือเอกลักษณ์บวกกับเคอร์เนลตอบสนองขอบ ผลลัพธ์คือภาพต้นฉบับบวกกับสำเนาของขอบ ดังนั้นลักษณะความถี่สูงจะถูกขยายสัญญาณเมื่อเทียบกับพื้นที่เรียบ

เคอร์เนลทำให้คมชัดมาตรฐาน 4 ทิศทางเพิ่ม Laplacian 4 ทิศทางเข้ากับเอกลักษณ์:

sharpen = [ 0, -1,  0,
           -1,  5, -1,
            0, -1,  0]

img.morph(1, sharpen)

การอ่านเคอร์เนล: น้ำหนักตรงกลางคือ identity (1) + Laplacian centre (4) = 5 และรอบข้างตรงกับของ Laplacian พื้นที่แบนให้ผลลัพธ์ 5 * 1 - 4 * 1 = 1 เท่าของค่าตรงกลาง -- เอกลักษณ์ ขอบให้ต้นฉบับบวกกับการตอบสนอง Laplacian ผลรวมของน้ำหนักคือ 1 ดังนั้น mul และ add ยังคงเป็นค่าเริ่มต้น

สำหรับการทำให้คมชัดที่แรงกว่า ตัวแปร 8 ทิศทางจะไปไกลกว่า:

sharpen_strong = [-1, -1, -1,
                  -1,  9, -1,
                  -1, -1, -1]

img.morph(1, sharpen_strong)

น้ำหนักตรงกลาง 9 คือ identity (1) + Laplacian-8 centre (8) ตรรกะเดียวกัน การขยายสัญญาณมากกว่า ความเสี่ยงในการขยายสัญญาณรบกวน sensor มากกว่าด้วย

เคอร์เนลทำให้คมชัดอย่างรุนแรงนั้นโดยพื้นฐานแล้วคือ gaussian() ที่ใช้ unsharp=True เพียงแต่แสดงออกมาโดยตรงในรูปแบบเคอร์เนลแทนที่จะผ่านแฟล็ก unsharp-mask พฤติกรรมในระดับพิกเซลเหมือนกัน การเลือกขึ้นอยู่กับความสะดวกของเมธอดที่ตั้งชื่อแล้วกับการควบคุมอย่างละเอียดของเคอร์เนลที่ปรับแต่งด้วยมือ

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 แบบดั้งเดิมเคยต้องการ จากนั้นก็เป็นเรื่องของการลองน้ำหนัก ดูผลลัพธ์ และทำซ้ำ