【小林的OpenCV基礎課 13】畫輪廓的新人
新學期開始了,天気は暖かくなっています
這一話我們來學習OpenCV中輪廓檢測的基本方法。關於輪廓,同學們一定能想起前面的邊緣檢測,但直接進行邊緣檢測後會出現一個尷尬的情況:只計算出了邊緣,但對機器來說不知道哪些是物體的輪廓,而很多時候我們確實肥腸希望能找出物體的輪廓。
輪廓檢測能較好的化解這一尷尬的情況。對於輪廓,官方指導中給出了這樣的解釋:
「輪廓可以理解為圖像中具有相同顏色或密度的位於邊界的連續點的集合,輪廓是形狀分析和對象識別的有利工具。」
在OpenCV中,我們常用findContours函數來計算輪廓,每個獨立的輪廓都是以Numpy array的點坐標的形式呈現。
為了在圖像中顯示出計算出的輪廓,我們使用drawContours函數。通常這個函數會配合findContours使用。
輪廓檢測的一般步驟:
- 圖像預處理,為達到良好的精度,通常要使用二值化圖像,因此常用閾值化處理或邊緣檢測對圖像進行預處理
- 使用findContours計算輪廓
- 使用drawContours或其他方法畫出輪廓
API:
image, contours, hierarchy=cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]])
- image:源圖像,8位單通道圖像,非0像素值均按像素值為1處理
- contours:輪廓信息,每個輪廓由點集組成,而所有的輪廓構成了contours列表
- hierarchy:可選參數,表示輪廓的層次信息(拓撲信息,有種樹結構的感覺),每個輪廓元素contours[i]對應4個hierarchy元素hierarchy[i][0]~hierarchy[i][3],分別表示後一個輪廓、前一個輪廓、父輪廓和內嵌輪廓的編號,若無對應項,則該參數為負值
- mode:輪廓檢索模式
- method:輪廓逼近方法
關於輪廓的檢索模式,有:
- cv2.RETR_EXTERNAL:只檢測最外層輪廓,並置hierarchy[i][2]=hierarchy[i][3]=-1
- cv2.RETR_LIST:提取所有輪廓並記錄在列表中,輪廓之間無等級關係
- cv2.RETR_CCOMP:提取所有輪廓並建立雙層結構(頂層為連通域的外圍輪廓,底層為孔的內層邊界)
- cv2.RETR_TREE:提取所有輪廓,並重新建立輪廓層次結構
關於輪廓的逼近方法,有:
- cv2.CHAIN_APPROX_NONE:獲取每個輪廓的每個元素,相鄰像素的位置差不超過1,即連續的點,但通常我們並不需要所有的點
- cv2.CHAIN_APPROX_SIMPLE:壓縮水平方向、垂直方向和對角線方向的元素,保留該方向的終點坐標,如矩形的輪廓可用4個角點表示,這是一種常用的方法,比第一種方法能得出更少的點
- cv2.CHAIN_APPROX_TC89_L1和cv2.CHAIN_APPROX_TC89_KCOS:對應Tch-Chain鏈逼近演算法
image=cv2.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]])
- image:目標輸出圖像,可理解為畫布
- contours:所有的輸入輪廓,可直接傳入findContours函數中的contours參數
- contourIdx:輪廓繪製的指示變數,即繪製哪個輪廓,若為負值則繪製所有輪廓
- 剩餘參數均有對應的默認值,可查看API
下面看一個Demo,素材取自人見人愛的哆啦A夢:
import cv2import numpy as npimg = cv2.imread(duolaameng.jpeg)# 圖像預處理grayImg = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)_, binImg = cv2.threshold(grayImg, 100, 255, cv2.THRESH_BINARY)# 尋找輪廓_, contours, hierarchy = cv2.findContours(binImg, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# 畫出輪廓cv2.drawContours(img, contours, 1, (0, 200, 0), 2)cv2.imshow(Contours Image, img)# 當前逼近模式下該輪廓有798個點 若逼近模式為CHAIN_APPROX_NONE則有1609個點print(contours[1].shape)cv2.waitKey()cv2.destroyAllWindows()
托爾醬表示並不過癮,因為她感覺這和Canny邊緣檢測從效果上看太像了。
為了滿足托爾醬的對人類世界的求知慾,下面我們來學習使用多邊形包圍輪廓,效果上(看起來)類似於AI中的物體檢測(但原理上二者差別甚多)。
需要學習下面5個函數,這5個函數都是把點的集合圈起來:
- boundingRect:返回外部邊界矩形,即四平八穩的矩形框
- minAreaRect:尋找最小包圍矩形,即外接矩形,也是會旋轉的矩形框
- minEnclosingCircle:尋找最小包圍圓形
- fitEllipse:用橢圓擬合二維點集
- approxPolyDP:逼近多邊形曲線
# retval:(x,y,w,h)四個參數 其中w和h為寬和高retval=cv2.boundingRect(points)# retval:((x,y),(w,h),angle)依次為中心坐標 矩形的寬高和旋轉角度retval=cv2.minAreaRect(points)retval=cv2.fitEllipse(points)
- points:點集
center, radius=cv2.minEnclosingCircle(points)
- center、radius:圓心和半徑
- points:點集
approxCurve=cv2.approxPolyDP(curve, epsilon, closed[, approxCurve])
這裡強調下常用的minAreaRect函數。通常將返回值(Box2D結構)傳入boxPoints函數得到旋轉矩形四個頂點的坐標,這個封裝在cv2里的boxPoints函數最初的版本是封裝在cv里的BoxPoints(不在cv2里),如果要使用BoxPoints則需要安裝OpenCV 2.xx版本,所以我們直接調用cv2.boxPoints函數即可。
下面看一個Demo,我們把上面的哆啦A夢用矩形框出來:
import cv2import numpy as npimg = cv2.imread(duolaameng.jpeg)# 圖像預處理grayImg = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)_, binImg = cv2.threshold(grayImg, 100, 255, cv2.THRESH_BINARY)# 尋找輪廓_, contours, hierarchy = cv2.findContours(binImg, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)# 畫出輪廓cv2.drawContours(img, contours, 1, (0, 200, 0), 2)x, y, w, h = cv2.boundingRect(contours[1])cv2.rectangle(img, (x, y), (x+w, y+h), (0, 0, 200), 2)cv2.imshow(Contours Image, img)cv2.waitKey()cv2.destroyAllWindows()
再來一個Demo,使用minAreRect函數得到能包圍輪廓的最小矩形,因為這個矩形通常是旋轉一定角度的,而網上的一些常式也使用了仿射變換等高端操作,所以小林另闢蹊徑,直接將矩形的四個點連起來得到矩形,並封裝成了一個函數,注意了,rectangle函數是沒法畫出旋轉矩形的:
import cv2import numpy as npdef drawRotatedRect(rect, image): box = cv2.boxPoints(rect) x0, y0 = box[0] for i in range(3): x, y = box[i] x1, y1 = box[i + 1] cv2.line(image, (x, y), (x1, y1), (0, 0, 255), 2) if i is 2: cv2.line(image, (x1, y1), (x0, y0), (0, 0, 255), 2)img = cv2.imread(duolaameng.jpeg)# 圖像預處理grayImg = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)_, binImg = cv2.threshold(grayImg, 100, 255, cv2.THRESH_BINARY)# 尋找輪廓_, contours, hierarchy = cv2.findContours(binImg, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)# 得到矩形框的相關參數rect = cv2.minAreaRect(contours[1])x, y = rect[0]w, h = rect[1]angle = rect[2]print(center:+str(int(x))+,+str(int(y))+ w,h:+str(int(w))+,+str(int(h))+ angle:+str(int(angle)))# 畫出旋轉的矩形框drawRotatedRect(rect, img)cv2.imshow(Contours Image, img)cv2.waitKey()cv2.destroyAllWindows()
這一話的代碼已經同步到Github了,對應Class 4 Image Processing文件夾下的C4 Contours.py 和C4 ApproxRectContours.py。
作業君來啦
請同學們嘗試在你的書桌上擺放幾件物品,然後檢測輪廓並用矩形框出來。
最後的最後
如果喜歡小林的專欄,就收藏了吧!してください!
推薦閱讀:
※菜鳥學tensorflow
※【小林的OpenCV基礎課 10】Canny邊緣檢測
※圖像檢索之Large-Scale Image Retrieval with Attentive Deep Local Features
※【生成高清人臉】ProgressiveGAN 筆記
※[計算機視覺論文速遞] 2018-04-03