如何找到不同角度的两个同心轮廓之间的距离?

6 python opencv image-processing

我有一个带有两个轮廓的图像,其中一个轮廓总是在另一个“内部”。我想找到 90 个不同角度的两个轮廓之间的距离(意思是每 4 度的距离)。我该怎么做?

这是一个示例图像:

在此处输入图片说明

谢谢!

Ann*_*Zen 5

拍摄两组两个形状的图像:

在此输入图像描述

我们想要找到每组形状的边缘之间的距离,包括边缘重叠的位置。

  1. 首先,我们导入必要的模块:
import cv2
import numpy as np
Run Code Online (Sandbox Code Playgroud)
  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
Run Code Online (Sandbox Code Playgroud)

和参数将确定不会被遮盖到图像之外的最小 HVS 值和最大 HSV 值lowerupper给定权限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
Run Code Online (Sandbox Code Playgroud)

传递蒙版图像会给你

在此输入图像描述

  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]
Run Code Online (Sandbox Code Playgroud)

语句中的列表理解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]
Run Code Online (Sandbox Code Playgroud)

函数的名称非常不言自明;第一个返回轮廓的中心点,第二个返回给定点数组 中的一个点,该点相对于给定点 成给pts定角度。函数中的可以将起始角度 移动到​​ x 轴正方向,因为默认情况下它位于 y 轴正方向。angptnp.whereget_pt_at_angle0

  1. 是时候定义返回距离的函数了。首先定义一下,以便可以传入这五个参数:
def get_distances(img, cnt1, cnt2, center, step):
Run Code Online (Sandbox Code Playgroud)

对各个参数的简单解释:

  • img,图像数组
  • cnt1,第一个形状
  • cnt2,第二个形状
  • center,距离计算的原点
  • step,每个值要跳跃的度数
  1. 定义一个字典来存储距离,以角度为键,距离为值:
    angles = dict()
Run Code Online (Sandbox Code Playgroud)
  1. 循环遍历您想要检索两个形状的边缘距离的每个角度,并使用我们之前定义的函数找到两个轮廓的坐标,即迭代的 ct 角度 ,angle相对于原点 ,。centerget_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)
Run Code Online (Sandbox Code Playgroud)
  1. 检查两个轮廓中是否存在相对于原点呈特定角度的点:
        if np.any(pt1) and np.any(pt2):
Run Code Online (Sandbox Code Playgroud)
  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
Run Code Online (Sandbox Code Playgroud)
  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)
Run Code Online (Sandbox Code Playgroud)

使用四个形状的图像,您可以看出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
Run Code Online (Sandbox Code Playgroud)

根据我选择的值,我得到:

在此输入图像描述

在此输入图像描述

  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)
Run Code Online (Sandbox Code Playgroud)

请注意,我使用了30作为步骤;该数字可以更改为4,我使用30这样可视化会更清晰。

  1. 最后,我们可以显示图像:
cv2.imshow("Image", img)
cv2.waitKey(0)
Run Code Online (Sandbox Code Playgroud)

共:

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)
Run Code Online (Sandbox Code Playgroud)

输出:

{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}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

注意:对于某些形状,字典中可能缺少某些角度。那是由process函数引起的;如果您调低某些值(例如模糊西格玛),您会得到更准确的结果


tfv*_*tfv 3

在下面的代码中,我只是给出了垂直线的示例,其余的可以通过旋转线来获得。结果看起来像这样,您可以使用坐标来计算距离,而不是绘图。

在此输入图像描述

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()  
Run Code Online (Sandbox Code Playgroud)