软硬约束下的轨迹生成:理论与代码详解

本项目代码:

github.com/liangwq/robot_motion_planing

轨迹约束中的软硬约束

前面的几篇文章已经介绍了,轨迹约束的本质就是在做带约束的轨迹拟合。输入就是waypoint点list,约束条件有两种硬约束和软约束。所谓硬约束对应到数学形式就是代价函数,硬约束对应的就是最优化秋季的约束条件部分。对应到物理意义就是,为了获得机器人可行走的安全的轨迹有:

把轨迹通过代价函数推离障碍物的方式给出障碍物之间的可行走凸包走廊,通过硬约束让机器人轨迹必须在凸包走廊行走

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

软硬约束下的轨迹如何生成,理论&代码详解!

软硬约束下的轨迹如何生成,理论&代码详解!

上图展示的是软硬约束下Bezier曲线拟合的求解的数学框架,以及如何把各种的约束条件转成数学求解的代价函数(软约束)或者是求解的约束条件(软约束)。image.png

软硬约束下的轨迹如何生成,理论&代码详解!

上面是对常用的代价函数约束的几种表示方式的举例。image.png

Bezier曲线拟合轨迹

前面已经一篇文章介绍过贝赛尔曲线拟合的各种优点:

端点插值。贝塞尔曲线始终从第一个控制点开始,结束于最后一个控制点,并且不会经过任何其他控制点。凸包。贝塞尔曲线 ( ) 由一组控制点 完全限制在由所有这些控制点定义的凸包内。速度曲线。贝塞尔曲线 ( ) 的导数曲线 ′( ) 被称为速度曲线,它也是一个由控制点定义的贝塞尔曲线,其中控制点为 ∙ ( +1− ),其中 是阶数。固定时间间隔。贝塞尔曲线始终在 [0,1] 上定义。

软硬约束下的轨迹如何生成,理论&代码详解!

Fig1.一段轨迹用bezier曲线拟合image.png

软硬约束下的轨迹如何生成,理论&代码详解!

上面的两个表达式对应的代码实现如下:

def bernstein_poly(n, i, t):"""Bernstein polynom.:param n: (int) polynom degree:param i: (int):param t: (float):return: (float)"""return scipy.special.comb(n, i) * t ** i * (1 - t) ** (n - i)def bezier(t, control_points):"""Return one point on the bezier curve.:param t: (float) number in [0, 1]:param control_points: (numpy array):return: (numpy array) Coordinates of the point"""n = len(control_points) - 1return np.sum([bernstein_poly(n, i, t) * control_points[i] for i in range(n + 1)], axis=0)

要用Bezier曲线来表示一段曲线,上面已经给出了表达式和代码实现,现在缺的就是给定控制点,把控制点带进来用Bezier曲线表达式来算出给定终点和结束点中需要画的点坐标。下面代码给出了4个控制点、6个控制点的Bezier曲线实现;其中为了画曲线需要算170个线上点。代码如下:

def calc_4points_bezier_path(sx, sy, syaw, ex, ey, eyaw, offset):"""Compute control points and path given start and end position.:param sx: (float) x-coordinate of the starting point:param sy: (float) y-coordinate of the starting point:param syaw: (float) yaw angle at start:param ex: (float) x-coordinate of the ending point:param ey: (float) y-coordinate of the ending point:param eyaw: (float) yaw angle at the end:param offset: (float):return: (numpy array, numpy array)"""dist = np.hypot(sx - ex, sy - ey) / offsetcontrol_points = np.array([[sx, sy], [sx + dist * np.cos(syaw), sy + dist * np.sin(syaw)], [ex - dist * np.cos(eyaw), ey - dist * np.sin(eyaw)], [ex, ey]])path = calc_bezier_path(control_points, n_points=170)return path, control_pointsdef calc_6points_bezier_path(sx, sy, syaw, ex, ey, eyaw, offset):"""Compute control points and path given start and end position.:param sx: (float) x-coordinate of the starting point:param sy: (float) y-coordinate of the starting point:param syaw: (float) yaw angle at start:param ex: (float) x-coordinate of the ending point:param ey: (float) y-coordinate of the ending point:param eyaw: (float) yaw angle at the end:param offset: (float):return: (numpy array, numpy array)"""dist = np.hypot(sx - ex, sy - ey) * offsetcontrol_points = np.array([[sx, sy], [sx + 0.25 * dist * np.cos(syaw), sy + 0.25 * dist * np.sin(syaw)], [sx + 0.40 * dist * np.cos(syaw), sy + 0.40 * dist * np.sin(syaw)], [ex - 0.40 * dist * np.cos(eyaw), ey - 0.40 * dist * np.sin(eyaw)], [ex - 0.25 * dist * np.cos(eyaw), ey - 0.25 * dist * np.sin(eyaw)], [ex, ey]])path = calc_bezier_path(control_points, n_points=170)return path, control_pointsdef calc_bezier_path(control_points, n_points=100):"""Compute bezier path (trajectory) given control points.:param control_points: (numpy array):param n_points: (int) number of points in the trajectory:return: (numpy array)"""traj = []for t in np.linspace(0, 1, n_points):traj.append(bezier(t, control_points))return np.array(traj)

有了一段Bezier曲线的拟合方式,接下来要做的就是如何生成多段Bezier曲线合成一条轨迹;并且是需要可以通过代价函数方式(软约束)+必须经过指定点+制定点衔接要连续(硬约束)来生成一条光滑的轨迹曲线。

软硬约束下的轨迹如何生成,理论&代码详解!

Fig2.无障碍物,带bondary轨迹做了smooth优化Figure_1.png

多段Bezier曲线生成代码如下,其实原理很简单,给定多个waypoint点,每相邻两个wayponit生成一段Bezizer曲线,代码如下:

# Bezier path one as per the approach suggested in# https://users.soe.ucsc.edu/~elkaim/Documents/camera_WCECS2008_IEEE_ICIAR_58.pdfdef cubic_bezier_path(self, ax, ay):dyaw, _ = self.calc_yaw_curvature(ax, ay)cx = []cy = []ayaw = dyaw.copy()for n in range(1, len(ax)-1):yaw = 0.5*(dyaw[n] + dyaw[n-1])ayaw[n] = yawlast_ax = ax[0]last_ay = ay[0]last_ayaw = ayaw[0]# for n waypoints, there are n-1 bezier curvesfor i in range(len(ax)-1):path, ctr_points = calc_4points_bezier_path(last_ax, last_ay, ayaw[i], ax[i+1], ay[i+1], ayaw[i+1], 2.0)cx = np.concatenate((cx, path.T[0][:-2]))cy = np.concatenate((cy, path.T[1][:-2]))cyaw, k = self.calc_yaw_curvature(cx, cy)last_ax = path.T[0][-1]last_ay = path.T[1][-1]return cx, cy

代价函数计算包括:曲率代价 + 偏差代价 + 距离代价 + 连续性代价,同时还有边界条件,轨迹必须在tube内的不等式约束,以及问题优化求解。具体代码实现如下:

# Objective function of cost to be minimizeddef cubic_objective_func(self, deviation):ax = self.waypoints.x.copy()ay = self.waypoints.y.copy()for n in range(0, len(deviation)):ax[n+1] -= deviation[n]*np.sin(self.waypoints.yaw[n+1])ay[n+1] += deviation[n]*np.cos(self.waypoints.yaw[n+1])bx, by = self.cubic_bezier_path(ax, ay)yaw, k = self.calc_yaw_curvature(bx, by)# cost of curvature continuityt = np.zeros((len(k)))dk = self.calc_d(t, k)absolute_dk = np.absolute(dk)continuity_cost = 10.0 * np.mean(absolute_dk)# curvature costabsolute_k = np.absolute(k)curvature_cost = 14.0 * np.mean(absolute_k)# cost of deviation from input waypointsabsolute_dev = np.absolute(deviation)deviation_cost = 1.0 * np.mean(absolute_dev)distance_cost = 0.5 * self.calc_path_dist(bx, by)return curvature_cost + deviation_cost + distance_cost + continuity_cost# Minimize objective function using scipy optimize minimizedef optimize_min_cubic(self):print("Attempting optimization minima")initial_guess = [0, 0, 0, 0, 0]bnds = ((-self.bound, self.bound), (-self.bound, self.bound), (-self.bound, self.bound), (-self.bound, self.bound), (-self.bound, self.bound))result = optimize.minimize(self.cubic_objective_func, initial_guess, bounds=bnds)ax = self.waypoints.x.copy()ay = self.waypoints.y.copy()if result.success:print("optimized true")deviation = result.xfor n in range(0, len(deviation)):ax[n+1] -= deviation[n]*np.sin(self.waypoints.yaw[n+1])ay[n+1] += deviation[n]*np.cos(self.waypoints.yaw[n+1])x, y = self.cubic_bezier_path(ax, ay)yaw, k = self.calc_yaw_curvature(x, y)self.optimized_path = Path(x, y, yaw, k)else:print("optimization failure, defaulting")exit()

带障碍物的Bezier曲线轨迹生

软硬约束下的轨迹如何生成,理论&代码详解!

带有障碍物的场景,通过代价函数让生成的曲线远离障碍物。从而得到一条可以安全行走的轨迹,下面是具体的代码实现。optimizer_k中lambda函数f就是在求解轨迹在经过障碍物附近时候的代价,penalty1、penalty2就是在求曲线经过障碍物附近的具体代价值;
b.arc_len(granuality=10)+B.arc_len(granuality=10)+m_k + penalty1 + penalty2就是轨迹的整体代价。for循环部分用scipy的optimize的minimize来求解轨迹。

def optimizer_k(cd, k, path, i, obs, curve_penalty_multiplier, curve_penalty_divider, curve_penalty_obst):"""Bezier curve optimizer that optimizes the curvature and path length by changing the distance of p1 and p2 from points p0 and p3, respectively. """p_tmp = copy.deepcopy(path)if i+3 > len(path)-1:b = CubicBezier()b.p0 = p_tmp[i]x, y = calc_p1(p_tmp[i], p_tmp[i + 1], p_tmp[i - 1], i, cd[0])b.p1 = Point(x, y)x, y = calc_p2(p_tmp[i-1], p_tmp[i + 0], p_tmp[i + 1], i, cd[1])b.p2 = Point(x, y)b.p3 = p_tmp[i + 1]B = CubicBezier()else:b = CubicBezier()b.p0 = p_tmp[i]x, y = calc_p1(p_tmp[i],p_tmp[i+1],p_tmp[i-1], i, cd[0])b.p1 = Point(x, y)x, y = calc_p2(p_tmp[i],p_tmp[i+1],p_tmp[i+2], i, cd[1])b.p2 = Point(x, y)b.p3 = p_tmp[i + 1]B = CubicBezier()B.p0 = p_tmp[i]x, y = calc_p1(p_tmp[i+1], p_tmp[i + 2], p_tmp[i], i, 10)B.p1 = Point(x, y)x, y = calc_p2(p_tmp[i+1], p_tmp[i + 2], p_tmp[i + 3], i, 10)B.p2 = Point(x, y)B.p3 = p_tmp[i + 1]m_k = b.max_k()if m_k>k:m_k= m_k*curve_penalty_multiplierelse:m_k = m_k/curve_penalty_dividerf = lambda x, y: max(math.sqrt((x[0] - y[0].x) ** 2 + (x[1] - y[0].y) ** 2) * curve_penalty_obst, 10) if math.sqrt((x[0] - y[0].x) ** 2 + (x[1] - y[0].y) ** 2) < y[1] else 0b_t = b.calc_curve(granuality=10)b_t = zip(b_t[0],b_t[1])B_t = B.calc_curve(granuality=10)B_t = zip(B_t[0], B_t[1])penalty1 = 0penalty2 = 0for o in obs:for t in b_t:penalty1 = max(penalty1,f(t,o))for t in B_t:penalty2 = max(penalty2,f(t,o))return b.arc_len(granuality=10)+B.arc_len(granuality=10)+m_k + penalty1 + penalty2# Optimize the initial path for n_path_opt cyclesfor m in range(n_path_opt):if m%2:for i in range(1,len(path)-1):x0 = [0.0, 0.0]bounds = Bounds([-1, -1], [1, 1])res = minimize(optimizer_p, x0, args=(path, i, obs, path_penalty), method='TNC', tol=1e-7, bounds=bounds)x, y = res.xpath[i].x += xpath[i].y += yelse:for i in range(len(path)-1,1):x0 = [0.0, 0.0]bounds = Bounds([-1, -1], [1, 1])res = minimize(optimizer_p, x0, args=(path, i, obs, path_penalty), method='TNC', tol=1e-7, bounds=bounds)x, y = res.xpath[i].x += xpath[i].y += y

带飞行走廊的Bezier轨迹生成

得益于贝赛尔曲线拟合的优势,如果我们可以让机器人可行走的轨迹转成多个有重叠区域的凸多面体,那么轨迹完全位于飞行走廊内。

代码小浣熊 代码小浣熊

代码小浣熊是基于商汤大语言模型的软件智能研发助手,覆盖软件需求分析、架构设计、代码编写、软件测试等环节

代码小浣熊 51 查看详情 代码小浣熊

软硬约束下的轨迹如何生成,理论&代码详解!

image.png

飞行走廊由凸多边形组成。每个立方体对应于一段贝塞尔曲线。此曲线的控制点被强制限制在多边形内部。轨迹完全位于所有点的凸包内。

如何通过把障碍物地图生成可行凸包走廊

生成凸包走廊的方法目前有以下三大类的方法:

平行凸簇膨胀方法

从栅格地图出发,利用最小凸集生成算法,完成凸多面体的生成。其算法的思想是首先获得一个凸集,再沿着凸集的表面进行扩张,扩张之后再进行凸集检测,判断新扩张的集合是否保持为凸。一直扩张到不能再扩张为止,再提取凸集的边缘点,利用快速凸集生成算法,生成凸多面体。该算法的好处在于可以利用这种扩张的思路,将安全的多面体的体积尽可能的充满整个空间,因此获得的安全通道更大。但其也具有一定的缺点,就是计算量比较大,计算所需要的时间比较长,为了解决这个问题,在该文章中,又提出了采用GPU加速的方法,来加速计算。

基于凸分解的安全通道生成

基于凸分解的安全通道生成方法由四个步骤完成安全通道的生成,分别为:找到椭球、找到多面体、边界框、收缩。

半定规划的迭代区域膨胀

为了获取多面体,这个方法首先构造一个初始椭球,由一个以选定点为中心的单位球组成。然后,遍历障碍物,为每个障碍物生成一个超平面,该超平面与障碍物相切并将其与椭球分开。再次,这些超平面定义了一组线性约束,它们的交集是一个多面体。然后,可以在那个多面体中找到一个最大的椭球,使用这个椭球来定义一组新的分离超平面,从而定义一个新的多面体。选择生成分离超平面的方法,这样椭圆体的体积在迭代之间永远不会减少。可以重复这个过程,直到椭圆体的增长率低于某个阈值,此时我们返回多面体和内接椭圆体。这个方法具有迭代的思想,并且具有收敛判断的标准,算法的收敛快慢和初始椭球具有很大的关系。

软硬约束下的轨迹如何生成,理论&代码详解!

Fig3.半定规划的迭代区域膨胀。每一行即为一次迭代操作,直到椭圆体的增长率低于阈值。image.png

这篇文章介绍的是“半定规划的迭代区域膨胀”方法,具体代码实现如下:

# 根据输入路径对空间进行凸分解def decomp(self, line_points: list[np.array], obs_points: list[np.array], visualize=True):# 最终结果decomp_polygons = list()# 构建输入障碍物点的kdtreeobs_kdtree = KDTree(obs_points)# 进行空间分解for i in range(len(line_points) - 1):# 得到当前线段pf, pr = line_points[i], line_points[i + 1]print(pf)print(pr)# 构建初始多面体init_polygon = self.initPolygon(pf, pr)print(init_polygon.getInterPoints())print(init_polygon.getVerticals())# 过滤障碍物点candidate_obs_point_indexes = obs_kdtree.query_ball_point((pf + pr) / 2, np.linalg.norm([np.linalg.norm(pr - pf) / 2 + self.consider_range_, self.consider_range_]))local_obs_points = list()for index in candidate_obs_point_indexes:if init_polygon.inside(obs_points[index]):local_obs_points.append(obs_points[index])# 得到初始椭圆ellipse = self.findEllipse(pf, pr, local_obs_points)# 根据初始椭圆构建多面体polygon = self.findPolygon(ellipse, init_polygon, local_obs_points)# 进行保存decomp_polygons.append(polygon)if visualize:# 进行可视化plt.figure()# 绘制路径段plt.plot([pf[1], pr[1]], [pf[0], pr[0]], color="red")# 绘制初始多面体verticals = init_polygon.getVerticals()# 绘制多面体顶点plt.plot([v[1] for v in verticals] + [verticals[0][1]], [v[0] for v in verticals] + [verticals[0][0]], color="blue", linestyle="--")# 绘制障碍物点plt.scatter([p[1] for p in local_obs_points], [p[0] for p in local_obs_points], marker="o")# 绘制椭圆ellipse_x, ellipse_y = list(), list()for theta in np.linspace(-np.pi, np.pi, 1000):raw_point = np.array([np.cos(theta), np.sin(theta)])ellipse_point = np.dot(ellipse.C_, raw_point) + ellipse.d_ellipse_x.append(ellipse_point[0])ellipse_y.append(ellipse_point[1])plt.plot(ellipse_y, ellipse_x, color="orange")# 绘制最终多面体# 得到多面体顶点verticals = polygon.getVerticals()# 绘制多面体顶点plt.plot([v[1] for v in verticals] + [verticals[0][1]], [v[0] for v in verticals] + [verticals[0][0]], color="green")plt.show()return decomp_polygons# 构建初始多面体def initPolygon(self, pf: np.array, pr: np.array) -> Polygon:# 记录多面体的平面polygon_planes = list()# 得到线段方向向量dire = self.normalize(pr - pf)# 得到线段法向量dire_h = np.array([dire[1], -dire[0]])# 得到平行范围p_1 = pf + self.consider_range_ * dire_hp_2 = pf - self.consider_range_ * dire_hpolygon_planes.append(Hyperplane(dire_h, p_1))polygon_planes.append(Hyperplane(-dire_h, p_2))# 得到垂直范围p_3 = pr + self.consider_range_ * direp_4 = pf - self.consider_range_ * direpolygon_planes.append(Hyperplane(dire, p_3))polygon_planes.append(Hyperplane(-dire, p_4))# 构建多面体polygon = Polygon(polygon_planes)return polygon# 得到初始椭圆def findEllipse(self, pf: np.array, pr: np.array, obs_points: list[np.array]) -> Ellipse:# 计算长轴long_axis_value = np.linalg.norm(pr - pf) / 2axes = np.array([long_axis_value, long_axis_value])# 计算旋转rotation = self.vec2Rotation(pr - pf)# 计算初始椭圆C = np.dot(rotation, np.dot(np.array([[axes[0], 0], [0, axes[1]]]), np.transpose(rotation)))d = (pr + pf) / 2ellipse = Ellipse(C, d)# 得到椭圆内的障碍物点inside_obs_points = ellipse.insidePoints(obs_points)# 对椭圆进行调整,使得全部障碍物点都在椭圆外while inside_obs_points:# 得到与椭圆距离最近的点closest_obs_point = ellipse.closestPoint(inside_obs_points)# 将最近点转到椭圆坐标系下closest_obs_point = np.dot(np.transpose(rotation), closest_obs_point - ellipse.d_) # 根据最近点,在椭圆长轴不变的情况下对短轴进行改变,使得,障碍物点在椭圆上if Compare.small(closest_obs_point[0], axes[0]):axes[1] = np.abs(closest_obs_point[1]) / np.sqrt(1 - (closest_obs_point[0] / axes[0]) ** 2)# 更新椭圆ellipse.C_ = np.dot(rotation, np.dot(np.array([[axes[0], 0], [0, axes[1]]]), np.transpose(rotation)))# 更新椭圆内部障碍物inside_obs_points = ellipse.insidePoints(inside_obs_points, include_bound=False)return ellipse# 进行多面体的构建def findPolygon(self, ellipse: Ellipse, init_polygon: Polygon, obs_points: list[np.array]) -> Polygon:# 多面体由多个超平面构成polygon_planes = copy.deepcopy(init_polygon.hyper_planes_)# 初始化范围超平面remain_obs_points = obs_pointswhile remain_obs_points:# 得到与椭圆最近障碍物closest_point = ellipse.closestPoint(remain_obs_points)# 计算该处的切平面的法向量norm_vector = np.dot(np.linalg.inv(ellipse.C_), np.dot(np.linalg.inv(ellipse.C_), (closest_point - ellipse.d_)))norm_vector = self.normalize(norm_vector)# 构建平面hyper_plane = Hyperplane(norm_vector, closest_point)# 保存到多面体平面中polygon_planes.append(hyper_plane)# 去除切平面外部的障碍物new_remain_obs_points = list()for point in remain_obs_points:if Compare.small(hyper_plane.signDist(point), 0):new_remain_obs_points.append(point)remain_obs_points = new_remain_obs_pointspolygon = Polygon(polygon_planes)return polygon

软硬约束下的轨迹如何生成,理论&代码详解!

上面图是给定16个障碍物点,必经6个路径点后得到的凸包可行走廊,具体代码如下:

def main():# 路径点line_points = [np.array([-1.5, 0.0]), np.array([0.0, 0.8]), np.array([1.5, 0.3]), np.array([5, 0.6]), np.array([6, 1.2]), np.array([7.6, 2.2])]# 障碍物点obs_points = [np.array([4, 2.0]),np.array([6, 3.0]),np.array([2, 1.5]),np.array([0, 1]),np.array([1, 0]),np.array([1.8, 0]),np.array([3.8, 2]),np.array([0.5, 1.2]),np.array([4.3, 0]),np.array([8, 0.9]),np.array([2.8, -0.3]),np.array([6, -0.9]),np.array([-0.5, -0.5]),np.array([-0.75 ,-0.5]),np.array([-1, -0.5]),np.array([-1, 0.8])]convex_decomp = ConvexDecomp(2)decomp_polygons = convex_decomp.decomp(line_points, obs_points, False)#convex_decomp.decomp(line_points, obs_points,False)plt.figure()# 绘制障碍物点plt.scatter([p[0] for p in obs_points], [p[1] for p in obs_points], marker="o")# 绘制边界for polygon in decomp_polygons:verticals = polygon.getVerticals()# 绘制多面体顶点plt.plot([v[0] for v in verticals] + [verticals[0][0]], [v[1] for v in verticals] + [verticals[0][1]], color="green")#plt.plot(x_samples, y_samples)plt.show()

带凸包走廊求解

带凸包走廊的光滑轨迹生成。前面已经求解得到了可行的凸包走廊,这部分可以做为硬约束作为最优化求解的不等式条件。要求的光滑路径和必须经过点的点,这部分可以把必须经过点作为等式约束,光滑路径可以通过代价函数来实现。这样就可以把带软硬约束的轨迹生成框架各种技能点都用上了。

软硬约束下的轨迹如何生成,理论&代码详解!

下面看具体代码实现:

# 进行优化def optimize(self, start_state: np.array, end_state: np.array, line_points: list[np.array], polygons: list[Polygon]):assert(len(line_points) == len(polygons) + 1)# 得到分段数量segment_num = len(polygons)assert(segment_num >= 1)# 计算初始时间分配time_allocations = list()for i in range(segment_num):time_allocations.append(np.linalg.norm(line_points[i+1] - line_points[i]) / self.vel_max_)# 进行优化迭代max_inter = 10cur_iter = 0while cur_iter < max_inter:# 进行轨迹优化piece_wise_trajectory = self.optimizeIter(start_state, end_state, polygons, time_allocations, segment_num)# 对优化轨迹进行时间调整,以保证轨迹满足运动上限约束cur_iter += 1# 计算每一段轨迹的最大速度,最大加速度,最大jerkcondition_fit = Truefor n in range(segment_num):# 得到最大速度,最大加速度,最大jerkt_samples = np.linspace(0, time_allocations[n], 100)v_max, a_max, j_max = self.vel_max_, self.acc_max_, self.jerk_max_for t_sample in t_samples:v_max = max(v_max, np.abs(piece_wise_trajectory.trajectory_segments_[n][0].derivative(t_sample)), np.abs(piece_wise_trajectory.trajectory_segments_[n][1].derivative(t_sample)))a_max = max(a_max, np.abs(piece_wise_trajectory.trajectory_segments_[n][0].secondOrderDerivative(t_sample)), np.abs(piece_wise_trajectory.trajectory_segments_[n][1].secondOrderDerivative(t_sample)))j_max = max(j_max, np.abs(piece_wise_trajectory.trajectory_segments_[n][0].thirdOrderDerivative(t_sample)), np.abs(piece_wise_trajectory.trajectory_segments_[n][1].thirdOrderDerivative(t_sample)))# 判断是否满足约束条件if Compare.large(v_max, self.vel_max_) or Compare.large(a_max, self.acc_max_) or Compare.large(j_max, self.jerk_max_):ratio = max(1, v_max / self.vel_max_, (a_max / self.acc_max_)**0.5, (j_max / self.jerk_max_)**(1/3))time_allocations[n] = ratio * time_allocations[n]condition_fit = Falseif condition_fit:breakreturn piece_wise_trajectory# 优化迭代def optimizeIter(self, start_state: np.array, end_state: np.array, polygons: list[Polygon], time_allocations: list, segment_num):# 构建目标函数 inter (jerk)^2inte_jerk_square = np.array([[720.0, -1800.0, 1200.0, 0.0, 0.0, -120.0],[-1800.0, 4800.0, -3600.0, 0.0, 600.0, 0.0],[1200.0, -3600.0, 3600.0, -1200.0, 0.0, 0.0],[0.0, 0.0, -1200.0, 3600.0, -3600.0, 1200.0],[0.0, 600.0, 0.0, -3600.0, 4800.0, -1800.0],[-120.0, 0.0, 0.0, 1200.0, -1800.0, 720.0]])# 二次项系数P = np.zeros((self.dim_ * segment_num * self.freedom_, self.dim_ * segment_num * self.freedom_))for sigma in range(self.dim_):for n in range(segment_num):for i in range(self.freedom_):for j in range(self.freedom_):index_i = sigma * segment_num * self.freedom_ + n * self.freedom_ + iindex_j = sigma * segment_num * self.freedom_ + n * self.freedom_ + jP[index_i][index_j] = inte_jerk_square[i][j] / (time_allocations[n] ** 5)P = P * 2P = sparse.csc_matrix(P)# 一次项系数q = np.zeros((self.dim_ * segment_num * self.freedom_,))# 构建约束条件equality_constraints_num = 5 * self.dim_ + 3 * (segment_num - 1) * self.dim_inequality_constraints_num = 0for polygon in polygons:inequality_constraints_num += self.freedom_ * len(polygon.hyper_planes_)A = np.zeros((equality_constraints_num + inequality_constraints_num, self.dim_ * segment_num * self.freedom_))lb = -float("inf") * np.ones((equality_constraints_num + inequality_constraints_num,))ub = float("inf") * np.ones((equality_constraints_num + inequality_constraints_num,))# 构建等式约束条件(起点位置、速度、加速度;终点位置、速度;连接处的零、一、二阶导数)# 起点x位置A[0][0] = 1lb[0] = start_state[0]ub[0] = start_state[0]# 起点y位置A[1][segment_num * self.freedom_] = 1lb[1] = start_state[1]ub[1] = start_state[1]# 起点x速度A[2][0] = -5 / time_allocations[0]A[2][1] = 5 / time_allocations[0]lb[2] = start_state[2]ub[2] = start_state[2]# 起点y速度A[3][segment_num * self.freedom_] = -5 / time_allocations[0]A[3][segment_num * self.freedom_ + 1] = 5 / time_allocations[0]lb[3] = start_state[3]ub[3] = start_state[3]# 起点x加速度A[4][0] = 20 / time_allocations[0]**2A[4][1] = -40 / time_allocations[0]**2A[4][2] = 20 / time_allocations[0]**2lb[4] = start_state[4]ub[4] = start_state[4]# 起点y加速度A[5][segment_num * self.freedom_] = 20 / time_allocations[0]**2A[5][segment_num * self.freedom_ + 1] = -40 / time_allocations[0]**2A[5][segment_num * self.freedom_ + 2] = 20 / time_allocations[0]**2lb[5] = start_state[5]ub[5] = start_state[5]# 终点x位置A[6][segment_num * self.freedom_ - 1] = 1lb[6] = end_state[0]ub[6] = end_state[0]# 终点y位置A[7][self.dim_ * segment_num * self.freedom_ - 1] = 1lb[7] = end_state[1]ub[7] = end_state[1]# 终点x速度A[8][segment_num * self.freedom_ - 1] = 5 / time_allocations[-1]A[8][segment_num * self.freedom_ - 2] = -5 / time_allocations[-1]lb[8] = end_state[2]ub[8] = end_state[2]# 终点y速度A[9][self.dim_ * segment_num * self.freedom_ - 1] = 5 / time_allocations[-1]A[9][self.dim_ * segment_num * self.freedom_ - 2] = -5 / time_allocations[-1]lb[9] = end_state[3]ub[9] = end_state[3]# 连接处的零阶导数相等constraints_index = 10for sigma in range(self.dim_):for n in range(segment_num - 1):A[constraints_index][sigma * segment_num * self.freedom_ + n * self.freedom_ + self.freedom_ - 1] = 1A[constraints_index][sigma * segment_num * self.freedom_ + (n+1) * self.freedom_] = -1lb[constraints_index] = 0ub[constraints_index] = 0constraints_index += 1# 连接处的一阶导数相等for sigma in range(self.dim_):for n in range(segment_num - 1):A[constraints_index][sigma * segment_num * self.freedom_ + n * self.freedom_ + self.freedom_ - 1] = 5 / time_allocations[n]A[constraints_index][sigma * segment_num * self.freedom_ + n * self.freedom_ + self.freedom_ - 2] = -5 / time_allocations[n]A[constraints_index][sigma * segment_num * self.freedom_ + (n+1) * self.freedom_] = 5 / time_allocations[n + 1]A[constraints_index][sigma * segment_num * self.freedom_ + (n+1) * self.freedom_ + 1] = -5 / time_allocations[n + 1]lb[constraints_index] = 0ub[constraints_index] = 0constraints_index += 1# 连接处的二阶导数相等for sigma in range(self.dim_):for n in range(segment_num - 1):A[constraints_index][sigma * segment_num * self.freedom_ + n * self.freedom_ + self.freedom_ - 1] = 20 / time_allocations[n]**2A[constraints_index][sigma * segment_num * self.freedom_ + n * self.freedom_ + self.freedom_ - 2] = -40 / time_allocations[n]**2A[constraints_index][sigma * segment_num * self.freedom_ + n * self.freedom_ + self.freedom_ - 3] = 20 / time_allocations[n]**2A[constraints_index][sigma * segment_num * self.freedom_ + (n+1) * self.freedom_] = -20 / time_allocations[n + 1]**2A[constraints_index][sigma * segment_num * self.freedom_ + (n+1) * self.freedom_ + 1] = 40 / time_allocations[n + 1]**2A[constraints_index][sigma * segment_num * self.freedom_ + (n+1) * self.freedom_ + 2] = -20 / time_allocations[n + 1]**2lb[constraints_index] = 0ub[constraints_index] = 0constraints_index += 1# 构建不等式约束条件for n in range(segment_num):for k in range(self.freedom_):for hyper_plane in polygons[n].hyper_planes_:A[constraints_index][n * self.freedom_ + k] = hyper_plane.n_[0]A[constraints_index][segment_num * self.freedom_ + n * self.freedom_ + k] = hyper_plane.n_[1]ub[constraints_index] = np.dot(hyper_plane.n_, hyper_plane.d_)constraints_index += 1assert(constraints_index == equality_constraints_num + inequality_constraints_num)A = sparse.csc_matrix(A)# 进行qp求解prob = osqp.OSQP()prob.setup(P, q, A, lb, ub, warm_start=True)res = prob.solve()if res.info.status != "solved":raise ValueError("OSQP did not solve the problem!")# 根据参数进行轨迹解析trajectory_x_params, trajectory_y_params = list(), list()for n in range(segment_num):trajectory_x_params.append(res.x[self.freedom_ * n: self.freedom_ * (n+1)])trajectory_y_params.append(res.x[segment_num * self.freedom_ + self.freedom_ * n: segment_num * self.freedom_ + self.freedom_ * (n+1)])piece_wise_trajectory = PieceWiseTrajectory(trajectory_x_params, trajectory_y_params, time_allocations)return piece_wise_trajectory

小结:

这篇文章介绍了带软硬约束的轨迹优化算法框架。第一部份介绍了软硬约束对应到最优求解问题数学上如何表示。第二部份介绍了贝赛尔曲线的代码实现,给出了具体的代码实现和讲解;并针对没有障碍物场景只给定waypoint点,生成光滑的Bezier轨迹的朴素求解代码实现。第三部份给出了带障碍物情况下如何做最优化求解,如何通过代价函数的方式来给轨迹施加推力让轨迹远离障碍物的代码实现。第四部分是一个综合性的例子,把软硬约束最优轨迹生成的求解框架做了一个综合呈现。详细的介绍了如何利用障碍物地图生成最大可行区域的凸包走廊,如何利用Bezier曲线的特性给定凸包两点生成路径一定在凸包中;以及如何利用代价行数来保证轨迹的光滑性、安全性,通过等式、不等式约束实现轨迹必须经过哪些点,某个点的运动状态如何。
这一系列的文章已经进入结尾的阶段,后面会简单介绍在碰到移动的物体时候单机器人如何处理;以及在多个机器人运行环境如何协同,最后会给出一个Motion Planning的综合实现例子讲解实际环境数据输入、前端规划、后端轨迹生成。至于定位和感知部分的内容后面可以根据情况而定是否在开一个新的系列来讲解介绍,对于更前沿的技术点会跟进论文做些文章分享。
最后本系列文章的代码在以下git链接,这部分代码相对零碎主要是配合文章理论来讲的,里面很多片段直接来源于网络整合。后面这可项目会持续维护,把项目代码(应该是c++实现,更体系)、整合进来,根据需要在看看有没必要整合出一个库。

软硬约束下的轨迹如何生成,理论&代码详解!

原文链接:https://mp.weixin.qq.com/s/0EVgYKTxLzUj64L5jzMVug

以上就是软硬约束下的轨迹生成:理论与代码详解的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/441198.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月7日 18:42:15
下一篇 2025年11月7日 18:44:01

相关推荐

  • Microsoft Teams如何设置访客权限 Microsoft Teams外部协作的安全管理

    首先登录Microsoft 365管理中心启用Teams访客访问功能,接着在Azure AD中配置目录范围与信息可见性限制,最后通过敏感度标签、审核日志、DLP策略及文件共享设置实施沟通与内容安全管控,实现外部协作的安全管理。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 D…

    2025年12月6日 科技
    000
  • Linux下安装SQLServer2019的方法

    可以直接参考官方文档:https://www.php.cn/link/32824c14387bff0a269b11c976c1d0d0 安装SQL Server 首先,下载 SQL Server 2019 (15.x) 的 Red Hat 存储库配置文件: sudo curl -o /etc/yum…

    2025年12月4日
    000
  • Linux实现自动挂载autofs的方法详解

    ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ 目录 实现自动挂载-autofs autofs工具简单使用 autofs配置详细说明 自动挂载资源有两种格式 优化Linux系统性能 安装Tuned 选择调整配置文件 检查系统推荐的调整配置文件…

    2025年12月4日
    000
  • 苹果发布 Safari 技术预览版 223:聚焦稳定性与性能优化

    近日,苹果公司推出了 safari 技术预览版的最新版本 223,该版本属于其专为开发者和早期用户打造的实验性浏览器更新。本次更新的重点在于修复已知问题并提升性能表现,目的是为未来正式版 safari 浏览器打下更坚实的基础。 自 2016 年首次发布以来,Safari 技术预览版一直是开发者测试 …

    2025年12月2日
    000
  • 构建AI智能体:决策树的核心机制(一):刨根问底鸢尾花分类中的参数推理计算

    ​一、初识决策树 想象一个生活中的场景,我们去水果店买一个西瓜,该怎么判断一个西瓜是不是又甜又好的呢?我们可能会问自己一系列问题: 首先看看它的纹路清晰吗?如果“是”,那么它可能是个好瓜。如果“否“,那我们可能会问下一个问题:敲起来声音清脆吗? 如果“是”,那么它可能还是个不错的瓜。如果“否“,那我…

    2025年12月2日 科技
    000
  • DeepSeek怎样用代码解释器绘函数图_DeepSeek用代码解释器绘函数图【函数绘图】

    首先确保导入numpy和matplotlib库,然后定义函数表达式并生成x、y坐标点,接着调用plt.plot()绘制曲线并添加标签和网格,最后使用plt.show()显示图像或plt.savefig()保存为文件。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSe…

    2025年12月2日 科技
    000
  • 无需电池即可实现「自动驾驶」,华盛顿大学开发出无限续航的机器人

    不装电池,也能%ignore_a_1%的“车”出现了。 甚至还会自动收集能量持续运行,完全没有里程焦虑(手动狗头)。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ 不错,这么一个小机器人,其实靠的是光和无线电波供能。其名MilliMobil…

    2025年12月2日 科技
    000
  • ICCV 2023揭晓:ControlNet、SAM等热门论文斩获奖项

    在法国巴黎举行了国际计算机视觉大会ICCV(International Conference on Computer Vision)本周开幕 作为全球计算机视觉领域顶级的学术会议,ICCV 每两年召开一次。 ICCV的热度一直以来都与CVPR不相上下,屡创新高 在今天的开幕式上,ICCV官方公布了今…

    2025年12月2日 科技
    000
  • BEV下的Radar-Camera 融合跨数据集实验研究

    原标题:cross-dataset experimental study of radar-camera fusion in bird’s-eye view论文链接:https://arxiv.org/pdf/2309.15465.pdf作者单位:opel automobile gmbh rhein…

    2025年12月2日 科技
    000
  • 遥遥领先!BEVHeight++:针对路侧视觉3D目标检测新方案!

    回归到地面的高度,以实现距离不可知的公式,从而简化仅相机感知方法的优化过程。在路侧camera的3d检测基准上,方法大大超过了以前所有以视觉为中心的方法。它比bevdepth产生了+1.9%的nds和+1.1%的map的显著改善。在nuscenes测试集上,方法取得了实质性的进步,nds和map分别…

    2025年12月2日 科技
    000
  • 改进自动驾驶在不确定环境下的轨迹规划方法

    论文题目:《基于改进的模型预测控制的自动驾驶车辆在不确定环境下的轨迹规划方法》 发表期刊:IEEE Transactions on Intelligent Transportation Systems 发布日期:2023年04月 以下是我自己的論文閱讀筆記,主要是我自己覺得重點的部分,非全文翻譯,該…

    2025年12月2日 科技
    000
  • LeCun对自动驾驶独角兽的造假行为深感失望

    你以为这是一个普通的自动驾驶视频吗? ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ 图片 这个内容需要重新写成中文,而不改变原来的意思 没有一帧是“真的”。 图片 不同路况、各种天气,20多种情况都能模拟,效果以假乱真。 图片 世界模型再次…

    2025年12月2日 科技
    000
  • 实战部署:动态时序网络用于端到端检测和跟踪

    本文经自动驾驶之心公众号授权转载,转载请联系出处。 相信除了少数自研芯片的大厂,绝大多数自动驾驶公司都会使用英伟达NVIDIA芯片,那就离不开TensorRT. TensorRT是在NVIDIA各种GPU硬件平台下运行的一个C++推理框架。我们利用Pytorch、TF或者其他框架训练好的模型,可以首…

    2025年12月2日 科技
    000
  • 制造领域中的人工智能应用

    在制造市场中,机器视觉已经成为许多人工智能应用的重要组成部分。随着人工智能进入制造车间,这些标准变得尤为关键 讯飞智作-虚拟主播 讯飞智作是一款集AI配音、虚拟人视频生成、PPT生成视频、虚拟人定制等多功能的AI音视频生产平台。已广泛应用于媒体、教育、短视频等领域。 6 查看详情 在推动视觉应用的多…

    2025年12月2日
    000
  • Kubernetes调试终极武器: K8sGPT

    随着人工智能和机器学习技术的不断发展,企业和组织开始积极探索创新战略,以利用这些技术来提升竞争力。 K8sGPT[2]是该领域内功能强大的工具之一,它是基于k8s的GPT模型,兼具k8s编排的优势和GPT模型出色的自然语言处理能力。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使…

    2025年12月2日 科技
    000
  • 国产屏下 3D 人脸识别技术进入测试阶段 华为还是小米首发?

    7 月 14 日,”数码闲聊站” 消息称,有厂商正在实验室中对国产屏下 3d 人脸识别技术进行版本测试,这表明该技术正逐步迈向成熟,未来有望在智能手机领域实现应用。 近年来,屏下 3D 人脸识别技术成为智能手机设计的重要突破,其目标是将 3D 人脸识别模块完全置于屏幕下方,从…

    2025年12月2日
    000
  • 腾讯AI开放平台如何申请API密钥_腾讯AI开放平台API密钥申请全过程

    首先需注册并登录腾讯AI开放平台,然后创建应用获取AppID和AppKey,接着为应用开通所需AI能力,最后在项目中安全配置API密钥以完成调用准备。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ 如果您尝试在腾讯AI开放平台调用AI能力,…

    2025年12月2日 科技
    000
  • AI视频智能拆条怎么操作_AI长视频智能拆条功能与使用技巧

    一、使用AI浏览器可一键自动拆条,系统分析画面、语音和字幕生成片段;二、专业平台支持基础或自定义模式拆条,通过设置In/Out时间精准分割;三、开发者可通过OpenAPI调用SubmitSegmentationJob接口实现批量自动化处理;四、手动创建项目可在时间轴上精确定位入点和出点,逐段提取集锦…

    2025年12月2日 科技
    000
  • gemini2怎么调整模型温度_gemini2模型温度调整参数详尽说明

    调整Gemini 2模型输出的关键是设置temperature参数,通过API或Google AI Studio可调节其值(0.0–2.0)以控制随机性,结合topP参数协同优化生成效果。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ 如果…

    2025年12月2日 科技
    000
  • 快过 ChatGPT,蚂蚁灵光 AI 助手上线 4 天下载突破 100 万

    蚂蚁灵光上线 4 天下载量突破 100 万,冲上 App Store 中国区免费榜第六,灵光首个百万下载速度超过 ChatGPT、Sora2、DeepSeek 等全球主流 AI 产品。 11 月 18 日,蚂蚁集团官宣推出全模态通用 AI 助手“灵光”,首批上线三大功能 ——“灵光对话”、“灵光闪应…

    2025年12月2日
    000

发表回复

登录后才能评论
关注微信