OpenCv學習筆記2--輪廓檢測,多邊形 直線 圓檢測
此opencv系列部落格只是為了記錄小編對<<opencv3計算機視覺-pyhton語言實現>>的學習筆記,所有程式碼可以在我的github主頁https://github.com/RenDong3/OpenCV_Notes.
歡迎star,不定時更新...
一 輪廓檢測
在計算機視覺中,輪廓檢測是另一個比較重要的任務,不單是用來檢測影象或者視訊幀中物體的輪廓,而且還有其他操作與輪廓檢測相關。這些操作中,計算多邊形邊界,形狀逼近和計算機感 興趣區域。這是與影象資料互動時的簡單操作,因為numpy中的矩陣中的矩形區域可以使用陣列切片(slice)定義。在介紹物體檢測(包括人臉)和物體跟蹤的概念時會大量使用這種技術。
為了從一幅影象中提取我們需要的部分,應該用影象中的每一個畫素點的灰度值與選取的閾值進行比較,並作出相應的判斷(閾值的選取依賴於具體的問題,物體在不同的影象中可能會有不同的灰度值)。opencv提供了threshold()函式對影象的閾值進行處理,threshold()共支援五中型別的閾值化方式,分別是二進位制閾值化、反二進位制閾值化、截斷閾值化、閾值化為0和反閾值化為0。返回閾值操作後的影象。
- src: 輸入影象,影象必須為單通道8位或32位浮點型影象
- thresh: 設定的閾值
- maxval: 使用cv2.THRESH_BINARY和cv2.THRESH_BINARY_INV型別的最大值
- type: 閾值化型別,可以通過ThresholdTypes檢視,下面給出opencv中五種閾值化型別及其對應公式:
opencv中提供findContours()函式來尋找影象中物體的輪廓,並結合drawContours()函式將找到的輪廓繪製出。這個函式會修改輸入影象,因此建議使用原始影象的一份拷貝(比如說img.copy()作為輸入影象)。函式返回三個值:返回修改後的影象,影象的輪廓以及它們的層次。
- image:輸入影象,函式接受的引數是二值圖,即黑白的(不是灰度圖),我們同樣可以使用cv2.compare,cv2.inRange,cv2.threshold,cv2.adaptiveThreshold,cv2.Canny等函式來建立二值影象,如果第二個引數為cv2.RETR_CCOMP或cv2.RETR_FLOODFILL,輸入影象可以是32-bit整型影象(cv2.CV_32SC1)
- mode輪廓檢索模式,如下
- method:輪廓近似方法
該函式返回繪製有輪廓的影象。
- image:輸入/輸出影象,指明在哪個影象上繪製輪廓。並且該函式會修改源影象image。
- contours:使用findContours檢測到的輪廓資料,傳入一個list。
- contourIdx:繪製輪廓的索引變數(表示繪製第幾個輪廓),如果為負值則繪製所有輸入輪廓。
- color:輪廓顏色。
- thickness:繪製輪廓所用線條粗細度,如果值為負值,則在輪廓內部繪製。
- lineTpye:線條型別,有預設值LINE_8,有如下可選型別
- hierarchy:可選層次結構資訊
- maxLevel:用於繪製輪廓的最大等級。
- offset:可選輪廓便宜引數,用制定偏移量offset=(dx, dy)給出繪製輪廓的偏移量。
- 完整程式碼如下:
-
#-*_ coding:utf-8 -*- import cv2 import numpy as np ''' created on Tues jan 08:28:51 2018 @author: ren_dong contour detection cv2.findContours() 尋找輪廓 cv2.drawContours() 繪製輪廓 ''' #載入影象img img = cv2.imread('1.jpg') cv2.imshow('origin', img) ''' 灰度化處理,注意必須呼叫cv2.cvtColor(), 如果直接使用cv2.imread('1.jpg',0),會提示影象深度不對,不符合cv2.CV_8U ''' gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) cv2.imshow('gray', gray) #呼叫cv2.threshold()進行簡單閾值化,由灰度影象得到二值化影象 # 輸入影象必須為單通道8位或32位浮點型 ret, thresh = cv2.threshold(gray, 127, 255, 0) cv2.imshow('thresh', thresh) #呼叫cv2.findContours()尋找輪廓,返回修改後的影象,輪廓以及他們的層次 image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) cv2.imshow('image', image) print('contours[0]:',contours[0]) print('len(contours):',len(contours)) print('hierarchy.shape:',hierarchy.shape) print('hierarchy:',hierarchy) #呼叫cv2.drawContours()在原圖上繪製輪廓 img = cv2.drawContours(img, contours, -1, (0, 255, 0), 2) cv2.imshow('contours', img) cv2.waitKey() cv2.destroyAllWindows()
執行後的結果如下:
二 邊界框、最小矩形區域和最小閉圓的輪廓
找到一個正方形輪廓很簡單,要找到到不規則的,歪斜的以及旋轉的形狀,可以用Open CV的cv2.findContours()函式,它能得到最好的結果,下面來看一副圖:
現實的應用會對目標的邊界框,最小矩形面積,最小閉圓特別感興趣,將cv2.findContours()函式和少量的OpenCV的功能相結合就非常容易實現這些功能:
使用boundingRect()函式計算包圍輪廓的矩形框,使用minEnclosingCircle()函式計算包圍輪廓的最小圓包圍。
1、先計算一個簡單的邊界框(水平矩形):
x,y,w,h = cv2.boundingRect(c)
- 。函式計算並返回點集最外面的矩形邊界(引數一般傳入一個輪廓,contours[0]),函式返回四個值,分別是x,y,w,h。x,y是矩陣左上點的左邊,w,h是矩陣的寬和高。
-
然後畫出這個矩形(在原圖img上繪製):這個操作非常簡單,它將輪廓資訊轉換為(x,y)座標,並加上矩形的高度和寬度。
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
下面來將如何找到一個旋轉的矩陣和圓形輪廓。 首先載入圖片,然後在源影象的灰度影象上面執行一個二值化操作。這樣之後,可在這個灰度影象上執行所有計算輪廓的操作,但在源影象上可利用色彩資訊來畫這些輪廓。
2、計算包含出包圍目標的最小矩形區域(旋轉矩形):
#找到最小區域 rect = cv2.minAreaRect(c) #計算最小矩形的座標 box = cv2.boxPoints(rect) #座標轉換為整數 box = np.int0(box)
這裡用到一個非常有趣的機制:Open CV沒有函式能直接從輪廓資訊中計算出最小矩形頂點的座標。所以需要計算最小矩形區域,然後計算這個矩形的頂點。注意計算出來的頂點左邊是浮點型,但是所得畫素的座標值是整數,所以需要做一個轉換。
函式 cv2.minAreaRect() 返回一個tuple:(最小外接矩形的中心(x,y),(寬度,高度),旋轉角度)。
但是要繪製這個矩形,我們需要矩形的4個頂點座標box, 通過函式 cv2.cv.BoxPoints() 獲得,box:[ [x0,y0], [x1,y1], [x2,y2], [x3,y3] ]
最小外接矩形的4個頂點順序、中心座標、寬度、高度、旋轉角度(是度數形式,不是弧度數)的對應關係如下:
注意:旋轉角度θ是水平軸(x軸)逆時針旋轉,與碰到的矩形的第一條邊的夾角。並且這個邊的邊長是width,另一條邊邊長是height。也就是說,在這裡,width與height不是按照長短來定義的。
在opencv中,座標系原點在左上角,相對於x軸,逆時針旋轉角度為負,順時針旋轉角度為正。在這裡,θ∈(-90度,0]。
然後畫出這個矩形(在原圖img上繪製):
cv2.drawContours(img,[box],0,(255,0,0),3)
首先,該函式與所有繪圖函式一樣,它會修改源,其次該函式的第二個引數接收一個儲存著輪廓的陣列,從而可以在一次操作中繪製一系列的輪廓。因此如果只有一組點來表示多邊形輪廓,可以把這組點放到一個list中,就像前面例子裡處理方框(box)那樣。這個函式第三個引數是繪製的輪廓陣列的索引,-1表示繪製所有的輪廓,否則只繪製輪廓數組裡指定的輪廓。
大多數繪圖函式把繪圖的顏色和線寬放在最後兩個引數裡。
3、最後檢查的邊界輪廓為最小閉圓。
-
#計算閉圓中心店和和半徑 (x,y),radius = cv2.minEnclosingCircle(c) #轉換為整型 center = (int(x),int(y)) radius = int(radius) #繪製閉圓(在原圖img上繪製) img = cv2.circle(img,center,radius,(0,255,0),2)
-
points:輸入的二維點集,一般傳入一個輪廓 contours[0]
cv2.minEnclosingCircle()函式會返回一個元組,第一個元素為圓心的座標組成的元素,第二個元素為圓的半徑值。把這些值轉換為整數後就能很容易地繪製出圓來。
完整程式碼如下:
-
#-*- coding:utf-8 -*- import cv2 import numpy as np ''' created on Tues jan 09:36:30 2018 @author:ren_dong cv2.boundingRect() 邊界框即直邊界矩形 cv2.minAreaRect() 最小矩形區域即旋轉的邊界矩形 cv2.minEnclosingCircle() 最小閉圓 ''' #載入影象img img = cv2.pyrDown(cv2.imread('star.jpg', cv2.IMREAD_UNCHANGED)) #灰度化 gray = cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY) #二值化 ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) #尋找輪廓 image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) print('hierarchy[0]:', hierarchy[0]) print('len(contours):', len(contours)) print('hierarchy.shape:', hierarchy.shape) #遍歷每一個輪廓 for C in contours: #計算邊界框座標 x, y, w, h = cv2.boundingRect(C) ##在Img影象上繪製矩形框,顏色為green, 線寬為2 cv2.rectangle(img, (x, y), (x + w, y + h), (0,255,0), 2) #計算包圍目標的最小矩形區域 rect = cv2.minAreaRect(C) #計算最小矩形的座標 box = cv2.boxPoints(rect) #座標變為整數 box = np.int0(box) #繪製矩形框輪廓 顏色為red 線寬為3 cv2.drawContours(img, [box], 0, (0,0,255),3) #最小閉圓的圓心和半徑 (x,y),radius = cv2.minEnclosingCircle(C) #轉換為整型 center = (int(x), int(y)) radius = int(radius) #繪製最小閉圓 img = cv2.circle(img,center, radius, (255, 0, 0), 2) cv2.drawContours(img,contours, -1, (0, 0, 255), 1) cv2.imshow('contours',img) cv2.waitKey() cv2.destroyAllWindows()
執行後的結果:
-
三 凸輪廓與Douglas-Peucker演算法
大多數處理輪廓的時候,圖的形狀(包括凸形狀)都是變化多樣的。凸形狀內部的任意兩點的連線都在該形狀內部。
cv2.approxPloyDP函式,它用來計算近似的多邊形框。該函式有三個引數:
- 第一個引數為輪廓
- 第二個引數為ε值,它表示源輪廓與近似多邊形的最大差值(這個值越小,近似多邊形與源輪廓越接近)
- 第三個引數為布林標記,它表示這個多邊形是否閉合。
-
為什麼有了一個精確表示的輪廓卻還需要得到一個近似多邊形呢?這是因為一個多邊形由一組直線構成,能夠在一個區域裡定義多邊形,以便於之後進行操作與處理,這在許多計算機視覺任務中非常重要。
-
ε值對獲取有用的輪廓非常重要,所以需要理解它表示什麼意思。ε是為所得到的近似多邊形周長與源輪廓周長之間的最大差值,這個值越小,近似多邊形與源輪廓就越相似。
在瞭解了ε值是什麼之後,需要得到輪廓的周長資訊來作為參考值。這可以通過cv2.arcLength函式來完成:#arcLength獲取輪廓的周長 epsilon = 0.01*cv2.arcLength(cnt,True) #計算矩形的多邊形框 approx = cv2.approxPolyDP(cnt,epsilon,True)
可以通過OpenCV來有效地計算一個近似多邊形。為了計算凸形狀,需要利用cv2.convexHull來處理獲取的輪廓資訊。
-
#從輪廓資訊中計算得到凸形狀 hull = cv2.convexHull(cnt)
為了理解源輪廓、近似多邊形和凸包的不同之處,可以把他們放在一副圖片中進行觀察:
-
#-*- coding:utf-8 -*- import cv2 import numpy as np ''' created on Tues jan 10:49:30 2018 @author:ren_dong 凸輪廓和Douglas-Peucker演算法 cv2.approxPloyDP() CV2.arcLength() cv2.convexHull() ''' #讀入影象img img = cv2.pyrDown(cv2.imread('arc.jpg', cv2.IMREAD_COLOR)) #resize #img = cv2.resize(img, None, fx=0.6, fy=0.6, interpolation=cv2.INTER_CUBIC) #建立空白影象,用來繪製多邊形輪廓 curve = np.zeros(img.shape,np.uint8) #灰度變換 gray = cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY) #使用定製kernel 進行中值濾波,去除一些噪聲 kernel = np.ones((3,3),np.float32) / 9 #這裡的-1表示目標影象和原影象具有同樣的深度,比如cv2.CV_8U gray = cv2.filter2D(gray,-1,kernel) #閾值化 gray image --> binary image # 輸入影象必須為單通道8位或32位浮點型 # 這裡使用cv2.THRESH_BINARY_INV 實現效果畫素>125 設定為0(黑) 否則設定為255(白) ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV) #尋找輪廓 返回修改後的影象, 影象輪廓 以及他們的層次 image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) ##假設contours[0]為最大輪廓 cnt = contours[0] max_area = cv2.contourArea(cnt) ##遍歷contours, 和原始設定比較,獲得最大輪廓區域 for i in contours: if cv2.contourArea(i) > max_area: cnt = i max_area = cv2.contourArea(cnt) print('max_area:',max_area) ##獲得輪廓周長 epsilon = 0.01 * cv2.arcLength(cnt, True) #計算得到近似的多邊形框 approx = cv2.approxPolyDP(cnt, epsilon, True) #得到凸包 hull = cv2.convexHull(cnt) print('contours', len(contours), type(contours)) print('cnt.shape', cnt.shape, type(cnt)) print('approx.shape', approx.shape, type(approx)) print('hull.shape', hull.shape, type(hull)) #在原影象得到原始的輪廓 cv2.drawContours(img, contours, -1, (255, 0 , 0),2) #在空白影象中得到最大輪廓, 多邊形輪廓, 凸包輪廓 cv2.drawContours(curve, [cnt], -1, (0, 0, 255), 1) # cv2.drawContours(curve, [hull], -1, (0, 255, 0), 2) ##綠色多邊形輪廓 線寬2 cv2.drawContours(curve, [approx], -1, (255, 0, 0), 3) ##藍色凸包輪廓 線寬3 cv2.imshow('contours',img) cv2.imshow('all',curve) cv2.waitKey() cv2.destroyAllWindows()
執行結果如下:
-
如上圖所示,凸包是由綠色表示,然後裡面是近似多邊形,使用黃色表示,在兩者之間的是源圖片中一個最大的輪廓,它主要由弧線構成.
-
四 直線和圓檢測
-
檢測邊緣和輪廓不僅重要,還經常用到,它們也是構成其他複雜操作的基礎。直線和形狀檢查與邊緣和輪廓檢測有密切的關係。
Hough變換是直線和形狀檢測背後的理論基礎,它由Richard Duda和Peter Hart發明,他們是對Paul Hough在20世紀60年代早期所做工作的擴充套件。
1、直線檢測
首先介紹直線檢測,這可通過HoughLines和HoughLinesP函式來完成,它們僅有的差別是:第一個函式使用標準的Hough變換,第二個函式使用概率Hough變換(因此名稱裡有一個P)。
HoughLinesP函式之所以稱為概率版本的Hough變換是因為它只通過分析點的子集並估計這些點都屬於一條直線的概率,這是標準Hogh變換的優化版本。該函式的計算代價會少一些,執行會變得更快。
-
完整程式碼如下:
-
#-*- coding:utf-8 -*- import cv2 import numpy as np ''' created on Tues jan 13:57:30 2018 @author:ren_dong Hough變換檢測直線 cv2.HoughLine() cv2.HoughLines() ''' #載入圖片img img = cv2.imread('line.jpg') #灰度化 --> gray gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ##中值濾波去除噪聲 影象平滑 gray = cv2.medianBlur(gray, ksize=3) #Canny邊緣檢測 edges = cv2.Canny(gray, 50, 120) #最小直線長度, 更短的直線會被消除 minLineLength = 10 #最大線段間隙,一條線段的間隙長度大於這個值會被認為是兩條分開的線段 maxLineGap = 5 ##Hough 變換檢測直線 lines = cv2.HoughLinesP(edges, 1, np.pi/180, 80, minLineLength, maxLineGap) for i in range(len(lines)): for x1, y1, x2, y2 in lines[i]: #給定兩點 在原始圖片繪製線段 cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imshow('edges', edges) cv2.imshow('lines', img) cv2.waitKey() cv2.destroyAllWindows()
實現結果如下:
-
除了HoughLinesP函式呼叫是這段程式碼的關鍵點以外,設定最小直線長度(更短的直線會被消除)和最大線段間隙也很重要,一條線段長度大於這個值會被視為兩條分開的線段。
注意:HoughLinesP函式會接收一個由Candy邊緣檢測濾波器處理過的單通道二值影象。不一定需要Candy濾波器,但是一個經過去噪並且只有邊緣的影象當中Hough變換的輸入會很不錯,因此使用Candy濾波器是一個普遍的慣例。
HoughLinesP函式引數如下:
- 需要處理的影象,需要是灰度圖。
- 線段的幾何表示rho和theta,一般分別取1和np.pi/180。
-
該函式返回一個numpy.array型別,形狀為[num,1,4],每一行對應一條直線,每條直線形狀為(1,4),這4個數值表示起始點和終止點座標。
- 閾值。低於該閾值的直線會被忽略。Hough變換可以理解為投票箱和投票數之間的關係,每一個投票箱代表一個直線,投票數達到閾值的直線會被保留,其他的會被刪除。
- 最小直線長度。
- 最大線段間隙。
-
2、圓檢測
OpenCV的HoughCircles函式可用來檢測圓,其主要是利用霍爾變換在影象中尋找圓。我們知道,一個圓形的表示式為(x-x_center)2+(y-y_center)2=r2,一個圓環的確定需要三個引數,那麼霍爾變換的累加器必須是三維的,但是這樣的計算效率很低,而opencv採用了霍夫梯度的方法,這裡利用了邊界的梯度資訊。
首先對影象進行Candy邊緣檢測,對邊緣中的每一個非0點,通過sobel運算元進行計算區域性梯度。那麼計算得到的梯度方向,實際上就是圓切線的法線。三條法線即可確定一個圓心,同理在累加器中對圓心通過的法線進行累加,就得到可圓環的判定。
- img為輸入影象,需要是灰度圖。
- method為檢測方法,常用cv2.HOUGH_GRADIENT
-
下面是一個例子:
- dp為檢測內側圓心的累加器影象的解析度於輸入影象之比的倒數,如dp=1,累加器和輸入影象具有相同的解析度,如果dp=2.累加器便有輸入影象一半那麼大的寬度和高度。
- minDist表示兩個圓之間圓心的最小距離。
- param1有預設值100,它是method設定的檢測方法對應的引數,對當前唯一的方法霍夫梯度法cv2.HOUGH_GRADIENT,它表示傳遞給Candy邊緣檢測運算元的高閾值,而低閾值為高閾值的一半。
- param2有預設值100,它是method設定的檢測方法對應的引數,對當前唯一的方法霍夫梯度法cv2.HOUGH_GRADIENT,它表示在檢測階段圓心的累加器閾值,它越小,就越可以檢測到更多根本不存在的圓,而它越大的話,能通過檢測的圓就更接近完美的圓形了。
- minRadius有預設值0,圓半徑的最小值。
- maxRadius有預設值0,圓半徑的最大值。
- 下面是一個檢測例子:
-
#-*- coding:utf-8 -*- import cv2 import numpy as np ''' created on Tues jan 14:37:10 2018 @author:ren_dong Hough變換檢測圓 cv2.HoughCricles() ''' #載入圖片img img = cv2.imread('plant.jpg') #灰度化處理 --> gray image gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ##中值濾波 影象平滑 gray = cv2.medianBlur(gray, 7) ##Hough變換檢測圓 circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 200, param1=200, param2=20, minRadius= 0, maxRadius=0 ) circles = np.uint16(np.around(circles)) ##繪製圓 for i in circles[0,:]: cv2.circle(img, (i[0], i[1]), i[2], (0, 255, 0), 2) cv2.circle(img, (i[0], i[1]), 2, (0, 0, 255), 2) cv2.imshow('HoughCircles',img) cv2.waitKey() cv2.destroyAllWindows()
實現效果如下:
-
3、檢測其他形狀
Hough變換能檢測的形狀僅限於圓,但是前面曾提到過檢測任何形狀的方法,特別是用approxPloyDP函式來檢測。該函式提供多邊形的近似,所以如果你的影象有多邊形,再結合cv2.findContous函式和cv2.approxPloyDP函式,就可以相當準確的檢測出來。