ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

具身智能入门:基于ROS 2搭建感知-决策-控制抓取原型

具身智能入门:基于ROS 2搭建感知-决策-控制抓取原型 最近具身智能赛道又传来密集的融资消息。继宇树科技在四足机器人领域站稳脚跟后其背后的部分投资机构又联手把目光投向了一个新的具身智能创业团队。虽然具体投资金额和股东名单需要以官方披露为准但一个明显趋势是具身智能Embodied AI已经从实验室概念快速走向了资本与产业共同押注的核心赛道。对开发者来说与其只盯着“谁投了谁”不如看清这个方向背后的技术栈。具身智能团队要解决的核心问题不只是“让大模型会聊天”而是让机器人能真实地感知环境、做出决策、执行物理动作。这篇文章不讨论具体投资标的而是从技术开发视角拆解具身智能的基础概念、核心系统组成并带你从零搭建一个基于 ROS 2 的简易具身抓取原型。读完你会理解感知-决策-控制闭环如何落地也能掌握 ROS 2 多节点开发的基本套路。1. 背景与核心概念1.1 具身智能是什么“具身智能”这个词听起来比较学术但拆开看并不复杂。它指的是让智能体拥有一个物理身体并通过这个身体与环境持续交互从而获取信息、做出决策、完成动作。这里的“身体”可以是机械臂、四足机器人、人形机器人也可以是搭载了摄像头和底盘的移动机器人。与我们熟悉的大语言模型不同大语言模型主要处理文本和图像输出的是文字、代码或分析结果它本身不会移动任何物理物体。而具身智能强调的是“知行合一”既要有感知能力也要有操作能力。一个典型的具身智能系统通常由三部分构成感知模块通过摄像头、激光雷达、触觉传感器、惯性测量单元等设备获取环境信息。决策模块根据感知结果和任务目标规划下一步动作。执行模块通过机械臂、夹爪、轮式底盘等硬件把决策转化为真实世界的物理动作。这也是为什么具身智能团队往往同时具备算法、系统、硬件三方面能力。资本重仓这类团队本质上是在押注一个判断AI 的下一阶段必须要落到物理世界里去解决问题。1.2 具身智能解决的核心问题具身智能并不是一个单点技术而是一整套系统问题。它要解决的关键场景包括泛化操作在非结构化环境中识别并抓取从未见过的物体。桌面上的杂物、货架上的商品、家庭环境里的杯子都可能是目标。环境交互机器人不能只是“看得见”还要能根据动作反馈持续调整。比如夹爪第一次没夹稳第二次就要换一个角度和力度。安全与可靠性真实物理系统对错误非常敏感。机械臂运动过快可能伤人夹爪力度过大会损坏物体这在真实环境中是不可接受的。从技术实现角度看一个具身智能项目通常要处理目标检测、抓取姿态估计、运动规划、力控夹取等一系列问题。任何一环掉链子整个任务都会失败。这也是具身智能开发比纯算法开发更有挑战性的原因它要求开发者具备全链路视角。1.3 具身智能的关键技术方向目前业内比较关注的技术方向可以归纳为四块技术方向核心内容常见工具/框架感知2D/3D 目标检测、点云分割、多传感器融合OpenCV、PCL、YOLO、Segment Anything决策任务规划、强化学习、模仿学习、VLM 操作策略PyTorch、TensorFlow、Isaac Lab控制运动学/动力学控制、阻抗控制、模型预测控制ROS 2 Control、MuJoCo、OMPL仿真迁移在仿真中训练策略再迁移到真实机器人MuJoCo、Gazebo、Isaac Sim这里需要提醒一下仿真迁移Sim-to-Real是具身智能落地的关键一步。因为真机训练成本高、风险大通常先在仿真环境里大规模训练策略再通过域随机化、数字孪生等手段迁移到真机。后面我们实现的示例虽然比较简单但也会体现这种“先在仿真/模拟数据里验证再对接真实设备”的思路。2. 环境准备与版本说明2.1 硬件选型参考如果是一个真实的具身智能团队硬件配置通常包含六轴或七轴协作机械臂电动夹爪或灵巧手RGB-D 相机例如 RealSense 系列机载计算平台NVIDIA Jetson 系列或工业 PC这些硬件决定了机器人的感知上限和执行上限。不过在入门阶段不一定要立刻购买真机完全可以用仿真环境和模拟图像来学习核心流程。本文为了降低门槛使用程序生成的模拟图像代替真实相机用打印日志代替真实机械臂驱动重点演示软件架构和多节点协作方式。2.2 软件环境本文示例的运行环境如下实际版本需要根据你的项目情况调整操作系统Ubuntu 22.04ROS 2 发行版Humble HawksbillPython 版本3.10依赖库OpenCV、NumPy、cv_bridge如果还没有安装 ROS 2可以先用官方安装脚本或二进制包安装。安装完成后建议再安装 colcon 构建工具和 cv_bridgesudo apt update sudo apt install ros-humble-desktop python3-colcon-common-extensions sudo apt install ros-humble-cv-bridge sudo apt install python3-opencv python3-numpy这里有一个细节需要注意在 ROS 2 环境里cv_bridge是基于系统 OpenCV 编译的因此推荐用apt安装python3-opencv尽量避免使用pip安装的opencv-python否则可能出现 ABI 不兼容的问题。安装完成后记得 source 一下环境source /opt/ros/humble/setup.bash2.3 示例项目结构我们接下来要搭建的项目是一个 ROS 2 Python 包包含四个节点分别负责模拟图像发布、目标感知、抓取决策和运动控制。项目结构如下ros2_ws/ └── src/ └── embodied_demo/ ├── package.xml ├── setup.py ├── setup.cfg ├── resource/ │ └── embodied_demo └── embodied_demo/ ├── __init__.py ├── image_publisher_node.py ├── perception_node.py ├── decision_node.py └── control_node.py这个结构是 ROS 2 Python 包的标准结构。setup.py负责声明包的元信息和可执行节点package.xml声明依赖embodied_demo/目录下放 Python 源码。3. 核心原理拆解感知-决策-控制闭环3.1 为什么要把系统拆成三个模块很多初学者在写机器人程序时习惯把“看目标、想动作、动机械臂”写在一个大循环里。这种写法在极简单的 Demo 中能跑通但一旦涉及真实项目就会非常痛苦目标检测换模型要改主流程运动规划调参数要改主流程机械臂驱动升级还要改主流程。把系统拆成感知、决策、控制三个模块最大的好处是解耦。每个模块只负责一件事模块之间通过标准消息通信。这样即使感知模型从颜色阈值检测换成深度神经网络决策和控制模块也完全不用改动。在 ROS 2 里这种模块化设计天然对应着“节点Node 话题Topic”的架构。用事件流来描述整个过程是这样的相机节点发布图像消息。感知节点订阅图像消息检测目标发布目标像素坐标消息。决策节点订阅目标坐标计算抓取点发布抓取指令消息。控制节点订阅抓取指令模拟机械臂运动返回执行结果。这种流水线结构在真实机器人系统中非常常见。3.2 感知模块从图像到目标位置感知模块负责从传感器数据中提取有效信息。在抓取任务中最常见的是从 RGB 图像中检测目标物体并输出目标在图像中的位置。实现感知的方式有很多传统方法基于颜色阈值、边缘检测、模板匹配优点是简单快速、可解释性强。深度学习方法基于 YOLO、Faster R-CNN 等目标检测模型优点是泛化能力强但需要数据标注和算力。基础模型方法基于 Segment Anything、CLIP 等能实现开放词汇分割适合复杂场景。本文示例使用颜色阈值检测因为它最容易复现也不依赖预训练权重。但你要明白这只是一个教学简化。真实场景中感知模块往往要输出目标的类别、位置、姿态甚至要结合点云信息做 6D 位姿估计。3.3 决策模块从像素到抓取点感知模块给出的是目标在像素坐标系下的位置而机械臂运动需要的是三维空间中的坐标。决策模块的核心工作就是完成从图像坐标到机器人坐标的转换。这里涉及到几个坐标系像素坐标系图像上的 (u, v) 坐标单位是像素。相机坐标系以相机光心为原点的三维坐标系单位是米。机械臂基座坐标系以机械臂底座为原点的三维坐标系单位是米。像素坐标转相机坐标需要用到相机内参焦距 fx、fy光心 cx、cy和深度值 zx_cam (u - cx) * z / fx y_cam (v - cy) * z / fy z_cam z相机坐标转基座坐标需要用到相机与机械臂之间的变换矩阵也就是常说的“手眼标定”。这个矩阵可以通过标定工具获得但在本文示例中我们为了保持代码简洁使用一个简化的固定平移偏移来近似。3.4 控制模块从目标点到关节运动控制模块接收到目标点后理论上要做的事情包括运动学逆解计算机械臂各关节角度使末端执行器到达目标位置。轨迹规划在关节空间或笛卡尔空间生成平滑轨迹避免加速度突变。闭环控制根据反馈不断修正误差最终精确到达目标点。这些工作在真实机械臂上非常复杂。本文为了聚焦整体流程使用“日志模拟”的方式代替真实运动控制控制节点收到抓取点后打印执行日志并发布一个抓取结果消息。这个简化不影响理解整体架构后续如果接入真实机械臂只需要替换控制节点的内部实现即可。3.5 仿真环境与 Sim-to-Real在真实团队里仿真环境几乎必不可少。常见的仿真工具有Gazebo与 ROS 集成度高适合搭建复杂场景。MuJoCo物理引擎轻量、计算速度快适合强化学习训练。Isaac Lab / Isaac SimNVIDIA 生态适合大规模并行训练和数字孪生。仿真环境的意义在于可以在虚拟世界里低成本试错模型没训练好、控制参数不合适、机械臂撞到障碍物都不会造成真实损失。仿真到真机的迁移Sim-to-Real则通过域随机化、噪声注入、真实感渲染等手段让仿真里学到的策略在真机上也能生效。4. 完整实战案例基于 ROS 2 搭建具身抓取原型下面我们就开始搭建一个最小可运行的具身抓取原型。整个流程不需要真机只需要一台安装了 ROS 2 的 Ubuntu 电脑。4.1 创建 ROS 2 工作空间和包打开终端先创建工作空间和包mkdir -p ~/ros2_ws/src cd ~/ros2_ws/src ros2 pkg create --build-type ament_python embodied_demo创建完成后进入包目录cd embodied_demo使用ros2 pkg create会自动生成package.xml、setup.py、setup.cfg和resource/目录。接下来我们需要覆盖这些文件。4.2 配置 package.xml编辑package.xml写入以下内容?xml version1.0? ?xml-model hrefhttp://download.ros.org/schema/package_format3.xsd schematypenshttp://www.w3.org/2001/XMLSchema? package format3 nameembodied_demo/name version0.0.1/version descriptionEmbodied AI grasp demo/description maintainer emailyour_emailexample.comyour_name/maintainer licenseApache-2.0/license exec_dependrclpy/exec_depend exec_dependstd_msgs/exec_depend exec_dependsensor_msgs/exec_depend exec_dependgeometry_msgs/exec_depend exec_dependcv_bridge/exec_depend export build_typeament_python/build_type /export /package这里声明了运行时依赖rclpy是 ROS 2 的 Python 客户端库std_msgs、sensor_msgs、geometry_msgs提供消息类型cv_bridge负责 OpenCV 图像和 ROS 图像消息之间的转换。4.3 配置 setup.py编辑setup.py写入以下内容from setuptools import setup package_name embodied_demo setup( namepackage_name, version0.0.1, packages[package_name], data_files[ (share/ament_index/resource_index/packages, [resource/ package_name]), (share/ package_name, [package.xml]), ], install_requires[setuptools], zip_safeTrue, maintaineryour_name, maintainer_emailyour_emailexample.com, descriptionEmbodied AI grasp demo, licenseApache-2.0, entry_points{ console_scripts: [ image_publisher_node embodied_demo.image_publisher_node:main, perception_node embodied_demo.perception_node:main, decision_node embodied_demo.decision_node:main, control_node embodied_demo.control_node:main, ], }, )在 ROS 2 Python 包中entry_points里的console_scripts会在构建后生成可执行命令映射到源码里的main函数。4.4 编写图像发布节点图像发布节点的作用是模拟相机循环发布一张包含红色方块的图像。这样即使没有真实摄像头也能驱动后续的感知流程。文件路径embodied_demo/embodied_demo/image_publisher_node.py#!/usr/bin/env python3 import cv2 import numpy as np import rclpy from rclpy.node import Node from sensor_msgs.msg import Image from cv_bridge import CvBridge class ImagePublisherNode(Node): def __init__(self): super().__init__(image_publisher_node) self.publisher_ self.create_publisher(Image, /image_raw, 10) self.bridge CvBridge() self.timer self.create_timer(0.1, self.timer_callback) def timer_callback(self): # 创建一张 640x480 的黑色背景图像 image np.zeros((480, 640, 3), dtypenp.uint8) # 在图像中央绘制一个红色方块 cv2.rectangle(image, (260, 190), (380, 290), (0, 0, 255), -1) # 转换为 ROS 2 Image 消息并发布 msg self.bridge.cv2_to_imgmsg(image, encodingbgr8) self.publisher_.publish(msg) self.get_logger().info(发布模拟图像) def main(argsNone): rclpy.init(argsargs) node ImagePublisherNode() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ __main__: main()这个节点每 0.1 秒发布一帧图像。图像中央的红色方块是我们要检测的目标。4.5 编写感知节点感知节点订阅/image_raw话题使用 OpenCV 的 HSV 颜色阈值检测红色目标并发布目标中心的像素坐标和深度值。文件路径embodied_demo/embodied_demo/perception_node.py#!/usr/bin/env python3 import cv2 import numpy as np import rclpy from rclpy.node import Node from sensor_msgs.msg import Image from std_msgs.msg import Float32MultiArray from cv_bridge import CvBridge class PerceptionNode(Node): def __init__(self): super().__init__(perception_node) self.subscription self.create_subscription( Image, /image_raw, self.image_callback, 10 ) self.publisher_ self.create_publisher(Float32MultiArray, /target_pixel, 10) self.bridge CvBridge() def image_callback(self, msg): # 将 ROS 2 Image 消息转换为 OpenCV 图像 frame self.bridge.imgmsg_to_cv2(msg, desired_encodingbgr8) hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 红色在 HSV 空间中分布在两个区间这里取第一个区间 lower_red np.array([0, 100, 100]) upper_red np.array([10, 255, 255]) mask cv2.inRange(hsv, lower_red, upper_red) # 查找轮廓 contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if len(contours) 0: self.get_logger().info(未检测到目标) return # 取面积最大的轮廓 c max(contours, keycv2.contourArea) M cv2.moments(c) if M[m00] 0: return # 计算目标中心像素坐标和深度值 u M[m10] / M[m00] v M[m01] / M[m00] depth 0.5 msg Float32MultiArray() msg.data [float(u), float(v), float(depth)] self.publisher_.publish(msg) self.get_logger().info(f检测到目标中心: u{u:.2f}, v{v:.2f}, depth{depth:.2f}) def main(argsNone): rclpy.init(argsargs) node PerceptionNode() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ __main__: main()这里需要注意OpenCV 4.x 中findContours返回两个值如果使用 OpenCV 3.x可能需要适配返回值。我们的环境以 Ubuntu 22.04 自带 OpenCV 4.x 为例。4.6 编写决策节点决策节点订阅目标像素坐标完成从像素坐标到机械臂基座坐标的转换然后发布抓取点。文件路径embodied_demo/embodied_demo/decision_node.py#!/usr/bin/env python3 import rclpy from rclpy.node import Node from std_msgs.msg import Float32MultiArray from geometry_msgs.msg import Point class DecisionNode(Node): def __init__(self): super().__init__(decision_node) self.subscription self.create_subscription( Float32MultiArray, /target_pixel, self.pixel_callback, 10 ) self.publisher_ self.create_publisher(Point, /grasp_command, 10) # 相机内参示例值实际需标定 self.fx 500.0 self.fy 500.0 self.cx 320.0 self.cy 240.0 # 相机坐标系到机械臂基座坐标系的简化平移 # 实际项目中应由手眼标定得到 self.cam_to_base [0.3, 0.0, 0.2] def pixel_callback(self, msg): u, v, z msg.data # 像素坐标 - 相机坐标 x_cam (u - self.cx) * z / self.fx y_cam (v - self.cy) * z / self.fy z_cam z # 相机坐标 - 机械臂基座坐标简化处理 x_base x_cam self.cam_to_base[0] y_base y_cam self.cam_to_base[1] z_base z_cam self.cam_to_base[2] # 发布抓取点 point Point(xx_base, yy_base, zz_base) self.publisher_.publish(point) self.get_logger().info(f计算抓取点: ({x_base:.3f}, {y_base:.3f}, {z_base:.3f})) def main(argsNone): rclpy.init(argsargs) node DecisionNode() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ __main__: main()在真实项目中相机坐标系到机械臂基座坐标系的变换不能这么简单。手眼标定会得到一个 4x4 的齐次变换矩阵包含旋转和平移两部分示例中只用了平移是为了让读者聚焦消息流转逻辑。4.7 编写控制节点控制节点接收抓取点模拟机械臂执行抓取动作并发布抓取结果。文件路径embodied_demo/embodied_demo/control_node.py#!/usr/bin/env python3 import rclpy from rclpy.node import Node from geometry_msgs.msg import Point from std_msgs.msg import String class ControlNode(Node): def __init__(self): super().__init__(control_node) self.subscription self.create_subscription( Point, /grasp_command, self.grasp_callback, 10 ) self.publisher_ self.create_publisher(String, /grasp_result, 10) def grasp_callback(self, point): self.get_logger().info( f[Control] 接收抓取点: x{point.x:.3f}, y{point.y:.3f}, z{point.z:.3f} ) self.get_logger().info([Control] 执行运动规划并闭合夹爪模拟执行) result String() result.data fsuccess{point.x:.3f},{point.y:.3f},{point.z:.3f} self.publisher_.publish(result) self.get_logger().info([Control] 抓取动作完成) def main(argsNone): rclpy.init(argsargs) node ControlNode() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ __main__: main()到这里四个节点都写完了。4.8 编译和运行回到工作空间根目录编译这个包cd ~/ros2_ws colcon build --packages-select embodied_demo source install/setup.bash编译成功后打开四个终端分别运行四个节点终端 1source ~/ros2_ws/install/setup.bash ros2 run embodied_demo image_publisher_node终端 2source ~/ros2_ws/install/setup.bash ros2 run embodied_demo perception_node终端 3source ~/ros2_ws/install/setup.bash ros2 run embodied_demo decision_node终端 4source ~/ros2_ws/install/setup.bash ros2 run embodied_demo control_node如果一切正常你会在终端 2 中看到类似输出[INFO] [perception_node]: 检测到目标中心: u320.00, v240.00, depth0.50终端 3 中会看到[INFO] [decision_node]: 计算抓取点: (0.300, 0.000, 0.700)终端 4 中会看到[INFO] [control_node]: [Control] 接收抓取点: x0.300, y0.000, z0.700 [INFO] [control_node]: [Control] 执行运动规划并闭合夹爪模拟执行 [INFO] [control_node]: [Control] 抓取动作完成这是因为红色方块中心正好位于图像中心 (320, 240)经过相机内参和深度转换再叠加相机到基座的平移最终得到基座坐标系下的抓取点。如果你想观察话题通信情况可以在任意终端运行ros2 topic list ros2 topic echo /grasp_commandros2 topic echo /grasp_command会实时打印决策节点发布的消息方便验证节点间的数据流转。5. 常见问题与排查思路在按照上述流程操作时可能会遇到一些经典问题。下面列一个排查表方便快速定位。问题现象常见原因解决思路colcon build提示找不到包环境变量没有 source执行source /opt/ros/humble/setup.bash后再 buildModuleNotFoundError: No module named cv_bridge未安装 cv_bridge执行sudo apt install ros-humble-cv-bridgeModuleNotFoundError: No module named cv2OpenCV 未安装或环境冲突使用sudo apt install python3-opencv避免 pip 安装感知节点一直提示“未检测到目标”HSV 阈值范围不匹配把图像保存为图片用 OpenCV 调试 HSV 范围抓取点坐标跳动较大目标检测不稳定给坐标加滤波或者提高轮廓面积阈值四个节点运行后无任何输出话题名称不匹配用ros2 topic list检查各节点实际发布/订阅的话题名决策节点计算出的坐标明显异常相机内参或相机到基座关系不对检查 fx、fy、cx、cy 和 cam_to_base 参数ros2 run找不到命令build 后没有 source install 环境执行source ~/ros2_ws/install/setup.bash排查的时候推荐按照“从数据流源头开始”的顺序先确认图像节点是否发布ros2 topic hz /image_raw。再确认感知节点是否检测到目标看终端日志。然后确认决策节点是否发布抓取点ros2 topic echo /grasp_command。最后确认控制节点是否收到消息。这样逐级排查能快速定位问题出在感知、决策还是控制环节。6. 最佳实践与工程建议6.1 仿真先行真机兜底具身智能开发最忌讳的是“直接上真机调参”。一次不合理的运动就可能损坏硬件或者造成安全隐患。正确做法是在仿真环境里验证算法逻辑再用仿真数据做初步调优最后在受控的真机环境中逐步验证。即使像本文这样的最小 Demo也可以先在模拟图像和模拟控制上跑通再替换真实传感器和机械臂驱动。6.2 用 ROS 2 的参数系统替代硬编码在示例代码中相机内参和坐标偏移直接写在类属性里。这种方式在复现时很清晰但到了真实项目中应该尽量使用 ROS 2 的参数系统。比如self.declare_parameter(fx, 500.0) self.fx self.get_parameter(fx).get_parameter_value().double_value这样调参与代码分离不需要改代码就能适配不同的相机和机械臂。6.3 统一消息协议感知、决策、控制三个节点之间通信的消息要提前设计好。不要今天用Float32MultiArray传坐标明天改成String传 JSON。消息结构一旦变动所有下游节点都要改。建议在项目初期就定义好接口消息例如分别定义TargetPose、GraspCommand等自定义消息而不是直接使用原始数组。6.4 日志与数据回放真实机器人调试时日志非常关键。除了打印关键节点状态还应该用 ROS 2 的 bag 工具录制话题数据ros2 bag record -o grasp_demo /image_raw /target_pixel /grasp_command录制下来的数据可以在离线环境下反复回放用于问题排查、算法迭代和回归测试。这个习惯能极大提升团队协作效率。6.5 安全边界与权限控制无论做哪类具身智能开发安全永远是第一优先级。建议至少做到这几点真机调试前先设置速度限制和力矩限制。确保急停按钮在任何情况下都能立即切断动力。控制代码要加看门狗机制防止节点崩溃后机械臂处于失控状态。涉及远程连接时使用最小权限账号避免暴露调试接口。在软件开发层面还要重视依赖版本锁定。ROS 2 发行版、Python 库版本、驱动版本都会影响运行结果建议用 Docker 镜像固化开发环境避免“在我电脑上能跑”的尴尬。7. 总结与下一步学习路线通过这篇文章我们从“宇树投资人重仓具身团队”这个行业信号切入梳理了具身智能的技术内涵和落地痛点并基于 ROS 2 亲手搭建了一个最小可运行的抓取原型。这个原型虽然只用了模拟图像和模拟控制但完整走通了“感知 → 决策 → 控制”的闭环流程也展示了 ROS 2 多节点协作的基本模式。接下来如果你想继续深入具身智能开发有几个方向值得重点学习机械臂运动学与控制学习正解、逆解、轨迹规划理解机械臂如何精确到达目标点。强化学习与模仿学习学习如何让机器人在仿真环境中通过试错获得操作策略。大模型与具身智能结合关注 VLM视觉语言模型如何帮助机器人理解自然语言指令并规划任务。仿真到真机迁移学习域随机化、系统辨识、数字孪生等 Sim-to-Real 技术。具身智能是一个典型的复合型领域既需要扎实的算法基础也需要系统集成能力。资本的重仓只是开始真正的价值还是要靠开发者把每一行代码、每一次调试、每一个稳定运行的系统落到实处。希望这篇教程能帮你迈出第一步如果过程中遇到其他问题也欢迎在评论区分享你的现象和日志我们一起排查。
返回列表