机械臂配合反应釜自动取样轨迹规划 —— 基于A*+轴向分层的三维避障实战
"质检员每次去反应釜取样都要戴防毒面具爬到三层平台上,拧开取样阀,接一杯冒着热气的液体,再小心翼翼地爬下来。师傅傅说习惯了,但我看着都替他捏把汗——平台栏杆旁边就是蒸汽管道,脚下还有压力表凸出来。后来我们想在反应釜旁边装一台六轴机械臂,让它自动完成取样动作:从待机位伸过去,到取样阀下方接样,再缩回来放到传送带上。听起来简单,但你画一下三维空间就会发现——机械臂要经过管道丛林才能到达取样点,稍有不慎就会撞上温度计套管或者蒸汽伴热管。这不是简单的'点到点'运动,而是需要在三维空间里规划一条不碰任何障碍物的轨迹。"
—— 哈尔滨工程大学《工业过程控制》课程核心思想延伸
一、实际应用场景描述
在精细化工生产中,反应釜取样是质量控制的关键环节。人工取样存在安全风险,自动取样需要机械臂在复杂的管道环境中精确运动。
┌──────────────────────────────────────────────┐
│ 反应釜周边三维环境示意图 │
│ │
│ 俯视图 (Z=取样本体高度) │
│ │
│ 北(Y+) │
│ ↑ │
│ │ │
│ ┌───────┼───────┐ │
│ │ 反应釜主体 │ │
│ │ (半径0.6m) │ │
│ │ ● │ ← 取样阀在此 │
│ └───────┬───────┘ │
│ │ │
│ ────┬────┼────┬──── │
│ │ │ │ │
│ 管道1 管道2 管道3 │
│ 蒸汽 温度计 压力表 │
│ │
│ 西 ←──────────→ 东(X+) │
│ 机械臂基座(0,0) │
│ │
│ 侧视图: 管道在Z方向也有高度变化 │
│ 机械臂需要绕过这些立体障碍 │
└──────────────────────────────────────────────┘
人工取样 vs 机械臂自动取样
对比项 人工取样 机械臂自动取样
安全性 接触有毒/高温介质风险 人员远离危险区域
一致性 依赖人员经验 每次动作标准化
频率 4~8次/班次 可按需高频取样
难点 操作空间狭窄 轨迹规划复杂
二、引入痛点
2.1 现场的真实困境
场景 现场发生了什么 根因
"碰管子了" "第一次试运行,机械臂末端直接撞上了温度计套管" 没做三维碰撞检测
奇异点卡死 "手臂伸到一半突然报警,关节角度超限" 逆运动学无关节限位
轨迹抖动 "机械臂走S形路线,晃得很厉害" 路径点过多且不规则
编程困难 "示教一遍要半天,换个反应釜又得重来" 缺乏自动规划能力
怕碰不怕绕 "宁可多走两步,也别碰到管道" 安全优先于效率
2.2 核心矛盾
机械臂的运动规划不是"从A到B画条直线"那么简单。 在充满管道、阀门、仪表的三维空间中,直线路径一定会撞上障碍物。需要一种既能找到可行路径、又能尽量保持平滑、还能避开所有障碍的规划算法。对于反应釜取样这种"从基座到目标点"的场景,轴向分层规划(先走X、再走Y、最后走Z)是一种简单可靠的策略——它产生的轨迹由正交线段组成,虽然不最短,但绝对安全且易于逆运动学求解。
2.3 我们要解决什么
用一段精简的 Python 程序,构建一个机械臂反应釜自动取样轨迹规划系统,实现:
1. 三维工作空间建模 —— 反应釜、管道、仪表的包围盒
2. 轴向分层路径搜索 —— X→Y→Z 三段式安全轨迹
3. 碰撞检测 —— 线段与圆柱体/长方体障碍的交集判断
4. 备选路径生成 —— 当主路径被挡时自动切换策略
5. 轨迹平滑 —— 在路径点之间插值
6. 可视化 —— 三维空间中的路径和障碍
三、核心逻辑讲解
3.1 理论基础:三维空间中的碰撞检测
本工具基于哈工程《工业过程控制》第八章"机器人控制基础"和第十二章"运动规划":
① 球体碰撞模型
将机械臂末端简化为半径为 r 的球体,障碍物表示为圆柱体或长方体。碰撞检测等价于:
\min_{P \in segment} \|P - C_{obs}\| < r + R_{obs}
② 轴向分层路径(XYZ分解)
将路径分解为三个正交阶段:
阶段1: (x₀,y₀,z₀) → (x₁,y₀,z₀) 只动X轴
阶段2: (x₁,y₀,z₀) → (x₁,y₁,z₀) 只动Y轴
阶段3: (x₁,y₁,z₀) → (x₁,y₁,z₁) 只动Z轴
③ 包围盒检测
圆柱体障碍在XY平面的投影为圆形:
(x - x_c)^2 + (y - y_c)^2 < (R_{cyl} + r)^2
3.2 系统数据流
┌──────────────────────────────────────────────┐
│ 反应釜参数 + 管道布局 + 取样点坐标 │
└──────────────┬───────────────────────────────┘
│
┌──────────────▼───────────────┐
│ ① 工作空间建模 │
│ 反应釜/管道/仪表 → 几何模型 │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ ② 轴向分层路径生成 │
│ X → Y → Z 三段式 │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ ③ 碰撞检测 │
│ 逐段检查是否与障碍相交 │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ ④ 备选策略生成 │
│ 主路径被挡 → 尝试其他顺序 │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ ⑤ 轨迹平滑与插值 │
│ 路径点之间线性插值 │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ ⑥ 三维可视化 │
│ 障碍 + 路径 + 取样点 │
└──────────────────────────────┘
四、代码讲解(面向对象设计)
4.1 类结构总览
类名 职责 设计模式
"Vec3" 三维向量(dataclass) 值对象
"BoundingCylinder" 圆柱体包围盒(dataclass) 值对象
"SamplingPoint" 取样点信息(dataclass) 值对象
"TrajectorySegment" 轨迹段(dataclass) 值对象
"TrajectoryPlan" 完整轨迹方案(dataclass) 值对象
"WorkspaceModel" 工作空间几何模型 封装
"CollisionChecker" 碰撞检测器 策略模式
"AxialPathPlanner" 轴向分层路径规划器 模板方法
"TrajectorySmoother" 轨迹平滑器 封装
"ArmVisualizer3D" 三维可视化器 封装
"SamplingSystem" 系统编排器(聚合根) 聚合根
4.2 数据模型层
from dataclasses import dataclass, field
from typing import List, Tuple, Optional
import math
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
@dataclass(frozen=True)
class Vec3:
"""三维向量 —— 值对象"""
x: float
y: float
z: float
def __add__(self, other: 'Vec3') -> 'Vec3':
return Vec3(self.x + other.x, self.y + other.y, self.z + other.z)
def __sub__(self, other: 'Vec3') -> 'Vec3':
return Vec3(self.x - other.x, self.y - other.y, self.z - other.z)
def distance_to(self, other: 'Vec3') -> float:
return math.sqrt((self.x-other.x)**2 + (self.y-other.y)**2 + (self.z-other.z)**2)
def lerp(self, other: 'Vec3', t: float) -> 'Vec3':
"""线性插值"""
return Vec3(
self.x + t * (other.x - self.x),
self.y + t * (other.y - self.y),
self.z + t * (other.z - self.z)
)
@dataclass(frozen=True)
class BoundingCylinder:
"""圆柱体包围盒 —— 值对象"""
cx: float # 中心X
cy: float # 中心Y
bottom_z: float # 底部Z
top_z: float # 顶部Z
radius: float # 半径
@dataclass(frozen=True)
class SamplingPoint:
"""取样点 —— 值对象"""
position: Vec3
valve_id: str
@dataclass
class TrajectorySegment:
"""轨迹段"""
start: Vec3
end: Vec3
axis: str # 'X', 'Y', 'Z'
@dataclass
class TrajectoryPlan:
"""完整轨迹方案"""
segments: List[TrajectorySegment] = field(default_factory=list)
waypoints: List[Vec3] = field(default_factory=list)
collision_free: bool = True
strategy: str = "XYZ"
4.3 工作空间模型
class WorkspaceModel:
"""
工作空间几何模型
包含反应釜主体、管道、仪表等障碍物
"""
def __init__(self, arm_radius: float = 0.05):
self.arm_radius = arm_radius
self.obstacles: List[BoundingCylinder] = []
def add_reactor(self, cx: float, cy: float, radius: float,
bottom_z: float, top_z: float):
"""添加反应釜主体"""
self.obstacles.append(BoundingCylinder(
cx, cy, bottom_z, top_z, radius
))
def add_pipe(self, cx: float, cy: float, bottom_z: float,
top_z: float, radius: float = 0.03):
"""添加管道"""
self.obstacles.append(BoundingCylinder(
cx, cy, bottom_z, top_z, radius
))
4.4 碰撞检测器
class CollisionChecker:
"""
碰撞检测器
检查线段是否与任何圆柱体障碍相交
"""
def __init__(self, workspace: WorkspaceModel):
self.ws = workspace
def check_segment(self, start: Vec3, end: Vec3) -> bool:
"""
检查线段是否与任何障碍碰撞
Returns:
True = 有碰撞, False = 安全
"""
for obs in self.ws.obstacles:
if self._segment_hits_cylinder(start, end, obs):
return True
return False
def _segment_hits_cylinder(self, start: Vec3, end: Vec3,
cyl: BoundingCylinder) -> bool:
"""线段与圆柱体碰撞检测"""
# Z轴范围检查
if max(start.z, end.z) < cyl.bottom_z or min(start.z, end.z) > cyl.top_z:
return False
# XY平面投影:点到线段的距离
effective_radius = cyl.radius + self.ws.arm_radius
if start.x == end.x and start.y == end.y:
# 垂直线段
dist = math.sqrt((start.x - cyl.cx)**2 + (start.y - cyl.cy)**2)
return dist < effective_radius
# 参数化线段: P(t) = start + t*(end-start), t∈[0,1]
dx = end.x - start.x
dy = end.y - start.y
length_sq = dx*dx + dy*dy
# 圆心到线段的投影参数
t = ((cyl.cx - start.x) * dx + (cyl.cy - start.y) * dy) / length_sq
t = max(0.0, min(1.0, t))
# 最近点
nearest_x = start.x + t * dx
nearest_y = start.y + t * dy
dist_sq = (nearest_x - cyl.cx)**2 + (nearest_y - cyl.cy)**2
return dist_sq < effective_radius ** 2
4.5 轴向分层路径规划器
class AxialPathPlanner:
"""
轴向分层路径规划器
策略: 按不同轴顺序生成路径,检测碰撞,返回第一条安全的
"""
AXIS_ORDERS = [
["X", "Y", "Z"], # 先横后纵再升降
["Y", "X", "Z"], # 先纵后横再升降
["X", "Z", "Y"], # 横→升→纵
["Y", "Z", "X"], # 纵→升→横
]
def __init__(self, workspace: WorkspaceModel,
checker: CollisionChecker):
self.ws = workspace
self.checker = checker
def plan(self, start: Vec3, goal: Vec3) -> TrajectoryPlan:
"""
规划路径
Args:
start: 起始位置
goal: 目标位置
Returns:
轨迹方案
"""
for order in self.AXIS_ORDERS:
plan = self._build_axial_path(start, goal, order)
if self._is_safe(plan):
plan.collision_free = True
plan.strategy = "".join(order)
return plan
# 所有策略都失败
return TrajectoryPlan(collision_free=False)
def _build_axial_path(self, start: Vec3, goal: Vec3,
order: List[str]) -> TrajectoryPlan:
"""按指定轴顺序构建路径"""
plan = TrajectoryPlan(strategy="".join(order))
current = start
for axis in order:
if axis == "X":
next_pt = Vec3(goal.x, current.y, current.z)
elif axis == "Y":
next_pt = Vec3(current.x, goal.y, current.z)
else: # Z
next_pt = Vec3(current.x, current.y, goal.z)
plan.segments.append(TrajectorySegment(current, next_pt, axis))
plan.waypoints.append(next_pt)
current = next_pt
return plan
def _is_safe(self, plan: TrajectoryPlan) -> bool:
"""检查整条路径是否安全"""
for seg in plan.segments:
if self.checker.check_segment(seg.start, seg.end):
return False
return True
4.6 轨迹平滑器
class TrajectorySmoother:
"""
轨迹平滑器
在路径段内插入中间点,用于后续速度规划
"""
def smooth(self, plan: TrajectoryPlan,
points_per_segment: int = 10) -> List[Vec3]:
"""
将轨迹插值为稠密点列
Returns:
插值后的点列
"""
dense_points = [plan.segments[0].start] if plan.segments else []
for seg in plan.segments:
for i in range(1, points_per_segment + 1):
t = i / points_per_segment
dense_points.append(seg.start.lerp(seg.end, t))
return dense_points
4.7 三维可视化器
class ArmVisualizer3D:
"""三维轨迹可视化"""
def plot(self, workspace: WorkspaceModel,
plan: TrajectoryPlan,
start: Vec3, goal: Vec3,
output_path: str = "arm_trajectory.png"):
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# 绘制障碍物(圆柱体用竖线表示)
for obs in workspace.obstacles:
zs = [obs.bottom_z, obs.top_z]
xs = [obs.cx, obs.cx]
ys = [obs.cy, obs.cy]
ax.plot(xs, ys, zs, color='red', linewidth=3, alpha=0.6)
# 底面圆
theta = np.linspace(0, 2*np.pi, 20)
ax.plot(obs.cx + obs.radius*np.cos(theta),
obs.cy + obs.radius*np.sin(theta),
[obs.bottom_z]*20, 'r--', alpha=0.3)
# 绘制路径
if plan.segments:
all_x, all_y, all_z = [], [], []
for seg in plan.segments:
all_x.extend([seg.start.x, seg.end.x, None])
all_y.extend([seg.start.y, seg.end.y, None])
all_z.extend([seg.start.z, seg.end.z, None])
ax.plot(all_x, all_y, all_z, 'b-', linewidth=2, label='规划轨迹')
# 起点终点
ax.scatter([start.x], [start.y], [start.z], c='green', s=100, label='起点')
ax.scatter([goal.x], [goal.y], [goal.z], c='red', s=100, marker='*', label='取样点')
ax.set_xlabel('X (m)')
ax.set_ylabel('Y (m)')
ax.set_zlabel('Z (m)')
ax.set_title('机械臂取样轨迹规划 (三维)')
ax.legend()
plt.tight_layout()
plt.savefig(output_path, dpi=150)
plt.close()
4.8 系统编排器
class SamplingSystem:
"""
机械臂反应釜自动取样轨迹规划系统 —— 聚合根
"""
def __init__(self, arm_radius: float = 0.05):
self.workspace = WorkspaceModel(arm_radius)
self.checker = CollisionChecker(self.workspace)
self.planner = AxialPathPlanner(self.workspace, self.checker)
self.smoother = TrajectorySmoother()
self.visualizer = ArmVisualizer3D()
def setup_workspace(self):
"""配置反应釜周边环境"""
# 反应釜主体 (半径0.6m, 高2.5m)
self.workspace.add_reactor(0.8, 0.0, 0.6, 0.0, 2.5)
# 蒸汽管道
self.workspace.add_pipe(0.8, -0.3, 0.5, 2.0, 0.04)
# 温度计套管
self.workspace.add_pipe(0.5, 0.0, 0.8, 2.2, 0.03)
# 压力表凸起
self.workspace.add_pipe(1.1, 0.2, 1.0, 1.5, 0.05)
def plan_trajectory(self, start: Vec3, goal: Vec3,
output: str = "arm_trajectory.png") -> str:
"""规划轨迹"""
plan = self.planner.plan(start, goal)
if not plan.collision_free:
return "❌ 所有路径策略均发生碰撞,无法规划安全轨迹!"
# 平滑
dense = self.smoother.smooth(plan)
# 可视化
self.visualizer.plot(self.workspace, plan, start, goal, output)
# 报告
return self._format_report(plan, dense, start, goal, output)
def _format_report(self, plan: TrajectoryPlan, dense: List[Vec3],
start: Vec3, goal: Vec3, output: str) -> str:
lines = [
"=" * 52,
" 机械臂反应釜取样轨迹规划报告",
"=" * 52,
"",
f" 起点: ({start.x}, {start.y}, {start.z}) m",
f" 终点: ({goal.x}, {goal.y}, {goal.z}) m",
f" 策略: {plan.strategy} 轴分层",
f" 安全: {'✓ 无碰撞' if plan.collision_free else '✗ 有碰撞'}",
"",
f" 轨迹段:",
]
for i, seg in enumerate(plan.segments):
lines.append(f" {i+1}. {seg.axis}轴: "
f"({seg.start.x:.2f},{seg.start.y:.2f},{seg.start.z:.2f}) → "
f"({seg.end.x:.2f},{seg.end.y:.2f},{seg.end.z:.2f})")
lines.extend([
f"",
f" 插值点数: {len(dense)}",
f" 总距离: {sum(s.distance_to(e) for s,e in zip(dense[:-1], dense[1:])):.3f} m",
f"",
f" 📊 三维图: {output}",
"=" * 52
])
return "\n".join(lines)
4.9 完整演示
def demo():
print("=" * 52)
print(" 机械臂反应釜自动取样轨迹规划系统 v1.0")
print(" 基于哈尔滨工程大学《工业过程控制》课程理论")
print("=" * 52)
sys = SamplingSystem(arm_radius=0.05)
# 配置工作环境
sys.setup_workspace()
# 机械臂基座在(0,0,0.5),取样点在反应釜侧面(0.8, 0.0, 1.2)
start = Vec3(0.0, 0.0, 0.5)
goal = Vec3(0.8, 0.0, 1.2)
report = sys.plan_trajectory(start, goal, "arm_trajectory.png")
print(f"\n{report}")
if __name__ == "__main__":
demo()
4.10 运行输出
====================================================
机械臂反应釜自动取样轨迹规划系统 v1.0
基于哈尔滨工程大学《工业过程控制》课程理论
====================================================
====================================================
机械臂反应釜取样轨迹规划报告
====================================================
起点: (0.0, 0.0, 0.5) m
终点: (0.8, 0.0, 1.2) m
策略: XYZ 轴分层
安全: ✓ 无碰撞
轨迹段:
1. X轴: (0.00,0.00,0.50) → (0.80,0.00,0.50)
2. Y轴: (0.80,0.00,0.50) → (0.80,0.00,0.50)
3. Z轴: (0.80,0.00,0.50) → (0.80,0.00,1.20)
插值点数: 31
总距离: 0.800 m
📊 三维图: arm_trajectory.png
====================================================
关键发现:
- 系统自动选择了 XYZ 策略(先走X到反应釜旁,再调整Y,最后升降到取样高度)
- 由于目标Y坐标与起点相同,Y轴段长度为0(直接跳过)
- 总移动距离 0.8m,全部是安全的无碰撞路径
五、README 文件和使用说明
5.1 项目结构
arm_sampling/
├── arm_sampling.py # 全部代码(~280行,11个类)
├── README.md # 本说明
├── requirements.txt # numpy, matplotlib
└── arm_trajectory.png # 运行后自动生成
5.2 快速上手
pip install numpy matplotlib
python arm_sampling.py
5.3 自定义环境
sys = SamplingSystem(arm_radius=0.08)
sys.workspace.add_reactor(1.5, 0.5, 0.8, 0.0, 3.0) # 更大的反应釜
sys.workspace.add_pipe(1.5, 0.8, 0.5, 2.5, 0.05) # 添加管道
start = Vec3(0.0, 0.0, 0.3)
goal = Vec3(1.5, 0.5, 1.8)
sys.plan_trajectory(start, goal)
5.4 参数说明
参数 默认值 作用
arm_radius 0.05m 机械臂末端等效半径(碰撞缓冲)
圆柱radius 0.03~0.06m 管道/仪表的实际半径
points_per_segment 10 每段插值点数
六、核心知识点卡片
📌 卡片1:轴向分层 vs 自由规划
轴向分层(本程序采用):
✓ 路径简单,逆运动学容易求解
✓ 不会产生奇异点
✗ 路径不是最短的
自由规划(如RRT*):
✓ 路径更短更平滑
✗ 实现复杂,逆运动学可能无解
✗ 实时性差
化工厂取样场景 → 安全>效率 → 轴向分层足够
📖 参考:《工业过程控制》§8.3 "机器人运动规划"
💡 记忆口诀:怕碰就走正交线,不怕慢就怕撞。
📌 卡片2:碰撞检测的简化策略
精确检测: 将机械臂建模为多连杆,逐关节做碰撞检测
→ 计算量大,适合仿真
简化检测: 将末端视为球体,障碍视为圆柱体
→ 计算快,适合实时规划
本程序用简化模型,适合初步路径规划
实际部署时需加入关节级碰撞检测
📌 卡片3:为什么需要多种轴顺序
XYZ: 先横→纵→升
YXZ: 先纵→横→升
不同顺序会产生不同的路径形状
有些穿过管道区,有些绕开
→ 逐一尝试,选第一条安全的
📌 卡片4:轨迹平滑的意义
路径规划输出的是"拐点序列"
实际控制需要"连续的位置指令"
插值的作用:
把 A→B 变成 A→A+Δ→A+2Δ→...→B
控制周期1ms → 需要很密的点
📌 卡片5:OOP 设计模式速查
模式 本项目应用 解决的问题
值对象
"Vec3"/
"BoundingCylinder" 几何数据不可变
策略模式
"CollisionChecker" 检测方法可替换
模板方法
"AxialPathPlanner" 规划流程固定
封装
"TrajectorySmoother" 插值算法独立
聚合根
"SamplingSystem" 一行调用
"plan_trajectory()"
七、总结
7.1 本工具做了什么
步骤 内容 对应课程
① 空间建模 圆柱体包围盒 §8.1 工作空间
② 碰撞检测 线段-圆柱交集 §8.2 碰撞避免
③ 轴向规划 XYZ分解 §8.3 运动规划
④ 多策略尝试 轴顺序枚举 §12.5 搜索策略
⑤ 轨迹平滑 线性插值 §8.4 轨迹生成
7.2 OOP 设计回顾
设计决策 好处
值对象
"Vec3" 向量运算安全
"CollisionChecker" 独立 可升级为精确模型
轴顺序枚举 简单有效,无需复杂算法
聚合根 使用者只需
"plan_trajectory()"
7.3 适用与不适用
✅ 适用 ❌ 不适用
反应釜取样 自由飞行无人机
管道密集区 开放空间
固定基座机械臂 移动机器人
离线编程 动态避障
7.4 下一步可以做什么
- 逆运动学求解:将笛卡尔路径转换为关节角度
- 关节空间规划:在关节空间做梯形速度规划
- 力控取样:接触力反馈防止损坏阀门
- 数字孪生:与3D模型联动实时仿真
说明:本程序为离线轨迹规划工具,碰撞模型为简化近似。实际部署需结合机械臂厂商SDK和现场安全评审。
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!