英文:
Calculating paths around complicated shape
问题
我有一些关于不规则二维形状的数据集。这些数据集包含组成形状轮廓的无序点,但偶尔包含循环或点的岛屿。下面是图1中的示例。该示例的坐标可在https://pastebin.com/0cHxPnua上找到。
图1
目的: 我正在尝试计算这些轮廓周围的连续线,以测量它们的周长,最终测量周长上两点之间的距离。
问题: 在像图1中所示的复杂形状的情况下,循环区域和不连续区域可能会导致生成不合理的轮廓。我通过忽略任何远大于最近点之间平均距离的连接来处理岛屿,但我尚未弄清楚如何处理循环。
如果您看下面的图2,到目前为止,我的路径查找可以处理大多数形状,并成功忽略了岛屿点,但在选择绕过循环的初始路径后失败。它必须转到一个新的未链接点,因此它会回到循环交叉点附近之前错过的点,但接下来最近的未链接点距离超过了忽略岛屿点的距离阈值。
图2 蓝色起点,红色终点。注意:最后一个点始终连接回第一个点(红色)
目标: 我的目标要么改变我的路径查找方法,以便能够一致地处理这些复杂的循环,要么找到一种完全不同的路径查找方法,已知适用于此问题。
可以在下面的测试代码中尝试我的代码。您需要从pastebin复制形状的点并粘贴到points =
行中。
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import scipy.spatial
import sys
import os
import numpy as np
import pandas as pd
import re
from scipy.interpolate import CubicSpline
from scipy.signal import savgol_filter
from matplotlib.collections import LineCollection
# 其余的代码...
英文:
I have a several datasets for irregular 2D shapes. These datasets contain unordered points that make up the outline of shapes, but occasionally contain loops or islands of points. An example is shown below in fig 1. Coordinates for the shown shape are available at https://pastebin.com/0cHxPnua.
Figure 1
Purpose: I am trying to calculate a continuous line around these outlines in order to measure their perimeter, and eventually the distance between 2 points in that perimeter.
Problem: In the instances of complex shapes like the one shown in figure 1, the looped regions and disconnected regions can cause issues in generating a reasonable outline. I handle the islands by disregarding any connections that are much longer than the average distance between nearest point, but I haven't figured out what to do with the loops yet.
If you look below to fig 2, my pathfinding so far is able to handle most of the shape and successfully ignores island points, but fails after picking a bad initial path around the loop. It has to go to a new unlinked point, so it doubles back to a previously missed point near the intersection of the loop, but is then stuck as the next nearest unlinked points are beyond the distance threshold for ignoring island points.
Figure 2 Blue start, Red end. Note: The last point ALWAYS connects back to the first (red)
Goal: My goal here is either to chage my pathfinding method so that it can consistently handle complicated loops like these, or to find a wholly alternative method for pathfinding that is known to be applicable to this issue.
A test version of my code is available below to trial. You need to copy the shape's points from pastebin and paste them on the points =
line
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import scipy.spatial
import sys
import os
import numpy as np
import pandas as pd
import re
from scipy.interpolate import CubicSpline
from scipy.signal import savgol_filter
from matplotlib.collections import LineCollection
def calculate_path_length(path):
"""
Calculate the path length.
Parameters:
path (array): An array of 2D points in the path.
Returns:
float: The total length of the path.
"""
return np.sum(np.sqrt(np.sum(np.diff(path, axis=0)**2, axis=1)))
def get_shortest_path(full_path, start_point, end_point):
"""
Get the shortest path between start and end points in a circular path.
Parameters:
full_path (array): An array of 2D points in the circular path.
start_point (array): The start point.
end_point (array): The end point.
Returns:
array: The shortest path.
"""
# Get the indices of the start and end points in the full path
start_index = np.where((full_path == start_point).all(axis=1))[0][0]
end_index = np.where((full_path == end_point).all(axis=1))[0][0]
# Calculate the paths
if start_index < end_index:
direct_path = full_path[start_index:end_index + 1]
indirect_path = np.concatenate((full_path[end_index:], full_path[:start_index + 1]))
else:
direct_path = full_path[end_index:start_index + 1]
indirect_path = np.concatenate((full_path[:end_index + 1], full_path[start_index:]))
# Calculate the lengths of the paths
direct_path_length = calculate_path_length(direct_path)
indirect_path_length = calculate_path_length(indirect_path)
# Return the shortest path
print(direct_path_length, indirect_path_length)
if direct_path_length < indirect_path_length:
return direct_path
else:
return indirect_path
def GetPointDistance(p1, p2):
return np.sqrt( ((p2[0] - p1[0])**2) + ((p2[1] - p1[1])**2) )
def AverageDistanceWithBuffer(points, buffer_size=20):
"""
Calculate the average distance of a group of points and add buffer_size standard deviations to
add a large ceiling for distance variances. This should still be able to exclude extremely
large deviations that occur when outliers are present.
Parameters:
points (list): 2D list of 2xN points.
buffer_size (int): Padding multiplier to use for outlier rejection.
Returns:
float: The average distance with buffer.
"""
distances = []
for i, point in enumerate(points):
if i != len(points) - 1:
distances.append(GetPointDistance(point, points[i+1]))
return np.mean(distances) + buffer_size * np.std(distances)
def random_downsample(arr, percentage):
"""
Randomly downsample an array by the given percentage.
Parameters:
arr (array): The input array.
percentage (float): The percentage of data to retain.
Returns:
array: The downsampled array.
"""
if percentage <= 0 or percentage > 100:
raise ValueError("Percentage should be between 0 and 100 (inclusive).")
sample_size = int(len(arr) * percentage / 100)
random_indices = np.random.choice(len(arr), sample_size, replace=False)
sampled_arr = arr[random_indices]
return sampled_arr
def shortest_path(points, start_point, end_point):
"""
Find the shortest path between start and end points.
Parameters:
points (array): An array of 2D points.
start_point (array): The start point.
end_point (array): The end point.
Returns:
array: The full path.
array: The shortest path between the start and end points.
"""
points = points.tolist()
start_point = start_point.tolist()
end_point = end_point.tolist()
points = points + [start_point, end_point]
current_point = start_point
full_path = [current_point]
points.remove(current_point)
use_cutoff = False
while points:
if len(full_path) == 1000:
buffered_avg_dist = AverageDistanceWithBuffer(full_path)
use_cutoff = True
if len(points) == 1 and points[0] == end_point:
full_path.append(end_point)
points.remove(end_point)
else:
closest_point = min(points, key=lambda x: np.linalg.norm(np.array(current_point) - np.array(x)))
closest_point_dist = GetPointDistance(current_point, closest_point)
if use_cutoff:
if closest_point_dist < buffered_avg_dist:
full_path.append(closest_point)
current_point = closest_point
else:
full_path.append(closest_point)
current_point = closest_point
points.remove(closest_point)
# To form a closed loop for the full perimeter path
full_path.append(start_point)
# Shortest path from start to end
shortest_path = get_shortest_path(np.array(full_path), start_point, end_point)
plt.scatter(shortest_path[:, 0], shortest_path[:, 1], c=range(len(shortest_path)), cmap='inferno')
plt.close()
# Convert lists into numpy arrays
full_path = np.array(full_path)
shortest_path = np.array(shortest_path)
return full_path, shortest_path
points = # Copy points from https://pastebin.com/0cHxPnua
points = np.array(points)
# Returns a full path and the shortest path between the start and end points
full, shortest = shortest_path(points, points[48], points[int(len(points)/2)]) # Arbitrary Start and End points
# Plot the outline
plt.scatter(points[0, 0], points[0, 1], s=200, facecolor='red', zorder=1000) # Plot start point
plt.scatter(points[int(len(points)/2), 0], points[int(len(points)/2), 1], s=200, facecolor='green', zorder=1000) # Plot end point
plt.scatter(points[:, 0], points[:, 1]) # Plot all points
plt.plot(full[:, 0], full[:, 1], color='k') # Plot path line
plt.axis('equal')
plt.show()
答案1
得分: 1
以下是您的代码的翻译部分:
def get_raw_path(start_index, dist_matrix, dist, dist_threshold, points):
# 起始索引 - 从哪里开始我们的路径
# 距离矩阵 - 距离矩阵的排序索引
# 距离 - 距离矩阵
# 阈值 - 如果距离 > 阈值,则不使用这些线段
walked_points = [start_index]
path = [start_index]
segments = []
for x in range(dist_matrix.shape[0]):
# 获取路径中最后一个点的距离
candidates = dist_matrix[path[-1]]
for point in candidates:
# 如果...则获取最近的点
if point not in walked_points and dist[path[-1],point] < dist_threshold:
walked_points.append(point)
path.append(point)
segments.append([path[-2], path[-1]])
break
# 检查是否存在交叉点,并在存在交叉点时修复
while True:
is_intersect, si = find_segment_for_intersection(segments, points)
if is_intersect:
#print ("开始解开...")
old_chain = segments[si:]
#print("旧链: -->", segments[si:])
segments[si][1], segments[-1][0] = segments[-1][0], segments[si][1]
segments[si+1:-1] = [x[::-1] for x in segments[si+1:-1][::-1]]
#print("新链: -->", segments[si:])
path = [start_index]
for s in segments:
path.append(s[-1])
else:
break
path.append(start_index)
return path
# 这段代码不是我的
def ccw(A,B,C):
return (C[1]-A[1])*(B[0]-A[0]) > (B[1]-A[1])*(C[0]-A[0])
def intersect(A,B,C,D):
return ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D)
def find_segment_for_intersection(segments, points):
last_segment = segments[-1]
C, D = points[last_segment[0]], points[last_segment[1]]
for si, segment in enumerate(segments):
if not (set(segment) & set(last_segment)):
A, B = points[segment[0]], points[segment[1]]
if intersect(A, B, C, D):
return (True, si)
return (False, 0)
# 加载点
points = np.load("points", allow_pickle=True)
print("加载点...")
print("points.shape -->", points.shape)
# 获取唯一点
unique_points = np.unique(points, axis=0)
print("获取 {} 个唯一点".format(unique_points.shape[0]))
print("计算距离矩阵...")
x, y = unique_points[:,0][:,None], unique_points[:,1][:,None]
dist = np.sqrt(np.square(x - x.T) + np.square(y - y.T))
print("排序距离...")
dist_sort = np.argsort(dist)
print("获取路径近似...")
path = get_raw_path(0, dist_sort, dist, 10, unique_points)
print("路径长度 -->", len(path))
print("{} 个点被孤立".format(len(unique_points) - len(path)))
path_coords = unique_points[path]
print(path)
# 查找所有交叉点...
# 绘制轮廓
plt.figure(figsize=(15,6))
#plt.scatter(upoints[0, 0], upoints[0, 1], s=200, facecolor='red', zorder=1000) # 绘制起始点
#plt.scatter(upoints[int(len(upoints)/2), 0], upoints[int(len(upoints)/2), 1], s=200, facecolor='green', zorder=1000) # 绘制终点
plt.scatter(unique_points[:, 0], unique_points[:, 1], s=0.5) # 绘制所有点
plt.plot(path_coords[:, 0], path_coords[:, 1], color='k', linewidth=0.3) # 绘制路径线
plt.axis('equal')
plt.show()
<details>
<summary>英文:</summary>
You can repair loops after obtaining closed path by finding intersected segments and repairing it by unraveling. Below my code and final result for your test points.
def get_raw_path(start_index, dist_matrix, dist, dist_threshold, points):
# start index - where to start our path
# dist matrix - sorted indexes of distance matrix
# dist - distance matrix
# threshold - if distance > threshold don't use this lines
walked_points = [start_index]
path = [start_index]
segments = []
for x in range(dist_matrix.shape[0]):
# Get distances for last point in path
candidates = dist_matrix[path[-1]]
for point in candidates:
# Get the nearest point if ...
if point not in walked_points and dist[path[-1],point] < dist_threshold:
walked_points.append(point)
path.append(point)
segments.append([path[-2], path[-1]])
break
# Check for intersection and mend if intersection exists
while True:
is_intersect, si = find_segment_for_intersection(segments, points)
if is_intersect:
#print ("Start unraveling ...")
old_chain = segments[si:]
#print("Old chain: -->", segments[si:])
segments[si][1], segments[-1][0] = segments[-1][0], segments[si][1]
segments[si+1:-1] = [x[::-1] for x in segments[si+1:-1][::-1]]
#print("New chain: -->", segments[si:])
path = [start_index]
for s in segments:
path.append(s[-1])
else:
break
path.append(start_index)
return path
This code is not mine
def ccw(A,B,C):
return (C1-A1)(B[0]-A[0]) > (B1-A1)(C[0]-A[0])
def intersect(A,B,C,D):
return ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D)
def find_segment_for_intersection(segments, points):
last_segment = segments[-1]
C, D = points[last_segment[0]], points[last_segment1]
for si, segment in enumerate(segments):
if not (set(segment) & set(last_segment)):
A, B = points[segment[0]], points[segment1]
if intersect(A, B, C, D):
return (True, si)
return (False, 0)
Load Points
points = np.load("points", allow_pickle=True)
print("Loading points ...")
print("points.shape -->", points.shape)
Get unique points
unique_points = np.unique(points, axis=0)
print("Get {} unique points".format(unique_points.shape[0]))
print("Calculating distance matrix ...")
x, y = unique_points[:,0][:,None], unique_points[:,1][:,None]
dist = np.sqrt(np.square(x - x.T) + np.square(y - y.T))
print("Sorting distances ...")
dist_sort = np.argsort(dist)
print("Getting path approximation ...")
path = get_raw_path(0, dist_sort, dist, 10, unique_points)
print("Path length -->", len(path))
print("{} points are isolated".format(len(unique_points) - len(path)))
path_coords = unique_points[path]
print(path)
Find all intersection ...
Plot the outline
plt.figure(figsize=(15,6))
#plt.scatter(upoints[0, 0], upoints[0, 1], s=200, facecolor='red', zorder=1000) # Plot start point
#plt.scatter(upoints[int(len(upoints)/2), 0], upoints[int(len(upoints)/2), 1], s=200, facecolor='green', zorder=1000) # Plot end point
plt.scatter(unique_points[:, 0], unique_points[:, 1], s=0.5) # Plot all points
plt.plot(path_coords[:, 0], path_coords[:, 1], color='k', linewidth=0.3) # Plot path line
plt.axis('equal')
plt.show()
Result image:
[![][1]][1]
[1]: https://i.stack.imgur.com/G2C8H.png
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论