簡體   English   中英

如何找到不同角度的兩個同心輪廓之間的距離?

[英]How to find the distance between two concentric contours, for different angles?

我有一個帶有兩個輪廓的圖像,其中一個輪廓總是在另一個“內部”。 我想找到 90 個不同角度的兩個輪廓之間的距離(意思是每 4 度的距離)。 我該怎么做?

這是一個示例圖像:

在此處輸入圖片說明

謝謝!

在下面的代碼中,我剛剛給出了垂直線的示例,其余的可以通過旋轉線獲得。 結果看起來像這樣,您可以使用坐標來計算距離,而不是繪圖。

在此處輸入圖片說明

import shapely.geometry as shapgeo
import numpy as np
import cv2


img = cv2.imread('image.jpg', 0)
ret, img =cv2.threshold(img, 128, 255, cv2.THRESH_BINARY)

#Fit the ellipses
_, contours0, hierarchy = cv2.findContours( img.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
outer_ellipse = [cv2.approxPolyDP(contours0[0], 0.1, True)]
inner_ellipse = [cv2.approxPolyDP(contours0[2], 0.1, True)]

h, w = img.shape[:2]
vis = np.zeros((h, w, 3), np.uint8)
cv2.drawContours( vis, outer_ellipse, -1, (255,0,0), 1)
cv2.drawContours( vis, inner_ellipse, -1, (0,0,255), 1)

##Extract contour of ellipses
cnt_outer = np.vstack(outer_ellipse).squeeze()
cnt_inner = np.vstack(inner_ellipse).squeeze()

#Determine centroid
M = cv2.moments(cnt_inner)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
print cx, cy

#Draw full segment lines 
cv2.line(vis,(cx,0),(cx,w),(150,0,0),1)

# Calculate intersections using Shapely
# http://toblerity.org/shapely/manual.html
PolygonEllipse_outer= shapgeo.asLineString(cnt_outer)
PolygonEllipse_inner= shapgeo.asLineString(cnt_inner)
PolygonVerticalLine=shapgeo.LineString([(cx,0),(cx,w)])


insecouter= np.array(PolygonEllipse_outer.intersection(PolygonVerticalLine)).astype(np.int)
insecinner= np.array(PolygonEllipse_inner.intersection(PolygonVerticalLine)).astype(np.int)
cv2.line(vis,(insecouter[0,0], insecinner[1,1]),(insecouter[1,0], insecouter[1,1]),(0,255,0),2)
cv2.line(vis,(insecouter[0,0], insecinner[0,1]),(insecouter[1,0], insecouter[0,1]),(0,255,0),2)

cv2.imshow('contours', vis)

0xFF & cv2.waitKey()
cv2.destroyAllWindows()  

以兩組兩個形狀的圖像為例:

在此處輸入圖片說明

我們想要找到每組形狀的邊緣之間的距離,包括邊緣重疊的位置。

  1. 首先,我們導入必要的模塊:
import cv2
import numpy as np
  1. 為此,我們首先需要檢索圖像中的每個形狀作為輪廓列表。 在上面的特定示例中,需要檢測 4 個形狀。 要檢索每個形狀,我們需要使用遮罩來遮蔽除感興趣形狀的顏色之外的所有顏色:
def get_masked(img, lower, upper):
    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(img_hsv, np.array(lower), np.array(upper))
    img_mask = cv2.bitwise_and(img, img, mask=mask)
    return img_mask

lower參數和upper參數將確定不會被圖像屏蔽的最小 HVS 值和最大 HSV 值。 有了正確的lowerupper的參數,你就能夠提取只與綠色形狀一個圖像,而只用藍色的形狀,一個形象:

在此處輸入圖片說明

  1. 使用蒙版圖像,您可以繼續將它們處理成更干凈的輪廓。 這是preprocess函數,其值可以在必要時進行調整:
def get_processed(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_blur = cv2.GaussianBlur(img_gray, (7, 7), 7)
    img_canny = cv2.Canny(img_blur, 50, 50)
    kernel = np.ones((7, 7))
    img_dilate = cv2.dilate(img_canny, kernel, iterations=2)
    img_erode = cv2.erode(img_dilate, kernel, iterations=2)
    return img_erode

傳入蒙版圖像會給你

在此處輸入圖片說明

  1. 圖像被屏蔽和處理后,它們將准備好讓 opencv 檢測它們的輪廓:
def get_contours(img):
    contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    return [cnt for cnt in contours if cv2.contourArea(cnt) > 500]

return語句中的列表推導式通過指定每個輪廓必須具有大於 500 的面積來過濾噪音。

  1. 現在,我們將定義一些稍后將使用的基本函數:
def get_centeroid(cnt):
    length = len(cnt)
    sum_x = np.sum(cnt[..., 0])
    sum_y = np.sum(cnt[..., 1])
    return int(sum_x / length), int(sum_y / length)

def get_pt_at_angle(pts, pt, ang):
    angles = np.rad2deg(np.arctan2(*(pt - pts).T))
    angles = np.where(angles < -90, angles + 450, angles + 90)
    found= np.rint(angles) == ang
    if np.any(found):
        return pts[found][0]

函數的名稱一目了然; 第一個返回輪廓的中心點,第二個返回給定點數組pts中的一個pts ,即相對於給定點pt的給定角度ang np.whereget_pt_at_angle功能是有移位的起始角度, 0默認,向正X軸,因為這將是在正y軸。

  1. 是時候定義返回距離的函數了。 首先定義它,以便可以傳入這五個參數:
def get_distances(img, cnt1, cnt2, center, step):

每個參數的簡要說明:

  • img ,圖像數組
  • cnt1 ,第一個形狀
  • cnt2 ,第二個形狀
  • center ,距離計算的原點
  • step ,每個值要跳躍的度數
  1. 定義一個字典來存儲距離,以角度為鍵,以距離為值:
    angles = dict()
  1. 循環遍歷每個要檢索兩個形狀邊緣距離的angle ,並使用get_pt_at_angle函數找到兩個輪廓的坐標,即迭代的 ct 角, angle ,相對於原點, center ,我們使用get_pt_at_angle函數之前定義的。
    for angle in range(0, 360, step):
        pt1 = get_pt_at_angle(cnt1, center, angle)
        pt2 = get_pt_at_angle(cnt2, center, angle)
  1. 檢查兩個輪廓中是否存在與原點成特定角度的點:
        if np.any(pt1) and np.any(pt2):
  1. 您可以使用np.linalg.norm方法來獲取兩點之間的距離。 我還讓它繪制了用於可視化的文本和連接線。 不要忘記將角度和值添加到angles字典中,然后您就可以跳出內部for循環。 在函數結束時,返回上面繪制了文本和線條的圖像:
            d = round(np.linalg.norm(pt1 - pt2))
            cv2.putText(img, str(d), tuple(pt1), cv2.FONT_HERSHEY_PLAIN, 0.8, (0, 0, 0))
            cv2.drawContours(img, np.array([[center, pt1]]), -1, (255, 0, 255), 1)
            angles[angle] = d

    return img, angles
  1. 最后,您可以利用在圖像上定義的函數:
img = cv2.imread("shapes1.png")

img_green = get_masked(img, [10, 0, 0], [70, 255, 255])
img_blue = get_masked(img, [70, 0, 0], [179, 255, 255])

img_green_processed = get_processed(img_green)
img_blue_processed = get_processed(img_blue)

img_green_contours = get_contours(img_green_processed)
img_blue_contours = get_contours(img_blue_processed)

使用四個形狀的圖像,您可以看出img_green_contoursimg_blue_contours將分別包含兩個輪廓。 但您可能想知道:我是如何選擇最小和最大 HSV 值的? 好吧,我使用了軌跡條代碼。 您可以運行以下代碼,使用軌跡欄調整 HSV 值,直到找到一個范圍,其中除了要檢索的形狀外,圖像中的所有內容都被屏蔽(黑色):

import cv2
import numpy as np

def empty(a):
    pass
    
cv2.namedWindow("TrackBars")
cv2.createTrackbar("Hue Min", "TrackBars", 0, 179, empty)
cv2.createTrackbar("Hue Max", "TrackBars", 179, 179, empty)
cv2.createTrackbar("Sat Min", "TrackBars", 0, 255, empty)
cv2.createTrackbar("Sat Max", "TrackBars", 255, 255, empty)
cv2.createTrackbar("Val Min", "TrackBars", 0, 255, empty)
cv2.createTrackbar("Val Max", "TrackBars", 255, 255, empty)

img = cv2.imread("shapes0.png")

while True:
    h_min = cv2.getTrackbarPos("Hue Min", "TrackBars")
    h_max = cv2.getTrackbarPos("Hue Max", "TrackBars")
    s_min = cv2.getTrackbarPos("Sat Min", "TrackBars")
    s_max = cv2.getTrackbarPos("Sat Max", "TrackBars")
    v_min = cv2.getTrackbarPos("Val Min", "TrackBars")
    v_max = cv2.getTrackbarPos("Val Max", "TrackBars")
    
    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    lower = np.array([h_min, s_min, v_min])
    upper = np.array([h_max, s_max, v_max])
    
    mask = cv2.inRange(img_hsv, lower, upper)
    img_masked = cv2.bitwise_and(img, img, mask=mask)

    cv2.imshow("Image", img_masked)
    if cv2.waitKey(1) & 0xFF == ord("q"): # If you press the q key
        break

使用我選擇的值,我得到了:

在此處輸入圖片說明

在此處輸入圖片說明

  1. 並行循環遍歷藍色形狀輪廓和綠色形狀輪廓,根據您希望原點位於哪個顏色形狀的中心,您可以將該顏色輪廓傳遞到我們之前定義的get_centeroid函數中:
for cnt_blue, cnt_green in zip(img_blue_contours, img_green_contours[::-1]):
    center = get_centeroid(cnt_blue)
    img, angles = get_distances(img, cnt_green.squeeze(), cnt_blue.squeeze(), center, 30)
    print(angles)

請注意,我使用30作為步長; 該數字可以更改為4 ,我使用了30以便可視化更清晰。

  1. 最后,我們可以顯示圖像:
cv2.imshow("Image", img)
cv2.waitKey(0)

總而言之:

import cv2
import numpy as np

def get_masked(img, lower, upper):
    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(img_hsv, np.array(lower), np.array(upper))
    img_mask = cv2.bitwise_and(img, img, mask=mask)
    return img_mask

def get_processed(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_blur = cv2.GaussianBlur(img_gray, (7, 7), 7)
    img_canny = cv2.Canny(img_blur, 50, 50)
    kernel = np.ones((7, 7))
    img_dilate = cv2.dilate(img_canny, kernel, iterations=2)
    img_erode = cv2.erode(img_dilate, kernel, iterations=2)
    return img_erode

def get_contours(img):
    contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    return [cnt for cnt in contours if cv2.contourArea(cnt) > 500]

def get_centeroid(cnt):
    length = len(cnt)
    sum_x = np.sum(cnt[..., 0])
    sum_y = np.sum(cnt[..., 1])
    return int(sum_x / length), int(sum_y / length)

def get_pt_at_angle(pts, pt, ang):
    angles = np.rad2deg(np.arctan2(*(pt - pts).T))
    angles = np.where(angles < -90, angles + 450, angles + 90)
    found= np.rint(angles) == ang
    if np.any(found):
        return pts[found][0]
        
def get_distances(img, cnt1, cnt2, center, step):
    angles = dict()
    for angle in range(0, 360, step):
        pt1 = get_pt_at_angle(cnt1, center, angle)
        pt2 = get_pt_at_angle(cnt2, center, angle)
        if np.any(pt1) and np.any(pt2):
            d = round(np.linalg.norm(pt1 - pt2))
            cv2.putText(img, str(d), tuple(pt1), cv2.FONT_HERSHEY_PLAIN, 0.8, (0, 0, 0))
            cv2.drawContours(img, np.array([[center, pt1]]), -1, (255, 0, 255), 1)
            angles[angle] = d
            
    return img, angles

img = cv2.imread("shapes1.png")

img_green = get_masked(img, [10, 0, 0], [70, 255, 255])
img_blue = get_masked(img, [70, 0, 0], [179, 255, 255])

img_green_processed = get_processed(img_green)
img_blue_processed = get_processed(img_blue)

img_green_contours = get_contours(img_green_processed)
img_blue_contours = get_contours(img_blue_processed)

for cnt_blue, cnt_green in zip(img_blue_contours, img_green_contours[::-1]):
    center = get_centeroid(cnt_blue)
    img, angles = get_distances(img, cnt_green.squeeze(), cnt_blue.squeeze(), center, 30)
    print(angles)

cv2.imshow("Image", img)
cv2.waitKey(0)

輸出:

{0: 5, 30: 4, 60: 29, 90: 25, 120: 31, 150: 8, 180: 5, 210: 7, 240: 14, 270: 12, 300: 14, 330: 21}
{0: 10, 30: 9, 60: 6, 90: 0, 120: 11, 150: 7, 180: 5, 210: 6, 240: 6, 270: 4, 300: 0, 330: 16}

在此處輸入圖片說明

注意:對於某些形狀,字典中可能缺少某些角度。 那將是由process功能引起的; 如果您調低某些值,例如模糊西格瑪,您將獲得更准確的結果

我從tfv's answer 中借用了使用Shapely的一般思想和基本代碼。 然而,迭代所需的角度、計算與形狀相交的正確線所需的端點、計算和存儲距離等都沒有,所以我添加了所有這些。

那將是我的完整代碼:

import cv2
import numpy as np
import shapely.geometry as shapgeo

# Read image, and binarize
img = cv2.imread('G48xu.jpg', cv2.IMREAD_GRAYSCALE)
img = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY)[1]

# Find (approximated) contours of inner and outer shape
cnts, hier = cv2.findContours(img.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
outer = [cv2.approxPolyDP(cnts[0], 0.1, True)]
inner = [cv2.approxPolyDP(cnts[2], 0.1, True)]

# Just for visualization purposes: Draw contours of inner and outer shape
h, w = img.shape[:2]
vis = np.zeros((h, w, 3), np.uint8)
cv2.drawContours(vis, outer, -1, (255, 0, 0), 1)
cv2.drawContours(vis, inner, -1, (0, 0, 255), 1)

# Squeeze contours for further processing
outer = np.vstack(outer).squeeze()
inner = np.vstack(inner).squeeze()

# Calculate centroid of inner contour
M = cv2.moments(inner)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])

# Calculate maximum needed radius for later line intersections
r_max = np.min([cx, w - cx, cy, h - cy])

# Set up angles (in degrees)
angles = np.arange(0, 360, 4)

# Initialize distances
dists = np.zeros_like(angles)

# Prepare calculating the intersections using Shapely
poly_outer = shapgeo.asLineString(outer)
poly_inner = shapgeo.asLineString(inner)

# Iterate angles and calculate distances between inner and outer shape
for i, angle in enumerate(angles):

    # Convert angle from degrees to radians
    angle = angle / 180 * np.pi

    # Calculate end points of line from centroid in angle's direction
    x = np.cos(angle) * r_max + cx
    y = np.sin(angle) * r_max + cy
    points = [(cx, cy), (x, y)]

    # Calculate intersections using Shapely
    poly_line = shapgeo.LineString(points)
    insec_outer = np.array(poly_outer.intersection(poly_line))
    insec_inner = np.array(poly_inner.intersection(poly_line))

    # Calculate distance between intersections using L2 norm
    dists[i] = np.linalg.norm(insec_outer - insec_inner)

    # Just for visualization purposes: Draw lines for some examples
    if (i == 10) or (i == 40) or (i == 75):

        # Line from centroid to end points
        cv2.line(vis, (cx, cy), (int(x), int(y)), (128, 128, 128), 1)

        # Line between both shapes
        cv2.line(vis,
                 (int(insec_inner[0]), int(insec_inner[1])),
                 (int(insec_outer[0]), int(insec_outer[1])), (0, 255, 0), 2)

        # Distance
        cv2.putText(vis, str(dists[i]), (int(x), int(y)),
                    cv2.FONT_HERSHEY_COMPLEX, 0.75, (0, 255, 0), 2)

# Output angles and distances
print(np.vstack([angles, dists]).T)

# Just for visualization purposes: Output image
cv2.imshow('Output', vis)
cv2.waitKey(0)
cv2.destroyAllWindows()

我為可視化目的生成了一些示例輸出:

輸出

而且,這是輸出的摘錄,顯示了角度和相應的距離:

[[  0  70]
 [  4  71]
 [  8  73]
 [ 12  76]
 [ 16  77]
 ...
 [340  56]
 [344  59]
 [348  62]
 [352  65]
 [356  67]]

希望代碼是不言自明的。 如果沒有,請不要猶豫,提出問題。 我很樂意提供進一步的信息。

----------------------------------------
System information
----------------------------------------
Platform:      Windows-10-10.0.16299-SP0
Python:        3.9.1
NumPy:         1.20.2
OpenCV:        4.5.1
Shapely:       1.7.1
----------------------------------------

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM