輪廓檢測
輪廓檢測也是圖像處理中經常用到的。OpenCV-Python接口中使用cv2.findContours()函數來查找檢測物體的輪廓。
實現
使用方式如下:
import cv2
img = cv2.imread("./test.jpg")
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,contours,-1,(0,0,255),3)
cv2.imshow("img", img)
cv2.waitKey(0)
需要注意的是cv2.findContours()函數接受的參數為二值圖,即黑白的(不是灰度圖),所以讀取的圖像要先轉成灰度的,再轉成二值圖,參見4、5兩行。第六行是檢測輪廓,第七行是繪製輪廓。
結果
原圖如下:
檢測結果如下:
注意,findcontours函數會“原地”修改輸入的圖像。這一點可通過下麵的語句驗證:
cv2.imshow("binary", binary)
contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.imshow("binary2", binary)
執行這些語句後會發現原圖被修改了。
cv2.findContours()函數
函數的原型為
cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]])
opencv2返回兩個值:contours:hierarchy。注:opencv3會返回三個值,分別是img, countours, hierarchy
參數
第一個參數是尋找輪廓的圖像;
第二個參數表示輪廓的檢索模式,有四種(本文介紹的都是新的cv2接口):
cv2.RETR_EXTERNAL 表示隻檢測外輪廓
cv2.RETR_LIST 檢測的輪廓不建立等級關係
cv2.RETR_CCOMP 建立兩個等級的輪廓,上麵的一層為外邊界,裏麵的一層為內孔的邊界信息。如果內孔內還有一個連通物體,這個物體的邊界也在頂層。
cv2.RETR_TREE 建立一個等級樹結構的輪廓。
第三個參數method為輪廓的近似辦法
cv2.CHAIN_APPROX_NONE 存儲所有的輪廓點,相鄰的兩個點的像素位置差不超過1,即max(abs(x1-x2),abs(y2-y1))==1
cv2.CHAIN_APPROX_SIMPLE 壓縮水平方向,垂直方向,對角線方向的元素,隻保留該方向的終點坐標,例如一個矩形輪廓隻需4個點來保存輪廓信息
cv2.CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS 使用teh-Chinl chain 近似算法
返回值
cv2.findContours()函數返回兩個值,一個是輪廓本身,還有一個是每條輪廓對應的屬性。
contour返回值
cv2.findContours()函數首先返回一個list,list中每個元素都是圖像中的一個輪廓,用numpy中的ndarray表示。這個概念非常重要。在下麵drawContours中會看見。通過
print (type(contours))
print (type(contours[0]))
print (len(contours))
可以驗證上述信息。會看到本例中有兩條輪廓,一個是五角星的,一個是矩形的。每個輪廓是一個ndarray,每個ndarray是輪廓上的點的集合。
由於我們知道返回的輪廓有兩個,因此可通過
cv2.drawContours(img,contours,0,(0,0,255),3)
和
cv2.drawContours(img,contours,1,(0,255,0),3)
分別繪製兩個輪廓,關於該參數可參見下麵一節的內容。同時通過
print (len(contours[0]))
print (len(contours[1]))
輸出兩個輪廓中存儲的點的個數,可以看到,第一個輪廓中隻有4個元素,這是因為輪廓中並不是存儲輪廓上所有的點,而是隻存儲可以用直線描述輪廓的點的個數,比如一個“正立”的矩形,隻需4個頂點就能描述輪廓了。
hierarchy返回值
此外,該函數還可返回一個可選的hiararchy結果,這是一個ndarray,其中的元素個數和輪廓個數相同,每個輪廓contours[i]對應4個hierarchy元素hierarchy[i][0] ~hierarchy[i][3],分別表示後一個輪廓、前一個輪廓、父輪廓、內嵌輪廓的索引編號,如果沒有對應項,則該值為負數。
通過
print (type(hierarchy))
print (hierarchy.ndim)
print (hierarchy[0].ndim)
print (hierarchy.shape)
可以看出,hierarchy本身包含兩個ndarray,每個ndarray對應一個輪廓,每個輪廓有四個屬性。
輪廓的繪製
OpenCV中通過cv2.drawContours在圖像上繪製輪廓。
cv2.drawContours()函數
cv2.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset ]]]]])
第一個參數是指明在哪幅圖像上繪製輪廓;
第二個參數是輪廓本身,在Python中是一個list。
第三個參數指定繪製輪廓list中的哪條輪廓,如果是-1,則繪製其中的所有輪廓。後麵的參數很簡單。其中thickness表明輪廓線的寬度,如果是-1(cv2.FILLED),則為填充模式。繪製參數將在以後獨立詳細介紹。
補充:
http://blog.csdn.net/sunny2038/article/details/12889059博客提到,可用下麵的方式計算得到輪廓的極值點,如下
pentagram = contours[1] #第二條輪廓是五角星
leftmost = tuple(pentagram[:,0][pentagram[:,:,0].argmin()])
rightmost = tuple(pentagram[:,0][pentagram[:,:,0].argmin()])
cv2.circle(img, leftmost, 2, (0,255,0),3)
cv2.circle(img, rightmost, 2, (0,0,255),3)
注意!假設輪廓有100個點,OpenCV返回的ndarray的維數是(100, 1, 2)!!!而不是我們認為的(100, 2)。切記!!!人民郵電出版社出版了一本《NumPy攻略:Python科學計算與數據分析》,推薦去看一下。
更新:關於pentagram[:,0]的意思
在numpy的數組中,用逗號分隔的是軸的索引。舉個例子,假設有如下的數組:
a = np.array([[[3,4]], [[1,2]],[[5,7]],[[3,7]],[[1,8]]])
其shape是(5, 1, 2)。與我們的輪廓是相同的。那麼a[:,0]的結果就是:
[3,4], [1,2], [5,7], [3,7], [1,8]
這裏a[:,0]的意思就是a[0:5,0],也就是a[0:5,0:0:2],這三者是等價的。
回頭看一下,a的shape是(5,1,2),表明是三個軸的。在numpy的數組中,軸的索引是通過逗號分隔的。同時冒號索引“:”表示的是該軸的所有元素。因此a[:, 0]表示的是第一個軸的所有元素和第二個軸的第一個元素。在這裏既等價於a[0:5, 0]。
再者,若給出的索引數少於數組中總索引數,則將已給出的索引樹默認按順序指派到軸上。比如a[0:5,0]隻給出了兩個軸的索引,則第一個索引就是第一個軸的,第二個索引是第二個軸的,而第三個索引沒有,則默認為[:],即該軸的所有內容。因此a[0:5,0]也等價於a[0:5,0:0:2]。
再詳細一點,a的全體內容為:[[[3,4]], [[1,2]],[[5,7]],[[3,7]],[[1,8]]]。去掉第一層方括號,其中有五個元素,每個元素為[[3,4]]這樣的,所以第一個索引的範圍為[0:5]。注意OpenCV函數返回的多維數組和常見的numpy數組的不同之處!
觀察[[3,4]],我們發現其中隻有一個元素,即[3, 4],第二個索引為[0:1]。
再去掉一層方括號,我們麵對的是[3,4],有兩個元素,所以第三個索引的範圍為[0:2]。
再次強調一下OpenCVPython接口函數返回的NumPy數組和普通的NumPy數組在組織上的不同之處。
參考資料:
1、《Opencv2 Computer Vision Application Programming Cookbook》
2、《OpenCV References Manule》
3、OpenCV官方文檔Contour部分
http://blog.csdn.net/jjddss/article/details/72674704
opencv3可能會報too many values to unpack (expected 2)的錯誤
最近在OpenCV-Python接口中使用cv2.findContours()函數來查找檢測物體的輪廓。
根據網上的 教程,Python OpenCV的輪廓提取函數會返回兩個值,第一個為輪廓的點集,第二個是各層輪廓的索引。但是實際調用時我的程序報錯了,錯誤內容如下:too many values to unpack (expected 2)
其實是接受返回值不符,如果你僅僅使用一個變量a去接受返回值,調用len(a),你會發現長度為3,也就是說這個函數實際上返回了三個值
第一個,也是最坑爹的一個,它返回了你所處理的圖像
第二個,正是我們要找的,輪廓的點集
第三個,各層輪廓的索引
使用方式如下:
import cv2
img = cv2.imread("./test.jpg")
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,contours,-1,(0,0,255),3)
cv2.imshow("img", img)
cv2.waitKey(0)
運行時出現錯誤: ValueError: too many values to unpack
原因:由於版本(使用的時3.2.0.7)問題 cv.findContours返回值個數發生變化,變為3個。因此應該為
aa, ctrs, hier = cv2.findContours(im_th.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
假如第一個參數不使用,可寫成
_, ctrs, hier = cv2.findContours(im_th.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
拓展:
ValueError: too many values to unpack 類錯誤,多為輸入或者輸出參數數量不一致導致。