Saturday, April 17, 2010

Learning OpenCV: Delaunay Triangulation, Voronoi Tesselation

Delaunay triangulation

เป็นวิธีการเชื่อมจุดใน space ให้เป็นสามเหลี่ยม (เรียกวา่ Delaunay) โดยคุณสมบัติคือ มุมที่น้อยที่สุดในสามเหลี่ยมทั้งหมดจะมีค่ามากสุด(ก็คงพยายามทำให้เป็ฯสามเหลี่ยมด้านเท่า เท่าที่เป็นไปได้) และ circum-circle( หมายถึงวงกลมที่ล้อมรอบสามเหลี่ยม) ทุกอันจะต้องไม่มี จุด อยู่ภาพในวงกลม(ไม่งั้นก็หมายความว่า รูปนี้สามารถสร้างสามเหลี่ยมเล็กลงไปได้อีก)

จริงๆ สองส่วนนี้สัมพันธ์กันในลักษณะที่ว่า ถ้ามีสามเหลี่ยมมี่ผอมๆ (มีมุมหนึ่งน้อยๆ imply ว่าต้องมีอย่างน้อยอีกมุมมากๆ) จะทำให้ จุดศูนย์กลางของ circum-circle อยู่นอกสามเหลี่ยมนั้น ความเป็นจริงต้องไม่มีมุมใดมีขนาดถึง 90 องศา ลองดูภาพ circumcenter (จุดศูนย์กลางภาพ) ในกรณีต่างๆ




ซ้ายเป็นภาพที่ไม่มีมุมไหนถึง 90 องศา กลางภาพสามเหลี่ยมมุมฉาก ขวาสามเหลี่ยมมุมป้าน


วิธีคำนวณหาสามเหลี่ยม Delaunay วิธีหนึ่งคือ

  1. เริ่มจากการกำหนดจุดนอก space นี้แล้วสร้างสามเหลี่ยมโดยร่วมกับอีกสองจุดใน space
  2. เพิ่มจุดใน space ไปในกลุ่มสามเหลี่ยมที่เราสร้างแล้ว (ในตอนแรกจะมีสามเหลี่ยมเดียวที่มีจุดจากนอก space มาด้วย) คำนวณหา circum-circle ทุกอันว่ามีอันไหนที่ล้อมรอบจุดใหม่นี้ ถ้ามี สามเหลี่ยมนั้นไม่ใช่ (ตามที่ได้อธิบาย) ลบสามเหลี่ยมนั้นทิ้ง
  3. จากขั้นที่สองสร้างสามเหลี่ยมใหม่ จากจุดที่เพิ่มเข้าไปในขั้นสอง และจุดที่โดนลบสามเหลี่ยมทิ้งไป
  4. ไปยังข้อสองจนกว่าทุกจุดใน space จะถูกเพิ่มเข้าไปใน สามเหลี่ยม Delaunay 

ในพื้นที่ระหว่างจุดใน space เราสามารถหาได้ว่ามันอยู่ในอิทธิพลของจุดใด (ในหนังสือเรียก owned by) เราจึงสามารถแบ่ง partition ของ space ที่ owned โดยจุดได้ partition นั้นเรียกว่า Voronoi tessellation



ภาพซ้ายคือ Delaunay triangle ที่มี circum-circle และ circumcenter
ภาพขวาคือ Voronoi tessellation

จากรูปจะเห็นได้ว่า Voronoi tessellation เกิดจากการเชื่อมต่อ circumcenter เข้าด้วยกัน ซึ่งก็สมเหตุผล เพราะ เราอาจจะมอง ว่า circumcenter เป็นส่วนในวงกลม ที่มีจุดศูนย์กลางที่จุดต่างๆ ก็ได้ ดังนั้นเส้นที่ลากระหว่าง circumcenter ก็อาจจะมองได้ว่าเป็นเส้นที่ลากเชื่อมระหว่างจุดตัดของวงกลมของวง ซึ่งเส้นนี้จะอยู่ห่างจากจุดศูนย์กลางของวงกลมเ่ท่ากันทั้งสองวง  จึงสามารถนำเส้นเชื่อมระหว่าง circumcenter มาใช้ในการแบ่งเขตของ จุดใน space ได้


Creating a Delaunay or Voronoi Subdivision

มาดูวิธีการสร้าง Delaunay Subdivision กัน


CvRect rect = { 0, 0, 600, 600 };                //Our outer bounding box
CvMemStorage* storage;                        //Storage for the Delaunay subdivsion
storage = cvCreateMemStorage(0);        //Initialize the storage
CvSubdiv2D* subdiv;                              //The subdivision itself
subdiv = init_delaunay( storage, rect); //See this function below


ขั้นแรกคือการกำหนดพื้นที่สี่เหลี่ยมกัน (อย่าลืมว่า OpenCV จะสร้างสามเหลี่ยมโดยเริ่มจาก external vertex)
สร้าง memory storage และใช้คำสั่ง init_delaunay ในการสร้าง CvSubdiv2D ขึ้นมา


หลังจากนั้นก็เพิ่มจุดเข้าไป


CvPoint2D32f fp;             //This is our point holder
for( i = 0; i < as_many_points_as_you_want; i++ ) {
     // However you want to set points
    //
    fp = your_32f_point_list[i];
    cvSubdivDelaunay2DInsert( subdiv, fp );
}

เราสามารถสร้างข้อมูล Voronoi Tesselation ใน cvSubdiv2D ได้โดยคำสั่ง cvCalcSubdivVoronoi2D และลบข้อมูลเหล่านั้นโดยคำสั่ง cvClearSubdivVoronoi2D


Navigating Delaunay Subdivisions

หลังจากได้ Delaunay Subdivisions แล้วเราคงต้องการจะทำอะไรสักอย่างกับทุก edge (โดยการ navigate)

structure cvQuadEdge2D จะเก็บ edge ของ Delaunay และ Voronoi (ต้องสร้างก่อนด้วยคำสั่ง cvCalcSubdivVoronoi2D ) ลองดูรูป


e คือ edge ของ Delaunay ที่จะเก็บ เส้นประคือ voronoi edge



ส่วน structure CvSubdiv2DPoint จะเก็บ  Delaunay edge กับ associate point

e คือ Delaunay edge ที่เริ่มที่จุดที่มีสี่เหลี่ยม

Walking on edges

จากรูปที่สามเมื่อเรามีข้อมูล input edge เราสามารถหา edge ที่เหลือทั้งสี่ได้โดยใช้คำสั่ง


CvSubdiv2DEdge cvSubdiv2DRotateEdge(
    CvSubdiv2DEdge edge,
    int type
);

edge คือ input edge
type

  • 0 หมายถึง input edge (e)
  • 1 หมายถึง rotated edge (eRot)
  • 2 หมายถึง reversed edge (reversed e)
  • 3 หมายถึง reversed rotated edge (reversed eRot)
หรือจะใช้้คำสั่ง cvSubdiv2DGetEdge  เพื่อหา edge ที่ associate กับ input edge (ดูรูปที่ 4 ประกอบ)

CvSubdiv2DEdge cvSubdiv2DGetEdge(
    CvSubdiv2DEdge edge,
    CvNextEdgeType type
); 

edge คือ input edge
type 
  • CV_NEXT_AROUND_ORG edge ถัดจากจุดกำเนิด (eOnext)
  • CV_NEXT_AROUND_DST edge ถัดจากจุดปลาย (eDnext)
  • CV_PREV_AROUND_ORG edge ก่อนจุดกำเนิด (reversed eRnext)
  • CV_PREV_AROUND_DST edge ก่อนจุดปลาย (reversed eLnext)
  • CV_NEXT_AROUND_LEFT edge ถัดไปใน left facet (eLnext)
  • CV_NEXT_AROUND_RIGHT edge ถัดไปใน right facet (eRnext)
  • CV_PREV_AROUND_LEFT edge ก่อนหน้าใน left facet (reversed eOnext)
  • CV_PREV_AROUND_RIGHT  edge ก่อนหน้าใน left facet (reversed eDnext)

เราสามารถหาจุดใดๆ จาก edge โดยคำสั่ง
CvSubdiv2DPoint* cvSubdiv2DEdgeOrg( CvSubdiv2DEdge edge );
CvSubdiv2DPoint* cvSubdiv2DEdgeDst( CvSubdiv2DEdge edge );

(เราอาจจะต้องแปลง จุดเหล่านี้ให้อยู่ในรูปที่เข้าใจง่าย  cvPointForm32f(output_Org->pt)





ก่อนอื่นที่จะ travel ไปใน edge หรือจุดต่างๆ บน subdivision ได้ ก่อนอื่น ต้องหาจุด หรือ edge แรกก่อน
วิธีแรกใช้ฟังก์ชัน

CvSubdiv2DPointLocation cvSubdiv2DLocate(
    CvSubdiv2D* subdiv,
    CvPoint2D32f pt,
    CvSubdiv2DEdge* edge,
    CvSubdiv2DPoint** vertex = NULL
);

subdiv คือ subdivision ที่จะหา edge
pt คือจุดใดๆ

ผลลัพธ์ที่ได้มีดังนี้


  • CV_PTLOC_INSIDE pt อยู่ใน จะคืนค่า edge หนึ่งใน facet กลับมาใน พารามิเตอร์ edge.
  • CV_PTLOC_ON_EDGE pt อยู่ใน edge จะคืนค่า edge กลับมาใน พารามิเตอร์ edge
  • CV_PTLOC_VERTEX pt เป็น vertex  จะคือนค่า vertex กลับมาในพารามิเตอร์ vertex
  • CV_PTLOC_OUTSIDE_RECT pt อยู่นอกกอรบ
  • CV_PTLOC_ERROR เกิดข้อผิดพลาดใน subdiv หรือ pt

วิธีที่สอง
เนื่องจากเราสร้าง Delaunay ด้วยการกำหนดจุด external มาก่อน (เพื่อสร้าง external triangle) เราสามารถเข้าถึงข้อมุลเหล่านี้ได้
วิธีหาค่า vertices ของ external triangle

CvSubdiv2DPoint* outer_vtx[3];
    for( i = 0; i < 3; i++ ) {
        outer_vtx[i] = (CvSubdiv2DPoint*)cvGetSeqElem( (CvSeq*)subdiv, I );
}


วิธีหาค่า edge  ของ external triangle

CvQuadEdge2D* outer_qedges[3];
    for( i = 0; i < 3; i++ ) {
        outer_qedges[i] = (CvQuadEdge2D*)cvGetSeqElem( (CvSeq*)(my_subdiv->edges), I );
}



Learning OpenCV: Inpainting and Mean-shift segmentation

Inpainting

เป็นวิธีในการกู้ image ที่เกิด noise เช่น ฝุ่นในภาพถ่าย


void cvInpaint(
    const CvArr* src,
    const CvArr* mask,
    CvArr* dst,
    double inpaintRadius,
    int flags
);

src คือ image ต้นฉบับ
mask ระบุถึงตำแหน่งที่ damaged ใน image (ตำแหน่งที่ต้องการซ่อม)
dst คือ image ผลลัพธ์
inpaintRadiuse กำหนดความขนาดพื้นที่รอบๆ จุดที่ต้องการ inpaint (ถ้ากว้างมากจุดจะโดนเฉลี่ยมากทำให้เกิดการ blur ได้)
flags คือวิธีการใน inpanting เลือกได้ระหว่า

  • CV_INPAINT_NS(Navier-Stokes method)
  • CV_INPAINT_TELEA (A. Telea’s method)
Mean-Shift Segmentation

ในตอนก่อนๆ ได้อธิบายเรื่องการ segmentation โดยใช้ image pyramids (cvPyrSegmentation) วิธีนี้จะใช้ color merge เพื่อ segment image. วิธีนี้จะใช้การ minimizing energy ของ image (energy กำหนดโดย link strength ซึงกำหนดด้วย ความเหมือนของสี อีกที)  cvPyrMeanShiftFiltering ก็ใช้แนวคิดนี้ในการทำ color segmentation ภาพ


void cvPyrMeanShiftFiltering(
    const CvArr* src,
    CvArr* dst,
    double spatialRadius,
    double colorRadius,
    int max_level = 1,
    CvTermCriteria termcrit = cvTermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS,5,1)
);

src คือ image ต้นฉบับ
dst คือ image ผลลัพธ์
saptialRadius, colorRadius คือรัศมีสำหรับ spatial และ color ตามลำดับ
max_level คือ level ในการทำ pyramid scale
termcrit  เป็น iterative algorithm ที่กำหนดว่าการ iteration จะหยุดอย่างไรลองดูการประกาศ

cvTermCriteria(
    int type; // CV_TERMCRIT_ITER, CV_TERMCRIT_EPS,
    int max_iter,
    double epsilon
);

int คือเป็นตัวบอกว่า iteration จะหยุดใด
  • CV_TERMCRIT_ITER จะหยุดเมื่อ iteration จนครบจำนวนครั้ง ค่านี้กำหนดใน max_iter
  • CV_TERMCRIT_EPS จะหยุดเมื่อ convergence metrix มีค่าเล็กลงถึงขนาดหนึ่ง กำหนดค่านี้ใน epsilon 
ค่า CV_TERMCRIT_ITER กับ CV_TERMCRIT_EPS สามารถใช้ด้วยกันได้



กลับมาที่ cvPyrMeanShiftFiltering กันต่อ ในกรณีที่ข้อมูลเราอยู่ในระนาบ x,y และ r,g,b  mean-shift จะ  หา ก้อนของข้อมูล(segment) ที่รวมกันอยู่อย่างหนาแน่นโดยกา้ร scan ตัว window ไปทั่ว space แต่ range ของ spatial กับ range ของ color จะแตกต่างกัน  ดังนั้น mean-shift ต้องการรัศมีที่ต่างกันในการ scan ระหว่าง spatial space (กำหนดใน spatialRadius) และ color space (colorRadius) ตลอดทางที่ mean-shift window เคลื่อนที่ (scan) จุดที่ converge (โดยสี) ไปยัง peak จะถูกจัดอยู่ในกลุ่มเดียว (owned by) กับ peak นั้นๆ

จุด peak และส่วนที่ owned by peak นั้นๆ จะถูกนับเป็น segment เดียวกัน จริงๆ แล้วการทำ color clustering จะทำใน scale pyramids (จากคำสั่ง cvPyrUp, cvPyrDown) ดังนั้น max_level จะเป็นตัวกำหนดจำนวน level ในการทำ image pyramids 

Learning OpenCV: Watershed Algorithm

ก่อนที่จะเข้าเรื่อง segmentation ที่เกี่ยวกับภาพเคลื่อนไหวมาเข้าเรื่องการ segmentation ที่ไม่ต้องใช้ภาพจากหลายๆ เวลากันก่อน


Watershed Algorithm
เป็นวิธี segmentation ที่ไม่ได้ใช้ concept ของการ remove background (เพราะในบางครั้งมันไม่มีหรือว่าสำคัญเท่ากันหมด)  คำอธิบายในหนังสืออ่านแล้วก็ไม่สื่อเท่าไร ลองไปค้นมาจาก wiki ก็ได้ความถึง กระบวนการของ watershed คือ เราสามารถมอง ภาพ intensity image(ก็ gray scale นั่นแหละ) ให้เปรียบเสมือนแผนที่ในโหมด topography (ไอ้โหมดที่สีเข้มแทนที่สูง สีน้ำเิงินแทนน้ำทะเล) เราก็จะเห็นภาพเปรียบเสมือนภูเขาและแอ่งน้ำได้ ทีนี้เราก็กำหนดจุด fill ซึ่งก็มีหลายวิธี ในหน้งสือจะอธิบายวิธี fill โดยใช้ flooding ง่ายๆ คือกำหนดจุดปล่อยน้ำให้น้ำไหลจากที่สูงไปที่ต่ำแล้วก็ขังเกิดเป็นลุ่มน้ำ (watershed) ซึ่งลุ่มน้ำนี้อาจจะถือได้ว่าเป็นการ segment ประเภทหนึ่ง (background)

 watershed ยังอนุญาติให้ ผู้ใช้คือ algorithm อื่นกำหนด background และ บอกว่า พื้นที่เหล่านี้เป็นพื้นที่ๆ เดียวกัน(ในกรณีที่มี noise แยกพื้นที่สองที่ๆ ควรจะเป็นที่เดียวกันออก)





ในภาพซ้ายเราเส้นสีขาวเป็นตัวบอกว่าพื้นที่ๆ ต่อกันด้วยเส้นเป็นพื้นที่เดียวกัน ผลลัพธ์ที่ได้ออกมาดังภาพขวา


void cvWatershed(
    const CvArr* image,

    CvArr* markers
);

image เป็นภาพที่ต้องการทำ watershed
markers มีขนาดเท่า image และมีค่าเป็น 0s ยกเว้นจะต้องการจะบอก cvWatershed ว่าพื้นที่ในส่วนต่างๆ เป็นพื้นที่เดียวกัน โดยกำหนดค่า ตัวเลขใดๆ ใน maskers ตัวเลขที่เท่ากันจะบอกถึงความเป็นพื้นที่เดียวกัน

Learning OpenCV: Contour Convexity and Pairwise Geometrical Histograms

ในตอนนี้จะรวมสองเรื่องที่เหลือในการ matching contour


Contour Convexity and Convexity Defects
เป็นวิธีในการหา convex hull (เปลือกที่นูน น่าจะหมายถึงเส้นรอบของ วัตถุที่ไม่มีส่วนเว้าเข้า) ของวัตถุ หลังจากนั้นคำนวณหา convexity defects เราจะนำ convexity defects เหล่านี้มาเป็นส่วนหนึั่งของ characteristic ของ complex object ได้

จากรูปมือที่่เห็น เส้นกรอปนอก(สีดำ)คือ convex hull พื้นที่ ตั้งแต่ a-h คือ convexity defects

ฟังก์ชันที่ใช้ในการคำนวณ convexity defects มีสามตัวคือ

#define CV_CLOCKWISE 1
#define CV_COUNTER_CLOCKWISE 2

CvSeq* cvConvexHull2(
    const CvArr* input,
    void* hull_storage = NULL,
    int orientation = CV_CLOCKWISE,
    int return_points = 0
);

int cvCheckContourConvexity(
    const CvArr* contour
);

CvSeq* cvConvexityDefects(
    const CvArr* contour,
    const CvArr* convexhull,
    CvMemStorage* storage = NULL
);


คำสั่ง cvConvexHull2 จะเป็นการสร้าง hull ของ contour คำสั่งที่สองจะเป็นการตรวจสอบว่า hull นั้น convex หรือไม่ คำสั่งสุดท้ายจะเป็นการหา convexity defects จาก contour และ convex hull

cvConvexHull2 จะเอา input array เป็น พารามิเตอร์แรก พารามิเตอร์ต่อมาคือ memory storage พารามิเตอร์ที่สามจะกำหนดว่า hull ที่ได้จะวนหรือตามเข็ม พารามิเตอร์สุดท้ายถ้าเป็น 1 จุดต่างๆ จะคืนค่ากลับมาใน array ไม่เช่นนั้น จะคืนค่ามาแต่ indices ที่อ้างไปยัง input

cvCheckContourConvexity รับค่า contour เข้าไปและสามารถตรวจสอบได้ว่า convex หรือไม่ วิธีนี้รวดเร็วและง่าย แต่ถ้า contour ตัดกัน วิธีนี้จะใช้ไม่ได้

cvConvexityDefects จะรับค่า contour, convex hull (จาก cvConvexHull2) มาคำนวณหา convexity defects



Pairwise Geometrical Histograms

เราได้อธิบายถึงการกำหนด contour โดยวิธี freeman chain codes ไปแล้ว ต่อไปนี้จะกล่าวถึงประโยชน์ที่กล่างถึงมากอันหนึ่งคือการนำไปใช้ใน Pairwise Geometrical Histograms (PGH) ซึ่งตัว PGH เป็น extension ชอง chain code histogram (CCH)

CCH เป็น histogram ที่คำนวณโดยนับความถี่ที่เกิดขึ้นในแต่ละ step (ทั้ง 8 steps) ที่ represent contour คุณสมบัิติอย่างหนึ่งของ histogram แบบนี้คือการหมุนวัตถุทุกๆ 45 องศา จะทำให้ histogram เกิดการ cyclic transformation เท่านั้น


รูปซ้าย เป็นรูป contour, freeman chain code และ CCH ของรูปรถถัง
รูปขวา เป็นรูป contour, freeman chain code และ CCH ของรูปรถถังที่หมุน 45 องศา


PGH จะใช้แนวคิดคล้ายๆ วิธีการสร้าง PGH คือให้ เลือก edge อันหนึ่งเป็น base edge แล้วหลังจากนั้นจับคู่กับ edge ที่เหลือทุกอัน เพื่อคำนวณหาค่า dmin, dmax, θ   โดยที่ dmin คือระยะที่สั้นที่สุดระหว่าง edge ทั้งสอง dmax คือระยะที่ยาวที่สุด θ คือมุมระหว่าง edge ทั้งสอง แล้วเปลี่ยน edge อันใหม่ให้เป็น base edge จนครบทุกคู่  PGH จะเป็น histogram สองมิติ ที่มิติแรกเป็น θ มิติที่สองเป็น d ในทุกๆ การจับคู่ของ edge จะมีค่า (dmax, θ) และ (dmin, θ) เกิดขึ้น bins ทั้งสองและระหว่างนั้น จะถูกเพิ่มค่า



ในภาพซ้ายแสดง edge ทั้งสองเส้น(เส้นทึบ)พร้อมค่า dmin, dmax, θ 
ในภาพขวาแสดงถึง histogram ที่จะถูกเพิ่มค่าจาก คู่ edge ทางด้านซ้าย

ฟังก์ชันในการคำนวณค่า PGH คือ

void cvCalcPGH(
    const CvSeq* contour,
    CvHistogram* hist
);

Learning OpenCV: Hierarchical Matching

การ matching ด้วย moments ไม่เพียงพอ (แต่เร็ว) ก่อนอื่นมาทำความรู้จักกับ contour tree ก่อน

Contour tree
contour tree ไม่ใช่การ represent contour ด้วย tree (อย่างที่ได้จากค่า cvFindContours) ลองมาดูวิธีการสร้าง contour tree กัน ก่อนอื่นเริ่มต้อนด้วยรูป


ขั้นตอนเริ่มจากการหาบริเวณที่มีส่วนยื่นออกมาหรือส่วนที่เว้าเข้าไปเป็นสามเหลี่ยม (จริงๆ ก็หาจุดสองจุดที่ไม่ติดกันอย่างในรูป) ส่วนที่ยื่นออกไป(A,B,D) จะถูกตัดทิ้ง ส่วนที่หดลงไปจะโดนถม (จะกลายเป็นรูปในเส้นประ) ทุกครั้งที่ิถมหรือตัดสามเหลี่ยมจะเป็นการลดจำนวนจุดใน contour และนำพื้นที่เหล่านี้ใสเข้าไปใน contour tree

ถ้าสามเหลี่ยมที่ถูกตัดหรือถูกถมนั้น มีด้านสองด้านอยู่ใน contour เดิมสามเหลี่ยมนั้นจะเป็น leaf node และถ้าด้านใดด้านหนึ่งของสามเหลี่ยมนั้น เป็นด้านในด้านหนึ่งของสามเหลี่ยมใน tree แล้ว สามเหลี่ยมนั้นจะเข้ามาเป็น parent ของ node นั้น ตัดไปเรื่อยๆ จนเหลือสี่เหลี่ยม (มี vertices เหลือ สี่อัน) แล้วก็หั่นเป็นสองท่อน เป็น child node ของ root

note อย่าลืมว่าทรีสร้างจากใบไปราก, สามเหลี่ยมหนึ่งๆ จะมีสามด้าน ด้านที่ถูกตัดทิ้งแรกๆ (คือมีสองด้านอยู่ในcontour ต้นฉบับ มีแนวโน้มเป็นใบ) parent ของมันจะโดนตัดเป็นอันดับถัดมา(y เป็น parent ของ c,d) ซึ่งแน่นอนว่า parent มีลูกได้แค่สอง(เพราะต่อกับสามเหลี่ยมได้อีกสองอัน อีกอันหนึ่งไว้ต่อกับ parent ของตัวเอง

หลังจาก contour tree สร้างแล้ว ก็จะมาเปรียบเทียบ contour tree ทั้งสองอัน โดยดูจากโหนดในทรีที่ตรงกันแล้วเปรียบเทียบระหว่างโหนดที่ตรงกันนั้นด้วย คุณลักษณะต่างๆ (เช่น ความยาว, moments, etc)

มีข้อควรระวังคือการสร้าง contour tree ของ OpenCV ไม่ค่อย robust การเปลี่ยนแปลงจุดเล็กน้อยใน contour อาจจะทำให้มีผลเปลี่ยนแปลงอย่างมากใน contour tree และ root ของทรี ก็เลือกแบบไม่มีหลักการเท่าใด ดังนั้นจึงแนะนำให้ เรียก cvApproxPoly และทำ cyclic shift

ฟังก์ชันในการเปรียบเทียบ contour มีดังนี้

  • cvCreateContourTree
  • cvContourFromContourTree
  • cvMatchContourTrees