ㄍ๏ สารบัญ ๏ㄟ
๛ เส้นเค้าโครงคืออะไร
๛ ว่าด้วยเรื่องการประมาณเส้นเค้าโครงโดยเว้นส่วนเส้นตรง
๛ การวาดเส้นเค้าโครงลงบนภาพ
๛ การหาเค้าโครงของภาพสี
๛ ลำดับชั้นของเส้นเค้าโครง
ต่อจาก
บทที่ ๑๒
บทนี้ว่าด้วยเรื่องของการหาเส้นเค้าโครงซึ่งกั้นล้อมแบ่งอาณาเขตภายในภาพ
เส้นเค้าโครงคืออะไร介
เส้นเค้าโครง (contour) คือเส้นที่กั้นระหว่าง ๒ บริเวณที่มีสีแตกต่างกันในภาพ
ยกตัวอย่างเช่นมีภาพง่ายๆที่แบ่งเป็นสีขาวดำแบบนี้อยู่
a13c01.png
เส้นเค้าโครงก็คือเส้นที่แบ่งเขตเชื่อมระหว่างสีขาวกับดำในภาพนี้ เส้นนี้มีความสำคัญ
ถ้าหาเส้นเค้าโครงได้ก็สามารถเอาไปใช้ทำอะไรได้มากมาย ดังที่จะเขียนถึงในหัวข้อต่อๆไป
ใน opencv มีฟังก์ชัน cv2.findContours() ไว้ใช้ค้นหาเส้นเค้าโครงทั้งหมดที่มีอยู่ในภาพให้โดยอัตโนมัติ
ค่าที่ต้องใส่ในฟังก์ชันนี้มีอยู่ ๓ ตัว คือ
ลำดับที่ |
ชื่อ |
ค่าที่ต้องใส่ |
ชนิดข้อมูล |
1 |
image |
อาเรย์รูปภาพ |
np.array |
2 |
mode |
วิธีการกำหนดชั้นของเส้นเค้าโครง |
flag (int) |
3 |
method |
วิธีการประมาณเส้นเค้าโครง |
flag (int) |
อาเรย์ภาพในที่นี้ต้องเป็นภาพขาวดำ ถ้าเป็นภาพสีก็ให้แปลงเป็นขาวดำก่อน
ค่าในอาเรย์อาจเป็นค่าอะไรก็ได้ตั้งแต่ 0 ถึง 255 แต่ cv2.findContours() จะพิจารณาแค่ว่าเป็น 0 (สีดำ) หรือไม่
ดังนั้นจุดแบ่งที่จะถูกตรวจจับก็คือบริเวณที่เปลี่ยนจากสีดำเป็นสีอื่น
ในส่วนของวิธีการกำหนดชั้นของเส้นเค้าโครง ใส่เป็นแฟล็กดังนี้
แฟล็ก |
ค่า |
ความหมาย |
cv2.RETR_EXTERNAL |
0 |
ค้นเอาเฉพาะวงนอกสุด |
cv2.RETR_LIST |
2 |
เอาทุกวงโดยไม่มีการสร้างเป็นลำดับชั้น |
cv2.RETR_CCOMP |
3 |
เอาทุกวงโดยมีการสร้างเป็นลำดับชั้นแต่ไม่ลึกลงไปกว่าสองชั้น |
cv2.RETR_TREE |
4 |
เอาทุกวงโดยพิจารณาลำดับชั้นให้ตัวข้างในเป็นเค้าโครงลูกของตัวนอกไล่ไปเรื่อยๆ |
๓ ตัวหลังต่างก็จะหาเส้นเค้าโครงทั้งหมดเท่าที่จะหาได้ แต่จะแตกต่างตรงลำดับชั้น
ซึ่งเกี่ยวกับเรื่องนี้จะเขียนถึงอีกทีภายหลัง
สำหรับกรณีทั่วไปที่ต้องการหาเส้นเค้าโครงทั้งหมดที่มีโดยไม่สนลำดับชั้นก็เลือกใช้ cv2.RETR_LIST
ส่วนตรงวิธีการประมาณเส้นเค้าโครงนั้นก็ใส่เป็นแฟล็ก ถ้าต้องการให้หาเค้าโครงแบบละเอียดเป็นจุดต่อจุดให้ใส่
cv2.CHAIN_APPROX_NONE แต่ในกรณีทั่วไปจะใช้ cv2.CHAIN_APPROX_SIMPLE ซึ่งหมายถึงประมาณเอาเท่าที่จำเป็น
ฟังก์ชันนี้จะคืนค่ามาให้ ๒ ตัว ตัวแรกคือข้อมูลของเส้นเค้าโครงที่หามาได้
ส่วนตัวหลังคือข้อมูลเรื่องลำดับชั้นของแต่ละเส้นเค้าโครง
ยกตัวอย่างการใช้โดยสร้างภาพแบบง่ายๆที่แค่มีสี่เหลี่ยมอยู่ตรงกลางภาพ
แล้วลองใช้ฟังก์ชัน cv2.findContours() เพื่อหาเค้าโครงดู
ผลลัพธ์ที่ได้ จะได้ข้อมูลของเส้นเค้าโครงมาดังนี้
ซึ่งจะได้เป็นลิสต์ของอาเรย์สามมิติซึ่งแสดงตำแหน่งจุดของเส้นเค้าโครง
ในที่นี้มีเส้นเค้าโครงเดียวเลยได้มาอาเรย์เดียว
มิติแรกของอาเรย์จะมีค่าเท่ากับจำนวนจุด ในที่นี้เป็นสี่เหลี่ยมจึงมี ๔ จุด ส่วนมิติที่ ๒ จะเป็น ๑ เสมอ และมิติที่ ๓
จะเป็น ๒ ค่าซึ่งแสดงถึง
ส่วนค่าคืนกลับตัวหลังคือ
ลำดับชั้น (hierarchy) ของเส้นเค้าโครง ตอนนี้จะขอละไว้ก่อนเพราะมีรายละเอียดมาก
จะเขียนถึงในหัวข้อถัดไป
ลองใส่สีที่ตำแหน่งแต่ละจุดของเส้นเค้าโครงเพื่อแสดงให้เห็นดูชัดว่าจุดมองของเค้าโครงคือตรงไหน
จะเห็นว่าจุดอยู่ที่ทั้ง ๔ มุมของของสี่เหลี่ยม
คราวนี้ลองหาเส้นเค้าโครงของภาพในตัวอย่างด้านบนสุดนั่น วาดจุดลงบนเส้น
เนื่องจากเต็มไปด้วยส่วนโค้งมาก จะได้เค้าโครงที่มีจุดอยู่มากมาย ในที่นี้มี ๓๖๒ จุด โดยล้อมอยู่รอบๆเค้าโครง
เว้นแค่ตรงที่เป็นเส้นตรงยาวๆ
ว่าด้วยเรื่องการประมาณเส้นเค้าโครงโดยเว้นส่วนเส้นตรง介
ดังที่ได้กล่าวถึงไปในตัวอย่างที่แล้วว่าวิธีการประมาณเส้นเค้าโครงมี cv2.CHAIN_APPROX_NONE กับ
cv2.CHAIN_APPROX_SIMPLE
ในที่นี้จะแสดงตัวอย่างเพื่อให้เห็นความแตกต่างชัดเจน โดยลองสร้างอาเรย์ภาพสี่เหลี่ยมง่ายๆแบบตัวอย่างที่แล้วขึ้นแล้วใช้
cv2.findContours() โดยใช้ทั้ง ๒ วิธีนี้เทียบกัน
ผลที่ได้จะเห็นว่า cv2.CHAIN_APPROX_SIMPLE จะมีแค่ ๔ จุด ก็คือที่มุมของสี่เหลี่ยม ในขณะที่ cv2.CHAIN_APPROX_NONE
จะได้จุดทั้งหมดที่อยู่รอบเส้นเค้าโครง จึงเป็น ๓๖ จุด
ในกรณีส่วนใหญ่มักไม่ได้จำเป็นต้องใช้จุดทั้งหมด เพราะถ้าเจอเส้นตรงยาวก็สามารถละได้ ดังนั้นจึงเลือก
cv2.CHAIN_APPROX_SIMPLE ก็พอ
การวาดเส้นเค้าโครงลงบนภาพ介
ใน opencv มีฟังก์ชัน cv2.drawContours() ซึ่งใช้สำหรับนำเอาข้อมูลเส้นเค้าโครงที่ได้มาจาก cv2.findContours()
มาวาดเป็นเส้นใส่ลงบนภาพ
ค่าที่ต้องใส่ในฟังก์ชันนี้
ลำดับที่ |
ชื่อ |
ค่าที่ต้องใส่ |
ชนิดข้อมูล |
1 |
image |
อาเรย์รูปภาพ |
np.array |
2 |
contours |
ลิสต์ของข้อมูลเส้นเค้าโครง |
list ของ np.array |
3 |
contourIdx |
ลำดับของเส้นเค้าโครงที่จะใช้ |
int |
4 |
color |
สี |
tuple ของ int |
5 |
thickness |
ความหนาของเส้นที่จะวาด (สามารถละได้ ถ้าละจะหนาเป็น 1 พิกเซล) |
int |
ขอใช้ภาพนี้เป็นตัวอย่าง
a13c06.png
นำภาพมาหาเส้นเค้าโครงแล้วนำมาวาดเส้นเค้าโครงทุกเส้นด้วยสีต่างกัน
a13c07.png
ในที่นี้ใช้ฟังก์ชัน plt.get_cmap() ของ matplotlib เพื่อสร้างสี รายละเอียดวิธีใช้อ่านได้ใน
numpy & matplotlib บทที่ ๒๔
ในตัวอย่างนี้ได้เส้นเค้าโครงออกมาทั้งหมด ๙ เส้น เนื่องจากด้านในมีส่วนสีดำเล็กๆน้อยแทรกอยู่
ทุกซอกหลืบเหล่านี้ก็นับด้วยเช่นกัน
การหาเค้าโครงของภาพสี介
ถ้ามีภาพสีที่ดูแล้วมีขอบเขตที่แน่นอนก็สามารถนำมาหาเค้าโครงแบ่งภาพได้เช่นกัน โดยแปลงเป็นขาวดำ แล้วใช้ cv2.threshold()
เพื่อทำให้เหลือแต่ 0 กับ 255 นำภาพนี้มาหาเค้าโครง
จากนั้นเมื่อได้เค้าโครงแล้วก็นำไปใช้วาดลงบนภาพเก่าที่เป็นภาพสีได้
ตัวอย่าง
miku13c01.jpg
miku13c02.jpg
ผลที่ได้ก็จะได้เส้นเค้าโครงออกมา แต่ก็จะเห็นว่าประกอบด้วยจุดเล็กน้อยเส้นย่อยซอกหลืบด้านใน
ซึ่งบางครั้งเราอาจไม่ได้ต้องการด้วย
เพื่อที่จะเอามันออกไป วิธีหนึ่งที่นิยมใช้ก็คือแปลงสัณฐาน cv2.morphologyEx() ด้วยโหมด MORPH_CLOSE
วิธีนี้จะช่วยลบจุดดำเล็กๆด้านในไปได้
ลองแปลงสัณฐานแล้วหาเส้นเค้าโครงใหม่เหมือนเดิม
miku13c03.jpg
คราวนี้เส้นเค้าโครงตามซอกเล็กซอกน้อยหายไป ดูแล้วเรียบกว่าเดิม
จากนั้นอาจลองนำเค้าโครงมาวาดด้วย matplotlib โดยใช้ plt.Polygon()
ลำดับชั้นของเส้นเค้าโครง介
ดังที่ได้กล่าวไปข้างต้นแล้วว่าฟังก์ชัน cv2.findContours()
นอกจากจะคืนข้อมูลตำแหน่งจุดของเส้นเค้าโครงมาแล้วก็ยังให้ข้อมูลลำดับชั้นของแต่ละเค้าโครงมาด้วย
โดยจะให้มาเป็นค่าคืนกลับตัวที่ ๒ ของฟังก์ชัน
ข้อมูลลำดับชั้นจะเป็นตัวเลข ๔ ตัว ซึ่งเป็นดัชนีของเส้นเค้าโครงตัวอื่น ซึ่งบอกถึง [ตัวถัดไป, ตัวก่อนหน้า,
ตัวลูกตัวแรก, ตัวพ่อแม่]
เลขลำดับนั้นจะไล่ตั้งแต่ 0 ไป ถ้าส่วนประกอบไหนขาดไปจะเป็น -1
ลองสร้างรูปง่ายๆที่มีสี่เหลี่ยมซ้อนกันหลายชั้นขึ้นมา
a13c08.png
ในภาพนี้ประกอบด้วยกรอบสี่เหลี่ยม ๖ อัน ถ้าหาเค้าโครงก็จะได้มา ๖ ตัว
ลองหาเค้าโครงดูโดยเลือกวิธีการกำหนดชั้นของเส้นเค้าโครงเป็น cv2.RETR_TREE แล้วดูข้อมูลลำดับชั้น
จะได้อาเรย์ขนาด 6×4 เป็นข้อมูลลำดับชั้นของเส้นเค้าโครงทั้ง ๖ ตัว
แถวแรกคือตัวนอกสุด [-1 -1 1 -1] จะเห็นว่าไม่มีทั้งตัวลำดับก่อนหน้าและหลังและไม่มีตัวพ่อแม่ด้วยเพราะเป็นตัวนอกสุดแล้ว
จึงเป็น -1 ส่วนตัวลูกตัวแรกคือหมายเลข 1 นั่นหมายถึงตัวถัดไป
ตัวที่แถวถัดมา [ 3 -1 2 0] แสดงให้เห็นว่ามีตัวลูกอยู่ คือตัวหมายเลข 2 และตัวพ่อแม่คือตัวหมายเลข 0 คือตัวนอกสุด
ที่เหลือก็ไล่ไปเรื่อยๆก็จะเห็นภาพและเข้าใจความหมาย เห็นความสัมพันธ์ของแต่ละตัวได้
หากเปลี่ยนเป็น cv2.RETR_LIST ผลจะกลายเป็นแบบนี้
จะเห็นว่าเลข ๒ ตัวหลังเป็น -1 หมด เพราะไม่มีการสร้างความสัมพันธ์พ่อแม่ลูกขึ้น แต่ละตัวถือว่าแยกจากกันไม่ได้ซ้อนกัน
ถือว่าอยู่ชั้นที่ ๑ หมด
ถ้าใช้เป็น cv2.RETR_CCOMP
ในที่นี้ตัวหมายเลข 3 คือตัวเค้าโครงนอกสุด ส่วนตัวหมายเลข 4 และ 5 คือช่องสีดำที่ซ้อนอยู่ในนั้น ส่วนอีก ๓ ตัวที่เหลือแม้จะอยู่ด้านในลงไปอีกแต่จะถือว่าไม่มีการสร้างลำดับชั้น
ถ้าเป็น cv2.RETR_EXTERNAL จะได้
คือจะได้มาเฉพาะตัวที่อยู่นอกสุดเท่านั้น ซึ่งมีตัวเดียว เหมาะจะใช้เวลาที่สนใจเฉพาะโครงด้านนอกสุดจริงๆ
อ่านบทถัดไป >>
บทที่ ๑๔