標籤:
輪廓是映像中表示邊界的一系列點的集合。雖然邊緣檢測演算法可以根據像素間的差異檢查出輪廓邊界的像素,但是它並沒有把輪廓做為一個整體表示出來。所以下一步工作是把這些邊緣檢測出來的像素組裝成輪廓。
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) Nextimgresult.Save("M:\影像處理實驗\輪廓提取\test-2-result.bmp")
OpenCV與EmguCV中的映像輪廓提取