简介:本资源是一个面向机器人算法工程师、ROS2初学者及高校科研人员的机械臂视觉抓取仿真系统,聚焦于“视觉感知—运动规划—物理仿真—人机交互”全链路闭环实现。包内共230个文件,涵盖61个Python核心脚本(含YOLOv8-OBB检测推理、MoveIt2运动规划接口、逆运动学求解模块)、28个YAML配置(ROS2参数与控制器定义)、28个SDF模型(Gazebo物理仿真环境)、17个XACRO宏文件(URDF机械臂建模)及STL/DAE三维模型、RVIZ可视化配置等,压缩包仅10.24MB,结构清晰、模块解耦。目前已有78人学习下载,资源提供完整可运行的ROS2 Humble级工程,包含PySide6图形界面实时显示目标检测框与机械臂状态、Gazebo中带真实物理碰撞的抓取仿真流程、以及基于IKFast的高效逆解实现,开箱即用,适合教学演示、算法验证与二次开发。
1. 这不是又一个“ROS2+Gazebo+MoveIt2”Demo:它把视觉抓取闭环真正跑通在Humble+Ubuntu 22.04上,且YOLOv8-OBB直接输出旋转框坐标喂给MoveIt2逆解模块
你肯定见过 dozens 个标着“ROS2机械臂抓取”的GitHub仓库——点开一看,要么是rviz里手动拖动目标、要么是用固定坐标硬编码抓取位姿、要么YOLO只输出类别和中心点,再靠玄学角度凑出抓取姿态。这个包不一样:它在标准Humble环境(Ubuntu 22.04)下,用真实Gazebo物理引擎加载Panda机械臂模型,YOLOv8-OBB实时检测场景中任意朝向的矩形物体(比如螺丝刀、药瓶、电路板),直接输出带角度的OBB边界框(cx, cy, w, h, θ),然后通过PySide6界面一键触发——从图像→像素坐标→相机标定→3D位姿估计→MoveIt2运动规划→逆运动学求解→Gazebo关节控制,全链路无硬编码、无手动干预、无rviz交互。我实测在i7-11800H + RTX3060笔记本上,端到端延迟稳定在320ms以内(YOLOv8s + OpenCV DNN后端)。适合正在做毕业设计、工业分拣原型或ROS2工程化落地的开发者,尤其适合被“rviz里点十次才动一下”折磨过的同学。
2. 环境搭建与依赖安装:为什么必须用Humble+Ubuntu 22.04,以及三个绕不开的编译陷阱
2.1 为什么锁定Humble而非Foxy或Iron?——Gazebo、MoveIt2与PySide6的版本咬合点
ROS2 Humble(2022年5月发布)是首个官方支持Ubuntu 22.04 LTS的长期支持版,也是目前唯一同时满足以下条件的发行版:
- Gazebo Classic(11.x)与ROS2 Humble的
gazebo_ros_pkgs完全兼容(Foxy仅支持Gazebo9,Iron已弃用Gazebo Classic); - MoveIt2正式版(2.6+)对Humble的
rclcppABI稳定,且moveit_ros_planning_interface已移除大量deprecated API; - PySide6 6.4+(本项目要求6.5.1.1)在Humble的
ament_cmake_python构建系统中能正确导出Qt元对象,避免qRegisterMetaType崩溃(这是Foxy下PySide6无法用于ROS2 GUI的核心原因)。
提示:不要尝试在Ubuntu 20.04+Foxy上强行移植——Gazebo物理参数加载失败率超70%,且YOLOv8-OBB的
cv2.dnn后端在OpenCV 4.5.4(Foxy默认)中不支持ONNX Runtime的RotatedRect输出解析。
2.2 安装步骤:四步到位,跳过所有apt源坑
# Step 1: 添加ROS2官方源(关键!必须用packages.ros.org,非aliyun镜像) sudo apt update && sudo apt install curl gnupg2 lsb-release curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add - echo "deb [arch=$(dpkg --print-architecture)] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2-latest.list # Step 2: 安装Humble核心(不含桌面完整版,节省空间) sudo apt update sudo apt install ros-humble-desktop ros-humble-gazebo-ros-pkgs ros-humble-moveit ros-humble-moveit-resources-panda-moveit-config # Step 3: 安装PySide6(必须指定版本,6.5.1.1是唯一通过QtWebEngine兼容测试的版本) pip3 install --upgrade pip pip3 install PySide6==6.5.1.1 # Step 4: 安装YOLOv8依赖(避开torch 2.0+与ROS2 C++ ABI冲突) pip3 install ultralytics==8.1.32 opencv-python-headless==4.8.1.78 numpy==1.24.4参数说明:
ros-humble-moveit-resources-panda-moveit-config是本项目唯一预配置的机械臂配置包,包含Panda的SRDF、URDF、kinematics.yaml及OMPL规划器配置;ultralytics==8.1.32是最后一个支持results.boxes.xywhr(即OBB五元组)且不强制升级torch的版本;opencv-python-headless避免GUI冲突,但保留cv2.dnn模块——本项目所有图像推理均在后台线程完成,PySide6仅负责显示。
2.3 编译工作空间:ament build的三个隐藏开关
进入解压后的项目根目录(假设为~/ros2_vision_grasp),执行:
cd ~/ros2_vision_grasp source /opt/ros/humble/setup.bash colcon build --symlink-install \ --cmake-args -DCMAKE_BUILD_TYPE=Release \ --no-warn-unused-cli \ --parallel-workers 4关键参数解释:
--symlink-install:避免每次修改Python脚本都要重新colcon build,直接软链接到install/目录,开发调试效率提升3倍;-DCMAKE_BUILD_TYPE=Release:启用O3优化,YOLOv8推理速度提升22%(实测从41ms→32ms);--parallel-workers 4:匹配主流4核CPU,过高会导致Gazebo模型加载竞争失败(现象:gazebo_ros节点卡在Waiting for service /spawn_entity)。
编译成功后,运行source install/setup.bash即可激活环境。
3. 核心模块拆解:YOLOv8-OBB如何把旋转框喂进MoveIt2运动规划器
3.1 YOLOv8-OBB推理层:从ONNX输出到ROS2消息的精准映射
本项目不使用ultralytics的model.predict()高层API,而是直接加载ONNX模型并解析原始输出,确保OBB角度θ与MoveIt2期望的RPY顺序严格一致:
# 文件:src/vision_node/yolo_detector.py import onnxruntime as ort import numpy as np from sensor_msgs.msg import Image from geometry_msgs.msg import PoseStamped class YOLOv8OBBDetector: def __init__(self): self.session = ort.InferenceSession("models/yolov8s-obb.onnx", providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) # ONNX输出格式:[batch, num_dets, 6] → [cx, cy, w, h, θ, conf] # 注意:θ单位为弧度,且以图像x轴为基准逆时针旋转(符合ROS2坐标系约定) def detect(self, cv_image: np.ndarray) -> List[PoseStamped]: # 图像预处理:BGR→RGB→归一化→NHWC→NCHW input_tensor = self.preprocess(cv_image) # 输出shape: (1, 3, 640, 640) outputs = self.session.run(None, {"images": input_tensor}) # 解析OBB输出(outputs[0].shape == (1, 100, 6)) detections = outputs[0][0] # [num_dets, 6] valid_dets = detections[detections[:, 5] > 0.5] # 置信度阈值 poses = [] for det in valid_dets: cx_px, cy_px, w_px, h_px, theta_rad, conf = det # 关键转换:像素坐标→相机坐标系3D点(需内参矩阵K) # K = [[fx, 0, cx], [0, fy, cy], [0, 0, 1]] # 此处省略深度图获取逻辑(实际使用RealSense ROS2驱动发布的`/camera/depth/image_rect_raw`) # 最终得到3D位置(x,y,z)及旋转四元数(由θ推导绕z轴旋转) pose = self.pixel_to_pose(cx_px, cy_px, w_px, h_px, theta_rad, depth_mm) poses.append(pose) return poses逻辑说明:
theta_rad直接作为绕z轴的旋转角传入tf_transformations.quaternion_from_euler(0, 0, theta_rad),生成四元数;w_px/h_px不直接用于抓取宽度,而是反推物体在相机坐标系下的3D尺寸(需结合深度值和焦距),确保夹爪开口宽度动态匹配;- 所有坐标转换均调用
tf2_ros.Buffer.lookup_transform("panda_link0", "camera_color_optical_frame", ...),保证坐标系对齐。
3.2 MoveIt2运动规划接口:绕过RVIZ,用Python API直连规划器
传统教程教你在RVIZ里点“Plan & Execute”,但本项目用moveit_py(MoveIt2官方Python绑定)实现全自动规划:
# 文件:src/planning_node/moveit_planner.py from moveit_py import MoveItPy from moveit_msgs.msg import MotionPlanRequest from geometry_msgs.msg import PoseStamped class PandaGraspPlanner: def __init__(self): self.moveit = MoveItPy(node_name="panda_grasp_planner") self.planning_component = self.moveit.get_planning_component("panda_arm") def plan_and_execute_grasp(self, target_pose: PoseStamped): # 设置目标位姿(含旋转) self.planning_component.set_goal_state(pose_stamped=target_pose, pose_reference_frame="panda_link0") # 关键:设置抓取姿态约束(避免手腕翻转) constraint = Constraints() orientation_constraint = OrientationConstraint() orientation_constraint.header.frame_id = "panda_link0" orientation_constraint.orientation = target_pose.pose.orientation orientation_constraint.absolute_x_axis_tolerance = 0.1 orientation_constraint.absolute_y_axis_tolerance = 0.1 orientation_constraint.absolute_z_axis_tolerance = 0.1 constraint.orientation_constraints = [orientation_constraint] self.planning_component.set_path_constraints(constraint) # 执行规划(同步阻塞) plan_result = self.planning_component.plan() if plan_result.successful: self.planning_component.execute(plan_result) else: self.get_logger().error("Planning failed!")参数说明:
pose_reference_frame="panda_link0"是Panda基座坐标系,所有位姿必须在此帧下定义;absolute_z_axis_tolerance=0.1允许±5.7°的z轴偏差,防止因YOLO角度噪声导致规划失败;plan_result包含完整的JointTrajectory,可直接写入/panda_arm_controller/joint_trajectorytopic。
3.3 PySide6图形界面:不是简单按钮,而是状态机驱动的抓取流程控制器
界面不是静态UI,而是基于QStateMachine的状态机:
# 文件:src/gui/main_window.py class GraspControlWindow(QMainWindow): def __init__(self): super().__init__() self.state_machine = QStateMachine() self.idle_state = QState() self.detecting_state = QState() self.planning_state = QState() self.executing_state = QState() # 状态转移逻辑 self.idle_state.addTransition(self.start_btn.clicked, self.detecting_state) self.detecting_state.addTransition(self.detector.signal_detections_ready, self.planning_state) self.planning_state.addTransition(self.planner.signal_plan_success, self.executing_state) self.executing_state.addTransition(self.executor.signal_execution_done, self.idle_state) self.state_machine.addState(self.idle_state) self.state_machine.addState(self.detecting_state) self.state_machine.addState(self.planning_state) self.state_machine.addState(self.executing_state) self.state_machine.setInitialState(self.idle_state) self.state_machine.start()设计价值:
- 避免用户连续点击“Start”导致多线程资源竞争;
- 每个状态对应ROS2节点生命周期(如
detecting_state自动启动yolo_detector节点,executing_state监听/panda_arm_controller/follow_joint_trajectory/status); - 界面按钮文字随状态自动切换(Idle→Detecting→Planning→Executing→Done)。
4. 避坑指南:五个让ROS2新手当场放弃的致命细节(附真实报错与修复)
4.1 现象:Gazebo启动后Panda模型悬浮在空中,关节无响应
原因:URDF中<gazebo>标签缺失<plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">,导致ros2_control插件未加载,关节控制器无法接收指令。
解决:检查urdf/panda.urdf.xacro,在</robot>前添加:
<gazebo> <plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so"> <parameters>$(find-pkg-share panda_moveit_config)/config/ros2_controllers.yaml</parameters> </plugin> </gazebo>4.2 现象:YOLOv8-OBB检测结果θ角全部为0,或出现π/2阶跃跳变
原因:ONNX模型输出的θ是[-π/2, π/2]范围,但quaternion_from_euler(0,0,theta)要求[-π, π],未做区间映射。
解决:在pixel_to_pose()中加入:
if theta_rad < 0: theta_rad += np.pi # 强制映射到[0, π],适配夹爪正向抓取 # 或更鲁棒的写法: theta_rad = (theta_rad + np.pi) % (2 * np.pi) - np.pi4.3 现象:PySide6界面启动报错ModuleNotFoundError: No module named 'shiboken6'
原因:pip install PySide6未自动安装shiboken6绑定库(尤其在conda环境或系统Python混用时)。
解决:显式安装:
pip3 install shiboken6==6.5.1.1注意:版本号必须与PySide6完全一致,否则
import PySide6.QtCore会段错误。
4.4 现象:MoveIt2规划成功但执行时机械臂抖动后停住
原因:ros2_control的joint_trajectory_controller默认state_publish_rate: 50Hz,而Gazebo仿真步长(<physics type='ode'>)为0.001s(1000Hz),控制器更新跟不上仿真节奏。
解决:修改config/ros2_controllers.yaml:
joint_trajectory_controller: ros__parameters: state_publish_rate: 100.0 # 提升至100Hz constraints: goal_time: 5.04.5 现象:colcon build报错CMake Error at CMakeLists.txt:17 (find_package): Could not find a package configuration file provided by "moveit_common"
原因:未正确source ROS2环境,或moveit相关包未安装(常见于只装了ros-humble-moveit但漏掉ros-humble-moveit-msgs)。
解决:确认安装完整MoveIt2套件:
sudo apt install ros-humble-moveit ros-humble-moveit-msgs ros-humble-moveit-ros-planning-interface5. 实战验证:三步验证你的系统是否真正跑通(不依赖RVIZ)
5.1 验证YOLOv8-OBB输出精度:用OpenCV可视化旋转框
启动视觉节点后,运行:
ros2 topic echo /detected_objects --no-log你会看到类似输出:
header: stamp: sec: 1712345678 nanosec: 123456789 frame_id: camera_color_optical_frame poses: - header: stamp: sec: 0 nanosec: 0 frame_id: panda_link0 pose: position: x: 0.421 y: -0.135 z: 0.128 orientation: x: 0.0 y: 0.0 z: 0.707 w: 0.707验证点:z:0.707 w:0.707对应绕z轴旋转90°(π/2),与图像中物体水平放置一致。若物体竖直,应为z:0.0 w:1.0。
5.2 验证MoveIt2规划器响应:绕过GUI直发PoseStamped
用ros2 topic pub模拟检测结果:
ros2 topic pub /target_pose geometry_msgs/PoseStamped "{ header: {frame_id: 'panda_link0'}, pose: { position: {x: 0.4, y: -0.2, z: 0.15}, orientation: {x: 0.0, y: 0.0, z: 0.707, w: 0.707} } }" -1观察/panda_arm_controller/joint_trajectory是否有消息输出,且/panda_arm_controller/state中desired.position随轨迹变化。
5.3 验证Gazebo物理响应:检查关节力矩是否合理
在Gazebo GUI中,右键Panda模型→View→Joint Trajectory Controller,查看各关节Effort曲线。正常抓取过程应呈现:
panda_joint1~3:缓慢上升至15~25 N·m(支撑臂体);panda_finger_joint1/2:峰值达30~40 N·m(夹紧力),且两指力矩差<2 N·m(防偏载);- 若某关节
Effort持续为0,说明控制器未激活或topic名不匹配(检查ros2 topic list | grep trajectory)。
6. 进阶技巧:把YOLOv8-OBB换成你自己训练的模型,三步替换不改一行C++代码
6.1 模型替换清单:只需改四个文件,无需重编译
| 文件路径 | 修改内容 | 说明 |
|---|---|---|
models/yolov8s-obb.onnx | 替换为你训练好的ONNX模型 | 必须是task=obb导出,且输入尺寸为640×640 |
config/yolo_config.yaml | 修改conf_thres: 0.5→0.7 | 针对你模型的置信度阈值 |
src/vision_node/yolo_detector.py | 修改self.input_shape = (640, 640) | 若你模型输入尺寸不同(如1280×1280),此处同步调整 |
launch/vision_launch.py | 修改params={'model_path': 'models/my_custom.onnx'} | 指向新模型路径 |
6.2 自定义数据集标注规范:LabelImg-OBB插件必须启用的三个选项
你用LabelImg标注时,务必勾选:
- ✅
Save as YOLO OBB format(生成.txt每行6个数字:class_id cx cy w h theta_rad); - ✅
Rotate image when saving(确保θ以弧度存储,非角度); - ✅
Use absolute path for images(避免相对路径导致ultralytics export失败)。
6.3 训练命令模板:Humble环境下最稳的ultralytics 8.1.32训练参数
yolo train \ model=yolov8s-obb.yaml \ data=my_dataset.yaml \ epochs=200 \ batch=16 \ imgsz=640 \ device=0 \ workers=4 \ optimizer=AdamW \ lr0=0.001 \ cos_lr=True \ save_period=10 \ project=runs/train \ name=my_obb_panda \ exist_ok=True关键参数解释:
optimizer=AdamW:比默认SGD收敛更稳,尤其对小样本OBB任务;cos_lr=True:余弦退火学习率,避免后期loss震荡;save_period=10:每10 epoch保存一次,防止训练中断丢失进度。
6.4 ONNX导出与验证:绕过yolo export的坑,手动生成可靠模型
yolo export有时会丢弃OBB头,推荐手动导出:
from ultralytics import YOLO model = YOLO("runs/train/my_obb_panda/weights/best.pt") model.export( format="onnx", opset=12, dynamic=True, simplify=True, half=False, # ROS2不支持FP16推理 imgsz=(640, 640) )导出后,用Netron打开best.onnx,确认输出节点名为output0且shape为(1, 100, 6)。
从那以后我每次换模型,都强制走一遍这三步验证:①用OpenCV读ONNX跑单张图看输出shape;②用ros2 topic echo确认消息字段;③在Gazebo里放个已知尺寸的立方体,测抓取位姿误差是否<1.5cm。这套流程跑下来,再没因为模型替换翻过车。希望帮到你。
本文还有配套的精品资源,点击获取