日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

opencv轮廓及点在轮廓内判断

發(fā)布時(shí)間:2025/4/16 114 豆豆
生活随笔 收集整理的這篇文章主要介紹了 opencv轮廓及点在轮廓内判断 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

查找輪廓

輪廓到底是什么?一個(gè)輪廓一般對(duì)應(yīng)一系列的點(diǎn),也就是圖像中的一條曲線.表示的方法可能根據(jù)不同情況而有所不同.有多重方法可以表示曲線.在openCV中一般用序列來(lái)存儲(chǔ)輪廓信息.序列中的每一個(gè)元素是曲線中一個(gè)點(diǎn)的位置.關(guān)于序列表示的輪廓細(xì)節(jié)將在后面討論,現(xiàn)在只要簡(jiǎn)單把輪廓想象為使用CvSeq表示的一系列的點(diǎn)就可以了.

函數(shù)cvFindContours()從二值圖像中尋找輪廓.cvFindContours()處理的圖像可以是從cvCanny()函數(shù)得到的有邊緣像素的圖像,或者是從cvThreshold()及cvAdaptiveThreshold()得到的圖像,這時(shí)的邊緣是正和負(fù)區(qū)域之間的邊界.


圖8-2描述了cvFindContours的函數(shù)功能,圖像的上半部分是神色背景和白色區(qū)域(被從A到E標(biāo)記)的測(cè)試圖像.下半部分是使用cvFindCountours()函數(shù)后會(huì)得到的輪廓的說(shuō)明.這些輪廓被標(biāo)記為cX或hx,"c"表示"輪廓(contour)","h"表示"孔(hole)","X表述數(shù)字".其中一些輪廓用虛劃線表示;表明他們是白色區(qū)域的外部邊界(例如,非0區(qū)域).孔(hole)的外部邊界(例如,非0區(qū)域)即白色區(qū)域的內(nèi)部邊界.在圖中是用電線表示外部邊界的.OpenCV的cvFindContours()函數(shù)可區(qū)分內(nèi)部和外部邊界.

包含的概念在很多應(yīng)用中都非常重要.因此.OpenCV允許得到的輪廓被聚合成一個(gè)輪廓樹(shù),從而把包含關(guān)系編碼到樹(shù)結(jié)構(gòu)中.這個(gè)測(cè)試圖的輪廓樹(shù)在根節(jié)點(diǎn)的輪廓叫c0,孔h00和h01是它的字子節(jié)點(diǎn).這些輪廓中直接包含輪廓稱(chēng)為他們的子節(jié)點(diǎn),以此類(lèi)推.

現(xiàn)在來(lái)看cvFindContours()函數(shù)

[cpp]?view plaincopy
  • int??cvFindContours(?CvArr*?image,?CvMemStorage*?storage,?CvSeq**?first_contour,????
  • ????????????????????????????int?header_size?CV_DEFAULT(sizeof(CvContour)),????
  • ????????????????????????????int?mode?CV_DEFAULT(CV_RETR_LIST),????
  • ????????????????????????????int?method?CV_DEFAULT(CV_CHAIN_APPROX_SIMPLE),????
  • ????????????????????????????CvPoint?offset?CV_DEFAULT(cvPoint(0,0)));????
  • 第一個(gè)參數(shù) image是輸入圖像,圖像必須是8位單通道圖像,并且應(yīng)該被轉(zhuǎn)化成二值的(例如,所有非0像素的值都是一個(gè)定值).cvFindContours()運(yùn)行的時(shí)候,這個(gè)圖像會(huì)被直接涂改,因此如果是將來(lái)還有用的圖像,應(yīng)該復(fù)制之后再傳給cvFindContours().

    storage 是內(nèi)存存儲(chǔ)器,cvFindContours()找到的輪廓記錄在此內(nèi)存里.正如之前所說(shuō),這個(gè)存儲(chǔ)器的空間應(yīng)該由cvCreateMemStorage()分配.

    first_contour 是指向CvSeq*的一個(gè)指針firstContour.無(wú)需動(dòng)手,cvFindContours()會(huì)自動(dòng)分配該指針.實(shí)際上,只要在這里傳一個(gè)指針就可以了函數(shù)會(huì)自動(dòng)設(shè)置.不需要分配和釋放(new/delete或者malloc/free).就是這個(gè)指針(例如,*firstContour)指向輪廓樹(shù)的首地址(head).cvFindContours()返回值是,找到的所有輪廓的個(gè)數(shù)

    cvSeq* firstContout = NULL;

    cvFindContours(..., &firstContour, ...);

    headerSize告訴cvFindContours()更多有關(guān)對(duì)象分配的信息,它可以被設(shè)定為sizeof(CvContour)或者sizeof(CvChain)(當(dāng)近似方法參數(shù)method被設(shè)定為CV_ChAIN_CODE時(shí)使用后者).最后是mode和method參數(shù),他們分別指定計(jì)算方法和如何計(jì)算.

    mode變量可以被設(shè)置為以下四個(gè)選項(xiàng)之一: CV_RETR_ExTERNAL, CV_RETR_LIST, CV_RETR_CCOMP或CV_RETR_TREE.mode的值向cvFindeContours()說(shuō)明需要的輪廓類(lèi)型,和希望的放回值形式.具體說(shuō)來(lái),mode的值決定把找到的輪廓如何掛到輪廓樹(shù)節(jié)點(diǎn)變量(h_prev,h_next,v_prev和v_next)上,圖8-3展示了四種可能的mode值所得到的結(jié)果的拓?fù)浣Y(jié)構(gòu).


    每中情況下,結(jié)構(gòu)都可以看成是被"橫向"連接(h_next和h_prev)聯(lián)系和被"縱向"連接(v_next和v_prev)不同的"層次".

    CV_RETR_EXTERNAL 只檢測(cè)出最外的輪廓.圖8-2中,只有一個(gè)最外輪廓,因此圖8-3中第一個(gè)輪廓指向最外的序列,除此之外沒(méi)有別的連接

    CV_RETR_LIST 檢測(cè)所有的輪廓并將他們保存到表(list)中.圖8-3描繪了從圖8-2樣圖中得到的表.在這個(gè)例子中,有8條輪廓被找到,他們相互之間有h_prev和h_next連接(這里并沒(méi)有使用v_prev和v_next)

    CV_RETR_CCOMP 檢出所有的輪廓并將他們組織成雙層結(jié)構(gòu)(two-level hierarchy),頂層邊界是所有成份的外界邊界,第二層邊界是空的邊界.圖8-3中,我們能看到5個(gè)外部邊界,其中3個(gè)包含孔.孔被v_next和v_prev可以只包括一個(gè)值,此節(jié)點(diǎn)可以只有一個(gè)子節(jié)點(diǎn).c0中有兩個(gè)孔,因?yàn)関_next可以值包括一個(gè)值,次節(jié)點(diǎn)可以只有一個(gè)子節(jié)點(diǎn).c0之內(nèi)的所有孔相互間有h_prev和h_next指針連接.

    CV_RETR_TREE 檢出所有輪廓并且重新建立網(wǎng)狀的輪廓結(jié)構(gòu).在我們給出的例子中(圖8-2和8-3中),這意味著根節(jié)點(diǎn)是最外的輪廓c0.c0之下是空h00,在同一層次中與另一個(gè)孔h01相連接.同理,每個(gè)孔都有子節(jié)點(diǎn)(相對(duì)應(yīng)的是c000和c010),這些子節(jié)點(diǎn)與父節(jié)點(diǎn)被垂直連接起來(lái).這個(gè)步驟一直持續(xù)到圖像最內(nèi)層的輪廓,這些輪廓會(huì)成為樹(shù)葉節(jié)點(diǎn).

    以下的五個(gè)值與方法相關(guān)(例如輪廓會(huì)如何被近似).

    ?CV_CHAIN_CODE 用freeman鏈碼輸出輪廓,其他方法輸出多邊形(頂點(diǎn)的序列)

    CV_CHAIN_APPROX_NONE 將鏈碼編碼中的所有點(diǎn)轉(zhuǎn)換為點(diǎn)

    CV_CHAIN_APPROX_SIMPLE 壓縮水平,垂直或斜的部分,只保存最后一個(gè)點(diǎn)

    CV_CHAIN_APPROX_TC89_L1或CV_CHAIN_APPROX_TC89_KCOS使用Ten-Chin鏈逼近算法中的一個(gè)

    CV_LINK_RUNS 與上述算法完全不同的算法,連接所有水平層次的輪廓.此方法只可與Cv_RETR_LIST搭配使用.

    使用序列表示輪廓

    當(dāng)調(diào)用cvFindContours函數(shù)的時(shí)候,返回多個(gè)序列.序列的類(lèi)型依賴(lài)與調(diào)用cvFindContours時(shí) 所傳遞的參數(shù).默認(rèn)情況下使用CV_RETR_LIST和CV_CHAIN_APPROX_SIMPLE參數(shù).

    序列中保存一系列的點(diǎn),這些點(diǎn)構(gòu)成輪廓,輪廓是本章的重點(diǎn).輪廓只是序列所能表示物體的一種.輪廓的點(diǎn)的序列,可以用來(lái)表示圖像空間中的曲線.這種點(diǎn)的序列很常用,所有需要有專(zhuān)門(mén)的函數(shù)來(lái)幫助我們對(duì)他進(jìn)行處理.下面是一組這樣的處理函數(shù).

    [cpp]?view plaincopy
  • int??cvFindContours(?CvArr*?image,?CvMemStorage*?storage,?CvSeq**?first_contour,????
  • ????????????????????????????int?header_size?CV_DEFAULT(sizeof(CvContour)),????
  • ????????????????????????????int?mode?CV_DEFAULT(CV_RETR_LIST),????
  • ????????????????????????????int?method?CV_DEFAULT(CV_CHAIN_APPROX_SIMPLE),????
  • ????????????????????????????CvPoint?offset?CV_DEFAULT(cvPoint(0,0)));????
  • [cpp]?view plaincopy
  • CvContourScanner??cvStartFindContours(?CvArr*?image,?CvMemStorage*?storage,????
  • ????????????????????????????int?header_size?CV_DEFAULT(sizeof(CvContour)),????
  • ????????????????????????????int?mode?CV_DEFAULT(CV_RETR_LIST),????
  • ????????????????????????????int?method?CV_DEFAULT(CV_CHAIN_APPROX_SIMPLE),????
  • ????????????????????????????CvPoint?offset?CV_DEFAULT(cvPoint(0,0)));????
  • [cpp]?view plaincopy
  • CvSeq*??cvFindNextContour(?CvContourScanner?scanner?);????
  • [cpp]?view plaincopy
  • void??cvSubstituteContour(?CvContourScanner?scanner,?CvSeq*?new_contour?);????
  • [cpp]?view plaincopy
  • /*?Releases?contour?scanner?and?returns?pointer?to?the?first?outer?contour?*/????
  • CvSeq*??cvEndFindContours(?CvContourScanner*?scanner?);????
  • [cpp]?view plaincopy
  • /*?Approximates?a?single?Freeman?chain?or?a?tree?of?chains?to?polygonal?curves?*/????
  • CvSeq*?cvApproxChains(?CvSeq*?src_seq,?CvMemStorage*?storage,????
  • ????????????????????????????int?method?CV_DEFAULT(CV_CHAIN_APPROX_SIMPLE),????
  • ????????????????????????????double?parameter?CV_DEFAULT(0),????
  • ????????????????????????????int??minimal_perimeter?CV_DEFAULT(0),????
  • ????????????????????????????int??recursive?CV_DEFAULT(0));????
  • 第一函數(shù)是cvFindContours(),在前面已經(jīng)提到.接著是cvStartFindContiors()函數(shù),它和cvFin的Contours()功能類(lèi)似.但是cvStartFindContours()每次放回一個(gè)輪廓,而不像cvFinContours()那樣一次查找所有輪廓然后統(tǒng)一返回.調(diào)用cvStartFindContours()函數(shù)后,返回一個(gè)CvSequenceScanner結(jié)構(gòu).CvSequenceScanner結(jié)構(gòu)中包含一些狀態(tài)信息,這些信息不可讀.你可以通過(guò)在cvSequenceScabber結(jié)構(gòu)上依次調(diào)用cvFinNextContour()來(lái)查找剩余的輪廓.當(dāng)全部輪廓都被找完之后,cvFindNextContour()將放回NULL

    cvSubstituteContour()函數(shù)用于替換scanner指向的輪廓.該函數(shù)的一個(gè)特性是,如果參數(shù) new_contour為NULL,那么當(dāng)前的輪廓將被從Scanner指定的樹(shù)或鏈表中刪除(受影響的序列會(huì)作適當(dāng)更新,來(lái)保證不會(huì)有指針指向不存在的物體).

    函數(shù)cvEndFindContour()結(jié)束輪廓查找,并且將scanner設(shè)置為結(jié)束狀態(tài).注意,scanner并沒(méi)有被刪除,實(shí)際上該函數(shù)返回的是指針?biāo)感蛄械牡谝粋€(gè)元素.

    最后一個(gè)函數(shù)cvApproxChains()函數(shù).該函數(shù)將Freeman鏈轉(zhuǎn)換為多邊形表示(精確轉(zhuǎn)換或者近似擬合).

    Freeman鏈碼

    一般情況下,通過(guò)cvFindCountours獲取的輪廓是一系列頂點(diǎn)的序列.另一種不同的表達(dá)是設(shè)置method參數(shù)為CV_CHAIN_CODE,然后生成輪廓.當(dāng)選者CV_CHAIN_CODE標(biāo)志的時(shí)候,檢測(cè)的輪廓通過(guò)Freemain鏈碼[Freeman67](圖8-4)的方式返回.在Freeman鏈碼中,多邊形被表示為一系列的位移,每一個(gè)位移有8個(gè)方向,這8個(gè)方向使用整數(shù)0到7表示.Freeman鏈碼對(duì)于識(shí)別一些形狀的物體很有幫助.如果得到的是Freeman鏈碼,可以通過(guò)以下兩個(gè)函數(shù)讀出每個(gè)點(diǎn)

    [cpp]?view plaincopy
  • void?cvStartReadChainPoints(?CvChain*?chain,?CvChainPtReader*?reader?);????
  • [cpp]?view plaincopy
  • CvPoint?cvReadChainPoint(?CvChainPtReader*?reader?);????
  • 第一個(gè)函數(shù)用來(lái)初始化Freeman鏈CvChainPtReader結(jié)構(gòu),第二個(gè)函數(shù)通過(guò)CvChainptReader來(lái)讀每個(gè)點(diǎn),CvChainPtReader對(duì)應(yīng)當(dāng)前狀態(tài).結(jié)構(gòu)CvChain從CvSeq擴(kuò)展得來(lái).和CvContourScanner從多個(gè)輪廓間迭代一樣,CvChainPtReader用于迭代一個(gè)使用Freemain鏈碼表示輪廓中的每個(gè)點(diǎn).CvChainPtReader和CvSeqReader的用法類(lèi)似.如您所期望,當(dāng)所有點(diǎn)都讀完后,返回CvChainPtReader值為NULL.

    繪制輪廓

    一個(gè)經(jīng)常使用的功能是在屏幕上繪制檢測(cè)到的輪廓.繪制可以用cvDrawContours函數(shù)完成

    [cpp]?view plaincopy
  • /*?Draws?contour?outlines?or?filled?interiors?on?the?image?*/????
  • void??cvDrawContours(?CvArr?*img,?CvSeq*?contour,????
  • ?????????????????????????????CvScalar?external_color,?CvScalar?hole_color,????
  • ?????????????????????????????int?max_level,?int?thickness?CV_DEFAULT(1),????
  • ?????????????????????????????int?line_type?CV_DEFAULT(8),????
  • ?????????????????????????????CvPoint?offset?CV_DEFAULT(cvPoint(0,0)));????
  • 第一個(gè)參數(shù)為要繪制輪廓的圖像.第二個(gè)參數(shù)是要繪制的輪廓,他不像乍看上去那么簡(jiǎn)單,他是輪廓樹(shù)的根節(jié)點(diǎn).其他的參數(shù)(主要是max_level)將會(huì)控制如何繪制輪廓樹(shù).下一個(gè)參數(shù)很容易理解,是繪制輪廓所用的顏色.但是hole_color那?請(qǐng)回憶輪廓的分類(lèi),有外輪廓,也有"洞"(圖8-2中的虛劃線和點(diǎn)線).無(wú)論繪制單個(gè)輪廓還是輪廓樹(shù)中的所有輪廓,標(biāo)記為"洞"的輪廓都會(huì)使用hole_color指定的顏色繪制.

    通過(guò)max_level變量可以告訴cvDrawConturs() 如何處理通過(guò)節(jié)點(diǎn)樹(shù)變量連結(jié)到一個(gè)輪廓上的其他任何輪廓.此變量可以被設(shè)置為遍歷輪廓的最大深度.因此max_level = 0表示與輸入輪廓屬于同意等級(jí)的所有輪廓(更具體的說(shuō),輸入輪廓和與其相鄰的輪廓被畫(huà)出),max_level = 1表示與輸入輪廓屬于同一登記的所有輪廓與其子節(jié)點(diǎn)被畫(huà)出,以此類(lèi)推.如果項(xiàng)要畫(huà)的輪廓是由cvFindContous()的CV_RETR_CCOMP或CV_RETR_TREE模式得到的話,max_level的負(fù)值也是被支持的.在這種情況下,max_level=-1表示只有輸入輪廓被畫(huà)出,以此類(lèi)推,max_level = -2 表示輸入輪廓與其直系(僅直接相連的)子節(jié)點(diǎn)會(huì)被畫(huà)出,以此類(lèi)推.

    參數(shù)thickness和line_type就如其字面含義所示.最后,我們可以給繪圖程序一個(gè)偏移量,這樣輪廓可以被畫(huà)在指定的精確坐標(biāo)上.當(dāng)輪廓坐標(biāo)被轉(zhuǎn)換成質(zhì)心坐標(biāo)或其他局部坐標(biāo)系的時(shí)候,這個(gè)特性非常有用.
    如果在圖像上的不同感興趣的區(qū)域多次執(zhí)行cvFindContour(),然后又想將所有結(jié)果在原來(lái)大圖像上顯示出來(lái),便宜量offset也很有用.相反,可以先從大圖提取出一個(gè)輪廓,然后在用offset和填充,在小圖像上形成和輪廓對(duì)應(yīng)的蒙板(mask);

    輪廓例子

    首先創(chuàng)建一個(gè)窗口用于顯示圖像,滑動(dòng)條(trackbar)用于設(shè)置閾值,然后對(duì)采二值化后的圖像提取輪廓并繪制輪廓.當(dāng)控制參數(shù)的滑動(dòng)條變換時(shí),圖像被更新.

    [cpp]?view plaincopy
  • #include?"stdafx.h"????
  • #include?<cv.h>????
  • #include?<highgui.h>????
  • ????
  • IplImage*?g_image?=?NULL;????
  • IplImage*?g_gray?=?NULL;????
  • int?g_thresh?=?100;????
  • CvMemStorage*?g_storage?=?nullptr;????
  • void?on_trackbar(int)????
  • {????
  • ????if?(g_storage?==?nullptr)????
  • ????{????
  • ????????g_gray?=?cvCreateImage(cvGetSize(g_image),8,1);????
  • ????????g_storage?=?cvCreateMemStorage(0);????
  • ????}????
  • ????else????
  • ????{????
  • ????????cvClearMemStorage(g_storage);????
  • ????}????
  • ????CvSeq*?contours?=?NULL;????
  • ????cvCvtColor(g_image,g_gray,CV_BGR2GRAY);????
  • ????cvThreshold(g_gray,g_gray,g_thresh,255,CV_THRESH_BINARY);????
  • ????cvFindContours(g_gray,g_storage,&contours);????
  • ????cvZero(g_gray);????
  • ????if?(contours)????
  • ????{????
  • ????????cvDrawContours(g_gray,contours,cvScalarAll(255),cvScalarAll(255),100);????
  • ????}????
  • ????cvShowImage("Contours",g_gray);????
  • }????
  • ????
  • int?_tmain(int?argc,?_TCHAR*?argv[])????
  • {????
  • ????g_image?=?cvLoadImage("C:\\Users\\chenchao\\Desktop\\細(xì)胞圖象\\正常的紅細(xì)胞\\5.bmp");????
  • ????cvNamedWindow("Contours",1);????
  • ????cvCreateTrackbar("Threshold","Contours",&g_thresh,300,on_trackbar);????
  • ????on_trackbar(0);????
  • ????cvWaitKey(0);????
  • ????printf("HELLO");????
  • ????return?0;????
  • }????
  • 如果全局參數(shù)g_storage為NULL的話,則用cvCreateMemSotrage(0)創(chuàng)建一個(gè)內(nèi)存存儲(chǔ)器.g_gray被初始化為和g_image同樣大小的黑色圖像,但是為單通道圖像.如果g_storage非空的話,則先用cvClearMemStorage清空內(nèi)存存儲(chǔ)器的中間,這樣以便重復(fù)利用內(nèi)存存儲(chǔ)器中的資源.然后創(chuàng)建一個(gè)CvSeq*指針,該指針用來(lái)保存cvFindCountours()檢測(cè)到的輪廓.

    然后g_image被轉(zhuǎn)換為灰度圖像,接著用g_thresh為參數(shù)進(jìn)行二值化處理,得到的二值圖像保存在g_gray中.cvFindContours從二值圖像g_gray查找輪廓,然后將得到的輪廓用cvDrawContours()函數(shù)繪制為白色到灰度圖像.最終圖像在窗口中顯示出來(lái),并將在回調(diào)函數(shù)開(kāi)始處申請(qǐng)的結(jié)構(gòu)釋放.

    另一個(gè)輪廓的例子

    在上例中,我們檢測(cè)出輸入圖像的輪廓,然后逐個(gè)繪制沒(méi)格輪廓.從這個(gè)例子中,我們可以了解到輪廓檢測(cè)方法(如代碼中是CV_RETR_LIST)以及max_depth(代碼中是0)等參數(shù)的細(xì)節(jié).如果設(shè)置的max_depth是一個(gè)比較大的值,你可以發(fā)現(xiàn)cvFindCountours()返回的輪廓是通過(guò)h_next連接被遍歷.對(duì)于其他一些拓?fù)浣Y(jié)構(gòu)(CV_RETR_TREE,CV_REER_CCOMP等),你會(huì)發(fā)現(xiàn)有些輪廓被畫(huà)過(guò)不只一次

    例8-3 在輸入圖像上尋找并繪制輪廓

    [cpp]?view plaincopy
  • int?_tmain(int?argc,?_TCHAR*?argv[])????
  • {????
  • ????cvNamedWindow("src");????
  • ????IplImage*?img_8uc1?=?cvLoadImage("C:\\Users\\chenchao\\Desktop\\細(xì)胞圖象\\正常的紅細(xì)胞\\5.bmp",0);????
  • ????IplImage*?img_edge?=?cvCreateImage(cvGetSize(img_8uc1),8,1);????
  • ????IplImage*?img_8uc3?=?cvCreateImage(cvGetSize(img_8uc1),8,3);????
  • ????cvThreshold(img_8uc1,img_edge,128,255,CV_THRESH_BINARY);????
  • ????CvMemStorage*?storage?=?cvCreateMemStorage(0);????
  • ????CvSeq*?first_contour?=?nullptr;????
  • ????int?Nc?=?cvFindContours(img_edge,storage,&first_contour,sizeof(CvContour),CV_RETR_LIST);????
  • ????int?n?=?0;????
  • ????printf("Total?Contours?Detected?:?%d?\n",Nc);????
  • ????for?(CvSeq*?c?=?first_contour;?c!=?NULL;?c=?c->h_next)????
  • ????{????
  • ????????cvCvtColor(img_edge,img_8uc3,CV_GRAY2BGR);????
  • ????????cvDrawContours(img_8uc3,c,cvScalar(0,255,0),cvScalar(0,0,255),0,2,8);????
  • ????????printf("contours?#%d\n",n);????
  • ????????cvShowImage("src",img_8uc3);????
  • ????????printf("???%d?elements:?\n",c->total);????
  • ????????for?(int?i=0?;?i<c->total;?++i)????
  • ????????{????
  • ????????????CvPoint*?p??=??CV_GET_SEQ_ELEM(CvPoint,c,i);????
  • ????????????printf("?(%d,%d)?\n",p->x,p->y);????
  • ????????}????
  • ????????cvWaitKey(0);????
  • ????????n++;????
  • ????}????
  • ????
  • ????printf("Finished?all?contours.\n");????
  • ????cvCvtColor(img_8uc1,img_8uc3,CV_GRAY2BGR);????
  • ????cvShowImage("src",img_8uc3);????
  • ????cvWaitKey(0);????
  • ????cvDestroyWindow("src");????
  • ????cvReleaseImage(&img_8uc1);????
  • ????cvReleaseImage(&img_8uc3);????
  • ????cvReleaseImage(&img_edge);????
  • ????return?0;????
  • }????
  • 深入分析輪廓

    多邊形逼近

    當(dāng)我們繪制一個(gè)多邊形或者進(jìn)行形狀分析的時(shí)候,通常需要使用多邊形畢竟一個(gè)輪廓,使頂點(diǎn)數(shù)目變少.有多種方法可以實(shí)現(xiàn)這個(gè)功能,OpenCV實(shí)現(xiàn)了其中的一種逼近算法.函數(shù)cvApproxPoly是該算法的一種實(shí)現(xiàn),可以處理輪廓的序列.

    [cpp]?view plaincopy
  • (CvSeq*)??cvApproxPoly(?const?void*?src_seq,????
  • ?????????????????????????????int?header_size,?CvMemStorage*?storage,????
  • ?????????????????????????????int?method,?double?eps,????
  • ?????????????????????????????int?recursive?CV_DEFAULT(0));????
  • 我們可以傳遞一個(gè)列表或者數(shù)狀序列給cvApproxPoly,然后對(duì)其表示的輪廓進(jìn)行處理.函數(shù)返回值對(duì)應(yīng)第一個(gè)輪廓,同樣我們可用通過(guò)h_next(以及v_next)來(lái)訪問(wèn)返回其他的輪廓.

    因?yàn)閏vApproxPoly在返回結(jié)果的時(shí)候需要?jiǎng)?chuàng)建新的對(duì)象,因此 需要指定一個(gè)內(nèi)存存儲(chǔ)器以及頭結(jié)構(gòu)大小.(一般為sizeof(CvContour)).

    逼急算法目前只可使用CV_POLY_APPROx_DP.另外兩個(gè)參數(shù)為逼近算法參數(shù)(目前只用到第一個(gè)).eps參數(shù)指定逼近的精度.如果想了解這個(gè)參數(shù)如何起作用的的必須仔細(xì)了解具體的算法.最后一個(gè)參數(shù)指定是否針對(duì)全部的輪廓(通過(guò)h_next和v_next可達(dá)的)進(jìn)行逼近

    如果為0,則表示只處理src_seq指向輪廓.

    下面簡(jiǎn)要介紹一下算法的工作原理.參考圖8-5,算法先從輪廓(圖b)選擇2個(gè)最遠(yuǎn)的點(diǎn),然后將2個(gè)連成一個(gè)線段(圖c),然后再查找輪廓上到線段距離最遠(yuǎn)的點(diǎn),添加到逼近后的心輪廓(圖d).算法反復(fù)迭代,不斷將最遠(yuǎn)點(diǎn)的添加到結(jié)果中.直到所有點(diǎn)的點(diǎn)到多邊形的最短距離小于eps參數(shù)指定的精度(圖f).從這里可以看出,精度和輪廓的周長(zhǎng),或者外包矩形周長(zhǎng)的幾分之一比較合適.

    曲線逼近的過(guò)程和尋找關(guān)掉點(diǎn)的過(guò)程密切相關(guān)。跟曲線上的其他點(diǎn)相比,關(guān)鍵點(diǎn)是那些包含曲線信息比較多的點(diǎn)。關(guān)鍵點(diǎn)在逼近算法以及其他應(yīng)用中都會(huì)涉及。函數(shù)cvFindDominantPoints()實(shí)現(xiàn)了被稱(chēng)為IPAN*[Chetvreikov99]的算法.

    [cpp]?view plaincopy
  • CvSeq??cvFindDominantPoints(CvSeq*?contour,CvMemStorage*?storage,int?metod?=?CV_DOMINANT_IPAN,double?parameter1?=?0,double?parameter2?=?0,double?parameter3?=?0,double?parameter4?=?0);??
  • ? ? ? ?本質(zhì)上,IPAN算法通過(guò)掃描輪廓上并在曲線內(nèi)部使用可能頂點(diǎn)構(gòu)造三角形來(lái)實(shí)現(xiàn).對(duì)于三角形的大小和張角有特殊要求.在此某一特定的全局閾值和它的相鄰的張角小的情況下,具有大張角的點(diǎn)被保留.

    函數(shù)cvFindDominantPoints()按照慣例使用參數(shù)CvSeq* 和CvMemStorage* .并且要求指定一個(gè)方法,和cvApproxPoly()相同,目前可供選擇的方法只有一個(gè),就是CV_DOMINANT_IPAN.


    接下來(lái)四個(gè)參數(shù)是:最短距離dmin,最長(zhǎng)距離dmax,相鄰距離dn和最大角度θmax.如圖8-6所示,算法首先把所有兩邊距離rpa和rpb在dmin和dmax之間,θab <?θmax的三角形找出來(lái).然后保留對(duì)于距離dn(dn的大小不得超過(guò)dmax)有最小夾角θab的所有點(diǎn)p.dmin,dmax,dn和θmax典型值可以是7,9,9,150(最后一個(gè)參數(shù)是以度數(shù)為單位的角大小).

    特性概括

    輪廓處理中經(jīng)常遇到的另一個(gè)任務(wù)是計(jì)算一些輪廓變化的概括特性.這可能包括長(zhǎng)度或者其他一些反映輪廓整體大小的度量.另一個(gè)有用的特性是輪廓的輪廓矩(contourmoment),可以用來(lái)概括輪廓的總形狀特性

    長(zhǎng)度

    函數(shù)cvContourPerimeter()作用于一個(gè)輪廓并返回其長(zhǎng)度.事實(shí)上,此函數(shù)是一個(gè)調(diào)用函數(shù)cvArcLength()的宏.

    [cpp]?view plaincopy
  • CVAPI(double)??cvArcLength(?const?void*?curve,????
  • ????????????????????????????CvSlice?slice?CV_DEFAULT(CV_WHOLE_SEQ),????
  • ????????????????????????????int?is_closed?CV_DEFAULT(-1));????
  • ????
  • CV_INLINE?double?cvContourPerimeter(?const?void*?contour?)????
  • {????
  • ????return?cvArcLength(?contour,?CV_WHOLE_SEQ,?1?);????
  • }????
  • cvArcLength()的第一參數(shù)是輪廓,其形式可以是點(diǎn)的序列(CvContour*或CvSeq*)或任一n×2的點(diǎn)的數(shù)組.后邊的參數(shù)是slice,以及表明是否將輪廓視為閉合的一個(gè)布爾類(lèi)型(例如,是否將輪廓的最后一個(gè)點(diǎn)視為和第一個(gè)點(diǎn)有連接).slice可以讓我們只選擇曲線上的點(diǎn)的部分集合.

    一個(gè)和cvArcLength()有緊密關(guān)系的函數(shù)是cvContourArea(),如其名稱(chēng)所示,這個(gè)函數(shù)同于計(jì)算輪廓的面積.函數(shù)的參數(shù)contour和slice和cvArcLength()一樣.

    [cpp]?view plaincopy
  • CVAPI(double)??cvContourArea(?const?CvArr*?contour,????
  • ??????????????????????????????CvSlice?slice?CV_DEFAULT(CV_WHOLE_SEQ),????
  • ??????????????????????????????int?oriented?CV_DEFAULT(0));????
  • 邊界框

    當(dāng)然長(zhǎng)度和面積只是輪廓的簡(jiǎn)單特性,更復(fù)雜一些的特性描述應(yīng)該是矩形邊界框,圓形邊界框或橢圓形邊界框.有兩種方法可以得到矩形邊界框,圓形與橢圓形編輯框各只有一種方法.

    [cpp]?view plaincopy
  • CVAPI(CvRect)??cvBoundingRect(?CvArr*?points,?int?update?CV_DEFAULT(0)?);????
  • [cpp]?view plaincopy
  • CVAPI(CvBox2D)??cvMinAreaRect2(const?CvArr*?points,CvMemStorage*?storage?CV_DEFAULT(NULL));????
  • 最簡(jiǎn)單的方法是調(diào)用函數(shù)cvBoundingRect();它將放回一個(gè)包圍輪廓的CvRect.第一個(gè)參數(shù)points可以是由點(diǎn)組成的序列,一個(gè)輪廓(CvContour*)或者一個(gè)n×1雙通道的矩陣(CvMat*).為了理解第二個(gè)參數(shù)update,我們需要想想前面的描述,當(dāng)時(shí)說(shuō)CvContour并不完全等于CvSeq;CvSeq能實(shí)現(xiàn)的CvContour都可以實(shí)現(xiàn),CvContour甚至能做的更多一點(diǎn).其中一個(gè)附加功能就是CvRect成員可以記載輪廓自己的邊界框.如果調(diào)用函數(shù)cvBoundingRect()時(shí)參數(shù)update設(shè)置為0,便可以直接從CvCoutour的成員中獲取邊界框;如果將uodate設(shè)置為1,邊界框便會(huì)被計(jì)算出(CvContour成員的內(nèi)容也會(huì)被更新).

    cvBoundingRect()得到的長(zhǎng)方形的一個(gè)問(wèn)題是,cvRect只能表現(xiàn)一個(gè)四邊水平和豎直的長(zhǎng)方形.然而函數(shù)cvMinAreaRect2()可以返回一個(gè)包圍輪廓最小的長(zhǎng)方形,這個(gè)長(zhǎng)方形可能是傾斜的;請(qǐng)看圖8-7,該函數(shù)的參數(shù)和cvBoundingRect()的相似.opencv的數(shù)據(jù)類(lèi)型CvBox2D就是用來(lái)表述這樣的長(zhǎng)方形狀的.

    [cpp]?view plaincopy
  • typedef?struct?CvBox2D????
  • {????
  • ????CvPoint2D32f?center;??/*?Center?of?the?box.??????????????????????????*/????
  • ????CvSize2D32f??size;????/*?Box?width?and?length.???????????????????????*/????
  • ????float?angle;??????????/*?Angle?between?the?horizontal?axis???????????*/????
  • ??????????????????????????/*?and?the?first?side?(i.e.?length)?in?degrees?*/????
  • }????
  • CvBox2D;????

  • 圓形和橢圓形邊界

    接著我們來(lái)看函數(shù)cvMinEnclosingCircle().該函數(shù)和矩形邊界框的作用基本相同,輸入同樣很靈活,可以是點(diǎn)的序列,也可以是二維點(diǎn)的數(shù)組.

    [cpp]?view plaincopy
  • CVAPI(int)??cvMinEnclosingCircle(const?CvArr*?points,CvPoint2D32f*?center,?float*?radius);????
  • OpenCV里沒(méi)有專(zhuān)門(mén)用來(lái)表示圓的結(jié)構(gòu),因此需要給函數(shù)cvMinEnclosingCircle()傳遞中心和浮點(diǎn)型半徑的兩個(gè)指針來(lái)獲取計(jì)算結(jié)果.

    與最小包圍圓一樣,OpenCV提供一函數(shù)來(lái)擬合一組點(diǎn),以獲取最佳擬合橢圓

    [cpp]?view plaincopy
  • CVAPI(CvBox2D)?cvFitEllipse2(?const?CvArr*?points?);????
  • cvMinEnclosingCircle()和cvFitEllipse2()的細(xì)微差別在于,前者只簡(jiǎn)單計(jì)算完全包圍已有輪廓的最小圓,而后者使用擬合函數(shù)返回一個(gè)與輪廓最相近似的橢圓.這意味著并不是輪廓中所有的點(diǎn)都會(huì)被包在cvFitEllipse2()返回的橢圓中.該擬合由最小二乘擬合方法算出.

    橢圓的擬合結(jié)果由CvBox2D結(jié)構(gòu)體返回,給出的矩形正好完全包圍橢圓,如圖8-8所示.



    幾何

    在處理CvBox2D或多邊形邊界的時(shí)候,經(jīng)常需要進(jìn)行多邊形以及邊界框的重疊判斷.OpenCV提供了一組方便的小函數(shù)用于此類(lèi)測(cè)試.

    [cpp]?view plaincopy
  • CVAPI(CvRect)??cvMaxRect(?const?CvRect*?rect1,?const?CvRect*?rect2?);????
  • CVAPI(void)?cvBoxPoints(?CvBox2D?box,?CvPoint2D32f?pt[4]?);????
  • /*?Initializes?sequence?header?for?a?matrix?(column?or?row?vector)?of?points?-??
  • ???a?wrapper?for?cvMakeSeqHeaderForArray?(it?does?not?initialize?bounding?rectangle!!!)?*/????
  • CVAPI(CvSeq*)?cvPointSeqFromMat(?int?seq_kind,?const?CvArr*?mat,????
  • ?????????????????????????????????CvContour*?contour_header,????
  • ?????????????????????????????????CvSeqBlock*?block?);????
  • /*?Checks?whether?the?point?is?inside?polygon,?outside,?on?an?edge?(at?a?vertex).??
  • ???Returns?positive,?negative?or?zero?value,?correspondingly.??
  • ???Optionally,?measures?a?signed?distance?between??
  • ???the?point?and?the?nearest?polygon?edge?(measure_dist=1)?*/????
  • CVAPI(double)?cvPointPolygonTest(?const?CvArr*?contour,????
  • ??????????????????????????????????CvPoint2D32f?pt,?int?measure_dist?);????
  • 第一個(gè)函數(shù)cvMaxRect()根據(jù)輸入的2個(gè)矩形計(jì)算,他們的最小外包矩形.

    下一個(gè)使用函數(shù)cvBoxPoints()用于計(jì)算CvBox2D結(jié)構(gòu)表示矩形的4個(gè)頂點(diǎn).當(dāng)然你也可以自己通過(guò)三角函數(shù)計(jì)算,不過(guò)這令人頭大,而簡(jiǎn)單調(diào)用一下這個(gè)函數(shù)則可求出.

    第三實(shí)用函數(shù)cvPointSeqFromMat()從mat中初始化序列.這在你需要使用輪廓相關(guān)的函數(shù),但是函數(shù)又不支持矩陣參數(shù)的時(shí)候使用.第一個(gè)參數(shù)用于指定點(diǎn)序列類(lèi)型,seq_kind可以為以下類(lèi)型:點(diǎn)集為0;曲線為CV_SEQ_KIND_CURVE;封閉曲線為CV_SEQ_KIND_CURVE|Cv_SEQ_FLAG_CLOSED.第二個(gè)參數(shù)是輸入的矩陣,該參數(shù)是連續(xù)的1維向量.矩陣類(lèi)型必須為cv_32C2或CV_32FC2.

    下面的兩個(gè)參數(shù)是指針,指針指向的內(nèi)容通過(guò)該函數(shù)填充.contour_header參數(shù)對(duì)應(yīng)輪廓結(jié)構(gòu),一般要事先創(chuàng)建,不過(guò)由該函數(shù)負(fù)責(zé)初始化.block參數(shù)同樣如此,也是由該函數(shù)復(fù)雜初始化.最后,該函數(shù)放回一個(gè)類(lèi)型為CvSeq*的序列指針,指向你輸入的序列頭*contour_header.返回值跟輸入?yún)?shù)相同只是為了使用該函數(shù)時(shí)更方便,因?yàn)檫@樣你就可以將該函數(shù)當(dāng)作某個(gè)輪廓函數(shù)的參數(shù)使用,代碼寫(xiě)入同一行.

    最后一個(gè)平面幾個(gè)相關(guān)的函數(shù)是cvPointPolygonTest(),用于測(cè)試一個(gè)點(diǎn)是否在多邊形的內(nèi)部.如果參數(shù)measure_dist非零,函數(shù)返回值是點(diǎn)到多邊形最近距離.如果measure_dist為0,函數(shù)返回+1,-1,0,分別表示在內(nèi)部,外部,在多邊形邊上.參數(shù)contour可以是序列,也可以是2通道矩陣向量.

    總結(jié)

    以上是生活随笔為你收集整理的opencv轮廓及点在轮廓内判断的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。