OpenCV-Python實戰(11)——OpenCV輪廓檢測相關應用
- 0. 前言
- 1. 輪廓繪制
- 2. 輪廓篩選
- 3. 輪廓識別
- 4. 輪廓匹配
- 小結
- 系列鏈接
0. 前言
在計算機視覺領域,輪廓通常指影像中物件邊界的一系列點,因此,輪廓通常描述了物件邊界的關鍵資訊,包含了有關物件形狀的主要資訊,該資訊可用于形狀分析與物件檢測和識別,我們已經在《OpenCV輪廓檢測》中介紹了如何檢測和繪制輪廓,在本文中,我們將繼續學習如何利用獲取到的輪廓,進行形狀分析以及物件檢測和識別,
1. 輪廓繪制
在《OpenCV輪廓檢測》中,我們介紹了如何從影像矩計算獲取輪廓屬性(例如,質心,面積,圓度或偏心等),除此之外,OpenCV 還提供了一些其他用于進一步描述輪廓的函式,
cv2.boundingRect() 回傳包含輪廓所有點的最小邊界矩形:
x, y, w, h = cv2.boundingRect(contours[0])
cv2.minAreaRect() 回傳包含輪廓所有點的最小旋轉(如有必要)矩形:
rotated_rect = cv2.minAreaRect(contours[0])
為了提取旋轉矩形的四個點,可以使用 cv2.boxPoints() 函式,回傳旋轉矩形的四個頂點:
box = cv2.boxPoints(rotated_rect)
cv2.minEnclosingCircle() 回傳包含輪廓所有點的最小圓(該函式回傳圓心和半徑):
(x, y), radius = cv2.minEnclosingCircle(contours[0])
cv2.fitEllipse() 回傳包含輪廓所有點的橢圓(具有最小平方誤差):
ellipse = cv2.fitEllipse(contours[0])
cv2.approxPolyDP() 基于給定精度回傳給定輪廓的輪廓近似,此函式使用 Douglas-Peucker 演算法:
approx = cv2.approxPolyDP(contours[0], epsilon, True)
其中,epsilon 引數用于確定精度,確定原始曲線之間的最大距離及其近似,因此,由此產生的輪廓是類似于給定的輪廓的壓縮輪廓,
接下來,我們使用與輪廓相關的 OpenCV 函式計算給定輪廓的外端點,在具體講解代碼時,首先看下結果影像,以更好的理解上述函式:

首先撰寫 extreme_points() 用于計算定義給定輪廓的四個外端點:
def extreme_points(contour):
"""檢測輪廓的極值點"""
extreme_left = tuple(contour[contour[:, :, 0].argmin()][0])
extreme_right = tuple(contour[contour[:, :, 0].argmax()][0])
extreme_top = tuple(contour[contour[:, :, 1].argmin()][0])
extreme_bottom = tuple(contour[contour[:, :, 1].argmax()][0])
return extreme_left, extreme_right, extreme_top, extreme_bottom
np.argmin() 沿軸回傳最小值的索引,在多個出現最小值的情況下回傳第一次出現的索引;而 np.argmax() 回傳最大值的索引,一旦索引 index 計算完畢,就可以利用索引獲取陣列的相應元素(例如,contour[index]——[[40 320]] ),如果要訪問第一個元素,則使用 contour[index][0]——[40 320];最后,我們將其轉換為元組:tuple(contour[index][0]——(40,320),用以繪制輪廓點,
def array_to_tuple(arr):
"""將串列轉換為元組"""
return tuple(arr.reshape(1, -1)[0])
def draw_contour_points(img, cnts, color):
"""繪制所有檢測到的輪廓點"""
for cnt in cnts:
squeeze = np.squeeze(cnt)
for p in squeeze:
pp = array_to_tuple(p)
cv2.circle(img, pp, 10, color, -1)
return img
def draw_contour_outline(img, cnts, color, thickness=1):
"""繪制所有輪廓"""
for cnt in cnts:
cv2.drawContours(img, [cnt], 0, color, thickness)
def show_img_with_matplotlib(color_img, title, pos):
"""影像可視化"""
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(2, 3, pos)
plt.imshow(img_RGB)
plt.title(title, fontsize=8)
plt.axis('off')
# 加載影像并轉換為灰度影像
image = cv2.imread("example.png")
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 閾值處理轉化為二值影像
ret, thresh = cv2.threshold(gray_image, 60, 255, cv2.THRESH_BINARY)
# 利用二值影像檢測影像中的輪廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# 顯示檢測到的輪廓數
print("detected contours: '{}' ".format(len(contours)))
# 創建原始影像的副本以執行可視化
boundingRect_image = image.copy()
minAreaRect_image = image.copy()
fitEllipse_image = image.copy()
minEnclosingCircle_image = image.copy()
approxPolyDP_image = image.copy()
# 1. cv2.boundingRect()
x, y, w, h = cv2.boundingRect(contours[0])
cv2.rectangle(boundingRect_image, (x, y), (x + w, y + h), (0, 255, 0), 5)
# 2. cv2.minAreaRect()
rotated_rect = cv2.minAreaRect(contours[0])
box = cv2.boxPoints(rotated_rect)
box = np.int0(box)
cv2.polylines(minAreaRect_image, [box], True, (0, 0, 255), 5)
# 3. cv2.minEnclosingCircle()
(x, y), radius = cv2.minEnclosingCircle(contours[0])
center = (int(x), int(y))
radius = int(radius)
cv2.circle(minEnclosingCircle_image, center, radius, (255, 0, 0), 5)
# 4. cv2.fitEllipse()
ellipse = cv2.fitEllipse(contours[0])
cv2.ellipse(fitEllipse_image, ellipse, (0, 255, 255), 5)
# 5. cv2.approxPolyDP()
epsilon = 0.01 * cv2.arcLength(contours[0], True)
approx = cv2.approxPolyDP(contours[0], epsilon, True)
draw_contour_outline(approxPolyDP_image, [approx], (255, 255, 0), 5)
draw_contour_points(approxPolyDP_image, [approx], (255, 0, 255))
# 檢測輪廓的極值點
left, right, top, bottom = extreme_points(contours[0])
cv2.circle(image, left, 20, (255, 0, 0), -1)
cv2.circle(image, right, 20, (0, 255, 0), -1)
cv2.circle(image, top, 20, (0, 255, 255), -1)
cv2.circle(image, bottom, 20, (0, 0, 255), -1)
# 可視化
show_img_with_matplotlib(image, "image and extreme points", 1)
show_img_with_matplotlib(boundingRect_image, "cv2.boundingRect()", 2)
show_img_with_matplotlib(minAreaRect_image, "cv2.minAreaRect()", 3)
show_img_with_matplotlib(minEnclosingCircle_image, "cv2.minEnclosingCircle()", 4)
show_img_with_matplotlib(fitEllipse_image, "cv2.ellipse()", 5)
show_img_with_matplotlib(approxPolyDP_image, "cv2.approxPolyDP()", 6)
plt.show()
我們還可以測驗在其他影像上的效果:

2. 輪廓篩選
如果想要計算檢測到的輪廓的大小,可以使用基于影像矩的方法或使用 OpenCV 函式 cv2.contourArea() 來計算檢測到的輪廓的大小,接下來,讓我們將根據每個檢測到的輪廓大小對其進行排序,在實踐中,某些小的輪廓可能是噪聲導致的,可能需要對輪廓進行篩選,
我們首先在畫布上繪制不同半徑的圓,用于后續檢測:
# 畫布
image = np.ones((300,700,3), dtype='uint8')
# 繪制不同半徑的圓
cv2.circle(image, (20, 20), 8, (64, 128, 0), -1)
cv2.circle(image, (60, 80), 25, (128, 255, 64), -1)
cv2.circle(image, (100, 180), 50, (64, 255, 64), -1)
cv2.circle(image, (200, 250), 45, (255, 128, 64), -1)
cv2.circle(image, (300, 250), 30, (35, 128, 35), -1)
cv2.circle(image, (380, 100), 15, (125, 255, 125), -1)
cv2.circle(image, (600, 210), 55, (125, 125, 255), -1)
cv2.circle(image, (450, 150), 60, (0, 255, 125), -1)
cv2.circle(image, (330, 180), 20, (255, 125, 0), -1)
cv2.circle(image, (500, 60), 35, (125, 255, 0), -1)
cv2.circle(image, (200, 80), 65, (125, 64, 125), -1)
cv2.circle(image, (620, 80), 48, (255, 200, 128), -1)
cv2.circle(image, (400, 260), 28, (255, 255, 0), -1)
接下來,檢測圖中輪廓:
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 閾值處理
ret, thresh = cv2.threshold(gray_image, 50, 255, cv2.THRESH_BINARY)
# 檢測輪廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# 列印檢測到的輪廓數
print("detected contours: '{}' ".format(len(contours)))
根據每個檢測到的輪廓大小進行排序:
def sort_contours_size(cnts):
"""根據大小對輪廓進行排序"""
cnts_sizes = [cv2.contourArea(contour) for contour in cnts]
(cnts_sizes, cnts) = zip(*sorted(zip(cnts_sizes, cnts)))
return cnts_sizes, cnts
(contour_sizes, contours) = sort_contours_size(contours)
最后進行可視化:
for i, (size, contour) in enumerate(zip(contour_sizes, contours)):
# 計算輪廓的矩
M = cv2.moments(contour)
# 質心
cX = int(M['m10'] / M['m00'])
cY = int(M['m01'] / M['m00'])
# get_position_to_draw() 函式與上例相同
(x, y) = get_position_to_draw(str(i + 1), (cX, cY), cv2.FONT_HERSHEY_SIMPLEX, 2, 5)
# 將排序結果置于形狀的質心
cv2.putText(image, str(i + 1), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255), 5)
# show_img_with_matplotlib() 函式與上例相同
show_img_with_matplotlib(image, 'image', 1)
show_img_with_matplotlib(image, "result", 2)
plt.show()
程式運行結果如下所示:

3. 輪廓識別
我們之前已經介紹了 cv2.approxPolyDP(),它可以使用 Douglas Peucker 演算法用較少的點來使一個輪廓逼近檢測的輪廓,此函式中的一個關鍵引數是 epsilon,其用于設定近似精度,我們使用 cv2.approxPolyDP(),以便根據被抽取的輪廓中的檢測到頂點的數量識別輪廓(例如,三角形,方形,矩形,五角形或六角形),為了減少點數,給定某個輪廓,我們首先計算輪廓的邊( perimeter ),基于邊,建立 epsilon 引數, epsilon 引數計算如下:
epsilon = 0.03 * perimeter
如果該常數變大(例如,從 0.03 變為 0.1 ),則 epsilon 引數也會更大,近似精度將減小,這導致具有較少點的輪廓,并且導致頂點的缺失,對輪廓的識別也將不正確,因為它基于檢測到的頂點的數量;另一方面,如果該常數較小(例如,從0.03 變為 0.001),則 epsilon 引數也將變小,因此,近似精度將增加,將產生具有更多點的近似輪廓,對輪廓的識別同樣會出現錯誤,因為獲得了虛假頂點,
# 構建測驗影像
image = np.ones((300,700,3), dtype='uint8')
cv2.circle(image, (100, 80), 65, (64, 128, 0), -1)
pts = np.array([[300, 10], [400, 150], [200, 150]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.fillPoly(image, [pts], (64, 255, 64))
cv2.rectangle(image, (450, 20),(650, 150),(125, 125, 255),-1)
cv2.rectangle(image, (50, 180),(150, 280),(255, 125, 0),-1)
pts = np.array([[365, 220], [320, 282], [247, 258], [247, 182], [320, 158]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.fillPoly(image, [pts], (125, 64, 125))
pts = np.array([[645, 220], [613, 276], [548, 276], [515, 220], [547, 164],[612, 164]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.fillPoly(image, [pts], (255, 255, 0))
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray_image, 50, 255, cv2.THRESH_BINARY)
# 輪廓檢測
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
image_contours = image.copy()
image_recognition_shapes = image.copy()
# 繪制所有檢測的輪廓
draw_contour_outline(image_contours, contours, (255, 255, 255), 4)
def detect_shape(contour):
"""形狀識別"""
# 計算輪廓的周長
perimeter = cv2.arcLength(contour, True)
contour_approx = cv2.approxPolyDP(contour, 0.03 * perimeter, True)
if len(contour_approx) == 3:
detected_shape = 'triangle'
elif len(contour_approx) == 4:
x, y, width, height = cv2.boundingRect(contour_approx)
aspect_ratio = float(width) / height
if 0.90 < aspect_ratio < 1.10:
detected_shape = "square"
else:
detected_shape = "rectangle"
elif len(contour_approx) == 5:
detected_shape = "pentagon"
elif len(contour_approx) == 6:
detected_shape = "hexagon"
else:
detected_shape = "circle"
return detected_shape, contour_approx
for contour in contours:
# 計算輪廓的矩
M = cv2.moments(contour)
# 計算輪廓的質心
cX = int(M['m10'] / M['m00'])
cY = int(M['m01'] / M['m00'])
# 識別輪廓形狀
shape, vertices = detect_shape(contour)
# 繪制輪廓
draw_contour_points(image_contours, [vertices], (255, 255, 255))
# 將形狀的名稱置于形狀的質心
(x, y) = get_position_to_draw(shape, (cX, cY), cv2.FONT_HERSHEY_SIMPLEX, 1.6, 3)
cv2.putText(image_recognition_shapes, shape, (x+35, y), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 3)
# 可視化
show_img_with_matplotlib(image, "image", 1)
show_img_with_matplotlib(cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR), "threshold = 100", 2)
show_img_with_matplotlib(image_contours, "contours outline (after approximation)", 3)
show_img_with_matplotlib(image_recognition_shapes, "contours recognition", 4)
plt.show()

4. 輪廓匹配
Hu 矩不變性可用于物件匹配和識別,我們將介紹如何基于 Hu 矩不變性的匹配輪廓,OpenCV 提供 cv2.matchShapes()函式可用于比較兩個輪廓,其包含三種匹配演算法,包括 cv2.CONTOURS_MATCH_I1,cv2.CONTOURS_MATCH_I2 和 cv2.CONTOURS_MATCH_I3,這些演算法都使用Hu矩不變性,
如果 A 表示第一個物件,B 表示第二個物件,則使用以下公式計算匹配性:
m
i
A
=
s
i
g
n
(
h
i
A
)
?
l
o
g
h
i
A
m
i
B
=
s
i
g
n
(
h
i
B
)
?
l
o
g
h
i
B
\begin{aligned} m_i^A&=sign(h_i^A)\cdot logh_i^A \\ m_i^B&=sign(h_i^B)\cdot logh_i^B \\ \end{aligned}
miA?miB??=sign(hiA?)?loghiA?=sign(hiB?)?loghiB??
其中,
h
i
A
h_i^A
hiA? 和
h
i
B
h_i^B
hiB? 分別是 A 和 B 的 Hu 矩,
接下來,我們使用 cv2.matchShapes() 來計算輪廓與給定圓形輪廓的匹配程度,
首先,通過使用 cv2.circle() 在影像中繪制圓形作為參考影像,之后,加載繪制了不同形狀的影像,然后在上述影像中查找輪廓:
def build_circle_image():
"""繪制參考圓"""
img = np.zeros((500, 500, 3), dtype="uint8")
cv2.circle(img, (250, 250), 200, (255, 255, 255), 1)
return img
# 加載影像
image = cv2.imread("example.png")
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image_circle = build_circle_image()
gray_image_circle = cv2.cvtColor(image_circle, cv2.COLOR_BGR2GRAY)
# 二值化影像
ret, thresh = cv2.threshold(gray_image, 70, 255, cv2.THRESH_BINARY_INV)
ret, thresh_circle = cv2.threshold(gray_image_circle, 70, 255, cv2.THRESH_BINARY)
# 檢測輪廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
contours_circle, hierarchy_2 = cv2.findContours(thresh_circle, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
result_1 = image.copy()
result_2 = image.copy()
result_3 = image.copy()
for contour in contours:
# 計算輪廓的矩
M = cv2.moments(contour)
# 計算矩的質心
cX = int(M['m10'] / M['m00'])
cY = int(M['m01'] / M['m00'])
# 使用三種匹配模式將每個輪廓與圓形輪廓進行匹配
ret_1 = cv2.matchShapes(contours_circle[0], contour, cv2.CONTOURS_MATCH_I1, 0.0)
ret_2 = cv2.matchShapes(contours_circle[0], contour, cv2.CONTOURS_MATCH_I2, 0.0)
ret_3 = cv2.matchShapes(contours_circle[0], contour, cv2.CONTOURS_MATCH_I3, 0.0)
# 將獲得的分數寫在結果影像中
(x_1, y_1) = get_position_to_draw(str(round(ret_1, 3)), (cX, cY), cv2.FONT_HERSHEY_SIMPLEX, 1.2, 3)
(x_2, y_2) = get_position_to_draw(str(round(ret_2, 3)), (cX, cY), cv2.FONT_HERSHEY_SIMPLEX, 1.2, 3)
(x_3, y_3) = get_position_to_draw(str(round(ret_3, 3)), (cX, cY), cv2.FONT_HERSHEY_SIMPLEX, 1.2, 3)
cv2.putText(result_1, str(round(ret_1, 3)), (x_1+10, y_1), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
cv2.putText(result_2, str(round(ret_2, 3)), (x_2+10, y_2), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.putText(result_3, str(round(ret_3, 3)), (x_3+10, y_3), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)

小結
在本文中,我們學習了如何利用獲取到的輪廓,進行形狀分析以及物件檢測和識別,可以利用 cv2.boundingRect()、cv2.minAreaRect()、cv2.minEnclosingCircle()、cv2.fillEllipse() 和 cv2.approxPolyDP() 進行輪廓繪制,使用 cv2.contourArea() 計算輪廓面積,以及 cv2.matchShapes() 進行輪廓的匹配,
系列鏈接
OpenCV-Python實戰(1)——OpenCV簡介與影像處理基礎
OpenCV-Python實戰(2)——影像與視頻檔案的處理
OpenCV-Python實戰(3)——OpenCV中繪制圖形與文本
OpenCV-Python實戰(4)——OpenCV常見影像處理技術
OpenCV-Python實戰(5)——OpenCV影像運算
OpenCV-Python實戰(6)——OpenCV中的色彩空間和色彩映射
OpenCV-Python實戰(7)——直方圖詳解
OpenCV-Python實戰(8)——直方圖均衡化
OpenCV-Python實戰(9)——OpenCV用于影像分割的閾值技術
OpenCV-Python實戰(10)——OpenCV輪廓檢測
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/332099.html
標籤:python
