使用b样条出现一些错误的解决方法

当我们在用b—line(b样条平滑路径),会出现诸如下面这种错误:
File "/home/lwf/arena_ws/src/arena/evaluation/arena_evaluation/scripts/create_plots", line 407, in create_episode_plots_for_namespaces
tck, u = splprep(path.T, s=2, k=3) # 使用 splprep 函数对路径进行参数化插值
File "/home/lwf/miniconda3/envs/wzl_d86/lib/python3.8/site-packages/scipy/interpolate/fitpack.py", line 155, in splprep
res = _impl.splprep(x, w, u, ub, ue, k, task, s, t, full_output, nest, per,
File "/home/lwf/miniconda3/envs/wzl_d86/lib/python3.8/site-packages/scipy/interpolate/_fitpack_impl.py", line 280, in splprep
t, c, o = _fitpack._parcur(ravel(transpose(x)), w, u, ub, ue, k,
ValueError: Invalid inputs.
这段错误信息显示在尝试使用 SciPy 的 splprep 函数时遇到了问题,具体是 ValueError: Invalid inputs. 这个错误通常意味着输入数据有某种格式或类型上的问题,不满足 splprep 函数的要求。让我们来分解一下这段错误信息中涉及的代码和可能的问题:
在你的代码中,create_plots_from_declaration_file 函数最终调用了 create_episode_plots_for_namespaces 函数,在那里你使用了 splprep 来对路径进行参数化插值。splprep 函数需要一系列的输入参数,包括要拟合的数据点(通常是一个多维数组),以及一些可选参数如平滑因子 s 和样条阶数 k。错误发生在 splprep 函数内部,这表明传入的 path.T 数据可能有问题。这里 path.T 表示 path 数组的转置。
splprep 是一个强大的函数,用于构建参数化样条曲线,它要求输入的数据在某些方面满足特定条件,尤其是数据必须沿着参数化方向单调增加。当你不使用 np.unique 去处理路径数据时,可能会遇到几个问题导致 splprep 抛出错误:
重复点:路径数据可能包含重复的点,即相同的坐标位置多次出现。对于 splprep 来说,重复点可能导致参数化方向上出现非单调性,这违反了函数的要求,从而导致错误。
非单调性:即使没有重复点,路径也可能因为数据采集或处理过程中的噪声或异常而表现出局部的非单调性。这种非单调性同样会破坏 splprep 正常运行的条件,因为它依赖于数据在参数化方向上的单调性。
数据不连续:路径数据可能包含突然的跳跃或断点,这些不连续性可能源于数据采集过程中的问题。splprep 需要连续的数据流来构建平滑的样条,不连续的数据会导致计算失败。
1.splprep 函数用于计算B样条插值,它要求输入的数据点在参数化方向上是单调的。如果路径中有重复点,特别是在参数化方向上有重复的点,可能会导致 splprep 计算时出现问题,如无法正确建立参数化曲线。通过使用 np.unique 去除重复点,可以避免这类问题,确保插值算法的稳定性和正确性。
2.保持路径的平滑性和连贯性:
去除重复点有助于保持路径的平滑性,避免不必要的曲折。这对于路径规划和导航任务尤其重要,因为平滑的路径更容易跟踪,也更符合现实世界的物理约束。
但是使用了np.unique 去处理路径数据后,又出现了数据顺序混乱的问题,路径绘制出来非常离谱,如下图

重点来了!!!!:
简单地使用 np.unique 并返回索引(return_index=True)当然不够,因为你还需要保持路径点的顺序。np.unique 默认并不保证保留顺序,特别是当去除重复项后。
np.unique 默认并不会保持原始数据的顺序。在上述代码中,通过额外的排序步骤
sorted_path_list = sorted(unique_path_list, key=lambda x: unique_path_idx_list[unique_path_list.index(x)])
确保了即使在去重之后,路径点的顺序也被保留下来,这对于保持路径的连贯性至关重要。
以下是实现上述要求后的代码:
def create_episode_plots_for_namespaces(self, dataset, title, save_name, episode=0, differentiate="local_planner",
desired_results=[], should_add_obstacles=False,
should_add_collisions=False):
robots_tested = list(set(dataset[differentiate].to_list()))
plt.close()
fig, ax = self.create_map_plot()
for namespace in robots_tested:
print(namespace)
paths_for_namespace = dataset[dataset[differentiate] == namespace][["path", "result", "start", "goal"]]
start_pos = paths_for_namespace["start"][0]
path_amount = len(paths_for_namespace.index)
iterator = list(range(path_amount))
if episode != None:
iterator = [episode]
if namespace == 'rosnav_convex':
start_pos = start_pos[:2]
for i in iterator:
path = paths_for_namespace["path"][i]
result = paths_for_namespace["result"][i]
path = np.array(path)
path = path[:, :2] # 只关注二维坐标 (x, y)
path = np.concatenate((np.array([start_pos]), path), axis=0) # 在路径的开头加上一个初始点
unique_path, unique_path_idx = np.unique(path, return_index=True,
axis=0) # return_index=True: 这个参数告诉np.unique函数不仅要返回唯一元素,还要返回一个数组,该数组包含了在原始数组path中这些唯一元素最初出现的索引。
# unique_path: 这是np.unique函数返回的第一个输出,包含了path中的唯一行。也就是说,所有重复的行都被去除了,剩下的就是没有重复的点集合。
# unique_path_idx: 这是np.unique函数返回的第二个输出,只有当return_index=True时才会返回。
# 这个数组包含了unique_path中的每一行在原始数组path中的索引位置。这对于追踪哪些行被保留下来以及它们在原始数据中的位置非常有用。
unique_path_list = unique_path.tolist() # 将 unique_path(一个 numpy 数组)转换为 Python 的列表
unique_path_idx_list = unique_path_idx.tolist() # tolist() 是 numpy 数组的一个方法,它将数组转换为相同结构的 Python 列表
"""
sorted() 是 Python 的内置函数,用于对列表进行排序。
key 参数接受一个函数,用于决定排序依据。在这里,使用了一个 lambda 函数,它根据 unique_path_idx_list 中的索引来决定排序顺序。
unique_path_list.index(x) 返回 x 在 unique_path_list 中的位置,然后通过这个位置在 unique_path_idx_list 中查找原始的索引。
"""
sorted_path_list = sorted(unique_path_list, key=lambda x: unique_path_idx_list[unique_path_list.index(x)]) # 对去重后的路径点进行排序,确保它们按照原始路径中的顺序排列
sorted_path = np.array(sorted_path_list) # 将排序后的 Python 列表转换回 numpy 数组
num_points = len(sorted_path) # 计算原始路径中唯一点的数量 num_points
num_interpolated_points = num_points * 100 # 插值后路径上的点数
t = np.linspace(0, 1, num_interpolated_points) # 长度等于插值后路径上点的数量
tck, u = splprep(sorted_path.T, s=2, k=3) # 使用 splprep 函数对路径进行参数化插值
x_spl, y_spl = splev(t, tck) # 使用 splev 函数根据 tck 参数和 new_t 对路径进行插值,得到新的 x 和 y 坐标的值
ax.plot(x_spl, y_spl, label=namespace) # 绘制插值后的路径,并给它添加一个标签 namespace
"""
sorted_path.T: 这里sorted_path应该是一个二维数组,其中每一行代表一个点,而每一列表示一个维度(比如x坐标和y坐标)。T操作符是对数组进行转置,所以原本每行的点现在变为每列的点。
这样做的原因是splprep函数期望接收一个形状为(n, d)的数组,其中n是数据点的数量,d是空间的维度。
s=2: 这个参数是平滑因子。在splprep中,s参数控制着拟合的平滑程度。一个较小的s值会使插值更加贴近原始数据点,可能导致过拟合;而较大的s值会产生更平滑的插值曲线,但可能会偏离数据点较远。
这里设置s=2意味着适度的平滑效果。
k=3: 这个参数定义了B样条的阶数。阶数k决定了样条曲线的复杂度,一般情况下,k等于想要的样条曲线的次数减一。例如,k=3表示的是三次样条插值。较高的阶数可以拟合更复杂的曲线,但可能会引入不必要的波动。
tck: 这是splprep函数返回的一个元组,包含了用于计算插值样条所需的所有信息。通常,tck包括一个结点向量(t)、样条系数(c)和样条的阶数(k),因此有时也被称为“tck元组”。
u: 这是一个与sorted_path中每个点相对应的一维数组,表示了沿着样条曲线的参数化位置。这个参数化通常不是等距的,而是基于数据点在样条曲线上的分布。
"""
print(f"{namespace}已平滑")
else:
for i in iterator:
path = paths_for_namespace["path"][i]
result = paths_for_namespace["result"][i]
path = np.concatenate((np.array([start_pos]), path), axis=0)
ax.plot(path[:, 0], path[:, 1], label=namespace)
self.add_obstacles_to_plot(ax, should_add_obstacles)
self.add_start_and_goal_to_plot(ax, paths_for_namespace["start"][0], paths_for_namespace["goal"][0])
plot(title, save_name)
最后成果图如下:

更多推荐



所有评论(0)