简介:这套基于Matlab的RRT算法与UR5双臂机器人路径规划源码包,面向机器人路径规划方向的研究生、开发者与工程技术人员,目标是解决双臂机器人在存在障碍物的环境中实现协调运动与安全避障的问题。资源内不仅实现了基本随机扩展树的经典流程,还根据双臂结构特点加入多臂协同规划、障碍物快速检测、生成路径平滑优化等改进模块,并配有主程序与多个可调用函数。资源包共十九个文件,以十五个m脚本为主体,分别承担主控逻辑、节点生成、最近邻查找、碰撞判定、路径改进等任务;两个json文件用于加载左右机械臂关节配置,两张png图片用来对比迭代次数与路径效果。整个压缩包仅六十一KB,轻量便于部署。已有约一千七百七十九人学习下载。通过分析源码,可以系统掌握RRT算法原理及其在六轴双臂机器人上的移植方法,也能基于Matlab工具箱对扩展逻辑和碰撞策略做二次修改,适合作为课题实验或项目开发的基础参考。
1. RRT 算法在双臂 UR5 上失效,采样规划真正难的是互碰撞
给单个 UR5 调通 RRT 不是难事:随机采样关节角,长出树,几分钟内看到一条无碰撞路径就能当成果。双臂版本完全不同——两个六轴机器人拼出 12 维关节空间,均匀采样的成功率随维度指数下降,两臂还会互相成为对方的动态障碍。很多 MATLAB 程序在单臂上 5 分钟出结果,改到双臂却挂一整晚跑不完。问题不在 RRT 本身,而在于你把它当成了“采样+找最近点”的短代码,没处理 12 维空间的连通性和双机互碰。这篇内容面向正在做双臂 UR5 路径规划的工程师和学生,覆盖 RRT 在 MATLAB 里的最小实现、UR5 运动学建模、双向 RRT 在 12 维空间中的实战写法,以及三个决定成败的必调参数。
2. 在 MATLAB 里跑通 RRT 最小实现,先抓住采样与步进
2.1 用向量化节点树写出可运行的 RRT
先看一段能直接运行的二维 RRT。它的结构是所有关节空间版本的公共骨架:节点数组、父节点索引、随机采样、最近邻搜索、步进、碰撞检测。
function [tree, path] = rrt2d(start, goal, obstacle, area, step, maxIter) % start/goal 为 [x,y],obstacle 为矩形 [xmin ymin xmax ymax] % tree.nodes 存放所有已生成节点,tree.parent 存父节点索引 tree.nodes = start; tree.parent = 0; for k = 1:maxIter % 10% 概率直接向终点采样,其余在区域内随机采 if rand < 0.10 sample = goal; else sample = [rand*area(3), rand*area(4)]; end % 向量化最近邻搜索:一次算所有节点到样点的欧氏距离 d2 = sum((tree.nodes - sample).^2, 2); [~, idx] = min(d2); nearest = tree.nodes(idx, :); % 沿 nearest -> sample 方向步进,步长不超过 step delta = sample - nearest; dist = norm(delta); new = nearest + delta / dist * min(step, dist); % 新节点落在障碍内则放弃,继续下一次采样 if new(1) > obstacle(1) && new(1) < obstacle(3) && ... new(2) > obstacle(2) && new(2) < obstacle(4) continue; end % 把新节点挂到最近节点下面 tree.nodes(end+1, :) = new; tree.parent(end+1, :) = idx; % 距离终点小于一步时补上终点并返回路径 if norm(new - goal) < step tree.nodes(end+1, :) = goal; tree.parent(end+1, :) = size(tree.nodes, 1) - 1; path = extractPath(tree); return; end end path = []; end这段代码的关键在节点数据结构。树上每个节点只存坐标和父节点索引,路径提取就是从末端节点沿 parent 回溯到起点,extractPath 只是一个 while 循环。最近邻搜索用 sum 和 min 一次完成,几百个节点时完全不需要 KD 树;等节点数上万再换成 MATLAB 的 KDTreeSearcher 不迟。0.10 的偏置概率是所有参数调试的起点,它让树在保持探索的同时不丢掉终点方向。
2.2 步长与最近邻搜索:从二维点换成 UR5 关节空间要改三处
把二维点替换成六维关节向量后,RRT 的代码结构不变,但三个细节必须改。第一是距离度量。关节角单位是弧度,直接算二范数意味着六个关节权重相同,而 UR5 的关节 2、3 负责大部分臂展方向运动,腕部关节 4、5、6 微小的角度差对末端位置影响小得多。常见做法是给关节设权重矩阵,距离写成sqrt((dq) * W * dq'),W 取diag([1 0.8 0.6 0.3 0.3 0.3])。
第二是关节角换装问题。UR5 的关节 1 和 6 工作范围超过一个周期,-170 度和 190 度是同一个物理位置,直接求差却得到 360 度,最近邻搜索会被误导。算距离前要对角度差做取模处理,把差值映射到 [-pi, pi] 区间。这一步不做,单臂规划偶尔能跑通,双臂 12 维空间里会频繁找错最近节点。
第三是步长语义。二维代码里 step=0.5 单位意义直观,关节空间里步长应该理解为“单步最多改变每个关节多少弧度”。我一般初始给 0.1 rad,调试时先追求连通,路径质量后面再处理。步长太大容易穿过细窄通道,太小则迭代次数暴涨,这个参数是第 5 章必调项里的第一位。
2.3 碰撞检测先做起来:线段插值代替单点判断
二维代码里的碰撞检测只查新节点,步长穿越障碍角落时会漏检。关节空间也一样,只检测步进终点的关节角远远不够。改成线段插值检测:
function hit = segmentCollision(p1, p2, obstacle, nSub) % nSub 是子步数,把线段均匀切段后逐点检测 for i = 1 : nSub t = i / nSub; p = p1 + (p2 - p1) * t; if p(1) > obstacle(1) && p(1) < obstacle(3) && ... p(2) > obstacle(2) && p(2) < obstacle(4) hit = true; return; end end hit = false; end对 UR5,线段变成了关节空间插值:每个子步的关节角都要过一次正运动学,再看连杆是否碰到障碍。这一步是双臂规划里最耗时的地方。所以第 3 章先把 UR5 运动学建模做扎实,第 4 章再讲怎么用包围球把碰撞检测从毫秒级降到微秒级。
3. 用标准 DH 参数建 UR5 运动学模型,再把采样空间接到关节角上
3.1 UR5 的 DH 参数表与一个 15 行的正解函数
双臂规划不管用什么调用方式,正运动学都是地基。UR5 常用的标准 DH 参数如下表,注意这里用的是标准 DH(SDH)约定,alpha 和 d 的符号约定和修正 DH 不同,别混用。
| i | a_i (m) | alpha_i (rad) | d_i (m) | theta_i |
|---|---|---|---|---|
| 1 | 0 | pi/2 | 0.089159 | q1 |
| 2 | -0.425 | 0 | 0 | q2 |
| 3 | -0.39225 | 0 | 0 | q3 |
| 4 | 0 | pi/2 | 0.10915 | q4 |
| 5 | 0 | -pi/2 | 0.09465 | q5 |
| 6 | 0 | 0 | 0.0823 | q6 |
对应正解函数可以写成下面这段。输入 1x6 关节角,返回末端法兰盘的 4x4 齐次矩阵。
function T = ur5_fk(q) % q 为 1x6 关节角,返回末端工具坐标系位姿 4x4 矩阵 a = [0, -0.425, -0.39225, 0, 0, 0]; d = [0.089159, 0, 0, 0.10915, 0.09465, 0.0823]; alpha = [pi/2, 0, 0, pi/2, -pi/2, 0]; T = eye(4); for i = 1:6 % 标准DH相邻坐标系变换矩阵 T = T * [cos(q(i)) -sin(q(i))*cos(alpha(i)) sin(q(i))*sin(alpha(i)) a(i)*cos(q(i)); sin(q(i)) cos(q(i))*cos(alpha(i)) -cos(q(i))*sin(alpha(i)) a(i)*sin(q(i)); 0 sin(alpha(i)) cos(alpha(i)) d(i); 0 0 0 1]; end end循环体内构造的就是 SDH 的 A 矩阵,第 i 次迭代把坐标系 i-1 变换到坐标系 i。需要留意的是仿真 q 和真实 UR5 控制器读数之间存在零位偏移,直接在仿真里用这个表没问题,要下发真实机器人时就得查官方零位数据做修正,否则末端位置会整体偏移几毫米到几厘米。
3.2 把笛卡尔目标位姿变成 RRT 的目标节点
RRT 的采样空间永远是关节角,但双臂任务的目标通常给的是末端位姿,比如“左臂 tool0 到达某点,右臂 tool0 到达另一点”。直接在一片 12 维关节空间里均匀采样,想落进目标位姿附近的小邻域,概率趋近于零。常见做法是先用逆运动学把目标位姿换算成关节角,再把这个关节角向量当作 RRT 的 goal 节点。
MATLAB 里用 Robotics System Toolbox 的 inverseKinematics 对象做这件事。这个对象需要手动给初始猜测 q0,而且逆解容易掉进局部极小值。我一般写一个包装函数:随机生成十几组 q0,依次调用 ik,把第一组满足位置误差小于 1e-4 的结果返回。注意 UR5 腕部有冗余,多组逆解对应同一末端位姿,RRT 只需要其中一个能连上的解,所以多试几组不亏。
更稳的方案是目标区域化:不把 goal 当单点,而是在目标位姿附近定义一个允许误差范围,采样时先在这个范围内随机取一个位姿,再逆解成关节角。这个思路对应机器人规划里的 TSR,能明显提高双臂 RRT 终点附近的连接成功率。
3.3 用刚体树模型给自写正解做交叉验证
自己抄 DH 表很容易出错。双臂实验开始前,先用 MATLAB 里的现成模型做一次一致性检查:loadrobot('universalUR5')加载官方刚体树模型,用getTransform(robot, q, 'tool0')算出同一组 q 下的末端位姿,再和自己写的ur5_fk(q)对比。两者误差超过 1e-6 时,优先检查 DH 表里 alpha 的符号和 d 的正负。
刚体树模型本身也能直接参与规划验证:把两个 robot 对象分别加载,右臂基座通过一个固定变换平移到工作台右侧。这个变换矩阵最后要写进双臂碰撞检测的坐标统一逻辑里,第 4 章会用到。自写正解加工具箱交叉验证的好处是,后面不管算 RRT 距离、碰撞检测还是轨迹导出,你都能确定运动学这一层没有暗病。
4. 双臂 UR5 协同规划:双向 RRT 与互碰撞检测的 MATLAB 实现
4.1 为什么直接拼出 12 维节点就废了
双臂规划最直接的做法是把关节角拼成q = [qL; qR],12 维向量参与距离计算和步进。问题在于采样效率崩塌:12 维均匀随机采样的有效覆盖体积远小于 6 维,而且两臂互为障碍,任意采样点要同时满足“左臂不碰环境”“右臂不碰环境”“两臂互不碰撞”三个条件。后一项的概率随两臂工作空间重叠程度急剧下降,纯随机 RRT 跑 10 万次采样常常连不上目标。
改进方向不是换掉 RRT,而是改采样分布和生长方式。常见做法是中心化 RRT:每次采样一个 12 维向量,但不再全空间均匀分布,而是在已有节点邻域内做高斯扰动。这样树沿着已探索区域向前推进,而不是把大量采样浪费在不可达区域。配合双向生长后,双臂同时运动的问题就落在“两棵树能否快速相遇”上。
4.2 双向 RRT 与目标偏置的 MATLAB 主循环
双向 RRT 的思路是同时从起点和终点各长一棵树,两棵树交替扩展,各自向对方方向逼近。下面是双臂场景下的主循环骨架:
function path = biRRT(robotL, robotR, qStartL, qStartR, qGoalL, qGoalR, step, maxIter) % 输入左右臂起止关节角,输出 12 维路径点列,每列为 [qL; qR] qStart = [qStartL(:); qStartR(:)]; qGoal = [qGoalL(:); qGoalR(:)]; T1.nodes = qStart; T1.parent = 0; T2.nodes = qGoal; T2.parent = 0; for k = 1:maxIter % 第一棵树:10% 概率直接指向终点,否则在现有节点邻域采 if rand < 0.1 qTarget = qGoal; else qTarget = sampleNearby(T1, step * 4); % 高斯扰动 end qNew1 = extendOneStep(T1, qTarget, step); if ~isempty(qNew1) % 第二棵树尝试连续向 qNew1 扩展,最多 6 步 qNew2 = extendTo(T2, qNew1, step, 6); if ~isempty(qNew2) && norm(qNew2 - qNew1) < step * 2 path = mergeTrees(T1, T2, qNew1, qNew2); return; end end % 每轮交换两棵树,保证交替生长 tmp = T1; T1 = T2; T2 = tmp; end path = []; end代码里的 qTarget 不再全空间均匀采样,而是从已有节点中随机选一个,在其邻域做高斯扰动。这个改动保留了 RRT 的探索能力,又避免了 12 维空间盲采的浪费。extendTo 让第二棵树连续扩展向第一棵树的新节点,而不是每轮只走一步,这是 RRT-Connect 的关键加速点,也是双臂场景下能让两棵树在几百次迭代内相遇的主要原因。
mergeTrees 时有一个方向陷阱:T1 的 parent 从起点指向终点方向,T2 的 parent 从终点指向起点方向,拼接时必须把 T2 的路径反转后再接到 T1 尾部。路径提取本身还是沿 parent 回溯,只是 T2 这边回溯出来的是从终点到相遇点的序列。
4.3 双臂互碰检测:用连杆包围球代替网格求交
双臂规划里每步扩展都要检查两臂是否互相穿过,这个检查在 12 维空间里会执行上万次。网格求交精度高,但 MATLAB 里算一次代价太大,跑 10 万次实验时间不可控。常见的工程方案是给每根连杆包上几个球,球心固定在连杆坐标系下,半径略微放大留安全余量:
| 连杆 | 球数 | 球半径 (m) |
|---|---|---|
| shoulder | 1 | 0.09 |
| upper arm | 1 | 0.06 |
| forearm | 1 | 0.055 |
| wrist 4/5 | 2 | 0.04 |
| tool0 | 1 | 0.03 |
检测逻辑是逐球计算球心距离,小于半径和即判定碰撞:
function hit = linkSphereCollision(spheresL, spheresR) % spheres 每行为 [x y z r],球心已转换到世界坐标系 hit = false; for i = 1 : size(spheresL, 1) for j = 1 : size(spheresR, 1) if norm(spheresL(i, 1:3) - spheresR(j, 1:3)) < ... spheresL(i, 4) + spheresR(j, 4) hit = true; return; end end end end球心坐标怎么来?第 3 节的 ur5_fk 在循环里每一步都在输出连杆坐标系的 T 矩阵,把每个球在连杆坐标系里的局部坐标乘上对应的 T,再乘上该臂基座在世界坐标系的变换矩阵,就统一到了同一坐标系。双臂场景下,右臂基座多一个固定偏移变换。环境对单臂的碰撞也用同一套逻辑,只需要把另一侧球列表换成环境障碍物的包围球,代码不用改。
5. 双臂 RRT 的三个必调参数、批量验证与 UR5 轨迹导出
5.1 三个必调参数
| 参数 | 建议初值 | 调大后果 | 调小后果 |
|---|---|---|---|
| stepSize | 0.1 rad | 路径粗糙、容易穿过细窄通道 | 迭代次数暴涨、单次规划时间不可接受 |
| pGoalBias | 0.10 | 收敛快但绕障能力明显下降 | 探索更充分但两棵树相遇变慢 |
| maxIter | 2e4 | 内存和时间占用变大 | 复杂场景下可能连不上目标 |
另外还有一个容易忽略的 connectTol:两棵树端点距离小于 2 倍步长时判定连接成功。阈值太严会变成永不相遇,太松会生成明显跳变的坏路径。所有实验前把这三个参数加阈值固定成一份公共配置,对比不同障碍场景才有意义。
5.2 批量实验与成功率统计
单次规划的路径图常常骗人,成功率才是硬指标。用下面的脚本框架跑批处理,随机起终点、记录耗时、计算成功率:
rng(42); trials = 20; success = zeros(trials, 1); times = zeros(trials, 1); for i = 1:trials qStartL = randomConfiguration(robotL); % 没有工具箱就均匀采样代替 qStartR = randomConfiguration(robotR); qGoalL = randomConfiguration(robotL); qGoalR = randomConfiguration(robotR); tic path = biRRT(robotL, robotR, qStartL, qStartR, ... qGoalL, qGoalR, 0.1, 2e4); times(i) = toc; if ~isempty(path) success(i) = 1; len = 0; for j = 2:size(path, 2) len = len + norm(path(:, j) - path(:, j-1)); end fprintf('trial %d: ok, len=%.3f, time=%.2fs\n', ... i, len, times(i)); else fprintf('trial %d: fail, time=%.2fs\n', i, times(i)); end end fprintf('success rate: %.1f%%\n', 100 * mean(success));批处理脚本本身也是检验算法实现是否正确的手段:成功率稳定在 70% 以上才谈得上参数调优;低于 30% 时优先怀疑碰撞检测或双向连接逻辑,而不是继续加 maxIter。新版 MATLAB 对脚本化批处理越来越友好,类似 Codex 的编程智能体也能像执行 Python 一样接手这类参数扫描实验,前提是你的实验入口函数保持无状态、参数全部从外部传入。
5.3 把路径点变成 UR5 可执行的轨迹
RRT 输出的是离散关节向量序列,直接下发真实 UR5 会看到明显的速度突变。常见做法是用trapveltraj在相邻路径点之间生成梯形速度轮廓的关节轨迹,插值密度按控制器周期设置,UR5 常见是 8ms 或 125Hz。真实部署前再做一次短接:把路径上能被直线无碰撞连接的两个节点间的中间点剔除,用前面 segmentCollision 的思路逐段验证,通常能减少 30% 左右的路径点数。剔除后的路径更短,后续速度规划也更平滑。
本文还有配套的精品资源,点击获取