OpenCV中輪廓處理簡介


一、OpenCV中的輪廓
        在OpenCV中,將 Canny 等邊緣檢測算法根據像素間的差異檢測出輪廓邊界的像素,作為一個整體來研究和分析,稱之為輪廓。比如對於這副圖片:
圖像的上半部分是一張白色背景上的測試圖像,包含了一系列標記 A E的區域。尋找到的輪廓被標記為 cX 或 hX, 其中c 代表 “輪廓(contour)”,h 代表 “孔(hole)”(也可以理解為內輪廓)。
同樣,左圖是原始圖片,右圖是尋找到的輪廓,它也采用了類似的標注方法。

二、函數調用細節
尋找輪廓的主要函數是 cv::findContours(),它的主要定義為:
void cv::findContours(
  cv::InputOutputArray    image,               // 輸入圖像,特別需要注意是二值圖像     
  cv::OutputArrayOfArrays contours,        //輸出結果
  cv::OutputArray         hierarchy,              // 層級結果
  int                     mode,                             //定義輪廓是如何提取
  int                     method,                          // 定義輪廓的尋找方法  
  cv::Point           offset = cv::Point()     // Offset every point
);
其中參數定義:
參數一:輸入圖像,8位單通道;
參數二:“an array of arrays”,一般采用“ an STL vector of STL vectors”,找到的輪廓、函數調用后的運算結果保存在這里;
參數三:hierarchy(層次,等級),可選輸出向量。包含圖像的拓撲信息。每個輪廓對應4元組,分別對應后一個輪廓、前一個輪廓、父輪廓和內嵌輪廓。
參數四:flag 輪廓檢索模式
參數五:flag 輪廓近似方法
其中,對於參數二它主要是以vector<cv::point>的形式保存尋找到的輪廓結果;
對於參數三一般表示為cv::Vec4i 型的元素,並且進一步按照以下結構來進行定義:

Index

Meaning

0

同級的下一條輪廓

1

同級的前一條輪廓

2

下級的第一個子節點

3

上級的父節點

通過這幅圖能夠很明顯地看出層次關系:


其對應的輪廓的相互關系為:


0  [ 7-11-1]
1  [-1-120]
2  [-1-131]
3  [-1-142]
4  [-1-153]
5  [ 6-1-14]
6  [-15-14]
7  [ 80-1-1]
8  [-17-1-1]

對於參數四,輪廓檢索模式:
對於參數五,輪廓近似方法:

      cv::CHAIN_APPROX_NONE

將輪廓編碼中的所有點轉換為點。 這一操作將會產生大量的點,每個點都將成為前一個點的8個鄰點之一, 不會減少返回的點數,

       cv::CHAIN_APPROX_SIMPLE

壓縮水平、垂直、斜的部分,只保留最后一個點。 在許多特殊情況下,這一操作將大大減少返回的點數。 極端例子是,對於一個沿着x-y x-y 軸方向的矩形(任意大小), 只會返回4個點。

       cv::CHAIN_APPROX_TC89_L1 or cv::CHAIN_APPROX_TC89_KCOS

使用Teh-Chin 鏈逼近算法中的一個。

三、重要函數
輪廓處理中經常遇到的任務是計算一些輪廓變化的概括特性這可能包括長度或其他一些反應輪廓整體大小的量度,包括以下函數:
cv::arcLength() 得到輪廓的長度
double  cv::arcLength(
  cv::InputArray  points,      // Array or vector of 2-dimensional points
  bool            closed       // If true, assume link from last to first vertex
);

第一個參數代表是輪廓,其形式可以是任何常見的輪廓表示方法(如標准模板庫的點向量,或二通道數組)。 

第二個參數closed表示該輪廓是否是閉合的。 假如輪廓是閉合的,則參數points中的最后一個點到第一個的距離也算入總弧長中。

cv::minAreaRect()活動輪廓的最小外接矩形
cv::RotatedRect cv::minAreaRect( // Return rectangle bounding the points
  cv::InputArray  points,        // Array or vector of 2-dimensional points
);
其返回的是一個RotatedRect 結構。

cv::minEnclosingCircle()獲得最小包圍圓
void  cv::minEnclosingCircle(
   cv::InputArray points,        // Array or vector of 2-dimensional points
   cv::Point2f&   center,        // Result location of circle center
   float&         radius         // Result radius of circle
);
center radius 參數是需要獲得的結果。

cv::convexHull()獲得輪廓的凸包
void cv::convexHull(
  cv::InputArray  points,               // Array or vector of 2-d points
  cv::OutputArray hull,                 // Array of points or integer indices
  bool            clockwise    = false, // true='output points will be clockwise'
  bool            returnPoints = true   // true='points in hull', else indices
);

pointPolygonTest 檢測點是否落在多邊形內
double cv::pointPolygonTest(   // Return distance to boundary (or just side)
  cv::InputArray contour,      // Array or vector of 2-dimensional points
  cv::Point2f    pt,           // Test point
  bool           measureDist   // true 'return distance', else {0,+1,-1} only
);
當參數measureDist設為真,函數將返回該點距離最近的輪廓邊緣的距離。若點在輪廓內,距離為0;若點在輪廓外,距離將是大於0的整數
基於pointPolygonTest,我們能夠實現尋找已知輪廓最大內接圓”,具體來說,就是找到這樣的結果:

細節可以參考:https://github.com/opencv/opencv/pull/12206,這個實現已經被OpenCV收錄。
四、快速連通區域分析

與輪廓分析緊密相關的另一種方法是連通區域分析.  采用閾值化等方法分割一張圖像后,我們可以采用連通區域分析來有效地對返回圖像逐張分離和處理。在以前,常用的方法是是先調用 cv::findContours() 函數(傳入cv::RETR_CCOMP 標志),隨后在得到的連通區域上循環調用 cv::drawContours() 

比如,我在GOCVHelper中這樣進行了實現
    //尋找最大的輪廓
    VP FindBigestContour(Mat src){    
         int  imax  =   0 //代表最大輪廓的序號
         int  imaxcontour  =   - 1 //代表最大輪廓的大小
        std : : vector < std : : vector < Point >> contours;    
        findContours(src,contours,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);
         for  ( int  i = 0 ;i < contours.size();i ++ ){
             int  itmp  =   contourArea(contours[i]); //這里采用的是輪廓大小
             if  (imaxcontour  <  itmp ){
                imax  =  i;
                imaxcontour  =  itmp;
            }
        }
         return  contours[imax];
    }
     //尋找並繪制出彩色聯通區域
    vector < VP >  connection2(Mat src,Mat &  draw){    
        draw  =  Mat : : zeros(src.rows,src.cols,CV_8UC3);
        vector < VP > contours;    
        findContours(src.clone(),contours,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);
         //由於給大的區域着色會覆蓋小的區域,所以首先進行排序操作
         //冒泡排序,由小到大排序
        VP vptmp;
         for ( int  i = 1 ;i < contours.size();i ++ ){
             for ( int  j = contours.size() - 1 ;j > = i;j -- ){
                 if  (contourArea(contours[j])  <  contourArea(contours[j - 1 ]))
                {
                    vptmp  =  contours[j - 1 ];
                    contours[j - 1 =  contours[j];
                    contours[j]  =  vptmp;
                }
            }
        }
   }
從OpenCV3開始實現專門函數 cv::connectedComponents() 和函數 cv::connectedComponentsWithStats()尋找
int  cv::connectedComponents (
    cv::InputArrayn image,                // input 8-bit single-channel (binary)
    cv::OutputArray labels,               // output label map
    int             connectivity = 8,     // 4- or 8-connected components
    int             ltype        = CV_32S // Output label type (CV_32S or CV_16U)
    );
int  cv::connectedComponentsWithStats (
    cv::InputArrayn image,                // input 8-bit single-channel (binary)
    cv::OutputArray labels,               // output label map
    cv::OutputArray stats,  // Nx5 matrix (CV_32S) of statistics:[x0, y0, width0, height0, area0;... ; x(N-1), y(N-1), width(N-1),height(N-1), area(N-1)]
    cv::OutputArray centroids,            // Nx2 CV_64F matrix of centroids:[ cx0, cy0; ... ; cx(N-1), cy(N-1)]
    int             connectivity = 8,     // 4- or 8-connected components
    int             ltype        = CV_32S // Output label type (CV_32S or CV_16U)
    );
由於connectedComponentsWithStatsconnectedComponents的增強版,所以我們主要介紹connectedComponentsWithStats。
參數二、labels用來保存尋找到的“聯通區域”,以0-N用來表示當前像素屬於該張圖片的第幾個輪廓;
這張動圖是label的結果,可以看到有數值的區域表示的就是“第幾個輪廓”
參數三、stats用來保存“聯通區域”之間的關系,它是一個5XN的表格,可以直接使用ImageWtach打開。
分別對應各個輪廓的x,y,width,height和area。注意0的區域標識的是background,所以出現了(0,0)。
參數四、centroids對應的是輪廓的重點


五、矩

5.1什么是矩(moment)
 
數學定義:實函數相對於值c的n階矩為
 
 
 從上述公式可以看到,它就是一個加了權重的積分,而權重是(x-c)n,其中n是階數(n階矩),如果把它想成一個平面直角系中,c是x軸上的一點,(x-c)n是個x點相對於c點值的n次方。以下是個積分的圖示,只要想象一下,它的每個小方塊再乘上權重:(xi-c)^n即可得到矩。
 
輪廓處理中用到的矩,是它在統計學中的應用。
以上公式是一元的情況,擴展到圖片所在的二元,想象我們有一個圖像矩陣,經過了尋找邊緣,轉換輪廓之后,矩陣中每個值點f(x,y)的值或為0(不是輪廓點),或為1(是輪廓點),當f(x,y)為0時,該積分項也為0,可以不計算,因此,對我們有意義的只有f(x,y)=1的n個點,即輪廓點。在后面公式中記為I(x,y),x,y為其在圖中的坐標,c點擴展到二元,可以視為輪廓的中心點,我們求得的所謂n階中心矩,就如上述公式所示,積分的權重是輪廓上各點相對於中心位置c的n次方。
此時我們可以得到一些統計規律,比如:輪廓邊界長度(零階矩),x/y方向上的均值(即質心,由一階矩求得),方差(由二階中心矩求得),形狀特性(Hu矩)
 
5.2. 常用的矩
 
1) 空間矩(spatial moment)
i. 用途
最簡單地輪廓比較,只能用於對比位置,大小,角度完全一致的輪廓。一般來說輪廓矩代表了一條輪廓、一幅圖像、一組點集的某些高級特征。

ii. 公式
 


在上式中,mp,q代表對象中所有像素的總和,其中每個像素x, y的像素值都乘以因子 xpyqm00時,這個因子等於1。因此若圖像為二值圖(如,所有像素都等於0或者1),則 m00代表圖像上所有值非零的區域。 當處理輪廓時,結果是輪廓的長度。

mpq表示圖像的(p+q)階矩,一般計算所有3階的矩(p+q<=3)。其中 I(x,y) 是象素點 (x, y) 的值,一般是1,n是輪廓上點的個數,p和q分別是x維度和y維度上的矩,即m00,m10,m01…m03。
零階矩m00是輪廓上點的簡單累加,即輪廓上有多少個點 。
一階矩m10,m01分別是x和y方向上的累加


2) 中心矩(central moment)
 
i. 用途
xavg和yavg由一階矩和零階矩的比值算出(見公式),它是重心坐標,中心矩即是根據x,y與重心的相對位置求取的矩,它使得結果與圖像相對於x,y軸的位置無關(與平移無關)。
ii. 公式
 
m10m01分別除以 m00,能得到整個對象的平均x值和y值。

中心矩常用μp, q標注,定義如下

其中:


 
3) 歸一化的中心矩
 
i. 用途
使用m00的冪對中心矩歸一化,使得結果與圖像大小無關
ii. 公式
 
 

 
4) Hu不變矩
 
i. 用途
Hu矩是歸一化中心矩的線性組合,它對於縮放,旋轉,鏡像映射具有不變性。
ii. 公式
 
 
最后,我想補充一下我認為適合輪廓分析的場合:

感謝閱讀至此,希望有所幫助。







免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM