简介:一套基于MATLAB的机械臂八组逆解碰撞检测与避障路径规划示例代码,面向机器人技术学习者、科研人员及机械臂应用开发者。压缩包内共3个.m文件,分别对应路径检测、逆运动学求解和碰撞检测三个核心模块,整体仅约5KB,结构精简,已有6663人浏览学习。通过阅读和运行这些脚本,能够直观理解八组逆解的含义:同一末端位姿通常对应多组关节角组合,程序逐组代入正运动学并计算各连杆空间位置,与障碍物边界比较后剔除碰撞状态,从而确保机械臂在任何姿态下都不发生干涉。路径检测脚本进一步演示了在检测到风险后怎样重新规划安全路径,并为后续结合ROS、激光雷达或深度相机环境建模以及SLAM地图数据开展真实场景验证打好基础。对正在学习机器人运动学或机械臂避障算法的读者而言,这是一份可直接运行的入门级代码包,便于二次开发与算法教学。
1. 八组逆解与碰撞检测:同样一个目标点,关节凭什么有八种摆法
给六轴机械臂一个末端位姿,也就是位置加姿态,逆解出来关节角组合,一上来就是 8 组。这不是数值求解器抽风,而是六轴串联臂的运动学结构决定的:前三关节决定腕心位置,后三关节负责末端姿态,每一段都有正反两个方向可选,2×2×2 叠出 8 组解。做过实际机械臂项目的人都知道,这 8 组解里往往只有一两组能让机械臂不碰障碍物、不卡奇异位形、不撞关节限位。机械臂碰撞检测要解决的,就是在关节空间里把既满足末端约束、又满足环境约束的可行分支挑出来。机械臂避障路径规划能不能可靠落地,第一步看的不是路径算法有多高级,而是这 8 组解怎么被保留、怎么被淘汰。这套取舍逻辑,就是机械臂安全运动规划的地基。
2. 八组逆解:六个关节角背后的 2×2×2 到底从哪来
2.1 正解是连乘,逆解是拆解
先交代模型。标准的六轴串联机械臂,每个关节可以用一组 Denavit-Hartenberg(DH)参数描述:连杆长度 a、连杆偏距 d、关节角 θ、连杆扭角 α。正运动学就是把六个关节的变换矩阵按顺序连乘起来,得到一个 4×4 的齐次变换矩阵:
import numpy as np # DH参数表:[a, d, alpha, theta_offset] # 这里列的是一个典型六轴臂的参数,实际以机械臂手册为准 dh_params = [ [0, 0.34, np.pi/2, 0], [0.24, 0, 0, 0], [0.24, 0, 0, 0], [0, 0.34, np.pi/2, 0], [0, 0, -np.pi/2, 0], [0, 0, 0, 0], ] def forward_kinematics(joint_angles): """正运动学:从关节角算末端位姿矩阵""" T = np.eye(4) for i, (a, d, alpha, theta_off) in enumerate(dh_params): theta = joint_angles[i] + theta_off # 单关节变换矩阵:绕Z转theta、沿Z平移d、沿X平移a、绕X转alpha T_i = np.array([ [np.cos(theta), -np.sin(theta)*np.cos(alpha), np.sin(theta)*np.sin(alpha), a*np.cos(theta)], [np.sin(theta), np.cos(theta)*np.cos(alpha), -np.cos(theta)*np.sin(alpha), a*np.sin(theta)], [0, np.sin(alpha), np.cos(alpha), d], [0, 0, 0, 1] ]) T = T @ T_i return T逆运动学就是反过来的问题:给一个 4×4 的末端位姿矩阵 T_06,反解出六个关节角 θ1 到 θ6。连乘容易,拆解难,因为三角函数方程存在多解性,同一个末端位姿可以对应多组关节角组合。实际工程里有两类解法:解析解法和数值迭代解法。机器人领域大多数控制器和开源求解器都优先用解析法,因为它快、稳定,而且能把全部解枚举出来。
2.2 为什么恰好是八组:腕心解耦与三条分支
六轴臂能解出多组逆解,前提是结构满足一个关键条件:后三个关节 J4、J5、J6 的转轴交于一点,这个点叫腕心。腕心存在时,后三个关节只负责调整末端姿态,不影响腕心位置。这样一来,整个逆解问题被拆成两段:
- 第一段:由末端位姿算出腕心的空间坐标,再反解关节 1、2、3。这个过程里,肩关节可以有“正肩/反肩”两种取法,肘关节也可以有“肘上/肘下”两种形态。
- 第二段:腕心位置确定后,由末端姿态反解关节 4、5、6。在给定前三个关节角的前提下,腕部翻转同样有两种方向。
三段各有两个方向,2×2×2 就是 8 组解。我用一个表格把这 8 组解的几何特征列出来,方便后续排查问题时对号入座:
| 解编号 | 肩关节形态 | 肘关节形态 | 腕部形态 |
|---|---|---|---|
| 解1 | 正肩 | 肘上 | 腕正 |
| 解2 | 正肩 | 肘上 | 腕反 |
| 解3 | 正肩 | 肘下 | 腕正 |
| 解4 | 正肩 | 肘下 | 腕反 |
| 解5 | 反肩 | 肘上 | 腕正 |
| 解6 | 反肩 | 肘上 | 腕反 |
| 解7 | 反肩 | 肘下 | 腕正 |
| 解8 | 反肩 | 肘下 | 腕反 |
需要强调一点:8 组解只是理论上限,实际运行时有些组会直接失效——关节超出限位、落入奇异位形、或者数值求解时出现 NaN。所以工程上常说的“八组逆解”,严格讲是“最多八组候选解”,真正可用的往往只有三四组。
2.3 数值迭代与解析解的差异
数值迭代法,比如雅可比矩阵的阻尼最小二乘,给定一个初始猜测后,只会收敛到离初始猜测最近的那组解。它天然无法枚举出全部 8 组。如果你用这种求解器做避障规划,很容易发生这样的情况:初始猜测在障碍物附近,收敛出来的解仍然撞障碍物,但同一末端位姿换一个初始猜测,却能收敛到另一组安全解。这就是为什么做机械臂避障路径规划,第一选择永远是解析逆解,先拿全 8 组候选,再做碰撞检测筛选。
理解了 8 组解的来源,碰撞检测的输入才算有了着落:它不是对单个 q 做检测,而是对 8 个 q 逐个做检测。
3. 碰撞检测:八组候选解为什么要逐组过一遍几何世界
3.1 把机械臂装进几何近似体
碰撞检测的第一步永远不是直接拿 STL 网格去算,而是把机械臂的连杆和障碍物都换成简单的几何原语。机械臂连杆通常近似成胶囊体、圆柱体或凸包,障碍物近似成球体、长方体或凸包。这么做有几个原因:几何原语之间的距离计算有闭式公式,计算量稳定可控;而三角网格之间的相交测试要处理几十万甚至上百万个三角形对,做一次检测就可能吃掉几十毫秒,实时性完全撑不住。
胶囊体是机械臂连杆最常见的近似形状。它本质上是一个圆柱加两个半球帽,数学定义是一条线段外扩一个半径。两个胶囊体之间的最短距离,可以转化为两条线段之间的最短距离再减去两个半径之和。这个计算只有几十行代码,而且完全没有分支抖动,很适合在控制周期里反复调用。
3.2 从关节角到碰撞判定:一次完整的检测管线
有了几何近似,接下来把逆解和碰撞检测串成一条管线。下面这段代码展示了对 8 组候选解逐组检测的完整逻辑:
def check_collision_all_solutions(target_pose, obstacles, robot_geom, safety_margin=0.02): """ 对同一末端位姿的8组逆解逐组做碰撞检测。 返回安全解列表,以及每个解被淘汰的原因。 """ solutions = inverse_kinematics_all(target_pose) # 解析逆解,返回全部8组候选 safe_list = [] for q in solutions: if q is None: # 该分支奇异或超关节限位 continue # 正运动学算每一根连杆的世界系位姿 link_poses = forward_links(q) collided = False reason = "" for link_name, (link_pose, link_shape) in robot_geom.items(): for obs_name, obs_shape in obstacles.items(): dist = distance_link_obstacle(link_pose, link_shape, obs_shape) if dist < safety_margin: collided = True reason = f"{link_name} 与 {obs_name} 距离 {dist:.3f} 米" break if collided: break if not collided: safe_list.append(q) return safe_list这段代码的核心是把逆解、正运动学、距离检测三件事耦合在一起。safety_margin 是个需要小心调的超参数,设大了机械臂不敢靠近障碍物,设小了容易出实际碰撞。注意这里针对同一个末端位姿做了 8 次完整的正运动学和距离计算,而不是只挑一组“看起来最近”的解。每组解对应的连杆空间姿态完全不同,碰撞风险也完全不同。
3.3 为什么不能只检一组“最优解”
最直观的偷懒方式:从 8 组解里选一组离当前关节角最近的解,然后只对它做碰撞检测。这组解通常叫“最小范数解”,因为关节空间里它离当前位姿最近,运动起来显得平滑。问题在于,最小范数解只管关节空间的距离,不管笛卡尔空间里的障碍物在哪。完全可能出现这种情况:最小范数解走的是穿过障碍物的路径,而另一组看似要“绕一大圈”的解反而能安全到达。
另一个常见误用是只对目标点做碰撞检测,认为目标点安全整条轨迹就安全。这就像一个只看目的地不看路况的导航:终点安全,但途中可能穿过桌子腿。真正的机械臂避障路径规划,必须把整条轨迹离散成多个路径点,每个路径点都做“逆解 + 碰撞检测”的完整检查,这就引出了下一章的核心问题。
4. 避障路径规划:从单帧安全到整条轨迹安全
4.1 笛卡尔空间与关节空间的来回映射
机械臂避障路径规划最常用的思路是笛卡尔空间规划。给一个起点位姿和一个终点位姿,在笛卡尔空间里做线性插值,然后把每个插值点送到逆解器里解出关节角。看一段简化实现:
def plan_cartesian_path(start_pose, end_pose, obstacles, step_size=0.01): """笛卡尔空间直线插值 + 逐点逆解 + 碰撞检测""" # 计算末端位移距离 displacement = np.linalg.norm(end_pose[:3, 3] - start_pose[:3, 3]) steps = int(displacement / step_size) last_q = None trajectory = [] for i in range(steps + 1): t = i / steps # 位置线性插值,姿态球面插值 pose = interpolate_pose(start_pose, end_pose, t) # 对该位姿枚举全部逆解 candidates = inverse_kinematics_all(pose) # 优先选与上一帧关节角最接近的候选解,避免跳变 q = select_continuous(candidates, last_q) if q is None: raise RuntimeError("该路径点在关节空间无可行解") # 对该路径点做碰撞检测 if not check_collision(q, obstacles): trajectory.append(q) last_q = q else: raise RuntimeError("路径点碰撞,需要重新规划该段") return trajectory这段代码里最容易出问题的是select_continuous。笛卡尔空间的每个插值点照理会解出 8 组逆解,相邻两个插值点之间如果各选各的“最近解”,很容易出现第 1 帧选中正肩、第 2 帧选中反肩的情况。在关节空间里看,某个关节角度猛地跳了几十度甚至上百度,轨迹直接崩掉。所以必须加连续性约束:下一帧的候选解只在与上一帧关节角最接近的几个分支里选。
4.2 连续性约束:逆解分支的追踪机制
关节空间连续性约束的实际做法是“分支追踪”。第一帧时,把 8 组解全部算出来,各自记为分支 1 到分支 8。后续每一帧都从上一帧对应的分支开始找,如果该分支在第 n 帧依然存在而且安全,就继续沿用;如果该分支消失(奇异或限位),就在剩余候选解里找距离最近的一个,并记录一次“分支切换”。分支切换次数多了,说明这条笛卡尔路径在关节空间里不可行,需要考虑绕路。
这个机制解释了为什么前面逆解必须用解析法拿全 8 组解:数值迭代法根本无法建立“分支”的概念,它只会从初始猜测出发给你一组解,到下一帧初始猜测变了,可能收敛到另一个完全无关的分支。轨迹连续性在数值迭代框架下是个很难解的痛点,只有解析逆解的 8 组候选才能显式地做分支管理和跳变检测。
4.3 采样型规划器:在关节空间里做避障
笛卡尔空间插值虽然直观,但有天然局限:末端走的直线路径可能在关节空间里撞墙。当直线路径连续被碰撞检测拦下来时,就得换思路,直接在关节空间里采样规划,RRT 和 PRM 是这里的常客。核心逻辑很简单:在关节空间里随机采样关节角度,做一个正运动学,把机械臂的各连杆位姿摆出来,再做碰撞检测,只有“安全”的采样点才会保留并加入路径生长。
关节空间采样规划的好处是天然避开逆解多解性的问题——采样到的就是具体的 q,不需要从末端位姿反推候选分支。代价是末端轨迹不可控,走的可能不是期望路径。实际工程里最常见的策略是混合使用:先用采样算法找一条无碰路径,再在安全通道内做轨迹优化,让末端尽量贴近期望路径。
5. 避坑:机械臂碰撞检测与逆解里的常见翻车现场
5.1 奇异位形下逆解跳变
现象:机械臂接近某个奇异位形时,上一帧还正常的轨迹突然出现某个关节角大范围跳变,机械臂在原地猛地甩了一下。
原因:奇异位形附近,雅可比矩阵接近奇异,腕部两个关节的旋转轴几乎共线,解析逆解里对应分支的关节角解附近极不稳定。微小位姿扰动会让逆解结果相差几十度。
解决:在分支追踪逻辑里加入奇异距离检测,估计雅可比矩阵的条件数,当条件数超过阈值时放弃当前分支,提前切换到备用分支,而不是等到跳变发生后再补救。调节速度规划参数也可以缓解,奇异区附近自动降速,给控制器更多收敛余地。
5.2 碰撞模型比实际机械臂小一圈
现象:仿真里机械臂离障碍物还有 5 厘米就通过了,实际现场运行咔一声撞上去了。
原因:碰撞检测用的胶囊体半径小于连杆实际外包络。最容易漏的地方是线缆、法兰盘、末端工具这三个位置。连杆本体画了胶囊体,但末端工具只画了一个球,球半径没有覆盖工具的实际长度。
解决:给末端工具单独建几何模型,不要偷懒和第六轴共用连杆碰撞体。线缆这类柔性体做不准,就直接包一个偏保守的圆柱体,宁可多留安全距离。safety_margin 不能完全依靠仿真验证,要在真实机械臂上用低速空跑逐段验证,把实测距离和仿真距离对一遍。
5.3 插值步长大于障碍物尺寸
现象:路径点全部通过碰撞检测,但机械臂在实际运行中还是撞上了障碍物边缘。
原因:笛卡尔空间插值步长设得太粗,相邻两个路径点之间的实际运动轨迹没有经过碰撞检测。如果一个障碍物刚好卡在两个检测点之间,路径就会像电子游戏里“穿模”一样直接穿过去。
解决:把步长改成动态计算。估算机械臂在该段运动速度下每个控制周期扫过的弧长,让插值步长小于最小的障碍物尺寸和弧长的 1/3。对接近障碍物的区段做二次细分检测,也就是把已安全的前一段与后一段之间再插入一个中间点重新检测。
5.4 只对路径点检测,忽略过渡段
现象:路径起点、终点、所有插值点都安全,但机械臂在两点之间的空间轨迹里绕了个弧线,弧线中部撞到障碍物。
原因:笛卡尔直线插值保证的是末端位姿在这两个点之间走直线,但机械臂的连杆在运动过程中整体扫过的空间体不是直线。关节运动是多轴联动,每根杆的空间轨迹是一段复杂曲线,只对点做检测覆盖不了扫过的体。
解决:把路径离散成更细的路标点只是补救。更可靠的方法是引入扫掠体检测,在两点之间构造连杆扫过的凸包,拿凸包直接对障碍物做碰撞检测。实际工程里如果来不及做扫掠体,就在相邻路径点之间再补 2 到 3 个插值点,做时间密集采样检测,相当于把可能的碰撞区间不断压缩。
5.5 世界坐标系与工具坐标系混用
现象:仿真里逆解结果正确,碰撞检测却频繁误报,机械臂明明离障碍物很远却判定碰撞。
原因:障碍物坐标用了世界坐标系,连杆位姿矩阵却输出在工具坐标系或基座标系偏移之后。最常见的是 DH 参数里 base frame 偏移没有补偿,或者末端工具在正运动学里额外加了一个变换,但碰撞检测里没用同一个变换。
解决:统一坐标基准。在正运动学函数的返回值里,明确注明返回的是哪个坐标系下的位姿,并且给每个连杆位姿额外附带一个frame_id字段。碰撞检测管线入口处断言所有几何体的坐标帧一致,不一致就直接抛异常。
6. 设计取舍:全检八组、先筛后检还是分层精检
写到这里,一个绕不开的工程问题浮出来了:8 组候选解逐组做碰撞检测,计算量是不是太大了?实时控制周期通常只有 8 到 20 毫秒,同一帧内如果要做 8 次完整的正运动学加胶囊体距离检测,很快就把预算耗完。
我一般会这么处理:动态规划分层策略,而不是固定全检或固定只检一组。离障碍物还很远时,先做粗检测,把所有连杆用一个大的包围球包住,用一个球去和障碍物做距离测试。这个检测只需要 8 次球面测试,微秒级开销,能直接干掉四五个明显安全的候选解。离障碍物近了,再对剩下的两三个候选解做精细的胶囊体距离计算。这相当于用两级预筛把 8 组解摊薄成两三组精检,既保住安全性又压住计算延迟。
这个取舍背后还有一个更深的点:高速运动时安全靠的不只是碰撞检测的准确性,更靠轨迹预测。如果你能提前三到五个周期判断某组解正在逼近障碍物,就可以提前减速或切换分支,而不是等到距离跌破阈值再救。这其实是碰撞检测从“当前帧安全”走向“未来帧安全”的关键一步。从那以后我每次做机械臂运动规划,都会强制把 8 组逆解全量可视化一遍,先看哪些分支在关节空间里是平滑的、哪些分支在拼命撞限位,再写碰撞检测逻辑。这个习惯帮我避开了不少看似没有答案的玄学崩溃。希望帮到你。
本文还有配套的精品资源,点击获取