|
一 輪廓檢測在計算機視覺中,輪廓檢測是另一個比較重要的任務(wù),不單是用來檢測圖像或者視頻幀中物體的輪廓,而且還有其他操作與輪廓檢測相關(guān)。這些操作中,計算多邊形邊界,形狀逼近和計算機感 興趣區(qū)域。這是與圖像數(shù)據(jù)交互時的簡單操作,因為numpy中的矩陣中的矩形區(qū)域可以使用數(shù)組切片(slice)定義。在介紹物體檢測(包括人臉)和物體跟蹤的概念時會大量使用這種技術(shù)。 1、cv2.threshold(src,thresh,maxval,type[,dst])函數(shù)用于圖像閾值操作。 為了從一幅圖像中提取我們需要的部分,應(yīng)該用圖像中的每一個像素點的灰度值與選取的閾值進行比較,并作出相應(yīng)的判斷(閾值的選取依賴于具體的問題,物體在不同的圖像中可能會有不同的灰度值)。opencv提供了threshold()函數(shù)對圖像的閾值進行處理,threshold()共支持五中類型的閾值化方式,分別是二進制閾值化、反二進制閾值化、截斷閾值化、閾值化為0和反閾值化為0。返回閾值操作后的圖像。
2、 cv2.findContours(image,mode,method[,contours,hierarchy[,offset]])用于尋找尋找圖像輪廓。 opencv中提供findContours()函數(shù)來尋找圖像中物體的輪廓,并結(jié)合drawContours()函數(shù)將找到的輪廓繪制出。這個函數(shù)會修改輸入圖像,因此建議使用原始圖像的一份拷貝(比如說img.copy()作為輸入圖像)。函數(shù)返回三個值:返回修改后的圖像,圖像的輪廓以及它們的層次。
其中
3、cv2.drawContours(image,contours,contourIdx,color[,thickness[,lineType[,hierarchy[,maxLevel[,offset]]]]])函數(shù)輪廓繪制。 該函數(shù)返回繪制有輪廓的圖像。
''' 輪廓檢測 ''' #加載圖像img img = cv2.imread('./image/img6.jpg',cv2.IMREAD_COLOR) cv2.imshow('img',img) #轉(zhuǎn)換為灰色gray_img gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) cv2.imshow('gray_img',gray_img) #對圖像二值化處理 輸入圖像必須為單通道8位或32位浮點型 ret,thresh = cv2.threshold(gray_img,127,255,0) cv2.imshow('thresh',thresh) #尋找圖像輪廓 返回修改后的圖像 圖像的輪廓 以及它們的層次 image,contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) cv2.imshow('image',image) print('contours[0]:',contours[0])
二 邊界框、最小矩形區(qū)域和最小閉圓的輪廓找到一個正方形輪廓很簡單,要找到到不規(guī)則的,歪斜的以及旋轉(zhuǎn)的形狀,可以用Open CV的cv2.findContours()函數(shù),它能得到最好的結(jié)果,下面來看一副圖: 現(xiàn)實的應(yīng)用會對目標的邊界框,最小矩形面積,最小閉圓特別感興趣,將cv2.findContours()函數(shù)和少量的OpenCV的功能相結(jié)合就非常容易實現(xiàn)這些功能: 使用boundingRect()函數(shù)計算包圍輪廓的矩形框,使用minEnclosingCircle()函數(shù)計算包圍輪廓的最小圓包圍。 1、先計算一個簡單的邊界框(水平矩形): x,y,w,h = cv2.boundingRect(c)
然后畫出這個矩形(在原圖img上繪制):這個操作非常簡單,它將輪廓信息轉(zhuǎn)換為(x,y)坐標,并加上矩形的高度和寬度。 cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2) 下面來將如何找到一個旋轉(zhuǎn)的矩陣和圓形輪廓。 首先加載圖片,然后在源圖像的灰度圖像上面執(zhí)行一個二值化操作。這樣之后,可在這個灰度圖像上執(zhí)行所有計算輪廓的操作,但在源圖像上可利用色彩信息來畫這些輪廓。 2、計算包含出包圍目標的最小矩形區(qū)域(旋轉(zhuǎn)矩形): #找到最小區(qū)域
rect = cv2.minAreaRect(c)
#計算最小矩形的坐標
box = cv2.boxPoints(rect)
#坐標轉(zhuǎn)換為整數(shù)
box = np.int0(box)
這里用到一個非常有趣的機制:Open CV沒有函數(shù)能直接從輪廓信息中計算出最小矩形頂點的坐標。所以需要計算最小矩形區(qū)域,然后計算這個矩形的頂點。注意計算出來的頂點左邊是浮點型,但是所得像素的坐標值是整數(shù),所以需要做一個轉(zhuǎn)換。 函數(shù) cv2.minAreaRect() 返回一個tuple:(最小外接矩形的中心(x,y),(寬度,高度),旋轉(zhuǎn)角度)。 但是要繪制這個矩形,我們需要矩形的4個頂點坐標box, 通過函數(shù) cv2.cv.BoxPoints() 獲得,box:[ [x0,y0], [x1,y1], [x2,y2], [x3,y3] ] 最小外接矩形的4個頂點順序、中心坐標、寬度、高度、旋轉(zhuǎn)角度(是度數(shù)形式,不是弧度數(shù))的對應(yīng)關(guān)系如下:
注意:旋轉(zhuǎn)角度θ是水平軸(x軸)逆時針旋轉(zhuǎn),與碰到的矩形的第一條邊的夾角。并且這個邊的邊長是width,另一條邊邊長是height。也就是說,在這里,width與height不是按照長短來定義的。 在opencv中,坐標系原點在左上角,相對于x軸,逆時針旋轉(zhuǎn)角度為負,順時針旋轉(zhuǎn)角度為正。在這里,θ∈(-90度,0]。 然后畫出這個矩形(在原圖img上繪制): cv2.drawContours(img,[box],0,(255,0,0),3) 首先,該函數(shù)與所有繪圖函數(shù)一樣,它會修改源,其次該函數(shù)的第二個參數(shù)接收一個保存著輪廓的數(shù)組,從而可以在一次操作中繪制一系列的輪廓。因此如果只有一組點來表示多邊形輪廓,可以把這組點放到一個list中,就像前面例子里處理方框(box)那樣。這個函數(shù)第三個參數(shù)是繪制的輪廓數(shù)組的索引,-1表示繪制所有的輪廓,否則只繪制輪廓數(shù)組里指定的輪廓。 大多數(shù)繪圖函數(shù)把繪圖的顏色和線寬放在最后兩個參數(shù)里。 3、最后檢查的邊界輪廓為最小閉圓。 #計算閉圓中心店和和半徑
(x,y),radius = cv2.minEnclosingCircle(c)
#轉(zhuǎn)換為整型
center = (int(x),int(y))
radius = int(radius)
#繪制閉圓(在原圖img上繪制)
img = cv2.circle(img,center,radius,(0,255,0),2)
points:輸入的二維點集,一般傳入一個輪廓 contours[0] cv2.minEnclosingCircle()函數(shù)會返回一個元組,第一個元素為圓心的坐標組成的元素,第二個元素為圓的半徑值。把這些值轉(zhuǎn)換為整數(shù)后就能很容易地繪制出圓來。 完整代碼如下: ''' 邊框 最小矩形區(qū)域和最小閉圓的輪廓 ''' img = cv2.pyrDown(cv2.imread('./image/img16.jpg',cv2.IMREAD_UNCHANGED)) #轉(zhuǎn)換為灰色gray_img gray_img = cv2.cvtColor(img.copy(),cv2.COLOR_BGR2GRAY) #對圖像二值化處理 輸入圖像必須為單通道8位或32位浮點型 ret,thresh = cv2.threshold(gray_img,127,255,cv2.THRESH_BINARY) #尋找最外面的圖像輪廓 返回修改后的圖像 圖像的輪廓 以及它們的層次 image,contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) print(type(contours)) print(type(contours[0])) print(len(contours)) #遍歷每一個輪廓 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) #找到最小區(qū)域 rect = cv2.minAreaRect(c) #計算最小矩形的坐標 box = cv2.boxPoints(rect) #坐標轉(zhuǎn)換為整數(shù) box = np.int0(box) #繪制輪廓 最小矩形 blue cv2.drawContours(img,[box],0,(255,0,0),3) #計算閉圓中心店和和半徑 (x,y),radius = cv2.minEnclosingCircle(c) #轉(zhuǎn)換為整型 center = (int(x),int(y)) radius = int(radius) #繪制閉圓 img = cv2.circle(img,center,radius,(0,255,0),2) cv2.drawContours(img,contours,-1,(0,0,255),2) cv2.imshow('contours',img) 運行后的結(jié)果:
三 凸輪廓與Douglas-Peucker算法大多數(shù)處理輪廓的時候,圖的形狀(包括凸形狀)都是變化多樣的。凸形狀內(nèi)部的任意兩點的連線都在該形狀內(nèi)部。 cv2.approxPloyDP函數(shù),它用來計算近似的多邊形框。該函數(shù)有三個參數(shù):
ε值對獲取有用的輪廓非常重要,所以需要理解它表示什么意思。ε是為所得到的近似多邊形周長與源輪廓周長之間的最大差值,這個值越小,近似多邊形與源輪廓就越相似。 為什么有了一個精確表示的輪廓卻還需要得到一個近似多邊形呢?這是因為一個多邊形由一組直線構(gòu)成,能夠在一個區(qū)域里定義多邊形,以便于之后進行操作與處理,這在許多計算機視覺任務(wù)中非常重要。 在了解了ε值是什么之后,需要得到輪廓的周長信息來作為參考值。這可以通過cv2.arcLength函數(shù)來完成: #arcLength獲取輪廓的周長 epsilon = 0.01*cv2.arcLength(cnt,True) #計算矩形的多邊形框 approx = cv2.approxPolyDP(cnt,epsilon,True) 可以通過OpenCV來有效地計算一個近似多邊形。為了計算凸形狀,需要利用cv2.convexHull來處理獲取的輪廓信息。 #從輪廓信息中計算得到凸形狀 hull = cv2.convexHull(cnt) 為了理解源輪廓、近似多邊形和凸包的不同之處,可以把他們放在一副圖片中進行觀察: img = cv2.imread('./image/img18.jpg',cv2.IMREAD_COLOR) img = cv2.resize(img,None,fx=0.6,fy=0.6,interpolation=cv2.INTER_CUBIC) #創(chuàng)建一個空白圖像,用來繪制輪廓 canvas = np.zeros(img.shape,np.uint8) #轉(zhuǎn)換為灰色gray_img gray_img = cv2.cvtColor(img.copy(),cv2.COLOR_BGR2GRAY) #進行均值濾波,去除一些噪聲 kernel = np.ones((3,3),np.float32)/9 gray_img = cv2.filter2D(gray_img,-1,kernel) #cv2.imshow('gray_img',gray_img) #對圖像二值化處理 輸入圖像必須為單通道8位或32位浮點型 像素>125 設(shè)置為0(黑) 否則設(shè)置為255(白) ret,thresh = cv2.threshold(gray_img,125,255,cv2.THRESH_BINARY_INV) #cv2.imshow('thresh',thresh) #尋找圖像輪廓 返回修改后的圖像 圖像的輪廓 以及它們的層次 image,contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) #獲取最大的一個輪廓 cnt = contours[0] max_area = cv2.contourArea(cnt) #對每一個輪廓進行遍歷 for cont in contours: if cv2.contourArea(cont) > max_area: cnt = cont max_area = cv2.contourArea(cont) print('max_area',max_area) '''計算最大輪廓的多邊形框''' #arcLength獲取輪廓的周長 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)) #在源圖像中繪制所有輪廓 傳入的死一個list cv2.drawContours(img,contours,-1,(0,255,0),2) #GREEN 繪制所有的輪廓 cv2.drawContours(canvas,[cnt],-1,(0,255,0),2) #GREEN 繪制最大的輪廓 cv2.drawContours(canvas,[approx],-1,(0,0,255),2) #RED 繪制最大輪廓對應(yīng)的多邊形框 cv2.drawContours(canvas,[hull],-1,(255,0,0),2) #BLUE 繪制最大輪廓對應(yīng)的凸包 cv2.imshow('img',img) cv2.imshow('ALL',canvas) cv2.waitKey() cv2.destroyAllWindows()
如上圖所示,凸包是由藍色表示,然后里面是近似多邊形,使用紅色表示,在兩者之間的是源圖片中一個最大的輪廓,它主要由弧線構(gòu)成。 四 直線和圓檢測檢測邊緣和輪廓不僅重要,還經(jīng)常用到,它們也是構(gòu)成其他復雜操作的基礎(chǔ)。直線和形狀檢查與邊緣和輪廓檢測有密切的關(guān)系。 Hough變換是直線和形狀檢測背后的理論基礎(chǔ),它由Richard Duda和Peter Hart發(fā)明,他們是對Paul Hough在20世紀60年代早期所做工作的擴展。 1、直線檢測首先介紹直線檢測,這可通過HoughLines和HoughLinesP函數(shù)來完成,它們僅有的差別是:第一個函數(shù)使用標準的Hough變換,第二個函數(shù)使用概率Hough變換(因此名稱里有一個P)。 HoughLinesP函數(shù)之所以稱為概率版本的Hough變換是因為它只通過分析點的子集并估計這些點都屬于一條直線的概率,這是標準Hogh變換的優(yōu)化版本。該函數(shù)的計算代價會少一些,執(zhí)行會變得更快。 img = cv2.imread('./image/img19.jpg') #轉(zhuǎn)換為灰度圖片 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #中值濾波 gray = cv2.medianBlur(gray,ksize=3) #邊緣檢測 edges = cv2.Canny(gray,50,100) minLineLength = 200 maxLineGap = 5 #直線檢測 lines = cv2.HoughLinesP(edges,1,np.pi/180,100, minLineLength,maxLineGap) print('len(lines)',len(lines),type(lines)) print('lines[0].shape',lines[0].shape) for i in range(len(lines)): for x1,y1,x2,y2 in lines[i]: cv2.line(img, (x1,y1), (x2,y2),(i*20,100+i*20,255),2) cv2. imshow("edges", edges) cv2. imshow("lines", img) cv2.waitKey() cv2.destroyAllWindows()
除了HoughLinesP函數(shù)調(diào)用是這段代碼的關(guān)鍵點以外,設(shè)置最小直線長度(更短的直線會被消除)和最大線段間隙也很重要,一條線段長度大于這個值會被視為兩條分開的線段。 注意:HoughLinesP函數(shù)會接收一個由Candy邊緣檢測濾波器處理過的單通道二值圖像。不一定需要Candy濾波器,但是一個經(jīng)過去噪并且只有邊緣的圖像當中Hough變換的輸入會很不錯,因此使用Candy濾波器是一個普遍的慣例。 HoughLinesP函數(shù)參數(shù)如下:
該函數(shù)返回一個numpy.array類型,形狀為[num,1,4],每一行對應(yīng)一條直線,每條直線形狀為(1,4),這4個數(shù)值表示起始點和終止點坐標。 2、圓檢測OpenCV的HoughCircles函數(shù)可用來檢測圓,其主要是利用霍爾變換在圖像中尋找圓。我們知道,一個圓形的表達式為(x-x_center)2+(y-y_center)2=r2,一個圓環(huán)的確定需要三個參數(shù),那么霍爾變換的累加器必須是三維的,但是這樣的計算效率很低,而opencv采用了霍夫梯度的方法,這里利用了邊界的梯度信息。 首先對圖像進行Candy邊緣檢測,對邊緣中的每一個非0點,通過sobel算子進行計算局部梯度。那么計算得到的梯度方向,實際上就是圓切線的法線。三條法線即可確定一個圓心,同理在累加器中對圓心通過的法線進行累加,就得到可圓環(huán)的判定。 cv2.HoughCircles(img,method,dp,minDist,circles,param1,param2,minRadius,maxRadius)函數(shù)的參數(shù)如下:
下面是一個例子: img = cv2.imread('./image/img20.jpg') #縮小 img = cv2.resize(img,None,fx=0.5,fy=0.5,interpolation=cv2.INTER_CUBIC) #轉(zhuǎn)換為灰度圖片 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #中值濾波 gray = cv2.medianBlur(gray,ksize=3) #圓檢測 circles = cv2.HoughCircles(gray,cv2.HOUGH_GRADIENT,1,120,param1=100,param2=30,minRadius=0,maxRadius=0) print('circles',type(circles),circles.shape) #circles <class 'numpy.ndarray'> (1, 3, 3) circles = np.uint16(np.around(circles)) for i in circles[0,:]: #繪制圓 (i[0],i[1])為圓心,i[2]為半徑 cv2.circle(img,(i[0],i[1]),i[2],(0,255,0),2) #繪制圓心 cv2.circle(img,(i[0],i[1]),2,(255,0,0),3) cv2.imshow('circles',img) cv2.waitKey() cv2.destroyAllWindows()
3、檢測其他形狀Hough變換能檢測的形狀僅限于圓,但是前面曾提到過檢測任何形狀的方法,特別是用approxPloyDP函數(shù)來檢測。該函數(shù)提供多邊形的近似,所以如果你的圖像有多邊形,再結(jié)合cv2.findContous函數(shù)和cv2.approxPloyDP函數(shù),就可以相當準確的檢測出來。 參考文章: [1]python-opencv2利用cv2.findContours()函數(shù)來查找檢測物體的輪廓 [2]Python下opencv使用筆記(十一)(詳解hough變換檢測直線與圓) |
|
|