Contour approximation 使用Ramer - Douglas - Peucker (RDP)算法,旨在通過減少給定閾值的頂點來簡化折線。通俗地說,我們採用一條曲線並減少其頂點數量,同時保留其大部分形狀。我將在這裡給出算法的粗略概念。給定曲線的起點和終點,算法將首先找到距離連接兩個參考點的線最大距離的頂點。讓我們將其稱為max_point。如果max_point距離小於閾值,我們會自動忽略起點和終點之間的所有頂點,使曲線成為一條直線。如果max_point超出閾值,我們將遞歸重複該算法,現在將其max_point作為參考之一,並重複檢查過程。
假如,我們要 開發一個自動導航的機器人,在機器人導航的過程中,必然會搜集大量的路徑數據,進而指導機器人的前進,但是很多時候,路徑上的拐點很多,這就加大了計算的負擔,如何能儘可能地保留原始路徑數據,又可以降低大量的計算,這就需要路徑的一些估算,這就要使用到本次的路徑輪廓近似原理。
原始路徑
輪廓近似後的圖片
cv2.approxPolyDP實現輪廓近似原理# -*- coding: utf-8 -*-
import numpy as np
import cv2
image = cv2.imread("C:/Users/angel/Desktop/image.png")
cv2.imshow("Image", image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 200, 255,
cv2.THRESH_BINARY_INV)[1]
cv2.imshow("Thresh", thresh)
cv2.waitKey(0)
首先我們導入需要的第三方包,這裡最主要的是CV2,然後使用imread函數加載一張本地的圖片,由於我們將使用圖像中形狀的邊界,因此我們將圖像從 RGB 轉換為灰度(第6 行)。一旦採用灰度格式,就可以使用 OpenCV 的threshold函數(第7-8行)輕鬆摳圖該形狀。
原圖片以及隔離開的圖形狀
# 在閾值圖像中找到最大的輪廓
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
#不同opencv版本的不同
if len(cnts) == 2:
cnts = cnts[0]
elif len(cnts) == 3:
cnts = cnts[1]
c = max(cnts, key=cv2.contourArea)
#在輸出圖像上繪製輪廓的形狀,計算
邊界框,並顯示輪廓中的點數
output = image.copy()
cv2.drawContours(output, [c], -1, (0, 255, 0), 3)
(x, y, w, h) = cv2.boundingRect(c)
text = "original, num_pts={}".format(len(c))
cv2.putText(output, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX,
0.5, (0, 255, 0), 1)
print("[INFO] {}".format(text))
cv2.imshow("Original Contour", output)
cv2.waitKey(0)
使用 OpenCV 的findContours函數,我們可以挑選出給定圖像中所有可能的輪廓,我們使用了RETR_EXTERNAL參數,它只返回可用輪廓的單一表示,使用的另一個參數是CHAIN_APPROX_SIMPLE。這將刪除單個鏈線連接中的許多頂點,這些頂點本質上是冗餘的。然後我們從輪廓數組中抓取最大的輪廓,並使用drawContours函數把外框形狀畫出來。
外框輪廓
for eps in np.linspace(0.001, 0.05, 10):
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, eps * peri, True)
output = image.copy()
cv2.drawContours(output, [approx], -1, (0, 255, 0), 3)
text = "eps={:.4f}, num_pts={}".format(eps, len(approx))
cv2.putText(output, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX,
0.5, (0, 255, 0), 1)
print("[INFO] {}".format(text))
cv2.imshow("Approximated Contour", output)
cv2.waitKey(0)
我們需要一個 value eps,它將作為測量頂點的閾值,利用此閾值進行輪廓近似計算的參數
在第2行,使用 計算輪廓的周長cv2.arcLength函數。然後我們使用該cv2.approxPolyDP函數,啟動輪廓近似過程(第3行)。eps×peri乘積值充當近似精度,通過遍歷不同的eps,進行輪廓的近似計算。
通過運行代碼,我們可以看出,隨著eps值不斷增加,其輪廓頂點數不斷減少,直到頂點數量不再減少為止,這表明輪廓近似算法確實有效。
[INFO] original, num_pts=402
[INFO] eps=0.0010, num_pts=30
[INFO] eps=0.0064, num_pts=25
[INFO] eps=0.0119, num_pts=18
[INFO] eps=0.0173, num_pts=15
[INFO] eps=0.0228, num_pts=14
[INFO] eps=0.0282, num_pts=10
[INFO] eps=0.0337, num_pts=7
[INFO] eps=0.0391, num_pts=6
[INFO] eps=0.0446, num_pts=5
[INFO] eps=0.0500, num_pts=5
輪廓近似原理圖
當然,在應用中,我們需要找到合適的eps的值,以便在保證輪廓精度的情況下,降低計算的成本。