opencv函数findcontours_OpenCV 中的轮廓应用
目錄:
輪廓就是連接所有連續(xù)點(沿著邊界)的曲線,具有相同的顏色或灰度值。輪廓是形狀分析、物體檢測和識別的有用工具。為了提高提取輪廓的精確度,需要先通過閾值處理或canny邊緣檢測將圖像轉(zhuǎn)換為二值圖像。
在 OpenCV 中,尋找輪廓就像從黑色背景中尋找白色物體,所以要找到的物體應該是白色的,背景應該是黑色的。
只羅列和輪廓相關的幾個函數(shù)沒啥意思,通過兩個例子可以對其用法有更深入的理解。
一、輪廓常用函數(shù)
1、查找輪廓
在二值圖像中獲取輪廓:
import cv2 im = cv2.imread('test.jpg') imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(imgray, 127, 255, 0) contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)cv2.findContours() 函數(shù)中有三個參數(shù):
- thresh:源圖像
- cv2.RETR_TREE:表示輪廓檢索模式
- cv2.CHAIN_APPROX_SIMPLE:表示輪廓近似方法
返回值為獲取到的輪廓 contours 和hierarchy。contours為包含圖像中所有輪廓的python列表(三維數(shù)組),每個輪廓是包含邊界所有坐標點(x, y)的Numpy數(shù)組。hierarchy 是一個三維數(shù)組,它儲存了所有等高線(輪廓)的層級結構,詳情可以查看 1 和 2。
輪廓是具有相同灰度值的形狀的邊界,即一個輪廓可以看做是一個等高線。它存儲形狀邊界的(x, y)坐標。我們可以使用第三個參數(shù)來指定是否存儲形狀邊界的所有坐標點。
第二個參數(shù)決定 hierarchy 采取什么樣的格式輸出。第三個參數(shù)可以指定兩個值,如果是 cv2.CHAIN_APPROX_NONE,則存儲形狀邊界的所有坐標點。但有時我們不需要所有的點,比如一個矩形的輪廓,我們只需要矩形的四個端點就可以了。這時我們就可以傳入 cv2.CHAIN_APPROX_SIMPLE,它會移除所有冗余的點并壓縮輪廓,從而節(jié)省內(nèi)存。
比如下面這個例子,我們標記出矩形所有輪廓點,第一張圖片是使用 cv2.CHAIN_APPROX_NONE 得到的結果,一共有734個點;第二張圖片是使用 cv2.CHAIN_APPROX_SIMPLE 得到的結果,只有4個點。
2、繪制輪廓
可以使用 cv2.drawContours() 函數(shù)來繪制輪廓,只要有輪廓的邊界點,就可以用來繪制任何形狀的輪廓。
下面是繪制輪廓的三個例子:
# To draw all the contours in an image: cv2.drawContours(img, contours, -1, (0,255,0), 3) # To draw an individual contour, say 4th contour: cv2.drawContours(img, contours, 3, (0,255,0), 3) # But most of the time, below method will be useful: cnt = contours[4] cv2.drawContours(img, [cnt], 0, (0,255,0), 3)cv2.drawContours() 函數(shù)中有三個參數(shù),第一個參數(shù)是源圖像;第二個參數(shù)是應該包含輪廓的Python列表;第三個參數(shù)是列表索引,用來選擇要繪制的輪廓,為-1時表示繪制所有輪廓;第四個參數(shù)是輪廓顏色、第五個參數(shù)是輪廓線的寬度,為-1時表示填充。
注意:指定輪廓顏色的值要和圖像通道數(shù)一致。
3、輪廓外接矩形
輪廓外接矩形分為正矩形和最小矩形。使用 cv2.boundingRect(cnt) 來獲取輪廓的外接正矩形,它不考慮物體的旋轉(zhuǎn),所以該矩形的面積一般不會最小;使用 cv.minAreaRect(cnt) 可以獲取輪廓的外接最小矩形。
兩者區(qū)別如下圖所示,綠線表示外接正矩形,紅線表示外接最小矩形:
cv2.boundingRect(cnt) 的返回值包含四個值,矩形框左上角的坐標(x, y)、寬度w和高度h。
x,y,w,h = cv2.boundingRect(cnt)cv.minAreaRect(cnt) 的返回值中還包含旋轉(zhuǎn)信息,返回值信息為包括中心點坐標(x,y),寬高(w, h)和旋轉(zhuǎn)角度。
然而我們繪制矩形需要矩形的四個頂點坐標,可以通過 cv.boxPoints() 來獲取,如下代碼所示:
rect = cv2.minAreaRect(cnt) print(rect) # center(x, y), (width, height), angle of rotation box = cv2.boxPoints(rect) # box.shape=(4, 2) box = np.int0(box) cv2.drawContours(img,[box],0,(0,0,255),2)angle的范圍為 (-90,-0],如上圖中的角
(與矩形框最低點相連的右邊的線),一個矩形逆時針旋轉(zhuǎn), 的值變化為:-0 -> -30 -> -60 -> -0,然后不斷循環(huán)。4、輪廓面積
我們可以通過 cv2.contourArea(cnt) 來獲取輪廓的面積,這里的面積表示該形狀內(nèi)包含的像素點數(shù)量。
5、輪廓周長
通過 cv2.arcLength(cnt,True) 來繪制輪廓周長或者曲線長度,第二個參數(shù)指定形狀是為閉合輪廓(True)還是普通曲線。這里的周長/長度表示該形狀邊界上的像素點數(shù)量。
6、輪廓近似
我們可以將一個輪廓/曲線近似為另一個頂點數(shù)量較少的輪廓/曲線,使得它們之間的距離小于或等于指定的精度,通過 cv2.approxPolyDP(cnt, epsilon, True) 來實現(xiàn)。第二個參數(shù)用于輪廓近似的精度,表示原始輪廓與其近似輪廓的最大距離,值越小,近似輪廓越擬合原輪廓。第三個參數(shù)指定近似輪廓是否是閉合的。
比如下面這張圖,其中物體的原始輪廓(紅線所示)如第一張圖所,第二張圖中綠線就是epsilon為原始輪廓周長的10%時的近似輪廓,第三張圖中綠線就是epsilon為原始輪廓周長的1%時的近似輪廓。
二、第一個應用
原圖為:
現(xiàn)在我們只想獲取圖中圓形的內(nèi)圓,不包含黑色邊緣部分,就使用我們上面介紹過的函數(shù)實現(xiàn)。
先讀取圖像并將其轉(zhuǎn)換為灰度圖:
import cv2 import numpy as npimg = cv2.imread("shapes.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)使用閾值處理將其轉(zhuǎn)換為二值圖:
ret, threshed = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)然后就可以查找二值圖中的輪廓:
contours, _ = cv2.findContours(threshed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)二值圖和查找到的輪廓如下所示:
我們得到了很多輪廓,但是只想要最中間的那個內(nèi)圓輪廓,所以我們需要對這些輪廓進行篩選:
# 按輪廓面積將輪廓進行升序排列 cnts = sorted(contours, key=cv2.contourArea) H, W = img.shape[:2] for cnt in cnts:# 獲取輪廓外接矩形的坐標和長寬x,y,w,h = cv2.boundingRect(cnt)# 得到第一個滿足條件的輪廓,就退出循環(huán)if cv2.contourArea(cnt) > 100 and (0.8 < w/h < 1.2) and (W/4 < x + w//2 < W*3/4) and (H/4 < y + h//2 < H*3/4):circle = cntbreak仔細觀察原圖,可以發(fā)現(xiàn)我們想獲取的那個圓形輪廓面積足夠大,長寬比接近1,所以滿足條件:
cv2.contourArea(cnt) > 100 and (0.8 < w/h < 1.2)而且該圓形輪廓的中心點在圖像的正中間部分,所以有:
(W/4 < x + w//2 < W*3/4) and (H/4 < y + h//2 < H*3/4)當通過滿足上述條件時,我們就可以獲取到我們想要的內(nèi)圓輪廓。
創(chuàng)建輪廓掩碼并與原圖進行逐位與運算:
mask = np.zeros(img.shape[:2], np.uint8) cv2.drawContours(mask, [circle], -1, 255, -1) dst = cv2.bitwise_and(img, img, mask=mask)得到最終結果:
三、第二個應用
在這個例子中我們使用輪廓相關知識把這個人物給摳出來,類似于摳圖。
還是先讀取圖像,并轉(zhuǎn)換為灰度圖,還要對其進行高斯模糊,事先過濾掉不同輪廓之間的細線連接。
第一個應用我們通過閾值處理得到二值圖,這里使用 Canny 邊緣檢測,得到二值圖,然后對其進行膨脹和腐蝕操作,方便稍后提取輪廓。關于形態(tài)學操作的內(nèi)容可以參考這篇文章。
import cv2 import numpy as npimg = cv2.imread('Levi.jpg') gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)blur = cv2.GaussianBlur(gray, (3, 3), 0)edges = cv2.Canny(blur, 10, 200) # Edge detection edges = cv2.dilate(edges, None) # 默認(3x3) edges = cv2.erode(edges, None)邊緣圖,膨脹圖和腐蝕圖如下所示:
接下來我們就可以獲取處理后的圖像中的輪廓。
先使用 cv2.findContours 獲取所有輪廓,再根據(jù)輪廓面積進行降序排列,并獲取到面積最大的輪廓,即圖中人物的輪廓。
接下來通過 cv2.arcLength 得到輪廓周長,并將周長的0.1%作為近似輪廓的精度。然后再使用 cv2.approxPolyDP 得到近似輪廓。
# Find contours in edges(binary image) contours, _ = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) contours = sorted(contours, key=cv2.contourArea, reverse=True) max_contour = contours[0]epsilon = 0.001*cv2.arcLength(max_contour, True) approx = cv2.approxPolyDP(max_contour, epsilon, True)然后根據(jù)近似輪廓建立一個輪廓的掩碼mask,在其上繪制出最大輪廓對應的填充多邊形,用于下一步摳圖。
對掩碼進行高斯模糊用于平滑邊緣,可以消除鋸齒。
mask = np.zeros(img.shape[:2], np.uint8) cv2.drawContours(mask, [approx], -1, 255, -1)mask = cv2.GaussianBlur(mask, (5, 5), 0) cv2.imshow('mask', mask)dst = cv2.bitwise_and(img, img, mask=mask) cv2.imshow('dst', dst)最后一步就是將掩碼和原圖像進行求與運算,即得到最終結果。
掩碼圖和結果如下所示:
這里說個題外話,這里我們通過 edges = cv2.Canny(blur, 10, 200) 得到二值圖,但是要手動設置兩個閾值參數(shù),不想手動設置的話可以使用如下函數(shù)來手動設置:
def auto_canny(image, sigma=0.33):# compute the median of the single channel pixel intensitiesv = np.median(image)# construct two thresholds using the medianlower = int(max(0, (1.0 - sigma) * v))upper = int(min(255, (1.0 + sigma) * v))edged = cv2.Canny(image, lower, upper)return edged所以將 edges = cv2.Canny(blur, 10, 200) 替換為 edges = auto_canny(blur, sigma=0.33) 即可。
當需要框出目標的輪廓時,推薦使用 cv2.Canny() 邊緣檢測而非閾值處理生成二值圖。理由:
參考:
邊緣檢測,框出物體的輪廓
幾何形狀識別與測量
Contour Features
remove the background of image:1,2
如果覺得有用,點個贊吧(? ??_??)?。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的opencv函数findcontours_OpenCV 中的轮廓应用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 5分绩点转4分_作为一名大学生,如何规划
- 下一篇: python找与7相关的数_Python