我正在尝试提出一种算法来确定x / y坐标轨迹中的转折点。下图说明了我的意思:绿色表示起点,红色表示轨迹的最终点(整个轨迹由~1500点组成):
在下图中,我手动添加了算法可能返回的可能(全局)转折点:
显然,真正的转折点总是有争议的,并且取决于一个人指定必须位于点之间的角度。此外,转折点可以在全球范围内定义(我试图用黑色圆圈做的),但也可以在高分辨率的局部尺度上定义。我对全球(整体)方向变化感兴趣,但我很乐意看到人们用来梳理全局与本地解决方案的不同方法的讨论。
到目前为止我尝试过:
不幸的是,这并没有给我任何强有力的结果。我可能也计算了多个点的曲率,但这只是一个想法。 我真的很感激任何可能对我有帮助的算法/想法。代码可以是任何编程语言,首选matlab或python。
编辑这里是原始数据(如果有人想要使用它):
答案 0 :(得分:23)
您可以使用Ramer-Douglas-Peucker (RDP) algorithm来简化路径。然后,您可以计算沿简化路径的每个段的方向变化。对应于方向最大变化的点可称为转折点:
可以找到RDP算法的Python实现on github。
import matplotlib.pyplot as plt
import numpy as np
import os
import rdp
def angle(dir):
"""
Returns the angles between vectors.
Parameters:
dir is a 2D-array of shape (N,M) representing N vectors in M-dimensional space.
The return value is a 1D-array of values of shape (N-1,), with each value
between 0 and pi.
0 implies the vectors point in the same direction
pi/2 implies the vectors are orthogonal
pi implies the vectors point in opposite directions
"""
dir2 = dir[1:]
dir1 = dir[:-1]
return np.arccos((dir1*dir2).sum(axis=1)/(
np.sqrt((dir1**2).sum(axis=1)*(dir2**2).sum(axis=1))))
tolerance = 70
min_angle = np.pi*0.22
filename = os.path.expanduser('~/tmp/bla.data')
points = np.genfromtxt(filename).T
print(len(points))
x, y = points.T
# Use the Ramer-Douglas-Peucker algorithm to simplify the path
# http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
# Python implementation: https://github.com/sebleier/RDP/
simplified = np.array(rdp.rdp(points.tolist(), tolerance))
print(len(simplified))
sx, sy = simplified.T
# compute the direction vectors on the simplified curve
directions = np.diff(simplified, axis=0)
theta = angle(directions)
# Select the index of the points with the greatest theta
# Large theta is associated with greatest change in direction.
idx = np.where(theta>min_angle)[0]+1
fig = plt.figure()
ax =fig.add_subplot(111)
ax.plot(x, y, 'b-', label='original path')
ax.plot(sx, sy, 'g--', label='simplified path')
ax.plot(sx[idx], sy[idx], 'ro', markersize = 10, label='turning points')
ax.invert_yaxis()
plt.legend(loc='best')
plt.show()
上面使用了两个参数:
tolerance
,即tolerance
表示简化路径的最大距离
可以偏离原来的道路。 min_angle
越大,简化路径越粗。min_angle
,它定义了什么被视为转折点。 (我正在转向原点路径上的任何一点,简化路径上的输入和退出矢量之间的角度大于{{1}})。答案 1 :(得分:8)
我将在下面给出numpy / scipy代码,因为我几乎没有Matlab经验。
如果曲线足够平滑,您可以将转折点标识为最高curvature。将点索引号作为曲线参数和central differences scheme,您可以使用以下代码计算曲率
import numpy as np
import matplotlib.pyplot as plt
import scipy.ndimage
def first_derivative(x) :
return x[2:] - x[0:-2]
def second_derivative(x) :
return x[2:] - 2 * x[1:-1] + x[:-2]
def curvature(x, y) :
x_1 = first_derivative(x)
x_2 = second_derivative(x)
y_1 = first_derivative(y)
y_2 = second_derivative(y)
return np.abs(x_1 * y_2 - y_1 * x_2) / np.sqrt((x_1**2 + y_1**2)**3)
您可能希望先将曲线平滑,然后计算曲率,然后确定最高曲率点。以下功能就是这样:
def plot_turning_points(x, y, turning_points=10, smoothing_radius=3,
cluster_radius=10) :
if smoothing_radius :
weights = np.ones(2 * smoothing_radius + 1)
new_x = scipy.ndimage.convolve1d(x, weights, mode='constant', cval=0.0)
new_x = new_x[smoothing_radius:-smoothing_radius] / np.sum(weights)
new_y = scipy.ndimage.convolve1d(y, weights, mode='constant', cval=0.0)
new_y = new_y[smoothing_radius:-smoothing_radius] / np.sum(weights)
else :
new_x, new_y = x, y
k = curvature(new_x, new_y)
turn_point_idx = np.argsort(k)[::-1]
t_points = []
while len(t_points) < turning_points and len(turn_point_idx) > 0:
t_points += [turn_point_idx[0]]
idx = np.abs(turn_point_idx - turn_point_idx[0]) > cluster_radius
turn_point_idx = turn_point_idx[idx]
t_points = np.array(t_points)
t_points += smoothing_radius + 1
plt.plot(x,y, 'k-')
plt.plot(new_x, new_y, 'r-')
plt.plot(x[t_points], y[t_points], 'o')
plt.show()
有些解释是有道理的:
turning_points
是您要识别的点数smoothing_radius
是在计算曲率之前应用于数据的平滑卷积的半径cluster_radius
是选择作为转折点的高曲率点的距离,其中不应将其他点视为候选。您可能需要稍微调整一下这些参数,但我得到的结果如下:
>>> x, y = np.genfromtxt('bla.data')
>>> plot_turning_points(x, y, turning_points=20, smoothing_radius=15,
... cluster_radius=75)
可能不足以进行全自动检测,但它非常接近你想要的。
答案 2 :(得分:4)
一个非常有趣的问题。这是我的解决方案,允许可变分辨率。虽然,微调可能并不简单,因为它主要是为了缩小范围
每k个点,计算凸包并将其存储为一组。通过最多k点并移除任何不在凸包中的点,使点不会失去原始顺序。
这里的目的是凸包将充当过滤器,去除所有“不重要的点”,只留下极端点。当然,如果k值太高,你最终会得到一些太接近实际凸壳的东西,而不是你真正想要的东西。
这应该从小k开始,至少4,然后增加它直到你得到你想要的。你也应该只包括角度低于一定数量的每3个点的中点,d。这将确保所有转弯至少为d度(未在下面的代码中实现)。但是,这可能应该逐步完成,以避免信息丢失,就像增加k值一样。另一个可能的改进是实际重新运行已被移除的点,并且只移除不在两个凸包中的点,但这需要更高的最小k值至少为8。
以下代码似乎运行良好,但仍可以使用改进效率和噪音消除。它在确定何时应该停止时也相当不优雅,因此代码实际上只能从k = 4到k = 14左右工作(如图所示)。
def convex_filter(points,k):
new_points = []
for pts in (points[i:i + k] for i in xrange(0, len(points), k)):
hull = set(convex_hull(pts))
for point in pts:
if point in hull:
new_points.append(point)
return new_points
# How the points are obtained is a minor point, but they need to be in the right order.
x_coords = [float(x) for x in x.split()]
y_coords = [float(y) for y in y.split()]
points = zip(x_coords,y_coords)
k = 10
prev_length = 0
new_points = points
# Filter using the convex hull until no more points are removed
while len(new_points) != prev_length:
prev_length = len(new_points)
new_points = convex_filter(new_points,k)
以下是k = 14的上述代码的屏幕截图。 61个红点是过滤器后留下的红点。
答案 3 :(得分:1)
您采用的方法听起来很有希望,但您的数据严重过采样。您可以先对x和y坐标进行过滤,例如使用宽高斯,然后进行下采样。</ p>
在MATLAB中,您可以使用x = conv(x, normpdf(-10 : 10, 0, 5))
,然后使用x = x(1 : 5 : end)
。您将不得不调整这些数字,具体取决于您跟踪的对象的内在持久性以及点之间的平均距离。
然后,您将能够使用您之前尝试的相同方法非常可靠地检测方向变化,基于我想象的标量积。
答案 4 :(得分:0)
另一个想法是在每个点检查左右环境。这可以通过在每个点之前和之后创建N个点的线性回归来完成。如果点之间的交叉角度低于某个阈值,则表示您有一个角。
这可以通过保持当前在线性回归中的点的队列并用新点替换旧点来有效地完成,类似于运行平均值。
您最终必须将相邻的角合并到一个角落。例如。选择具有最强角落属性的点。