5.26. การค้นหาเส้นและส่วนของเส้น¶
ลักษณะเด่นบางอย่างของฉากไม่ใช่บริเวณที่มีสีต่อเนื่องกัน แต่เป็นขอบตรงที่มีทิศทาง เช่น เส้นสีที่วาดบนพื้น รอยต่อระหว่างสองพื้นผิว ด้านข้างของสี่เหลี่ยมที่พิมพ์ หรือขอบประตู การขอให้ตัวตรวจจับบลอบค้นหาสิ่งเหล่านี้เป็นคำถามที่ผิด -- ขอบกว้างเพียงหนึ่งพิกเซล อัลกอริทึมบลอบต้องการพื้นที่ที่มีสี และผลลัพธ์จะว่างเปล่าหรือมีสัญญาณรบกวน
ตัวตรวจจับที่ถูกต้องสำหรับขอบที่มีทิศทางคือ Hough line transform โมดูล image นำเสนอสองแบบ: find_lines() คืนเส้น อนันต์ (ทุกเส้นขยายไปตลอดทั้งภาพ) find_line_segments() คืนส่วนของเส้น จำกัด (แต่ละเส้นมีจุดปลายภายในเฟรม) แอปพลิเคชันควรเลือกใช้อย่างใดขึ้นอยู่กับว่าขอบที่สนใจต่อเนื่องทั่วทั้งเฟรมหรือครอบคลุมเพียงบางส่วน
5.26.1. หลักการทำงานของ Hough transform¶
ตัวตรวจจับทั้งสองใช้แนวคิดหลักเดียวกัน จึงคุ้มที่จะทำความเข้าใจครั้งเดียว โมดูล image รันตัวกรองขอบแบบ Sobel บนอินพุตก่อนเพื่อให้คะแนนแต่ละพิกเซลว่ามีโอกาสแค่ไหนที่จะอยู่บนขอบที่มีทิศทาง พิกเซลขอบแต่ละพิกเซลจะ โหวต ให้กับทุกเส้นที่อาจผ่านมัน เส้นที่ได้รับโหวตมากที่สุดจะชนะ
เส้นหนึ่งเส้นถูกกำหนดพารามิเตอร์ใน Hough space ด้วยสองตัวเลข: theta คือมุมของเส้น (0 -- 179 องศา) และ rho คือระยะตั้งฉากจากจุดกำเนิดภาพถึงเส้น (มีเครื่องหมาย เป็นพิกเซล) ทุกเส้นที่ภาพมีคือจุดหนึ่งในปริภูมิ (theta, rho) พิกเซลขอบแต่ละพิกเซลในอินพุตให้โหวตหนึ่งโหวตแก่ทุกการรวมกัน (theta, rho) ที่สอดคล้องกับตำแหน่งของมัน -- เชิงแนวคิดคือเส้นโค้งหนึ่งในHough space จุดที่เส้นโค้งหลายเส้นตัดกัน หมายความว่าพิกเซลขอบหลายพิกเซลเห็นด้วยกับเส้นเดียวกัน และจุดตัดนั้นคือการตรวจจับ
ตัวตรวจจับคืนค่าค่าสูงสุดเฉพาะที่ใน Hough space ที่มีผลรวมโหวตเกินค่าขีดแบ่ง แต่ละ Line ที่คืนค่ามาจะมีทั้งสองรูปแบบ: x1, y1, x2, y2 สำหรับรูปแบบจุดปลาย (ตัดให้พอดีกับขอบภาพในกรณีอนันต์) theta, rho สำหรับรูปแบบ Hough และ length กับ magnitude สำหรับขนาดและผลรวมโหวตตามลำดับ
5.26.2. เส้นอนันต์¶
find_lines() รัน Hough transform และคืนเส้นที่แข็งแกร่งที่สุด โดยขยายแต่ละเส้นไปตลอดทั้งภาพ:
lines = img.find_lines(threshold=1500, theta_margin=25, rho_margin=25)
for l in lines:
img.draw_line(l, color=(255, 0, 0))
threshold คือผลรวมโหวตขั้นต่ำสำหรับเส้นที่จะได้รับการยอมรับ ผลรวมโหวตรวมขนาดขอบ Sobel ของทุกพิกเซลที่มีส่วนร่วม ดังนั้นค่า threshold ที่มากขึ้นต้องการขอบที่ยาวขึ้นหรือแข็งแกร่งขึ้นเพื่อผ่าน -- ทำให้ค่าที่เหมาะสมขึ้นอยู่กับความละเอียดภาพ (เส้นยาวกว่าที่ความละเอียดสูงกว่าสะสมโหวตมากกว่า) รวมถึงฉาก จึงต้องปรับสำหรับแอปพลิเคชันเฉพาะ จุดเริ่มต้นคร่าวๆ สำหรับการปรับ: 1000 สำหรับเส้นปานกลางในภาพที่ชัดเจน 500 หรือต่ำกว่าสำหรับความเปรียบต่างอ่อนหรือเส้นสั้น 2000 หรือมากกว่าสำหรับฉากที่วุ่นวายที่เกิด false-positive จากกลุ่มสัญญาณรบกวนขอบ
theta_margin และ rho_margin ควบคุม การรวม ของค่าสูงสุดที่อยู่ใกล้กัน ขอบจริงหนึ่งเส้นก่อให้เกิดกลุ่มเล็กของช่องที่มีโหวตสูงรอบ (theta, rho) จริงของมัน และตัวตรวจจับยุบแต่ละกลุ่มให้เหลือค่าสูงสุดก่อนคืนค่า theta_margin=25 (องศา) รวมจุดสูงสุดที่มีทิศทางภายใน 25 องศา rho_margin=25 (พิกเซล) รวมจุดสูงสุดที่มีระยะภายใน 25 พิกเซล ค่าเริ่มต้นสมเหตุสมผล การเพิ่มค่าจะคืนเส้น น้อยลง ที่แตกต่างกันมากขึ้น การลดค่าจะคืนเส้น มากขึ้น ซึ่งบางครั้งซ้ำกัน
x_stride และ y_stride กำหนดขั้นตอนการสแกนพิกเซลขอบระหว่างการโหวต เช่นเดียวกับที่ขั้นตอนเหล่านี้ทำในพิกเซลของ find_blobs() ค่าเริ่มต้น 2 และ 1 ใช้ได้กับกรณีทั่วไป การเพิ่มค่าจะเร่งการค้นหาแลกกับความละเอียด roi จำกัดการค้นหาเฉพาะบริเวณหนึ่งในเฟรม ซึ่งทั้งจำกัดเส้นที่คืนค่าและลดการคำนวณ
เส้นแต่ละเส้นที่คืนมาสามารถวาดได้โดยตรง: ออบเจกต์ Line ส่งตรงเข้า draw_line() ซึ่งอ่านฟิลด์จุดปลาย (x1, y1, x2, y2) จากด้านหน้า l.theta คือมุมเป็นองศา ซึ่งจำแนกเส้นเป็นแนวนอน แนวตั้ง หรือแนวทแยงได้ด้วยการเปรียบเทียบครั้งเดียว l.magnitude คือผลรวมโหวต ซึ่งเรียงลำดับเส้นที่คืนมาจากแข็งแกร่งที่สุดไปอ่อนที่สุด
5.26.3. ส่วนของเส้น¶
find_lines() เป็นตัวตรวจจับที่ถูกต้องสำหรับขอบที่ขยายไปตลอดทั้งเฟรม แต่ขอบจริงจำนวนมาก -- ด้านซ้ายของบาร์โค้ดที่พิมพ์ ขอบบนสุดของป้าย ด้านที่มองเห็นได้ของไม้บรรทัด -- วิ่งเพียงบางส่วนของภาพเท่านั้น find_line_segments() คืนส่วนของเส้น จำกัด ที่จุดปลายอยู่ภายในเฟรม:
segments = img.find_line_segments(merge_distance=5, max_theta_difference=10)
for s in segments:
img.draw_line(s, color=(0, 255, 0))
ตัวตรวจจับส่วนของเส้นติดตามตามพิกเซลขอบที่มีทิศทางโดยตรง แทนที่จะโหวตใน Hough space และผลลัพธ์คือชุดของช่วงตรงสั้นๆ merge_distance กำหนดช่องว่างพิกเซลสูงสุดที่ช่วงสั้นสองช่วงที่เป็นเส้นตรงเดียวกันสามารถมีและยังรวมเป็นส่วนเดียวที่คืนค่าได้ max_theta_difference กำหนดจำนวนองศาของทิศทางที่การรวมยอมรับระหว่างช่วงที่อยู่ติดกัน การรวมแบบกว้าง (merge_distance=10, max_theta_difference=15) คืนส่วนยาวจำนวนน้อยแลกกับการเชื่อมขอบที่แยกจริงบางครั้ง การรวมแบบเข้มงวด (merge_distance=0, max_theta_difference=5) คืนส่วนสั้นจำนวนมากและให้แอปพลิเคชันจัดการด้วย Python
ออบเจกต์ผลลัพธ์เป็นประเภท Line เดียวกับที่ find_lines() คืนมา พร้อมคุณสมบัติเดียวกัน ดังนั้น pipeline สามารถประมวลผลการตรวจจับทั้งสองประเภทผ่านเส้นทางโค้ดปลายน้ำเดียวกันได้ ความแตกต่างในทางปฏิบัติเพียงอย่างเดียวคือจุดปลายของส่วนเป็นจุดสิ้นสุดจริงของเส้นในภาพ ขณะที่จุดปลายของเส้นอนันต์คือจุดที่เส้นตัดขอบภาพ
5.26.4. เมื่อใดควรใช้แต่ละแบบ¶
การเลือกระหว่างสองวิธีขึ้นอยู่กับคำถามเดียว: แอปพลิเคชันสนใจว่าเส้นหยุดที่ไหนหรือไม่?
find_lines() คือเครื่องมือที่ถูกต้องเมื่อคำตอบคือไม่ หุ่นยนต์ตามเส้นต้องการทราบ ทิศทางที่เส้นมุ่งไป และ ตำแหน่งที่เส้นตัดขอบล่างของเฟรม เส้นนั้นเองวิ่งไปถึงขอบฟ้าและไกลกว่า ตัวตรวจจับขอบฟ้าต้องการขอบที่มีทิศทางแข็งแกร่งที่สุดในภาพ ไม่จำเป็นต้องทราบว่าขอบฟ้าสิ้นสุดที่ไหน
find_line_segments() คือเครื่องมือที่ถูกต้องเมื่อคำตอบคือใช่ การระบุสี่ด้านของสี่เหลี่ยมที่พิมพ์ต้องการสี่ส่วนที่มีจุดปลายที่ทราบ การติดตามนิ้วที่ชี้ไปยังจอแสดงผลหมายถึงการติดตามส่วนสั้นที่จุดปลายเป็นปลายนิ้วและฐานนิ้ว การวัดความยาวของรอยขีดข่วนที่มองเห็นต้องการขอบเขตจริงของส่วนเป็นพิกเซล
ตัวตรวจจับทั้งสองมีข้อจำกัดร่วมกัน: ต้องการ ความเปรียบต่าง ตัวกรองขอบ Sobel ที่ใช้ตอบสนองต่อการไล่ระดับความสว่าง ขอบที่มีสีบนพื้นหลังที่มีความสว่างเท่ากัน (เส้นสีแดงบนผนังสีเขียวที่มีความสว่างเท่ากัน) ไม่ก่อให้เกิดการไล่ระดับและไม่มีการตรวจจับ เมื่อพบกรณีนี้ในทางปฏิบัติ วิธีแก้คือดึงช่อง LAB ช่องเดียวออกมาเป็นภาพระดับสีเทาที่มีความเปรียบต่างที่เหมาะสมก่อนค้นหา -- to_grayscale() พร้อมเลือกช่อง b แยกสีแดงออกจากสีเขียวได้แม้ช่องความสว่างอยู่ในระดับเดียวกัน -- แล้วส่งภาพช่องนั้นให้ตัวตรวจจับเส้น