1. 程式人生 > >OpenCV與EmguCV中的影象輪廓提取

OpenCV與EmguCV中的影象輪廓提取

輪廓是影象中表示邊界的一系列點的集合。

雖然邊緣檢測演算法可以根據畫素間的差異檢查出輪廓邊界的畫素,但是它並沒有把輪廓做為一個整體表示出來。所以下一步工作是把這些邊緣檢測出來的畫素組裝成輪廓。
openCV中可以用findContours()函式來從二值影象中提取輪廓。openCV中一般用序列來儲存輪廓資訊。序列中的每一個元素是曲線中一個點的位置。函式findContours()從二值影象中尋找輪廓。findContours()處理的影象可以是Canny()後得到的有邊緣畫素的的影象,也可以是Threshold()後得到的影象,這時的邊緣是正負區域之間的邊界。
在介紹函式原型之前,我們還需要簡單瞭解下輪廓樹的概念。openCV允許得到的輪廓被聚合成一個輪廓樹,從而把包含的關係編碼到輪廓樹中。輪廓中直接包含的輪廓成為了它們的子節點。以此類推。

OpenCV3.0中的函式原型如下:

void findContours(InputOutArray image, OutputArrayOfArrays contours,  OutputArray hierarchy, int mode, int method, Point offset = Point())
  • 第一個引數,InputOutArray型別的image,源影象,應為8位單通道的Mat型別。影象的非零畫素被認為是1,0畫素被保留為0。此函式會在提取圖線輪廓的同時修改影象的內容。
  • 第二個引數,OutputArrayOfArrays型別的contours。函式呼叫後的運算結果儲存在這裡,即為檢測到的輪廓,每一個輪廓儲存為一個點向量,即用Point型別的vector表示。
  • 第三個引數,OutputArray型別的hierarchy,可選的輸出向量,包含影象的拓撲資訊。其作為輪廓數量的表示,包含了許多元素。每個輪廓contours[i]對應4個hierarchy元素hierarchy[i][0]~hierarchy[i][3],分別表示後一個輪廓、前一個輪廓、父輪廓、內嵌輪廓的索引編號。如果沒有對應的項,該hierarchy[i]值對應的設為負數。
  • 第四個引數,int型別的mode,輪廓檢索模式。
            RETR_EXTERNAL - 只提取最外層的輪廓 。對於所有輪廓設定hierarchy[i][2] = hierarchy[i][3] = -1。
            RETR_LIST - 提取所有輪廓,並且放置在 list 中
            RETR_CCOMP - 提取所有輪廓,並且將其組織為兩層的 hierarchy: 頂層為連通域的外圍邊界,次層為洞的內層邊界。            RETR_TREE - 提取所有輪廓,並且重構巢狀輪廓的全部 hierarchy
  • 第五個引數,int型別的method,輪廓逼近的方法。
            CHAIN_APPROX_NONE - 將所有點由鏈碼形式翻譯(轉化)為點序列形式
            CHAIN_APPROX_SIMPLE - 壓縮水平、垂直和對角分割,即函式只保留末端的象素點;
            CHAIN_APPROX_TC89_L1,CHAIN_APPROX_TC89_KCOS - 應用 Teh-Chin 鏈逼近演算法。
  • 第六個引數,Point型別的offset,每個輪廓點的可選偏移量,有預設值Point()當輪廓是從影象 ROI 中提取出來的時候,這個引數就可以排上用場了,因為可以從整個影象上下文來對輪廓做分析。
eg。
Mat srcImage = imread("M:/影象處理實驗/輪廓提取/test-1.bmp",1);
cvtColor(srcImage, srcImage, COLOR_BGR2GRAY);
adaptiveThreshold(srcImage,srcImage,255,ADAPTIVE_THRESH_GAUSSIAN_C,THRESH_BINARY, 35, 10);
Mat result = Mat::zeros(srcImage.size(), CV_8UC3);  
srcImage.copyTo(result);
Canny(srcImage,srcImage,3,6,3);
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours(srcImage, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_SIMPLE);
int areaMin  = srcImage.cols * srcImage.rows;
for (int i = 0; i < contours.size(); i++ ){
    double area = contourArea(((contours)._Myfirst)[i]);
    if (area > srcImage.rows * srcImage.cols/3){
        //選取滿足條件的最小的面積。認為改輪廓為答題卡的邊框。
        if (areaMin > area){
            areaMin = area;
        }else{
            continue;
        }
        double area = contourArea(((contours)._Myfirst)[i]);
        Scalar color(rand() & 255, rand() & 255, rand() & 255);
        drawContours(result, contours, i, color, CV_FILLED, 8, hierarchy, 0, Point());
    }            
}
imwrite("M:/影象處理實驗/輪廓提取/test-1-result.bmp", result);
以下為原圖及輪廓提取後的結果:

EmguCV3.0中的函式原型如下:

Public Shared Sub FindContours(image As Emgu.CV.IInputOutputArray, contours As Emgu.CV.IOutputArray, hierarchy As Emgu.CV.IOutputArray, mode As Emgu.CV.CvEnum.RetrType, method As Emgu.CV.CvEnum.ChainApproxMethod, Optional offset As System.Drawing.Point = Nothing)
  • 第一個引數,Emgu.CV.IInputOutputArray型別的image,源影象。影象的非零畫素被認為是1,0畫素被保留為0。此函式會在提取圖線輪廓的同時修改影象的內容。
  • 第二個引數,Emgu.CV.IOutputArray型別的contours。函式呼叫後的運算結果儲存在這裡,即為檢測到的輪廓,每一個輪廓儲存為一個點向量,即用Point型別的vector表示。
  • 第三個引數,Emgu.CV.IOutputArray型別的hierarchy,可選的輸出向量,包含影象的拓撲資訊。
  • 第四個引數,Emgu.CV.CvEnum.RetrType型別的mode,輪廓檢索模式。
            Emgu.CV.CvEnum.RetrType.External- 只提取最外層的輪廓 。
            Emgu.CV.CvEnum.RetrType.List- 提取所有輪廓,並且放置在 list 中
            Emgu.CV.CvEnum.RetrType.Ccomp - 提取所有輪廓,並且將其組織為兩層的 hierarchy: 頂層為連通域的外圍邊界,次層為洞的內層邊界。
            Emgu.CV.CvEnum.RetrType.Tree- 提取所有輪廓,並且重構巢狀輪廓的全部 hierarchy 
  • 第五個引數,Emgu.CV.CvEnum.ChainApproxMethod型別的method,輪廓逼近的方法。
            Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxNone- 將所有點由鏈碼形式翻譯(轉化)為點序列形式
            Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple- 壓縮水平、垂直和對角分割,即函式只保留末端的象素點;
            Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxTC89L1, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxTC89KCOS - 應用 Teh-Chin 鏈逼近演算法。
  • 第六個引數,Point型別的offset,每個輪廓點的可選偏移量,有預設值。當輪廓是從影象 ROI 中提取出來的時候,這個引數就可以排上用場了,因為可以從整個影象上下文來對輪廓做分析。

eg。
Dim bkGrayWhite As New Gray(255)
Dim img As Image(Of Gray, Byte) = New Image(Of Gray, Byte)("M:\影象處理實驗\輪廓提取\test-2.bmp")
Dim img_threshold As Image(Of Gray, Byte) = New Image(Of Gray, Byte)(img.Width, img.Height, bkGrayWhite)
Dim imgresult As Image(Of Rgb, Byte) = New Image(Of Rgb, Byte)(img.Width, img.Height, New Rgb(255, 255, 255))
img.CopyTo(img_threshold)
CvInvoke.AdaptiveThreshold(img_threshold, img, 255, CvEnum.AdaptiveThresholdType.MeanC, CvEnum.ThresholdType.Binary, 35, 10)
Dim imgCanny As Image(Of Gray, Byte) = New Image(Of Gray, Byte)(img.Width, img.Height, bkGrayWhite)
CvInvoke.Canny(img, imgCanny, 25, 25 * 2, 3)
Dim contours As Emgu.CV.Util.VectorOfVectorOfPoint = New Emgu.CV.Util.VectorOfVectorOfPoint()
Dim hierarchy As Emgu.CV.IOutputArray = New Image(Of Gray, Byte)(img.Width, img.Height, bkGrayWhite)
CvInvoke.FindContours(imgCanny,
                                     contours,
                                     hierarchy,
                                     Emgu.CV.CvEnum.RetrType.External,
                                     Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple
                                      )
Dim areaMax As Integer = img.Width * img.Height
    For i = 0 To contours.Size - 1
            Dim area As Integer = CvInvoke.ContourArea(contours(i))
            '篩選輪廓面積大於三分之一整體圖片面積的輪廓
            If area < areaMax / 3 Then
                Continue For
            End If
            CvInvoke.DrawContours(imgresult, contours, i, New MCvScalar(0, 0, 0), 2, CvEnum.LineType.EightConnected, hierarchy, 2147483647)
    Next
imgresult.Save("M:\影象處理實驗\輪廓提取\test-2-result.bmp")