OpenCV中的圖像處理 —— 輪廓入門+輪廓特征
OpenCV中的圖像處理 —— 輪廓入門+輪廓特征
1. OpenCV中的輪廓
1.1 輪廓概述
萬變不離其宗在學(xué)習(xí)OpenCV中的輪廓之前,我們先來了解一下什么是輪廓,輪廓可以簡單地解釋為連接具有相同顏色或強度的所有連續(xù)點(沿邊界)的曲線,輪廓是用于形狀分析以及對象及檢測和識別的有用工具
為了獲取更高的準確性,我們會使用二進制圖像,因此在尋找輪廓之前我們應(yīng)用閾值或Canny邊緣檢測
在OpenCV中,尋找輪廓就是從黑色背景中找到白色物體,因此我們要找的對象應(yīng)是白色,背景應(yīng)該是黑色(應(yīng)用閾值后或Canny邊緣檢測)
想要找到二進制圖像的圖像,我們需要用到一個函數(shù):cv.findContours(),這個函數(shù)中包括三個參數(shù),第一個是原圖像,第二個是輪廓檢索模式,第三個是輪廓逼近方法,這個函數(shù)輸出等高線和層次結(jié)構(gòu),輪廓指的是圖像中所有圖像的Python列表,每個單獨的輪廓是一個(x,y)坐標的Numpy數(shù)組的邊界點的對象
import numpy as np import cv2 as cv im = cv.imread('test.jpg') imgray = cv.cvtColor(im, cv.COLOR_BGR2GRAY) ret, thresh = cv.threshold(imgray, 127, 255, 0) contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
第二個參數(shù)輪廓檢索模式的取值可以為:
RETR_EXTERNAL:只檢索最外面的輪廓
RETR_LIST:檢索所有的輪廓,并將其保存到一條鏈表中
RETR_CCOMP:檢索所有的輪廓,并將它們組織為兩層:頂層是各部分的外部邊界,第二層是空洞的邊界
RETR_TREE:常用,檢索所有的輪廓,并重構(gòu)嵌套輪廓的整個層次。保存了所有的輪廓
第三個參數(shù)輪廓逼近方法的取值可以為:
CHAIN_APPROX_NONE:以Freeman鏈碼的方式輸出輪廓,所有其他方法輸出多邊形(頂點的序列)。正常畫出所有輪廓
CHAIN_APPROX_SIMPLE:壓縮水平的、垂直的、斜的部分,即函數(shù)只保留他們的終點坐標。壓縮得到更精簡的結(jié)果,例如一個矩形輪廓只需4個點來保存輪廓信息
資料參考來自文章:【opencv】(6) 圖像輪廓處理
1.2 輪廓繪制
通過cv.findContours()函數(shù)找到輪廓后,我們一般通過cv.drawContours()函數(shù)將輪廓繪制出來,只要有邊界點,它也可以用來你繪制任何形狀,這個函數(shù)的第一個參數(shù)為圖像資源,第二個參數(shù)是應(yīng)該作為Python列表傳遞的輪廓,第三個參數(shù)是輪廓的索引(在繪制單個輪廓時有用,如果需要繪制所有的輪廓,傳入?yún)?shù)-1即可),其余的參數(shù)是顏色厚度等公共參數(shù)
import cv2 as cv import numpy as np from matplotlib import pyplot as plt img = cv.imread('E:/image/test09.png') # 在尋找輪廓時要傳入單通道圖像 gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) ret, threshold = cv.threshold(gray, 127, 255, cv.THRESH_BINARY) contours, hierarchy = cv.findContours(threshold, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) # 繪制所有輪廓 img1 = cv.drawContours(img, contours, -1, (0, 255, 0), 3) # 繪制單個輪廓 img2 = cv.drawContours(img, contours, 3, (0, 255, 0), 3) # 常用的繪制輪廓的方法 cnt = contours[3] img3 = cv.drawContours(img, [cnt], 0, (0, 255, 0), 3) plt.subplot(1, 3, 1), plt.imshow(img, cmap='gray') plt.title('Original'), plt.xticks([]), plt.yticks([]) plt.subplot(1, 3, 2), plt.imshow(img, cmap='gray') plt.title('ALL_IMG'), plt.xticks([]), plt.yticks([]) plt.subplot(1, 3, 3), plt.imshow(img, cmap='gray') plt.title('Usually'), plt.xticks([]), plt.yticks([]) plt.show()
1.3 輪廓近似方法
我們前面說到的cv.findContours()函數(shù)有三個參數(shù)傳遞,第三個參數(shù)是輪廓逼近方法,也就是我們這里要說的輪廓近似方法。輪廓是強度相同的形狀的邊界,它存儲形狀邊界的(x,y)坐標,但是它也并不是存儲所有的坐標
而它存不存儲所有的坐標,就取決于輪廓逼近方法的使用,如果我們傳入了cv.CHAIN_APPROX_NONE參數(shù),那么輪廓將存儲所有的坐標,但是在實際情況中,我們有必要存儲所有的坐標嗎?
比如我們找到了一個矩形的輪廓,存儲所有的坐標會浪費很多空間,我們只存儲四個頂點的坐標就可以了,這個操作就是參數(shù)cv.CHAIN_APPROX_SIMPLE,這種輪廓逼近方法可以滿足我們的需求
2. 輪廓特征
2.1 特征矩
在學(xué)習(xí)特征矩之前我們先要了解它的概念,首先特征矩代表了一個輪廓、一幅圖像的全局特征,矩信息包含了對應(yīng)對象不同類型的幾何特征,特征矩分為三種:空間矩、中心矩和歸一化中心矩
中心矩:對于高階圖像,特征矩會隨著位置的變化而變化,為了解決這個問題中心矩就應(yīng)運而生,它通過減去均值而獲取平移的不變性,因而能比較不同位置的兩個對象是否一致,即中心矩具有平移不變性特征
歸一化中心矩:除平移之外,有些圖像我們還會碰到縮放的情況,即在縮放后也能判斷其特征,歸一化中心矩通過除以物體總尺寸而獲得縮放不變性
回到我們OpenCV的特征矩,它可以幫助我們計算一些特征,例如質(zhì)心、面積等,一個重要的函數(shù)cv.moments()提供了所有計算出矩值的字典
import numpy as np import cv2 as cv img = cv.imread('star.jpg',0) ret,thresh = cv.threshold(img,127,255,0) contours,hierarchy = cv.findContours(thresh, 1, 2) cnt = contours[0] M = cv.moments(cnt) print( M )
2.2 輪廓面積 + 周長
輪廓區(qū)域由函數(shù)cv.contourArea()或從矩 M[‘m00’] 中給出
周長也稱為弧長,可以使用cv.arcLength()函數(shù)找到它。第二個參數(shù)指定形狀是閉合輪廓( True )還是曲線
area = cv.contourArea(cnt) perimeter = cv.arcLength(cnt,True)
2.3 輪廓近似
輪廓近似就是根據(jù)我們指定的精讀,通過道格拉斯-普客算法,將輪廓形狀近似為頂點數(shù)量較少的其他形狀
說到道格拉斯-普客算法,那必須來瞧瞧它的本質(zhì):將圖像數(shù)字化時,對曲線進行采樣,即在曲線上取有限個點,將其連接變成折線,并且在一定程度上保持原有的形狀
import cv2 import numpy as np from matplotlib import pyplot as plt src = cv2.imread('E:/image/test10.png') gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY) contours, hierarchy = cv2.findContours(~thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) cnt = contours[0] img1 = src.copy() epslion1 = 0.05 * cv2.arcLength(cnt, True) approx1 = cv2.approxPolyDP(cnt, epslion1, True) img1 = cv2.drawContours(img1, [approx1], 0, (0, 0, 255), 2) img2 = src.copy() epslion2 = 0.01 * cv2.arcLength(cnt, True) approx2 = cv2.approxPolyDP(cnt, epslion2, True) img2 = cv2.drawContours(img2, [approx2], 0, (0, 255, 0), 2) plt.subplot(1, 2, 1), plt.imshow(img1, cmap='gray') plt.title('JinSi1'), plt.xticks([]), plt.yticks([]) plt.subplot(1, 2, 2), plt.imshow(img2, cmap='gray') plt.title('JInSi2'), plt.xticks([]), plt.yticks([]) plt.show()
作為前面知識的回顧,我們分析繪制出圖像的兩個精度不同的近似輪廓的全過程,首先在加載一些我們需要的第三方庫,方便我們在代碼中使用其方法等,然后在第5行代碼我們通過cv,imread()函數(shù)讀取了一個圖像,然后使用了cv.cvtColor()函數(shù)將其轉(zhuǎn)化為灰度圖,為什么要轉(zhuǎn)化為灰度圖呢?因為我們接下來要使用圖像閾值!在全局閾值函數(shù)cv.threshold()函數(shù)中我們要傳入的參數(shù)有四個,第一個參數(shù)是我們的源圖像,它一般是灰度圖像,這就是為什么要把img變?yōu)間ray,第二個參數(shù)是我們設(shè)置的閾值,它用于把圖像中的像素做一個分類,大于閾值的像素都將被設(shè)置為最大值,第三個參數(shù)就是我們設(shè)置的最大值,第四個參數(shù)是一個表示不同類型的標志
代碼到了第8行,從這里開始就要涉及到我們這一部分學(xué)到的內(nèi)容了,首當其沖就是找到輪廓,我們使用了cv.findContours()函數(shù),這個函數(shù)的三個參數(shù)分別是圖像、輪廓檢索模式和輪廓逼近方法
到了11行,一個陌生的東西出現(xiàn)了,它就是輪廓近似的核心代碼之一,epslion是一個與近似精度密切相關(guān)的參數(shù),它表示從輪廓到近似輪廓的最大距離,怎么算這個最大距離呢,那就要用到一個函數(shù)了:cv.arcLength(),是不是很眼熟,它就是我們上面說過的求輪廓周長的函數(shù),我們需要選擇正確的epsilon才能獲得正確的輸出
如果我們把11行代碼的0.05改成0.1,我們就會發(fā)現(xiàn)得不到我們想要的輸出結(jié)果,這就是這個參數(shù)epsilon太粗糙了,這個參數(shù)越小,我們獲得的輪廓就越近似
2.4 輪廓凸包
輪廓凸包與輪廓近似看起來像,其實一點兒關(guān)系沒有,關(guān)于輪廓凸包我們也有一個重要的函數(shù)cv.convexHull()函數(shù),它用來檢查曲線是否存在凸凹缺陷并對其進行校正,而校不校正呢,這就由這個函數(shù)的參數(shù)說了算
一般來說凸曲線一般都是凸出或平坦的曲線,如果在內(nèi)部凸出了(凹進去了)我們就稱其為凸度缺陷,凸度缺陷我們后面再細說
關(guān)于函數(shù)cv.convexHull()的語法我們攤開說說,它本來長這樣:
hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]
point是我們傳遞的輪廓(為什么用的是點(point)呢?是不是忘了輪廓的實質(zhì)是由具有相同顏色或強度的所有連續(xù)點連接起來的曲線),hull(凸出)是輸出,我們一般都忽略它,clockwise是方向標記,如果傳入True則是順時針,反之為逆時針,returnPoint默認情況下是True,它返回凸包的坐標,如果為False,則返回與凸包點相對應(yīng)的輪廓點的索引
然后上例子?。ú粩鄬嵺`才能成長?。?/p>
import cv2 import numpy as np from matplotlib import pyplot as plt src = cv2.imread('E:/image/test11.png') gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 尋找輪廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: hull = cv2.convexHull(cnt) length = len(hull) # 如果凸包點集中的點個數(shù)大于5 if length > 5: # 繪制圖像凸包的輪廓 for i in range(length): cv2.line(src, tuple(hull[i][0]), tuple(hull[(i + 1) % length][0]), (0, 0, 255), 2) cv2.imshow('line', src) cv2.waitKey()
文章借鑒來源:OpenCV入門之尋找圖像的凸包(convex hull)
2.5 邊界矩形(直角矩形+旋轉(zhuǎn)矩形)
直角矩形不考慮物體的旋轉(zhuǎn),所以直角邊界矩形的面積不是最小的,尋找這個矩形涉及了一個函數(shù):cv.boundingRect()
# (x,y)為左上角坐標,(w,h)為寬和高 x,y,w,h = cv.boundingRect(cnt) cv.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
旋轉(zhuǎn)邊界矩形是用最小面積繪制的,所以它考慮了物體的旋轉(zhuǎn),尋找旋轉(zhuǎn)邊界矩形涉及的函數(shù)是cv.minAreaRect(),它返回的是一個Box2D結(jié)構(gòu),其中包含了坐標、寬高和旋轉(zhuǎn)角度,但是我們想要得到這個矩形還是比較麻煩的,我們需要四個頂點的坐標
rect = cv.minAreaRect(cnt) box = cv.boxPoints(rect) box = np.int0(box) cv.drawContours(img,[box],0,(0,0,255),2)
2.8 最小閉合圓 + 擬合橢圓
查找對象的最小閉合圓需要用到函數(shù):cv.minEnclosingCircle(),它是一個以最小面積完全覆蓋物體的圓
(x,y),radius = cv.minEnclosingCircle(cnt) center = (int(x),int(y)) radius = int(radius) cv.circle(img,center,radius,(0,255,0),2)
擬合一個對象的橢圓會使用到函數(shù):cv.fitEllipse()
ellipse = cv.fitEllipse(cnt) cv.ellipse(img,ellipse,(0,255,0),2)
我們來看看應(yīng)用代碼:
import cv2 import numpy as np from matplotlib import pyplot as plt img = cv2.imread('E:/image/test13.png') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 設(shè)置閾值 ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) cnt = contours[0] x, y, w, h = cv2.boundingRect(cnt) img = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) rect = cv2.minAreaRect(cnt) box = cv2.boxPoints(rect) box = np.int0(box) img = cv2.drawContours(img, [box], 0, (0, 0, 255), 2) cv2.imshow('img', img) cv2.waitKey(0) cv2.destroyWindow()
(注:文章內(nèi)容參考OpenCV4.1中文官方文檔)
如果文章對您有所幫助,記得一鍵三連支持一下哦
AI OpenCV 圖像處理 機器學(xué)習(xí) 機器視覺
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。