OpenCV图像的轮廓的匹配
一個跟輪廓相關的最常用到的功能是匹配兩個輪廓.如果有兩個輪廓,如何比較它們;或者如何比較一個輪廓和另一個抽象模板.
矩
比較兩個輪廓最簡潔的方式是比較他們的輪廓矩.這里先簡短介紹一個矩的含義.簡單的說,矩是通過對輪廓上所有點進行積分運算(或者認為是求和運算)而得到的一個粗略特征.通常,我們如下定義一個輪廓的(p,q)矩:
在公式中p對應x緯度上的矩,q對應y維度上的矩,q對應y維度上的矩,階數表示對應的部分的指數.該計算是對輪廓邊界上所有像素(數目為n)進行求和.如果p和q全為0,那么m00實際上對輪廓邊界上點的數目.
下面的函數用于計算這些輪廓矩
void cvContoursMoments(CvSeq* contour,CvMoments* moments)
第一個參數是我們要處理的輪廓,第二個參數是指向一個結構,該結構用于保存生成的結果.CvMonments結構定義如下
/* Spatial and central moments */? typedef struct CvMoments? {? ??? double? m00, m10, m01, m20, m11, m02, m30, m21, m12, m03; /* spatial moments */? ??? double? mu20, mu11, mu02, mu30, mu21, mu12, mu03; /* central moments */? ??? double? inv_sqrt_m00; /* m00 != 0 ? 1/sqrt(m00) : 0 */? }? CvMoments;? 在cvContourMoments()函數中,只用到m00,m01,...,m03幾個參數;以mu開頭的參數在其他函數中使用.在使用CvMoment結構的時候,我們可以使用以下的函數來方便地一個特定的矩:
CVAPI(double)? cvGetSpatialMoment( CvMoments* moments, int x_order, int y_order );? 調用cvContoursMonments()函數會計算所有3階的矩(m21和m12會被計算,但是m22不會被計算).再論矩
剛剛描述的矩計算給出了一些輪廓的簡單屬性,可以用來比較兩個輪廓.但是在很多實際使用中,剛才的計算方法得到的矩并不是做比較時的最好的參數.具體說來,經常會用到歸一化的矩(因此,不同大小但是形狀相同的物體會有相同的值).同樣,剛才的小節中的簡單的矩依賴于所選坐標系,這意味這物體旋轉后就無法正確匹配.
OpenCV提供了計算Hu不變矩[Hu62]以及其他歸一化矩的函數.CvMoments結構可以用cvmoments或者cvContourMoments計算.并且,cvContourMoments現在只是cvMoments
的一個別名.
一個有用的小技巧是用cvDrawContour()描繪一幅輪廓的圖像后,調用一個矩的函數處理該圖像.使用無論輪廓填充與否,你都能用同一個函數處理.
以下是4個相關函數的定義:
/* Calculates all spatial and central moments up to the 3rd order */? CVAPI(void) cvMoments( const CvArr* arr, CvMoments* moments, int binary CV_DEFAULT(0));? CVAPI(double)? cvGetCentralMoment( CvMoments* moments, int x_order, int y_order );? CVAPI(double)? cvGetNormalizedCentralMoment(CvMoments* moments,int x_order, int y_order);? /* Calculates 7 Hu's invariants from precalculated spatial and central moments */? CVAPI(void) cvGetHuMoments( CvMoments*? moments, CvHuMoments*? hu_moments );? 第一個函數除了使用的是圖像(而不是輪廓)作為參數,其他方面和cvContoursMoments()函數相同,另外還增加了一個參數.增加的參數isBinary如果為CV_TRUE,cvMoments將把圖像當作二值圖像處理,所有的非0像素都當作1.當函數被調用的時候,所有的矩被計算(包含中心矩,請看下一段).除了x和y的值被歸一化到以0為均值,中心距本質上跟剛才描述的矩一樣.歸一化矩和中心矩也基本相同,除了每個矩都要除以m00的某個冪:
最后來介紹Hu矩,Hu矩是歸一化中心距的線性組合.之所以這樣做是為了能夠獲取代表圖像某個特性的矩函數,這些矩函數對于某些變化如縮放,旋轉和鏡像映射(除了h1)具有不變性.Hu矩是從中心矩中計算得到,其計算公式如下所示:
參考圖8-9和表8-1,我們可以直觀地看到每個圖像對應的7個Hu矩.通過觀察可以發現,當階數變高時,Hu矩一般會變小.對于這一點不必感到奇怪,因為根據定義,高階Hu矩由多個歸一化矩的高階冪計算得到,而歸一化矩都是小于1的,所以指數越大,計算所得的值越小.
需要特別注意的是"I",它對于180度旋轉和鏡面反射都是對稱的,它的h3到h7矩都是0;而"O"具有同樣的對稱特性,所有的Hu矩都是非0的.
使用Hu矩進行匹配
這些物體可以是灰度圖圖像或者輪廓.如果你提供了圖像,cvMatchShape()會在對比的進程之間為你計算矩.cvMatchShapes()使用的方法是表8-2中列出的三種中的一種.
?關于對比度量標準(metric)是如何被計算的,表8-2中的三個常量每個都用了不同的方法.這個度量標準最終決定了cvMatchShapes()的返回值.最后一個參數變量現在不能用,因此我們可以把它設成默認值0.
等級匹配
我們經常想要匹配兩個輪廓,然后用一個相似度量來計算輪廓所有匹配部分.使用概況參數的方法(比如矩)是相當快的,但是他們能夠表達的信息卻不是很多.
為了找到一個更精確的相似度量度,首先考慮一下輪廓樹的結構應該會有幫助.請注意,此外的輪廓樹是用來表述一個特定形狀(不是多個特定形狀)內各部分的等級關系.
類似于cvFindContours()著怎樣的函數放回多個輪廓,輪廓樹(contout tree)并不會把這些等級關系搞混,事實上,他正是對于某個特定輪廓形狀的登記描述.
理解了輪廓樹的創建會比較容易理解輪廓樹.從一個輪廓創建一個輪廓樹是從底端(葉節點)到頂端(根節點)的.首相搜索三角形突出或凹陷的形狀的周邊(輪廓上的每一個點都不是完全和它的相鄰點共線的).每個這樣的三角形被一條線段代替,這條線段通過連接非相鄰點的兩點得到;因此實際上三角形或者被削平(例如,圖8-10的三角形D)或者被填滿(三角形C).每個這樣的替代把輪廓的頂點減少1,并且給輪廓創建一個新節點.如果這樣一個三角形的兩側有原始的邊,那么它就是得到的輪廓樹的葉子;如果一側是已存在三角形,那么他就是那個三角形的父節點.這個過程的迭代最終把物體的外形剪成一個四邊形,這個四邊形也被剖開;得到的兩個三角形是根節點的兩個子節點.
結果的二分樹(圖8-11)最終將原始輪廓的形狀信息編碼.每個節點被它對應的三角形信息(比如三角形的大小,它的生成是被切出來還是被填進去的,這樣的信息)所注釋
這些樹一旦被建立,就可以很有效的對比兩個輪廓.這個過程開始定義兩個樹節點的對應關系,然后比較對應節點的特性.對吼的結果就是兩個樹的相似度.
事實上,我們基本不需要理解這個過程.OpenCV提供了一個函數從普通的CvContour對象自動生成輪廓樹并轉換返回;還提供一個函數用來對比兩個樹.不幸的是,建立的輪廓樹并不太魯棒(例如,輪廓上很小的改變可能會徹底改變結果的樹).同事,最初的三角形(樹的根節點)是隨意選取的.因此,為了得到較好的描述實現使用函數cvApproxPoly()之后將輪廓排列(運用循環移動)成最初的三角形不怎么受到旋轉影響的狀態.
CvContourTree* cvCreateContourTree(const CvSeq* contour,CvMemStorage* storage,double ?threshold);
CvSeq * cvContourFromContourTree(const CvContourTree* tree,CvMemStorage* storage, CvTermCriteria ?criteris);
double cvMatchContourTrees(const CvContourTree* tree1,const CvContourTree* tree2,int method,double threshold);
這個代碼提到了CvTremCriteria(),該函數細節將在第9章給出.現在可以用下面的默認值使用cvTermCriteria()簡單建立一個結構體.
CvTermCriteria termcrit = cvTermCriteria(CV_TERMCRIT_ITER | CV_TeRMCRT_EPS,5,1);
輪廓的凸包和凸缺陷
另一個理解物體形狀或輪廓的有用的方法是計算一個物體的凸包(convex hull)然后計算其凸缺陷(convexity defects)[Homma85].很多復雜物體的特性能很好的被這種缺陷表現出來.
圖8-12用人手舉例說明了凸缺陷這一概念.手周圍深色的線描畫出了凸包,A到H被標出的區域是凸包的各個"缺陷".正如所看到的,這些凸度缺陷提供了手以及手狀態的特征表現的方法.
enum? {? ??? CV_CLOCKWISE???????? =1,? ??? CV_COUNTER_CLOCKWISE =2? };? /* Calculates exact convex hull of 2d point set */? CVAPI(CvSeq*) cvConvexHull2( const CvArr* input,? ???????????????????????????? void* hull_storage CV_DEFAULT(NULL),? ???????????????????????????? int orientation CV_DEFAULT(CV_CLOCKWISE),? ???????????????????????????? int return_points CV_DEFAULT(0));? /* Checks whether the contour is convex or not (returns 1 if convex, 0 if not) */? CVAPI(int)? cvCheckContourConvexity( const CvArr* contour );? /* Finds convexity defects for the contour */? CVAPI(CvSeq*)? cvConvexityDefects( const CvArr* contour, const CvArr* convexhull,? ?????????????????????????????????? CvMemStorage* storage CV_DEFAULT(NULL));?OpenCV有三個關于凸包和凸缺陷的重要函數.第一個函數簡單計算已知輪廓的凸包,第二個函數用來檢查一個已知輪廓是否是凸的.第三個函數在已知輪廓是凸包的情況下計算凸缺陷.
函數cvConvexHull2()的第一個參數是點的數組,這個數組是一個n行2列的矩陣(n×2),或者是一個輪廓.如果是點矩陣,點應該是32位整型(CV_32SC1)或者是浮點型(CV_32F1).下一個參數是指向內存存儲的一個指針,為結果分配內存空間.下一參數是CV_CLOCkWISE或者是CV_COUNTERCLOCkWISE中的一個.這參數決定了程序返回點的排列方向.最后一個參數returnPoints,可以是0或1.如果設置為1,點會被存儲在返回數組中.如果設置為0,只有索引被存儲在返回數組中.索引是傳遞給cvConvexHull2()的原始數組索引.
讀著可能要問:"如果參數hull_storage是內存存儲,為什么它的類型是void* 而不是CvMemSotrage* ?",這是因為很多時候作為凸包放回的點的形式,數組可能比序列更加有用.可慮到這一點,參數hull_storage的另一個可能性是傳遞一個指向矩陣的指針CvMat*. 這種情況下,矩陣應該是一維的且和輸入點的個數相同.當cvConvexHull2()被調用的時候,它會修改矩陣頭來指明當前的列數.
有時候,已知一個輪廓但并不知道它是否是凸的.這種情況下,我們可以調用函數cvCheckContourConvexity().這個測試簡單快速,但是如果傳遞的輪廓自身有交叉的時候不會得到正確的結果.
第三個函數cvConvexityDefects(),計算凸缺陷返回一個缺陷的序列.為了完成這個任務,cvConvexityDefects()要求輸入輪廓,凸包和內存空間,從這個內存空間來獲得存放結果序列的內存.前兩個參數是CvArr*,和傳遞給cvConvexHull2()的參數input的形式相同.
typedef struct CvConvexityDefect? {? ??? CvPoint* start; /* point of the contour where the defect begins */? ??? CvPoint* end; /* point of the contour where the defect ends */? ??? CvPoint* depth_point; /* the farthest from the convex hull point within the defect */? ??? float depth; /* distance between the farthest point and the convex hull */? } CvConvexityDefect;? 函數cvConvexityDefects()返回一個CvConvexityDefect結構體的序列,其中包括一些簡單的參數用來描述凸缺陷.start和end是凸包上的缺陷的起始點和終止點.depth_point是缺陷中的距離凸包的邊(跟該缺陷有關的凸包便)最遠的點.最后一個參數depth是最遠點和包的邊(edge)的距離.成對幾何直方圖
Freeman鏈碼編碼是對一個多邊形的的序列如何"移動"的描述,每個這樣的移動有固定長度和特定的方向.但是,我們并沒有更多說明為什么需要用到這種描述.
Freeman鏈碼編碼的用處很多,但最常見的一種值得深入了解一下,因為它支持了成對幾何直方圖(PGH)的基本思想.
PGH實際上是鏈碼編碼直方圖(CCH)的一個擴展或延伸.CCH是一種直方圖,用來統計一個輪廓的Freeman鏈碼編碼每一種走法的數字.這種直方圖有一些良好的性質.最顯著的是,將物體旋轉45度,那么新的直方圖是老直方圖的循環平移(圖8-13).這就提供了一個不被此類旋轉影響的形狀識別方法.
PGH的構成如下圖所示(圖8-14).多邊形的每一個邊被選擇成為"基準邊".之后考慮其他的邊相對于這些基礎邊的關系,并且計算三個值:dmin,dmax和θ.dmin是兩條邊的最小距離,dmax是最大距離,θ是兩邊的夾角.PGH是一個二維直方圖,其兩個維度分別是角度和距離.對于每一對邊,有兩個bin,一個bin為(dmin,θ),另一個bin為(dmax,θ).對于這樣的每一組邊,這兩個bin都被增長,中間值d(dmin和dmax之間的值)同樣也被增長.
PGh的使用和FCC相似.一個重要不同是,PGH的描述能力更強,因此在嘗試解決復雜問題的時候很有用,比如說大量形狀需要被辨識,并且/或者有很多背景噪聲的時候.用來計算PGh的函數是
void cvCalcPGH(const CvSeq* contour,CvHistogram* hist)
在這里輪廓可以包含整數值的點的坐標;當然直方圖必須是二維的.
???????????再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://blog.csdn.net/jiangjunshow
總結
以上是生活随笔為你收集整理的OpenCV图像的轮廓的匹配的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 别再管你的API叫微服务了
- 下一篇: 蓝桥杯真题刷题