ROS2+Gazebo+VLA占位服务:纯仿真环境下的具身智能闭环实现

该文章已生成可运行项目,

1.安装虚拟机和ubuntu

到官网下载VMware虚拟机,注意mac电脑是用VMware Fusion Pro

官网链接:VMware Fusion Pro: Now Available Free for Personal Use - VMware Fusion Blog

这是博主年糕~milo的提供的VMware Fusion Pro 13.6链接:13.6版本 -- 提取码: 8ete

下载ubuntu镜像,推荐ubuntu 22.04.1 版本,注意M系列的Mac电脑要下载arm架构的镜像

清华源仓库链接:https://mirrors.tuna.tsinghua.edu.cn/

https://mirrors.ustc.edu.cn/ubuntu-old-releases中科大旧链接(Mac电脑首选ubuntu-22.04-live-server-arm64.iso):https://mirrors.ustc.edu.cn/ubuntu-old-releases

虚拟机安装ubuntu步骤可参考教程:https://blog.csdn.net/davidson1471/article/details/146910376

16G内存可设置ubuntu的内存为8192Mb,磁盘建议设置至少40G

2.安装ros和gazebo

在ubuntu安装ROS2,推荐用鱼香ros大佬的脚本,一步到位~

参考教程:https://blog.csdn.net/weixin_55944949/article/details/140373710

本人在home创建Project文件夹,准备放不同的项目。

打开终端,在Project文件夹运行git clone https://github.com/osrf/gazebo和

git clone https://github.com/ros-simulation/gazebo_ros_pkgs

注意gazebo不直接支持 ARM64 架构,如mac,需要手动编译安装。

参考教程:https://blog.csdn.net/codecyborgs/article/details/142934546

本人将教程source ~/.gazebo_ros/install/setup.bash改为source ~/Project/gazebo_ros_pkgs/install/setup.bash

终端依次输入gazebo和ros2 launch gazebo_ros gazebo.launch.py验证是否成功安装gazebo和 gazebo_ros_pkgs并写入环境。(提示:ctrl+c可以中断进程)

3.启动VLA占位API服务

这里在宿主机(也可以暂时在虚拟机,但以后可能要放模型推理,虚拟机里不方便硬件加速)采用uv(用conda或python原生也可以)配置Python环境

uv虚拟环境采用uv pip install fastapi uvicorn opencv-python numpy安装对应库

conda虚拟环境或python原生用pip install fastapi uvicorn opencv-python numpy

代码实现如下:

import base64
from typing import Any, Dict

import cv2
import numpy as np
from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn


app = FastAPI(title="VLA Server (stub)") # 初始化 FastAPI 应用,title可以随便起名


class ActRequest(BaseModel):
    instruction: str
    image_b64: str
    timestamp: float | None = None


def decode_image(image_b64: str) -> np.ndarray:
    # 1. 将Base64字符串解码为原始二进制图片数据
    # 先把字符串转utf-8字节串,再用base64解码得到图片的二进制数据(如jpg/png的原始字节)
    raw = base64.b64decode(image_b64.encode("utf-8"))
    
    # 2. 将二进制数据转换成numpy的uint8数组(OpenCV处理图像的基础格式)
    # uint8是因为图片像素值范围是0-255,刚好匹配无符号8位整数
    buf = np.frombuffer(raw, dtype=np.uint8)
    
    # 3. 用OpenCV解码numpy数组,得到彩色图像
    # cv2.IMREAD_COLOR 表示读取彩色图像(会自动忽略透明通道)
    img = cv2.imdecode(buf, cv2.IMREAD_COLOR)
    
    # 4. 异常校验:如果解码失败(比如Base64字符串无效/损坏),抛出明确的错误
    if img is None:
        raise ValueError("cv2.imdecode failed")
    
    # 5. 返回OpenCV可直接处理的图像数组(形状为[高度, 宽度, 3],3对应BGR三个通道)
    return img

def rule_vla(instruction: str, image_bgr: np.ndarray) -> Dict[str, Any]:
    """
    这是“占位 VLA”:保证 CPU 可跑、接口稳定。
    以后把这个函数替换成真正的 VLA(OpenVLA/Octo)推理即可。
    """
    t = (instruction or "").lower()

    # 最简单:用指令关键字决定动作(能立刻看到机器人动)
    if "stop" in t or "halt" in t:
        return {"linear_x": 0.0, "angular_z": 0.0}

    elif "left" in t:
        return {"linear_x": 0.0, "angular_z": +0.8}

    elif "right" in t:
        return {"linear_x": 0.0, "angular_z": -0.8}

    elif "back" in t:
        return {"linear_x": -0.15, "angular_z": 0.0}

    # 一个超简陋“看图避障”示例:画面越暗(靠近墙/阴影)就转一下
    gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)# 灰度转换,BGR彩色图转为单通道灰度图,既简化计算,又能聚焦于 “亮度” 这一核心特征(灰度值直接对应像素的亮度)
    center = gray[gray.shape[0] // 3 : 2 * gray.shape[0] // 3, gray.shape[1] // 3 : 2 * gray.shape[1] // 3]# 截取中心九宫格区域,避开图像边缘的干扰(比如边框、阴影、噪点),只分析核心区域的亮度,让结果更能代表图像的真实明暗
    mean_intensity = float(np.mean(center))# 计算平均亮度

    # 可以把这里替换成:目标检测 / 深度估计 / 真 VLA action
    if mean_intensity < 60:
        return {"linear_x": 0.0, "angular_z": 0.9}

    return {"linear_x": 0.20, "angular_z": 0.0}


@app.post("/act")
def act(req: ActRequest):
    img = decode_image(req.image_b64) # 解码图片
    action = rule_vla(req.instruction, img) # 根据指令处理图像后,返回具体的 “动作”结果
    return {"action": action, "model": "rule_vla_stub"} # 返回接口响应


if __name__ == "__main__":
    # 启动 Uvicorn 服务器
    # app: 要运行的 FastAPI 应用实例
    # host="0.0.0.0": 监听所有网络接口,允许外部访问
    # port=8000: 监听 8000 端口,若被占用可改端口号,但改后注意之后的代码对应的端口号也要改
    uvicorn.run(app, host="0.0.0.0", port=8000) 

在对应目录运行uv run vla_server.py(conda虚拟环境或python原生用python vla_server.py)启动

4.搭建 TurtleBot3 机器人仿真环境

在Project创建turtlebot3_ws,并在其中创建src

通过cd ~/Project/turtlebot3_ws/src进入src目录

执行git clone -b humble https://github.com/ROBOTIS-GIT/turtlebot3_simulations.git安装仿真包

执行git clone -b humble https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git克隆 turtlebot3_msgs(核心消息包)

执行git clone -b humble https://github.com/ROBOTIS-GIT/turtlebot3.git克隆 turtlebot3(包含 turtlebot3_bringup、robot_state_publisher 等)

回到工作空间turtlebot3_ws目录进行编译cd ~/Project/turtlebot3_ws && colcon build --symlink-install,将克隆的源码变成 ROS 2 能识别的功能包

colcon build(编译)的核心作用

  • 必须在工作空间根目录执行:colcon 是 ROS 2 的编译工具,它会默认扫描当前目录下的src文件夹,识别里面的功能包(比如turtlebot3_gazebo),然后把源码编译成可执行文件、库文件,同时生成install目录(相当于软件的 “安装目录”)。

    • 如果在src目录下执行colcon build,编译工具会找不到 ROS 2 工作空间的规范结构,直接报错。

  • --symlink-install是实用优化:它会给编译后的文件创建 “软链接”,后续你修改源码(比如改 launch 文件)时,不用重新编译,只需要重新 source 就能生效,节省开发时间。

执行source install/setup.bash注册路径,告知终端刚编译的仿真包的位置,把当前工作空间的功能包、launch 文件、可执行文件路径,添加到 ROS 2 的搜索环境变量中

执行export TURTLEBOT3_MODEL=waffle_pi告知 ROS 2 要使用的机器人型号是 waffle_pi(有相机)

执行ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py启动仿真环境(包含 Gazebo、机器人模型、驱动节点等)

gazebo中黑色圆点是 TurtleBot3 Waffle Pi 机器人,白色圆点是障碍物,绿色小六边形和黑边连接的六边形区域是 turtlebot3_world 场景的物理边界,限制机器人的移动范围,蓝色线是激光雷达的扫描射线

放大后可以看到圆柱障碍物之间的机器人的相机采集结果,即机器人视角下看到的场景内容

再放大,就可以看清机器人的外观,圆形的上方是相机

确认模型加载成功后,另开一个终端,需要确认话题。

执行ros2 topic list | grep camera ros2 topic,得到下面信息,可以验证相机话题通信正常

li@li:~$ ros2 topic list | grep camera
/camera/camera_info
/camera/image_raw

执行ros2 topic echo /camera/image_raw --once,得到下面信息,能够验证相机话题是否在发布数据

5.搭建 ROS 2 ↔ VLA 控制桥接包

确认话题后,准备搭建 ROS 2 ↔ VLA 控制桥接包,打通链路:ROS2 相机/指令(Topic) →(桥接:转换/打包)→ VLA(HTTP /act) →(桥接:解析/翻译)→ ROS2 /cmd_vel(Topic)

桥接(bridge)是“翻译 + 转发”的中间人,把两个本来不直接兼容的系统接起来:

  • ROS2 这边说的是 Topic 消息:
    相机 /camera/image_raw(Image)、指令 /vla/instruction(String)

  • VLA 服务这边说的是 HTTP 接口:
    你要把图像和指令打包成 HTTP 请求发给它,它回你一个动作结果

  • 机器人底盘需要的是 速度指令 Topic:
    /cmd_vel(Twist)

换句话说,这里就是把 ROS2 的数据转成 VLA 能懂的请求,再把 VLA 的输出翻译回 ROS2 能用的控制指令。

执行mkdir -p ~/Project/vla_ws/src创建VLA工作空间及其src源码文件夹
执行cd ~/Project/vla_ws/src进入src文件夹

执行ros2 pkg create vla_bridge --build-type ament_python --dependencies rclpy sensor_msgs geometry_msgs std_msgs,

生成一个标准 ROS2 Python 包骨架,包括:

  • package.xml:包的元信息与依赖声明

  • setup.py / setup.cfg:Python 打包/安装入口

  • resource/vla_bridge/ 等结构

  • 并声明它依赖:

    • rclpy(写 ROS2 节点用)

    • sensor_msgs(相机 Image 消息类型)

    • geometry_msgs(Twist,即 /cmd_vel 的常用类型)

    • std_msgs(String,用来发文本指令)

执行sudo apt-get install -y python3-opencv python3-requests ros-humble-cv-bridge安装系统依赖

把 ~/Project/vla_ws/src/vla_bridge/setup.py 改成(保留原来的元信息也行,关键是 entry_points),注册可执行入口,告诉 ROS2/ament 这个包里有哪些可运行的程序

from setuptools import setup

package_name = "vla_bridge"

setup(
    name=package_name,
    version="0.0.1",
    packages=[package_name],
    data_files=[
        ("share/ament_index/resource_index/packages", ["resource/" + package_name]),
        ("share/" + package_name, ["package.xml"]),
    ],
    install_requires=["setuptools", "requests"],
    zip_safe=True,
    maintainer="you",
    maintainer_email="you@example.com",
    description="ROS2 bridge: camera+instruction -> VLA /act -> cmd_vel",
    license="MIT",
    tests_require=["pytest"],
    entry_points={
        "console_scripts": [
            "vla_agent = vla_bridge.vla_agent_node:main",
            "send_instruction = vla_bridge.instruction_publisher:main",
        ],
    },
)

作用是编译安装后能得到两个命令(本质是 Python 入口):

  • ros2 run vla_bridge vla_agent

    启动桥接节点:订阅相机/指令 → 调 HTTP → 发布 cmd_vel
  • ros2 run vla_bridge send_instruction "xxx"

    发送一条文本指令到某个 ROS topic(默认 /vla/instruction

编写 ROS 节点 vla_agent_node.py作为桥接的主体,保存到 ~/Project/vla_ws/src/vla_bridge/vla_bridge目录中

import base64
import time
from typing import Optional

import cv2
import requests
# 导入ROS 2 Python核心库:创建节点、处理ROS通信
import rclpy
from rclpy.node import Node
# 导入ROS 2消息类型:传感器图像、字符串、运动指令
from sensor_msgs.msg import Image  # 相机图像消息
from std_msgs.msg import String    # 文本指令消息
from geometry_msgs.msg import Twist  # 机器人运动速度指令
# 导入ROS-OpenCV桥接库:实现ROS Image和OpenCV Mat格式互转
from cv_bridge import CvBridge


def cv_to_jpeg_b64(cv_img) -> str:
    """
    将OpenCV格式的图像编码为JPEG格式,并转换为Base64字符串(HTTP传输友好)
    
    Args:
        cv_img: OpenCV格式的图像(numpy.ndarray),BGR通道
    
    Returns:
        str: 编码后的Base64字符串(UTF-8编码)
    
    Raises:
        RuntimeError: 图像编码失败时抛出异常
    """
    # cv2.imencode:将OpenCV图像编码为JPEG格式的二进制缓冲区
    # [int(cv2.IMWRITE_JPEG_QUALITY), 80]:设置JPEG质量为80(平衡体积和画质)
    ok, buf = cv2.imencode(".jpg", cv_img, [int(cv2.IMWRITE_JPEG_QUALITY), 80])
    if not ok:
        raise RuntimeError("cv2.imencode failed")
    # 1. buf.tobytes():将numpy缓冲区转为二进制字节串
    # 2. base64.b64encode():对二进制数据进行Base64编码
    # 3. decode("utf-8"):将Base64二进制编码转为UTF-8字符串(方便JSON传输)
    return base64.b64encode(buf.tobytes()).decode("utf-8")


class VLABridgeNode(Node):
    """
    VLA桥接节点:实现ROS 2与VLA服务器的通信桥接,核心功能:
    1. 订阅ROS话题:
       - camera_topic (sensor_msgs/Image):机器人相机原始图像
       - instruction_topic (std_msgs/String):控制指令(如"前进"、"左转")
    2. 向VLA服务器发起HTTP POST请求:{vla_url}/act(携带指令+Base64编码的图像)
    3. 发布ROS话题:
       - cmd_vel_topic (geometry_msgs/Twist):机器人运动速度指令(来自VLA服务器返回)
    """
    def __init__(self):
        super().__init__("vla_bridge_node")  # 初始化ROS 2节点,节点名称为"vla_bridge_node"(标识)

        # ---- 声明ROS 2参数(支持运行时通过--ros-args -p 覆盖,提高灵活性)----
        self.declare_parameter("camera_topic", "/camera/image_raw")  # 相机图像话题名(默认:/camera/image_raw)
        self.declare_parameter("instruction_topic", "/vla/instruction")  # 控制指令话题名(默认:/vla/instruction)
        self.declare_parameter("cmd_vel_topic", "/cmd_vel")  # 机器人速度指令发布话题名(默认:/cmd_vel)
        self.declare_parameter("vla_url", "http://127.0.0.1:8000")  # VLA服务器地址(默认:http://127.0.0.1:8000)若VLA服务器是宿主机必须用宿主机局域网 IP
        self.declare_parameter("rate_hz", 2.0)  # 定时器频率(默认2Hz:每秒向VLA服务器请求2次)
        self.declare_parameter("http_timeout_s", 2.0)  # HTTP请求超时时间(默认2秒:防止请求卡住)
        self.declare_parameter("stop_when_no_instruction", True)  # 无指令时是否停止机器人(默认True:安全保护)

        # 读取参数值并赋值给实例变量(方便后续调用)
        self.camera_topic = self.get_parameter("camera_topic").value
        self.instruction_topic = self.get_parameter("instruction_topic").value
        self.cmd_vel_topic = self.get_parameter("cmd_vel_topic").value
        self.vla_url = self.get_parameter("vla_url").value.rstrip("/")
        self.rate_hz = float(self.get_parameter("rate_hz").value)
        self.http_timeout_s = float(self.get_parameter("http_timeout_s").value)
        self.stop_when_no_instruction = bool(self.get_parameter("stop_when_no_instruction").value)

        self.bridge = CvBridge()  # 初始化CvBridge:用于ROS Image <-> OpenCV Mat格式转换
        self.latest_img: Optional[Image] = None  # 存储最新的相机图像(初始为None:表示还未接收到图像)
        self.latest_instruction: str = ""  # 存储最新的控制指令(初始为空字符串)

        # ---- 创建ROS 2订阅器 ----
        # 订阅相机图像话题,回调函数on_image,队列大小10(缓存最多10条消息)
        self.sub_img = self.create_subscription(Image, self.camera_topic, self.on_image, 10)
        # 订阅控制指令话题,回调函数on_instruction,队列大小10
        self.sub_inst = self.create_subscription(String, self.instruction_topic, self.on_instruction, 10)
        # ---- 创建ROS 2发布器 ----
        self.pub_cmd = self.create_publisher(Twist, self.cmd_vel_topic, 10)  # 发布机器人速度指令,队列大小10

        period = 1.0 / max(self.rate_hz, 0.1)  # 计算定时器周期(秒),max避免rate_hz为0导致除零错误
        self.timer = self.create_timer(period, self.tick)  # 创建定时器:每隔period秒执行一次tick回调函数(核心业务逻辑)

        # 打印节点初始化信息(方便调试,确认参数是否正确加载)
        self.get_logger().info(f"camera_topic={self.camera_topic}")
        self.get_logger().info(f"instruction_topic={self.instruction_topic}")
        self.get_logger().info(f"cmd_vel_topic={self.cmd_vel_topic}")
        self.get_logger().info(f"vla_url={self.vla_url} (POST {self.vla_url}/act)")

    def on_image(self, msg: Image):
        """
        相机图像话题回调函数:接收到新图像时,更新最新图像缓存
        
        Args:
            msg: ROS 2 sensor_msgs/Image类型的消息(相机原始图像)
        """
        self.latest_img = msg

    def on_instruction(self, msg: String):
        """
        控制指令话题回调函数:接收到新指令时,更新最新指令缓存
        
        Args:
            msg: ROS 2 std_msgs/String类型的消息(控制指令文本)
        """
        self.latest_instruction = (msg.data or "").strip()  # strip():去除指令前后的空格/换行符,避免无效指令
        self.get_logger().info(f"Instruction updated: {self.latest_instruction!r}")  # 打印指令更新日志(方便调试,确认指令是否正确接收)

    def publish_stop(self):
        """发布停止指令:让机器人线性速度和角速度都为0(紧急停止/无指令时使用)"""
        tw = Twist()  # 创建空的Twist消息
        tw.linear.x = 0.0  # 线性速度(前进/后退)为0
        tw.angular.z = 0.0  # 角速度(左转/右转)为0
        self.pub_cmd.publish(tw)  # 发布停止指令
        # self.get_logger().info("🛑 发布停止指令")

    def tick(self):
        """
        定时器回调函数(核心业务逻辑):
        1. 检查指令/图像是否有效
        2. 图像格式转换(ROS→OpenCV→Base64)
        3. 向VLA服务器发送请求(指令+Base64图像)
        4. 解析服务器响应,发布运动指令
        """
        # 1. 无控制指令时的处理
        if not self.latest_instruction:
            if self.stop_when_no_instruction:
                self.publish_stop()  # 无指令则停止机器人
            return  # 直接返回,不执行后续逻辑
        # 2. 无相机图像时的处理
        if self.latest_img is None:
            self.get_logger().warn("⚠️ 未接收到相机图像,跳过本次请求")
            return

        # 3. ROS Image → OpenCV Mat 格式转换
        try:
            # imgmsg_to_cv2:将ROS图像转为OpenCV格式(bgr8:OpenCV默认的BGR通道)
            cv_img = self.bridge.imgmsg_to_cv2(self.latest_img, desired_encoding="bgr8")
        except Exception as e:
            self.get_logger().warn(f"❌ 图像格式转换失败: {e}")
            return

        # 4. OpenCV图像 → Base64字符串 编码
        try:
            image_b64 = cv_to_jpeg_b64(cv_img)
        except Exception as e:
            self.get_logger().warn(f"❌ 图像Base64编码失败: {e}")
            return

        # 5. 构造向VLA服务器发送的请求体(JSON格式)
        payload = {
            "instruction": self.latest_instruction,  # 控制指令
            "image_b64": image_b64,                  # Base64编码的图像
            "timestamp": time.time(),                # 请求时间戳(服务器可用于时序校验)
        }

        # 6. 向VLA服务器发起POST请求
        try:
            resp = requests.post(
                f"{self.vla_url}/act",  # 请求地址
                json=payload,           # 请求体(自动序列化为JSON)
                timeout=self.http_timeout_s,  # 请求超时时间
            )

            resp.raise_for_status()  # 如果HTTP状态码不是200(如404/500),抛出异常
            data = resp.json()  # 解析服务器返回的JSON响应            
        except Exception as e:
            self.get_logger().warn(f"❌ VLA服务器请求失败: {e}")
            return

        # 7. 解析服务器响应(预期格式:{"action": {"linear_x": float, "angular_z": float}}
        action = data.get("action", {})  # 提取action字段,无则返回空字典
        lin = float(action.get("linear_x", 0.0))  # 提取线性速度(默认0.0,避免KeyError)
        ang = float(action.get("angular_z", 0.0))  # 提取角速度(默认0.0,避免KeyError)

        # 8. 构造并发布机器人速度指令
        tw = Twist()
        tw.linear.x = lin   # 设置线性速度(正数前进,负数后退)
        tw.angular.z = ang  # 设置角速度(正数左转,负数右转)
        self.pub_cmd.publish(tw)
        self.get_logger().info(f"🚀 发布运动指令: 线性速度={lin:.2f} m/s, 角速度={ang:.2f} rad/s")


def main():
    """ROS 2节点主函数:标准启动流程"""
    rclpy.init()  # 初始化ROS 2上下文
    node = VLABridgeNode()  # 创建VLA桥接节点实例
    try:
        rclpy.spin(node)  # 自旋节点:持续处理回调函数(订阅/定时器),阻塞直到节点关闭
    finally:
        node.destroy_node()  # 确保节点正常销毁(释放资源)
        rclpy.shutdown()  # 关闭ROS 2上下文

if __name__ == "__main__":
    main()

这一步是在实现一个 ROS2 节点,它做三类事(结构层面):

  • 订阅(Subscribe)

    • 相机 topic:/camera/image_raw(Image)

    • 指令 topic:/vla/instruction(String)

  • 调用(Call)

    • 通过 HTTP POST 调VLA 服务:{vla_url}/act

  • 发布(Publish)

    • 速度指令 topic:/cmd_vel(Twist)

代码核心逻辑:ROS 2 数据采集 → 格式转换 → 网络请求 → 响应解析 → ROS 2 指令发布,实现 ROS 2 与 VLA 服务器的桥接。

编写一个最小指令注入器instruction_publisher.py,同样也保存到 ~/Project/vla_ws/src/vla_bridge/vla_bridge目录中,作用是把命令行传参发布字符串指令到指定 ROS 话题,无参数时默认发布forward

import sys
# 导入ROS 2 Python核心库:初始化ROS上下文、创建节点
import rclpy
from rclpy.node import Node
# 导入ROS 2标准字符串消息类型:用于发布文本指令
from std_msgs.msg import String

class InstructionPublisher(Node):
    """
    ROS 2指令发布节点:
         核心功能:向指定的ROS话题发布字符串类型的控制指令(如"forward"、"turn_left")
         支持通过ROS参数自定义发布的话题名,默认发布到 /vla/instruction
    """	
    def __init__(self):
        super().__init__("instruction_publisher")# 初始化ROS 2节点,节点名称为"instruction_publisher"(标识)
        # 声明ROS参数:自定义发布的话题名(默认值为/vla/instruction)
        # 支持运行时通过 --ros-args -p topic:=/xxx 覆盖默认值
        self.declare_parameter("topic", "/vla/instruction")
        # 读取参数值并赋值给实例变量,后续发布器将使用该话题名
        self.topic = self.get_parameter("topic").value
        # 创建ROS 2发布器:
        # 消息类型:String(std_msgs/String)
        # 话题名:self.topic(由参数指定)
        # 队列大小:10(缓存最多10条待发布的消息,避免消息丢失)
        self.pub = self.create_publisher(String, self.topic, 10)
        
        # 打印初始化日志,确认发布器创建成功
        self.get_logger().info(f"✅ 指令发布节点初始化完成,发布话题:{self.topic}")

    def send(self, text: str):
        """
        发布指定的文本指令到目标ROS话题
        
        Args:
            text: 要发布的指令文本(如"forward"、"stop"、"turn_right")
        """
        msg = String()  # 创建空的String消息对象(ROS 2消息必须通过对应类实例化)
        msg.data = text  # 给消息的data字段赋值(String消息的核心内容)
        self.pub.publish(msg)  # 发布消息到目标话题
        self.get_logger().info(f"📤 已发布指令到 {self.topic}:{text!r}")  # 打印日志,确认消息发布成功

def main():
    """
	节点主函数:
    1. 初始化ROS 2上下文
    2. 创建指令发布节点
    3. 处理命令行参数(获取要发布的指令,无参数则默认发"forward")
    4. 发布指令并确保消息发送完成
    5. 销毁节点并关闭ROS 2上下文
    """
    rclpy.init()# 初始化ROS 2上下文(必须第一步执行,否则无法创建节点/发布消息)
    node = InstructionPublisher()# 创建指令发布节点实例
    try:
        # 处理命令行参数:
        # sys.argv[0] 是脚本本身的路径/名称,sys.argv[1:] 是传入的所有参数
        # " ".join(...) 将参数列表拼接成字符串(支持带空格的指令,如"move forward")
        # strip() 去除首尾空格,避免空指令
        text = " ".join(sys.argv[1:]).strip()
        # 如果命令行未传入任何参数,使用默认指令"forward"
        if not text:
            text = "forward"
        node.send(text)# 调用send方法发布指令
        # 关键:spin_once让节点自旋0.2秒,确保消息被ROS 2的通信层发送出去
        # (如果直接销毁节点,可能消息还没发出去就终止了,导致接收端收不到)
        rclpy.spin_once(node, timeout_sec=0.2)
    finally:
        node.destroy_node()# 无论是否出现异常,都确保节点被销毁(释放ROS资源)
        rclpy.shutdown()# 关闭ROS 2上下文(必须最后执行,清理资源)

用 colcon 安装到 ROS2 环境里,使 ros2 run 直接启动

cd ~/Project/vla_ws
colcon build --symlink-install
source ~/Project/vla_ws/install/setup.bash

开始桥接ROS 2 与 VLA 服务器

source ~/Project/vla_ws/install/setup.bash

ros2 run vla_bridge vla_agent

若在宿主机上启动 VLA 桥接代理节点,则用ros2 run vla_bridge vla_agent --ros-args -p vla_url:="http://宿主机所局域网ip:8000"

Windows系统通过命令ipconfig查ip,在Linux/macOS 系统通过ifconfig或得到ip

开新终端,发布指令,控制机器人移动

source ~/Project/vla_ws/install/setup.bash
ros2 run vla_bridge send_instruction "forward"
# 试试:
# ros2 run vla_bridge send_instruction "left"
# ros2 run vla_bridge send_instruction "right"
# ros2 run vla_bridge send_instruction "stop"

创作不易,禁止抄袭,转载请附上原文链接及标题

本文章已经生成可运行项目
基于OpenCV开发的智元远征A2机器人右手抓取动作数据集采集工程,主要用于训练VLA(Visual Language Action)模型(源码),开箱即用。 该工程实现了从视频流采集、物体位置检测到机械臂抓取控制的完整流程,并记录抓取过程中的机械臂关节数据、手部数据和视频数据,用于后续的机器学习训练。 系统架构 核心功能模块 相机标定与参数管理:提供相机内参和外参的配置与加载 视频流采集:实时获取机器人头部前向相机的视频流 物体检测与定位:基于颜色识别检测黄色圆柱和绿色盒子,并计算其在机器人base_link坐标系下的位置 机械臂抓取控制:通过RPC接口控制机械臂执行抓取动作 数据记录与导出:记录抓取过程中的各种数据并导出为HDF5格式 项目结构 ├── camera_calibration_result.json # 相机标定结果文件 ├── create_calibration_target/ # 标定板创建工具 ├── grasp_step/ # 抓取步骤相关文件 ├── create_camera_calibration_result.py # 创建相机标定结果文件 ├── get_video_data.py # 视频数据采集脚本 ├── get_object_position_base_link_v1.py ~ get_object_position_base_link_v3.py # 物体位置计算脚本 ├── get_rl_object_position_base_link_v1.py ~ get_rl_object_position_base_link_v3.py # RL相关物体位置计算脚本 安装与配置 依赖项 Python 3.x OpenCV NumPy ROS2 PyAV (用于H264视频解码) requests
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

feasibility.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值