首頁 > 軟體

Python OpenCV實現圖形檢測範例詳解

2022-04-08 13:00:46

圖形檢測在計算機視覺開發中是一項非常重要的操作,演演算法通過對影象的檢測,分析出影象中可能存在哪些形狀。除此之外,除了讓計算機識別輪廓之外,輪廓也需要讓人看到,這就需要再分別讀這些輪廓的形狀進行描繪。

1. 輪廓識別與描繪

cv2.findContours() & cv2.drawContours() 方法

在Python中OpenCV提供了findContours() 方法來判斷影象的輪廓,drawContours()方法來繪製輪廓。

1.1 cv2.findComtours()方法

cv2.findComtours()方法的語法如下

cv2.contours, hierarchy = findContours(image, mode, method)

其中

  • image 即原影象
  • mode 輪廓檢索模式,具體引數被總結在了下表中
  • method 使用的方法,具體引數也被總結在了下表中

contours是一個列表,列表的每一個元素都是由某個輪廓的畫素的座標組成的陣列。

hierarchy是輪廓與輪廓之間的層次關係。

mode取值表

mode值描述
cv2.RETR_EXTERNAL只檢測外輪廓
cv2.RETR_LIST檢測所有輪廓,但不建立層次關係
cv2.RETR_CCOMP檢測所有輪廓,並建立兩級層次關係
cv2.RETR_TREE建立所有輪廓,並建立樹狀結構的層次關係

method取值表

cv2.PATH_APPROX_儲存輪廓上所有點
cv2.PATH_APPROX_NONE只儲存水平、垂直或對角線輪廓的端點
cv2.PATH_APPROX_SIMPLETen-Chinl
cv2.PATH_APPROX_TC89_L1Ten-Chinl近似演演算法的一種
cv2.PATH_APPROX_TC89_KCOSTen-Chinl近似演演算法的一種

1.2 cv2.drawContours() 方法

cv2.drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None)

  • image 目標影象
  • contours findComtours()方法得到的輪廓列表
  • contourldx 輪廓中列表中,繪製輪廓的物件的索引,如果為-1則表示繪製所有
  • color 繪製線條時的顏色,使用BGR格式描述
  • thickness 線條粗細程度,-1表示實心
  • lineType 繪製輪廓時線條的型別(可選引數)
  • hierarchy findComtours()方法得到的層次關係(可選引數)
  • maxLevel 繪製輪廓的層次深度,最深繪製在maxLevel層。(可選引數)
  • offset 偏移量 (可選引數)

drawContours() 方法返回的是一個影象(陣列)。

1.3 程式碼範例

以隊此小鳥圖操作為例(test1.jpg):

import cv2
img = cv2.imread("test1.jpg")  
# 彩色影象轉為變成單通道灰度影象
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  
# 二值化處理
t, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)  
# 檢測影象中出現的所有輪廓,記錄輪廓的每一個點
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# 繪製所有輪廓,寬度為5,顏色為紅色
cv2.drawContours(img, contours, -1, (0, 0, 255), 5)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

程式執行結果展示如下,圖中所有能夠識別出的輪廓被描出:

2. 輪廓擬合

輪廓擬合,即,將凹凸不平的輪廓用平整的幾何圖形體現出來。這裡展示使用矩形和圓形擬合兩種方法。

2.1 矩形包圍框擬合 - cv2.boundingRect()

在Python中OpenCV提供了cv2.boundingRect()來計算輪廓的最小矩形邊界的座標 ,其語法如下

retval = cv2.boundingRect(array)

其中引數array為輪廓陣列。即,cv2.findComtours()方法的執行結果中的contours中的元素。

返回值retval是一個包含著四個整數值的元組,四個值依次是左上角頂點的橫座標,左上角頂點的縱座標,矩形的寬,矩形的高。

常常也可以寫成x,y,w,h = retval = cv2.boundingRect(array)

還以這張小鳥圖片(test1.jpg)為例,在上一個部分的範例中,我們找出並繪製出了圖中所有的輪廓,經過偵錯,發現被識別出的輪廓共有94個。

我們要從陣列列表中,選擇出表示小鳥輪廓的位置的陣列。

從上圖中可以看出,小鳥的輪廓是所有輪廓中最大的。即該陣列的 shape[0] 最大。即我們只用找出 shape[0]最大的即可。

import cv2
img = cv2.imread("test1.jpg") 
# 從彩色影象變成單通道灰度影象
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  
# 將灰度影象進行二值化閾值處理
t, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 獲取二值化影象中的輪廓極輪廓層次資料
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# 找出小鳥的輪廓
n1 = 1
n2 = 0
index = 0
for arr in contours:
    if len(arr) > n1:
        n1 = len(arr)
        index = n2
    n2 += 1
print(index)

x, y, w, h = cv2.boundingRect(contours[index])
# 繪製紅色矩形
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)  
cv2.imshow("img", img) 
cv2.waitKey()
cv2.destroyAllWindows() 

繪製出矩形包圍框效果如下:

2.2圓形包圍框擬合 - cv2.minEnclosingCircle()

在Python中OpenCV提供了cv2.minEnclosingCircle()來計算輪廓的最小圓形邊界的圓心和半徑 ,其語法如下

center,radius = minEnclosingCircle(points)

其中

  • points的輪廓陣列
  • center最小圓形包圍框的圓心的橫縱座標。是元組型別。
  • radius是最小圓形包圍款更多半徑,浮點型別。

同樣的演演算法,只是這次呼叫cv2.minEnclosingCircle()方法,其他不變:

import cv2
img = cv2.imread("test1.jpg")  # 讀取原圖
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 從彩色影象變成單通道灰度影象
# 將灰度影象進行二值化閾值處理
t, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 獲取二值化影象中的輪廓極輪廓層次資料
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

n1 = 1
n2 = 0
index = 0
for arr in contours:
    if len(arr) > n1:
        n1 = len(arr)
        index = n2
    n2 += 1


center, radius = cv2.minEnclosingCircle(contours[index])
# 圓心點橫座標轉為近似整數
x = int(round(center[0]))
# 圓心點縱座標轉為近似整數  
y = int(round(center[1]))  
cv2.circle(img, (x, y), int(radius), (0, 0, 255), 2)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

繪製出圓形包圍框效果如下(因為尺寸問題,只畫出了一部分):

3. 凸包 繪製

使用矩形框和圓形框對圖形的貼合程度往往都會較差。為了提高這個貼合程度,我們可以使用“凸包”。

所謂凸包,就是最逼近輪廓的多邊形。

在Python中OpenCV提供了 cv2.bonvexHull()方法來計算凸包

cv2.bonvexHull()方法語法如下:

hull = convexHull(points, clockwise=None, returnPoints=None)

其中

  • points 是輪廓陣列
  • clockwise 是布林型別的引數,預設為True,表示凸包中的點按順時針排序,為False時則按逆時針 排序。
  • returnPoints 是布林型別的引數,預設為True時返回點座標。如果為False則返回點索引。

返回值hull是凸包的點陣陣列

依然以“test1.jpg為例”,為圖中的小鳥繪製凸包:

import cv2

img = cv2.imread("test1.jpg")
# 轉為灰度影象
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化閾值處理
ret, binary = cv2.threshold(gray, 127, 225, cv2.THRESH_BINARY)
# 檢測影象中出現的所有輪廓
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

n1 = 1
n2 = 0
index = 0
for arr in contours:
    if len(arr) > n1:
        n1 = len(arr)
        index = n2
    n2 += 1

hull = cv2.convexHull(contours[index])
cv2.polylines(img, [hull], True, (0, 0, 255), 2)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

4. Canny邊緣檢測 - cv2.Canny()

4.1 cv2.Canny() 用法簡介

Canny邊緣檢測演演算法是John F.Canny在1986年開發的一個多級邊緣檢測演演算法。

Canny邊緣檢測演演算法通過畫素的梯度變化尋找影象的邊緣,最終可以繪製出十分精細的二值邊緣影象

edges = cv2.Canny(image, threshold1, threshold2, apertureSize=None, L2gradient=None)

其中

  • image 即原影象
  • threshold1   第一個閾值,一般為最小閾值
  • threshold2   第二個閾值,一般為最大閾值
  • apertureSize  Sobel運算元的孔徑大小
  • L2gradient 計算影象梯度的標識。預設為False。為True時採用更精準的演演算法進行計算。

關於這兩個閾值怎麼用,這涉及到了演演算法的底層邏輯,還請自行探索。這裡一種可以接受的解釋是:低於閾值1的畫素點,會被認為不構成邊緣,而高於閾值2的畫素點,會被認為構成邊緣。

最後返回值edges是一個二值的灰度影象。

4.2 程式碼範例

下邊對test1.jpg以三組不同的閾值來做Canny邊緣檢測,根據處理結果感受演演算法效果:

- 當閾值為 10-50 時

import cv2
img = cv2.imread("test1.jpg")
r1 = cv2.Canny(img, 10, 50)
cv2.imshow("r1", r1)
cv2.waitKey()
cv2.destroyAllWindows()

- 當閾值為100-200時

import cv2
img = cv2.imread("test1.jpg")
r2 = cv2.Canny(img, 100, 200)
cv2.imshow("r2", r2)
cv2.waitKey()
cv2.destroyAllWindows()

- 當閾值為400-600時

import cv2
img = cv2.imread("test1.jpg")
r3 = cv2.Canny(img, 400, 600)

cv2.imshow("r3", r3)
cv2.waitKey()
cv2.destroyAllWindows()

5. 霍夫變換

5.1 概述

霍夫變換是一種特徵檢測,通過霍夫變換可以檢測出影象中存在的特殊的形狀。比如,直線,圓等。

霍夫變換檢測直線時,演演算法有兩個,

一個是cv2.HoughLines() 方法,用於檢測無限延長的直線;

另一個是cv2.HoughLinesP() 方法,用於檢測線段。

霍夫變換檢測圓,使用的是**cv2.HoughCircles()**方法。

使用這三個方法前,都要先對影象進行降噪處理(使用濾波器),以去除干擾。

5.2 cv2.HoughLines() 檢測直線

cv2.HoughLines()語法如下:

lines = cv.HoughLines( image, rho, theta, threshold[,srn][,stn])

其中

  • image 即原影象
  • rho 指的是搜尋直線使用的半徑步長,其值為1時表示檢測所有。(即極座標中的ρ)
  • theta 指的是搜尋直線的角度,值為π/180°時,表示檢測所有角度。(即極座標中的θ)
  • threshold 指的是閾值,點的數量(也稱投票數)。因為直線的長度取決於直線上的點的數量。所以如果達不到這個長度,就不會被
  • 判定為直線。同理,當該閾值越小,檢測出的直線也就越多。
  • srn 對於多尺度霍夫變換,srn表示對rho的 距離解析度 的除數
  • stn 對於多尺度霍夫變換,stn表示對theta的 距離解析度 的除數
  • min_theat 對於標準和多尺度Hough變換,檢查線條的最小角度。必須介於0和最大θ之間。
  • max_theat 對於標準和多尺度Hough變換,檢查線條的最大角度。必須介於min_theta和CV_PI之間。

返回值lines,是一個陣列,shape為(n, 1, 2),n表示檢測出的所有線段數目,每個線段用極座標(ρ, θ)表示。

以此跨海大橋圖(test2.jpg)為例,對其使用cv2.HoughLines()方法,並繪製直線:

import cv2
import numpy as np

img = cv2.imread("test2.jpg")
o = img.copy()
# 使用中值濾波進行降噪
o = cv2.medianBlur(o, 5)
# 從彩色影象變成單通道灰度影象
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
# 繪製邊緣影象
binary = cv2.Canny(o, 50, 150)

# 檢測直線 不限步長,不限角度,至少50個點確定一條線
lines = cv2.HoughLines(binary, 1, np.pi / 180, 50)
# print(lines)
# print(lines.shape)

for line in lines:
    rho, theta = line[0]
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a * rho
    y0 = b * rho
    x1 = int(x0 + 1000 * (-b))
    y1 = int(y0 + 1000 * (a))
    x2 = int(x0 - 1000 * (-b))
    y2 = int(y0 - 1000 * (a))

    cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

識別效果如下,可以自行調節引數進行改良,並在這個過程中具體感受每個引數的作用。

當選擇至少50個點確定一條線時,一共檢測出168根直線:

當選擇至少100個點確定一條線時,一共檢測出35根直線:

當選擇至少300個點確定一條直線時,符合要求的直線有3根:

當選擇至少380個點確定一條直線時,這樣的直線還剩一根:

相比檢測直線,檢測線段的cv2.HoughLinesP()相對要更常用些。

5.3 cv2.HoughLinesP() 檢測線段

lines = cv2.HoughLinesP(image, rho, theta, threshold, minLineLength=None, maxLineGap=None)

檢測線段

  • image 原圖
  • rho 檢測直線使用的半徑步長,值為1時表示所有可能的半徑步長
  • theta 搜尋直線的角度
  • threshold 閾值,該值越小,檢測出的直線越多。
  • minLineLength表示線段的最小長度,小於該長度的線段不會被記錄在結果中。值越大線段越少。
  • maxLineGap 表示允許將同一行的點連線起來的最大距離。 值越大線段越多。

返回值lines,是一個陣列,shape為(n, 1, 4),n表示檢測出的所有線段數目,4指的是每個線段的兩端端點的笛卡爾座標(x, y) 座標的四個點。

其中minLineLength(最小線段長度)和maxLineGap(最小線段距離)兩個引數,都是越大,識別的線段越少。

import cv2
import numpy as np

img = cv2.imread("test2.jpg")
o = img.copy()
# 使用中值濾波進行降噪
o = cv2.medianBlur(o, 5)
# 從彩色影象變成單通道灰度影象
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
# 繪製邊緣影象
binary = cv2.Canny(o, 50, 150)

# 檢測線段,不限步長,不限角度,至少100個點確定一條線。最大將距離為200的線段連城一條線。
lines = cv2.HoughLinesP(binary, 1, np.pi / 180, 50, minLineLength=100, maxLineGap=200)
print(lines.shape)
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.imshow("canny", binary)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

二值化邊緣圖案效果:

描繪線段效果(minLineLength=100)至少100個點確定一條線段(上邊程式碼),最大將距離為200的線段連線起來
共描繪了23條線:

描繪線段效果(minLineLength=500)至少500個點確定一條線段,最大將距離為200的線段連線起來
共描繪了6條線段:

描繪線段效果(minLineLength=720)至少720個點確定一條線段,最大將距離為200的線段連線起來
共描繪了一條線:

5.4 cv2.檢測圓 - HoughCircles()

circles = HoughCircles(image, method, dp, minDist, circles=None, param1=None, param2=None, minRadius=None, maxRadius=None)

其中

  • image 原影象(降噪、灰度處理後的)
  • method 檢測方法。
  • dp 累加器解析度與原圖解析度之比的倒數。值為1時累加器與原影象有著相同的解析度。通常選擇1作為引數。(值為2時則累加器的解析度是原影象的一半)
  • minDist 圓心之間的最小距離
  • param1 引數1,表示Canny邊緣檢測的最大閾值。是可選引數。
  • param2 引數2,表示檢測結果投票數。即至少多少個點確定一個圓。值越大,識別的圓越少,約精準。是可選引數。
  • minRadius 圓環的最小半徑(可選引數)
  • maxRadius 圓環的最大半徑(可選引數)

返回值circles是一個陣列,陣列內是所有檢測出的圓環,shape為(n,1,3)。其中3表示圓心的x座標,圓心的y座標和半徑長度三個指標。

瞭解完語法,

接下來我們來檢測下圖(test3.jpg)中的客家土樓中的圓形。

import cv2
import numpy as np

img = cv2.imread("test3.jpg")
# 使用中值濾波進行降噪
o = cv2.medianBlur(img, 11) 
# 從彩色影象變成單通道灰度影象
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)  
# 展示灰度影象
cv2.imshow('gray', gray)
# 檢測圓環,圓心最小間距為50,Canny最大閾值為40,投票數超過63。最小半徑為10,最大半徑為50
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 50, param1=40, param2=63, minRadius=10, maxRadius=100)
# 將陣列元素四捨五入成整數
circles = np.uint(np.around(circles))
# 遍歷圓環結果
for c in circles[0]:
    # 圓心橫座標、縱座標和圓半徑
    x, y, r = c
    # 繪製圓環
    cv2.circle(img, (x, y), r, (0, 0, 255), 3)
    # 繪製圓心
    cv2.circle(img, (x, y), 2, (0, 0, 255), 3)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

降過噪的灰度影象如下:

識別結果呈現如下,如圖成功識別出了圖中所有土樓的圓形:

以上就是Python OpenCV實現圖形檢測範例詳解的詳細內容,更多關於Python OpenCV圖形檢測的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com