จากตอนก่อนๆ เรื่องเกี่ยวกับการ detect edge โดยใช้ canny ขั้นตอนต่อไปคือการรวมจุดที่ประกอบเป็น edge นั้นรวมกัน เรียกว่า contour แน่นอน OpenCV มีฟังก์ชันสำหรับหา contour ไว้ให้
ก่อนอื่นมาดูนิยามของ contour ก่อน contour คือ list ของจุดที่แทน curve ในรูปภาพ ในกรณีของ Contour ใน OpenCV จะเก็บไว้ใน sequence ลองดูภาพประกอบ
ในภาพบนเป็นภาพที่เราต้องการหา contour ซึ่งประกอบด้วยส่วน A-E ส่วนด้านล่างคือ contour ที่ OpenCV หามาได้ (โดยฟังกชัน cvFindContours) ซึ่งกำกับไว้ด้วยคำว่า cX หรือ hX โดย c หมายถึง contour, h หมายถึง hole ตัวที่เป็นเส้นประ เป็น exterior boundaries ของพื้นที่สีขาว (ที่ไม่ใช่ 0 ก็คือกรอบของพื้นที่ขาว) ตัวเส้นจุดนั้นเป็น interior boundaries ของพื้นที่สีขาว หรือ exterior boundary ของพื้นที่ดำ (คืออยู่ในพื้นที่ขาว, หรือว่าเป็นกรอบของพื้นที่สีดำ) ซึ่ง OpenCV จะแยกความแตกต่างระหว่างสองตัวนี้
*notes ในความเป็นจริงระหว่างภาพ binary (ภาพขาวดำ) และ edge ที่ detect ได้จาก canny จะมีความแตกต่างกันเล็กน้อยในผลลัพธ์ เพราะจริงๆ แล้ว cvFindContours ไม่รู้จักว่า edge คืออะไร แต่จะมองเห็นเป็นพื้นที่สีขาวบางๆ (ไม่ได้เข้าใจว่ามันเป็น edge แต่อย่างไร) ผลลัพธ์ที่ได้ก็คือ ใน edge วงรอบปิดหนึ่งๆ จะได้ exterior contour และ interior contour มาสองอันที่ขนาดใกล้กันมาก
ลองมาดูฟังก์ชันกันซะที
int cvFindContours(
IplImage* img,
CvMemStorage* storage,
CvSeq** firstContour,
int headerSize = sizeof(CvContour),
CvContourRetrievalMode mode = CV_RETR_LIST,
CvChainApproxMethod method = CV_CHAIN_APPROX_SIMPLE
);
img คือรูปภาพซึ่ง OpenCV จะใช้พื้นที่นี้ในการคำนวณด้วย ถ้าต้องการเก็บข้อมูลส่วนนี้ให้ copy ไว้ก่อน
storage เป็น memory storage ที่เราจองไว้
firstContour เป็นผลลัพธ์ที่ได้จากฟังก์ชัน
headerSize ใช้ในการบอกถึงขนาด object ที่จะ allocate ปกติเป็น sizeof(CvContour) หรือ sizeof(CvChain)
mode มีตัวเลือกอยู่ 4 ตัว
- CV_RETR_EXTERNAL จะหาเฉพาะ extrme outer contours
- CV_RETR_LIST หาทุก contour และแทนด้วย list
- CV_RETR_CCOMP หาทุก contour และแทนด้วยต้น list สอบระนาบ ระนาบบนมีแต่ cX ระนาบล่างเป็น hX
- CV_RETR_TREE หาทุก contour และจัดเรียงเป็น tree
ลองดูตัวอย่างผลที่ได้จากการหา contour ในรูปแรกกัน
ตัวเลือกเหล่านี้จะบอกว่าเราต้องการหา contour แบบไหนและผลลัพธ์แบบไหน ซึ่งจริงๆ วิธีการลิงก์ linked list ของ sequence ด้วย h_prev, h_next, v_prev, v_next นั้นกำหนดได้จาก mode ในการหา contour
method คือวิธีการประมาณค่า contour
- CV_CHAIN_CODE ผลลัพธ์ contour จะอยู่ในรูปแบบ Freeman chain code วิธีอื่นจะเป็น polygons (sequence of vertics)
- CV_CHAIN_APPROX_NONE แปลง chian code ให้เป็น จุด
- CV_CHAIN_APPROX_SIMPLE
- CV_CHAIN_APPROX_TC89_L1 CV_CHAIN_APPROX_TC89_KCOS ใช้วิธี Teh-Chin approximation algorithm
- CV_LINK_RUN method นี้จะใช้ได้กับ mode CV_RETR_LIST เท่านั้น
ซึ่งวิธีการหา contour นอกจาก cvFindContours แล้ว ยังสามารถใช้กลุ่มฟังก์ชันเหล่านี้ในการหา contour ได้
- cvStartFindContours
- cvFindNextContour
- cvSubstituteContour
- cvEndFindContour
- cvApproxChains
แนวคิดของวิธีนี้คือเราจะไม่หา contour ออกมาทีเดียว เราจะออกมาทีละตัว โดยตัวแรกเราจะใช้คำสั่ง cvStartFindContours ซึ่งจะให้ผลลัพธ์มาเป็น CvSequenceScanner (คล้ายๆ กับการ เปิด file ก่อนด้วย fopen)
ซึ่งใน CvSequenceScanner เก็บ state เกี่ยวกับ contour ที่เราหาไปแล้ว (อย่าสับสนระหว่าง CvSequenceScanner กับ CvSeqReader, CvSeqReader ไว้ใช้อ่าน elements ที่เรียงเป็น sequence แต่ CvSequenceScanner เป็น list ของ contour (ซึ่งเป็น list ของ sequece อีกที)
เราสามารถหา contour ต่อไปได้เรื่อยๆ ด้วยคำสั่ง cvFindNextContour จนกว่าจะเจอค่า NULL หรือสั่งจบการหาเองด้วย cvEndFindContour (เหมือน fclose)
ฟังก์ชัน cvSubstituteContour ใช้ในการแทน contour ในตำแหน่งที่ปัจจุบันด้วย contour ใหม่ หรือจะใช้ลบ contour ปัจจุบันอยู่ก็ได้ (โดยใช้ค่า NULL ไปใน contour ที่จะแทนที่)
ฟังก์ชัน cvApproxChains ใช้ในการแปลง Freeman chians ไปเป็น polygon (จริงๆ มีการประมาณค่าด้วย)
Drawing Contours
ฟังก์ชันในการวาด contour คือ
void cvDrawContours(
CvArr* img,
CvSeq* contour,
CvScalar external_color,
CvScalar hole_color,
int max_level,
int thickness = 1,
int line_type = 8,
CvPoint offset = cvPoint(0,0)
);
img คือ image ที่จะวาด contour
contour คือ root node ของ contour tree
external_color, hole_color เป็นสี่ที่จะใช้ fill ระหว่าง external กับ hole
max_level จะเป็นการสั่งให้วาด contour ใน contour tree เท่ากับจำนวนชั้นที่กำหนด(ในกรณีของ CV_RETR_LIST มีชั้นเดียว ค่า 0 ก็พอ ในกรณี CV_RETR_CCOMP มีสองชั้นถ้าใส่หมดก็จะต้องให้ค่าเป็น 1 ยังมีค่าที่ติดลบสำหรับใช้ในกรณี CV_RETR_CCOMP และ CV_RETR_TREE)
thickness คือความกว้างของเส้นที่จะวาด ถ้าให้ค่าเป็น
line_type ประเภทของเส้นที่จะวาด
offset ใช้ในการกำหนดตำแหน่งการวาดแทนที่จะวาดเป็น absolute coordinate สามารถใช้ได้ในกรณีที่เรา หา contour โดยใช้ ROI