简介:围绕八组逆解碰撞检测与避障路径规划的机械臂运动控制,是机器人领域的核心难题之一。这套脚本面向机器人专业学生、科研人员和刚入门的运动规划开发者,聚焦正逆运动学多解问题,用轻量级代码演示碰撞检测和路径规划的基本流程。压缩包仅5KB,共3个M脚本,分别承担逆运动学求解变换、碰撞检测与路径检测,代码短小精悍,便于逐行阅读、修改和复用。已有6663人学习下载,说明该主题在实际项目中具有较高参考价值。脚本着重展示了八组逆解多解性如何用于碰撞检测:同一末端位姿对应多组关节角,通过逐一比较连杆空间位置与障碍物边界,规避潜在碰撞,并在此基础上规划出安全无碰撞路径;这套算法思维也可为后续结合ROS、深度相机与SLAM开发完整避障系统提供扎实基础。
1. 机械臂碰撞检测:八组逆解与避障规划,现场最怕的三件事
真正做过机械臂集成的人都知道,碰撞检测这件事从来不是「装个传感器」就能解决的。上下料工位里机械臂换夹爪、走过工件上方、缩回安全位,每一步都要在仿真和控制器里同时算清楚两件事:当前位姿下关节能不能动、动了会不会撞。最麻烦的是六轴臂逆解通常不止一组,同一末端位姿能反解出八组关节角,八组里可能一半撞在工装上,另一半在奇异位形附近。这套资源就是围绕这个核心问题展开的:给机械臂做八组逆解碰撞检测,再在碰撞约束下做避障路径规划。适合做工艺集成、机器人仿真、上下料或打磨工位的工程师,解决「逆解选哪组、碰撞怎么判定、路径怎么绕开」这三个现场高频问题。
2. 八组逆解的组合逻辑:从位姿到八组关节角,再到可执行解
2.1 六轴球腕臂为什么算出来是八组
标准六轴工业臂大多满足球腕结构,前三个关节决定腕心位置,后三个关节决定末端姿态。逆解时,位置和姿态是分层解的:先由腕心位置算出前三个关节角,再由末端姿态算出后三个关节角。前三个关节每组解通常能组合出多组,工程上常说的「八组解」来自三个二元组合的叠加——肩部前后两组、肘部上下两组、腕部翻转两组,2×2×2 正好是八组。
需要澄清一点:不是所有位姿都有八组解。腕心落在工作空间边缘时,可能只剩四组甚至两组;奇异位形附近,某几个关节角会退化。所以八组是「理论上限」,工程上要先把不存在的、超限的组合剔除,再谈碰撞检测。
逆解实现常用解析法,按 DH 参数逐关节反推。流程大致是:已知末端位姿矩阵 T,先分离出腕心位置,用余弦定理解出肩部和肘部关节角,再回代求腕部关节角,最后追加腕部翻转的±两组。整个过程不涉及迭代,单次求解微秒级,适合在规划循环里反复调用,这也是选解析法而不是数值法的主要原因。
2.2 从八组到可用:限位、奇异与连续性筛选
拿到八组原始解之后,不能直接拿去驱动机械臂。第一道筛选是关节限位,每个关节角必须落在硬件允许范围内,超出就剔除。第二道是奇异位形判断,典型是腕部关节接近零度或180度时,关节速度会突变,要设一个阈值把这些解标成不可用。第三道是连续性检查:规划器上一时刻用了哪组解,当前若有多组可选,优先选与上一时刻关节角距离最近的那组,避免机械臂在同一个点位附近来回切换构型。
这道筛选直接影响碰撞检测的可靠性。碰撞检测是在关节空间做的,有什么关节角才有什么空间位置。如果把奇异解或超限解放过去,即使碰撞检测结果正常,实际机械臂也执行不了。所以八组逆解的正确用法是:先过滤出 k 组可用解(k 可能小于八),再对每组解分别做碰撞检测,最后选一组既无碰撞、又离当前构型最近的结果作为轨迹点。
2.3 逆解求解与过滤的参考实现
下面是一段简化到只保留结构的参考代码,重点是让读者看清「八组解 → 可用解 → 最近解」的完整链条。实际部署时需要把里面的计算替换成自己机械臂的 DH 参数和正解函数。
def inverse_kinematics_solutions(T_target, dh_params, joint_limits): """返回满足限位和奇异约束的全部逆解""" raw_solutions = solve_analytic_ik(T_target, dh_params) # 解析法拿到理论解 valid = [] for sol in raw_solutions: if not within_joint_limits(sol, joint_limits): continue if is_near_singularity(sol, threshold_deg=5.0): continue valid.append(sol) return valid def pick_best_solution(solutions, prev_q): """选离上一帧最近的一组,避免构型跳变""" if not solutions: return None distances = [np.max(np.abs(np.array(sol) - np.array(prev_q))) for sol in solutions] return solutions[int(np.argmin(distances))]threshold_deg是奇异判定阈值,实际调试时先从 3 度试起,若发现实机腕部关节转速过高再逐步放大到 8 度。within_joint_limits里我给每个关节都留了 0.5 度的安全余量,防止后续轨迹平滑时轻微越界。solve_analytic_ik的返回结构是 8 组或 8 组以内的关节角列表,组间顺序不保证稳定,所以最终一定靠pick_best_solution做选择,不能依赖求解器的返回顺序。
3. 碰撞检测建模:球体、胶囊体、包围盒与网格各管一段
3.1 三种碰撞表示的工程取舍
机械臂碰撞检测很难用单一模型搞定,工程上按精度和速度分三层。最粗的是球体包络,把每个连杆看成一个或几个球,计算球心距离与半径和比较即可,速度快到可以放进规划内循环,但漏检率高——细长杆、薄壁件、夹爪开合结构都容易被球包络「包出一个大肚子」。中层是用胶囊体或 AABB/OBB 包围盒,胶囊体适合杆状连杆,包围盒适合基座、电柜、工装这类规则物体,计算量比球大但在可控范围。最细的是三角网格精确检测,直接把 STL/URDF 网格丢进检测库做三角形求交,精度最好但单次检测耗时可能到毫秒级,放进 RRT 采样循环里会被拖慢一两倍。
实际项目中我一般用混合方案:规划器内层用胶囊体和包围盒做粗检测,快速排除掉大部分无碰撞采样点;只有候选轨迹的终点和关键路径点,才用网格精确检测复核一次。这样既保证实时性,又不至于把规划器的「漏检」带到实机上。
3.2 自碰撞与环境碰撞的统一判定
机械臂碰撞要分两类:连杆与连杆之间的自碰撞,连杆与环境物体之间的碰撞。两者在检测逻辑上是同一套几何求交,区别只在于参与检测的物体集合不同。自碰撞检测要跳过相邻连杆和共享关节的连杆,因为它们物理上必然相接;环境和工装碰撞检测则要把所有连杆都纳入,同时把夹爪内部的活动件单独建碰撞体,避免把夹爪开合误判成自碰撞。
统一的判定接口建议设计成is_collision(q),输入一组关节角,输出布尔值。内部先算出每个连杆的位姿,再按已配置的碰撞体类型依次检测。这个接口同时服务三个调用方:逆解筛选时对每组解做点检测、RRT 采样时对路径点做实时检测、仿真回放时对轨迹逐帧做复核。接口签名统一,后续换碰撞库、加碰撞体都不用改上层逻辑。
3.3 胶囊体对胶囊体的碰撞检测实现
下面是胶囊体碰撞检测的核心代码。胶囊体可以看成一条线段外扩一个半径,两胶囊的碰撞等价于两线段之间的最短距离是否小于两半径之和。
def segment_distance(p1, p2, q1, q2): """计算线段 p1-p2 与 q1-q2 的最短距离,向量法,无迭代""" d1 = p2 - p1 d2 = q2 - q1 r = p1 - q1 a = np.dot(d1, d1) e = np.dot(d2, d2) f = np.dot(d2, r) if a <= 1e-12 and e <= 1e-12: return np.linalg.norm(r) if a <= 1e-12: s = 0.0 t = np.clip(f / e, 0.0, 1.0) else: c = np.dot(d1, r) if e <= 1e-12: t = 0.0 s = np.clip(-c / a, 0.0, 1.0) else: b = np.dot(d1, d2) denom = a * e - b * b if denom != 0.0: s = np.clip((b * f - c * e) / denom, 0.0, 1.0) else: s = 0.0 t = (b * s + f) / e if t < 0.0: t = 0.0 s = np.clip(-c / a, 0.0, 1.0) elif t > 1.0: t = 1.0 s = np.clip((b - c) / a, 0.0, 1.0) closest_p = p1 + s * d1 closest_q = q1 + t * d2 return np.linalg.norm(closest_p - closest_q) def capsule_collision(seg1, rad1, seg2, rad2): """两胶囊体是否碰撞,seg 为两端点坐标""" dist = segment_distance(seg1[0], seg1[1], seg2[0], seg2[1]) return dist < (rad1 + rad2)代码里直接把最短距离算到线段参数 s 和 t 上,好处是两个胶囊不仅知道撞没撞,还能知道碰撞点分别落在连杆的哪个位置,方便后续做避障方向的引导。rad1 + rad2是所有连杆碰撞检测里最常用的阈值,调参时先按连杆实际半径给,若规划频繁误报,单独给每个胶囊体加一个safety_radius_offset,通常 5~15 毫米,而不是全局统一加,否则靠近工装时会把可用空间全部堵死。
4. 避障路径规划:在关节空间采样,远比笛卡尔空间可靠
4.1 为什么选 RRT-Connect 而不是 PRM 或 A* 网格
机械臂避障路径规划的主流选择是采样规划,其中 RRT-Connect 在工程里表现最稳。PRM 需要预采样整个构型空间,适合多次查询同一场景,但单次规划的建图开销大,机械臂换一次工件位置就要重新采样。A* 之类基于网格的方法要先把关节空间离散成网格,六轴臂每个关节分 100 格就是 10 的 12 次方个状态,内存直接爆炸。RRT-Connect 每次从起点和终点各长一棵树,向对方方向随机采样扩展,找到连接即停止,内存占用小,规划速度快,非常适合单次查询场景。代价是路径不是最优的,这一点靠后端优化解决。
这里有个关键决策:在关节空间规划,不在笛卡尔空间规划。在笛卡尔空间把末端位姿做插值,看着路径直,但中间点对应的关节角可能突变,机械臂实际走起来会剧烈抖动甚至触发展位保护。关节空间规划把每个采样点都描述成完整关节角向量,碰撞检测直接对应该构型下的连杆位置,物理上可执行。
4.2 步长、迭代上限、碰撞采样密度怎么配
RRT-Connect 有四个参数直接影响成败。step_size是单次扩展的关节角步长,单位是弧度或度,取 5~10 度比较稳妥,太小则扩展缓慢,太大则容易在窄通道里反复穿过障碍。max_iterations一般从 2000 开始,若规划失败再放大到 5000,但超过 10000 次还没结果,通常是碰撞模型过保守或起点已被堵死。goal_bias控制树向目标点生长的概率,取 0.05~0.2,太大会退化成直线搜索,失去随机性。collision_check_resolution是单步扩展时对路径段的细分检查密度,每个步长内做 3~5 次等分碰撞检测,防止采样点之间发生跨越式漏检。
还有一个常用技巧:规划前先调用逆解筛选,拿到起始位姿和终止位姿各自的一组可用解,再把两者组合成多组规划任务。第一组失败了就换第二组,而不是只往规划器里丢一组解。这样能把八组逆解和路径规划真正串起来,规划成功率会明显提升。
4.3 碰撞感知的双向 RRT 参考实现
def rrt_connect(start_q, goal_q, is_collision, step_size=0.15, max_iterations=5000, goal_bias=0.1): tree_a = {tuple(start_q): None} tree_b = {tuple(goal_q): None} for _ in range(max_iterations): if random.random() < goal_bias: target = goal_q else: target = [random.uniform(lim[0], lim[1]) for lim in joint_limits] new_q = extend_tree(tree_a, target, step_size, is_collision) if new_q is not None: connect_q = extend_tree(tree_b, new_q, step_size, is_collision) if connect_q is not None and np.allclose(connect_q, new_q, atol=1e-3): return build_path(tree_a, tree_b, new_q) tree_a, tree_b = tree_b, tree_a # 对称扩展 return Noneextend_tree内部把新扩展点与最近节点之间的路径按collision_check_resolution细分做碰撞检测,途中任意一点碰撞就放弃本次扩展。对称扩展让两棵树交替生长,比单树 RRT 快很多。build_path把两棵树的父节点链拼接起来,得到的是「起点→交点→终点」的原始路径。注意joint_limits要从机械臂配置里读取,不能写死,因为采样范围越界会导致规划器生成不可执行的关节角。
4.4 路径后处理:剪枝与平滑
原始 RRT 路径是折线,直接下发给控制器会出现关节速度跳变。工程上至少做两步后处理。第一步是剪枝,从起点出发,尝试直接连接后续更远的路径点,若连接段整段无碰撞就跳过中间点,往复操作直到终点,路径点数能减少一半以上。第二步是平滑,对剪枝后的关节角序列做三次样条插值,再把插值点逐帧做碰撞检测和关节限位检查,最后按梯形速度曲线给定每个路径点的时间戳。平滑之后还要留一道检查:插值很可能让关节角略微越界,要重新过一遍逆解筛选时的限位逻辑。
后处理的参数里,样条平滑的张力系数值得单独说。系数偏大,路径贴近原始折线,安全性高但平滑效果差;系数偏小,轨迹圆润但可能切到障碍物附近。我一般从 0.1 起步,每次加 0.05,直到轨迹在可视化里不再触碰碰撞体边缘的安全余量层,就停止调整。
5. 避坑:八组逆解与碰撞规划中的常见翻车现场
5.1 现象:逆解全在奇异位形附近,机械臂走起来一顿一顿
某次联调时,机械臂末端跟随一条直线,走到中间位置突然六个关节同时高速反转,控制器直接报跟随误差超限。查下来发现逆解筛选只保留了限位内的解,没有做奇异判定。那次路径点的目标位姿正好落在腕部奇异附近,算法强行选了一组接近 180 度的腕部关节角,运动学上无碰撞,动力学上根本执行不了。解决方法是把奇异判定提前到逆解过滤阶段,阈值按实机调试结果修正,同时给规划器的连接点也加奇异过滤,避免轨迹平滑时把奇异点重新插出来。
5.2 现象:包围盒太保守,路径规划把空间全堵死了
一批工件夹具是细长的钣金件,用 AABB 包围盒表示后,规划器反复报无可用路径。原因在于 AABB 是轴对齐包围盒,细长件斜着摆放时,包围盒会比实物大出很多,甚至把旁边另一台设备的通行空间整个盖住。解决方法是把这类细长结构换成 OBB 或胶囊体,对杆状件优先用胶囊体,只有基座、电柜这类规则物体才保留 AABB。从那以后我对每个碰撞体都单独画一张可视化图,叠在实际工装照片上核对有没有「包过头」。
5.3 现象:规划路径贴着关节限位边界震荡
机械臂在靠近极限位置的目标点附近规划时,路径点经常出现某个关节角在 170 度和 178 度之间反复跳动。原因是限位检查只做了布尔判定,没有在边界附近留余量,采样点在边界两侧概率性通过,RRT 就来回试探。解决方法是把限位检查改成「限位区间向内收缩 2~3 度」,收缩量可以配置,并在平滑后再次做收紧检查。这个余量对实机尤其重要,因为控制器还有自身的软件限位,两套限位叠加后实际可动范围比理论值更小。
5.4 现象:碰撞体坐标和运动学基坐标没对齐
仿真里路径规划一切正常,部署到实机第一次运行就差点撞上立柱。排查时发现碰撞检测用的环境物体模型来自工艺布局图,坐标原点在厂房基准点,而机械臂基座坐标系原点在基座安装面,两者之间差了旋转偏移,导致仿真里的障碍物位置整体偏了几十毫米。解决方法是所有环境物体统一转换到机械臂基座坐标系,在加载阶段做一次坐标变换校验,用激光跟踪仪实测几个关键点位对账,而不是依赖图纸换算。这个坑最隐蔽,因为仿真画面里看不出偏差,只有实机对碰才暴露。
5.5 现象:夹爪碰撞体与实际尺寸不一致,导致误报
夹爪闭合状态和张开状态的轮廓完全不同,把碰撞体做成固定形状后,规划出来的路径在夹爪张开时距离工件太近,实机一开夹就报警。原因是夹爪的活动部件没有跟随关节角更新位姿。解决方法是把夹爪内部活动件定义成独立的碰撞体,随对应关节角实时刷新位置,并在规划时单独设置夹爪内部碰撞检测开关,只在夹持状态下开启内部检测,避免把开合动作误判成自碰撞。
6. 验证与调参技巧:把「能跑」变成「敢上线」
6.1 轨迹回放看关节角曲线,而不是看末端轨迹
仿真里末端轨迹漂亮,不代表实机走得好。正确做法是把规划轨迹逐帧输出关节角序列,画成六条曲线,重点看相邻帧的角速度是否有尖峰,同一路径点前后有没有构型切换。实践里我给自己定了一个硬指标:任意相邻路径点的关节角差不超过 3 度,超过就重新规划或加密插值。这个指标比看末端位置更早暴露奇异和逆解跳变问题。
6.2 碰撞体可视化叠加检查
把碰撞检测结果用半透明模型叠加到实机照片或三维场景里逐帧播放,只开碰撞体框架,确认每个碰撞体的尺寸、位置和朝向与实物一致。检查顺序是先静态对点,再低速空跑一段轨迹,最后才做带工件的满载测试。每次改过碰撞体参数,都强制过一遍这个流程,哪怕只是把某个胶囊体半径从 20 毫米改成 22 毫米。
6.3 规划耗时分配与参数回退
单次规划超过 3 秒,就要拆分耗时:逆解耗时、碰撞检测耗时、树扩展耗时、后处理耗时各打点统计。碰撞检测占比超过 60% 时,优先考虑把部分网格检测降级为胶囊体粗检;树扩展耗时高,优先减step_size。所有参数改完后保留一组能跑通的备份参数,方便调试翻车时快速回退。从那以后我每个新项目的规划参数都强制走一遍「参数备份 → 调参 → 记录效果 → 若有异常立即回退」这个流程,不再凭感觉乱试。希望帮到你,少走几个我走过的弯。
本文还有配套的精品资源,点击获取