天天看點

不可錯過!一文讓你學會OpenCV輪廓篩選與識别,全程幹貨

作者:人工智能-泰羅

輪廓篩選

在《OpenCV輪廓繪制詳解》中我們已經學習了如何繪制輪廓。接下來,如果想要計算檢測到的輪廓的大小,可以使用基于圖像矩的方法或使用 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()           

程式運作結果如下所示:

不可錯過!一文讓你學會OpenCV輪廓篩選與識别,全程幹貨

輪廓識别

我們之前已經介紹過了 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 get_position_to_draw(text, point, font_face, font_scale, thickness):
    """擷取圖形坐标中心點"""
    text_size = cv2.getTextSize(text, font_face, font_scale, thickness)[0]
    text_x = point[0] - text_size[0] / 2
    text_y = point[1] + text_size[1] / 2
    return round(text_x), round(text_y)

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()           
不可錯過!一文讓你學會OpenCV輪廓篩選與識别,全程幹貨

相關連結:

OpenCV輪廓檢測詳解 - 掘金:https://juejin.cn/post/7062929275426439204

OpenCV圖像矩詳解 - 掘金:https://juejin.cn/post/7063399022249279525

OpenCV Hu不變矩詳解 - 掘金:https://juejin.cn/post/7063654603480367112

OpenCV輪廓繪制詳解 - 掘金:https://juejin.cn/post/7064011386388480008

作者:盼小輝丶

連結:https://juejin.cn/post/7064397660291072014

來源:稀土掘金