当前位置: 首页 > article >正文

Webots仿真添加行人的走路模型,并且添加自定义ROS接口。

前言

最近做的导航项目仿真中需要在webots中测试动态避障效果,那么就需要在仿真环境中添加动态行人。幸运的是,webots基础库里已经提供了行人的模型和简单的控制程序,但是还需要将其与ros通信建立连接。自己也是摸索了很长时间,总算跑成功了。
大体可以分为两个部分:模型的添加和控制器的改造。我的webots版本是2023B,我的ROS2版本是humble。不同版本可能细节会有所不同,不过方法应该是一样的,我把我的过程记录分享一下,希望能帮到有同样困惑的同学。

第一步: webots 引入行人模型

点击"+"号导入模型
在这里插入图片描述

搜索"pedestrain" ,选择"humans->pedestrain->Pedestrain(robot)“”,点击add。

在这里插入图片描述
添加完成后,在左边可以看到行人pedestrain的各种属性。可以根据自己的喜好任意修改,我对这些属性不是很在意就是没有修改
在这里插入图片描述
我们可以看到这个模型自带默认控制器, 即"controller" 选项中“pedestrain” 我们可以点击edit看看这个默认的控制器是怎么写的,强烈建议花点时间看看这个py文件,下面我们改写的控制器也是基于这个文件。
在这里插入图片描述
那么如何使用这个默认控制器呢? 从他的py文件中我们可以看到,需要三个参数trajectory,speed,step"。 speed默认0.5m/s,step默认和仿真一致,这两个参数一般不用改。需要改trajectory参数,表示行人的起点和终点位置.

双击"controllerArgs" 添加"–trajectory=5 10, 2 0",表示起点坐标(5 10) 终点坐标(2 0)。
在这里插入图片描述
添加后保存,就可以看到行人模型在起点和终点之间来回巡航啦~

第二步: 控制器改造,可以跟ROS通信.

目前为止我们已经成功让出行人在webots里动起来了,可以要测试避障算法,我们需要知道行人的实时位置,所以下一步就是要改造控制器,让其可以跟ros通信,将行人的质心坐标试试发布出来。

安装webots_ros2

这个包如果用C++控制器需要安装,用python的话应该不需要。但是为了全面推荐大家事先都安装,安装方法很简单,直接clone后编译就行。或者apt安装
地址: https://github.com/cyberbotics/webots_ros2

新建控制器

cd 你的仿真文件目录
cd controllers 
mkdir  -p walk
cd walk
touch walk.py
chomd +x walk.py

上面我新建了一个"walk"控制器,名字随意,大家按照自己的喜好更改即可。

控制器修改

编辑walk.py,将下面的程序复制进去.

# Copyright 1996-2023 Cyberbotics Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Pedestrian class container."""
from controller import Supervisor
import optparse
import math
import rclpy
from nav_msgs.msg import Odometry
from geometry_msgs.msg import Point, Quaternion, Twist, Pose
from builtin_interfaces.msg import Time
import tf2_ros

class Pedestrian(Supervisor):
    """Control a Pedestrian PROTO and publish its position using ROS 2."""

    def __init__(self):
        """Constructor: initialize constants and ROS 2 setup."""
        # Webots constants and variables
        self.BODY_PARTS_NUMBER = 13
        self.WALK_SEQUENCES_NUMBER = 8
        self.ROOT_HEIGHT = 1.27
        self.CYCLE_TO_DISTANCE_RATIO = 0.22
        self.speed = 1.15
        self.current_height_offset = 0
        self.joints_position_field = []
        self.joint_names = [
            "leftArmAngle", "leftLowerArmAngle", "leftHandAngle",
            "rightArmAngle", "rightLowerArmAngle", "rightHandAngle",
            "leftLegAngle", "leftLowerLegAngle", "leftFootAngle",
            "rightLegAngle", "rightLowerLegAngle", "rightFootAngle",
            "headAngle"
        ]
        self.height_offsets = [
            -0.02, 0.04, 0.08, -0.03, -0.02, 0.04, 0.08, -0.03
        ]
        self.angles = [
            [-0.52, -0.15, 0.58, 0.7, 0.52, 0.17, -0.36, -0.74],  # left arm
            [0.0, -0.16, -0.7, -0.38, -0.47, -0.3, -0.58, -0.21],  # left lower arm
            [0.12, 0.0, 0.12, 0.2, 0.0, -0.17, -0.25, 0.0],  # left hand
            [0.52, 0.17, -0.36, -0.74, -0.52, -0.15, 0.58, 0.7],  # right arm
            [-0.47, -0.3, -0.58, -0.21, 0.0, -0.16, -0.7, -0.38],  # right lower arm
            [0.0, -0.17, -0.25, 0.0, 0.12, 0.0, 0.12, 0.2],  # right hand
            [-0.55, -0.85, -1.14, -0.7, -0.56, 0.12, 0.24, 0.4],  # left leg
            [1.4, 1.58, 1.71, 0.49, 0.84, 0.0, 0.14, 0.26],  # left lower leg
            [0.07, 0.07, -0.07, -0.36, 0.0, 0.0, 0.32, -0.07],  # left foot
            [-0.56, 0.12, 0.24, 0.4, -0.55, -0.85, -1.14, -0.7],  # right leg
            [0.84, 0.0, 0.14, 0.26, 1.4, 1.58, 1.71, 0.49],  # right lower leg
            [0.0, 0.0, 0.42, -0.07, 0.07, 0.07, -0.07, -0.36],  # right foot
            [0.18, 0.09, 0.0, 0.09, 0.18, 0.09, 0.0, 0.09]  # head
        ]
        Supervisor.__init__(self)

        # ROS 2 setup
        rclpy.init(args=None)
        self.node = rclpy.create_node('pedestrian_controller')
        self.publisher = self.node.create_publisher(Odometry, '/human_pose', 10)

        # For twist and odometry
        self.previous_position = [0, 0]
        self.previous_time = 0
        self.previous_angle = 0  # Initialize previous_angle to 0

    def run(self):
        """Set the Pedestrian pose and position, and publish position."""
        opt_parser = optparse.OptionParser()
        opt_parser.add_option("--trajectory", default="", help="Specify the trajectory in the format [x1 y1, x2 y2, ...]")
        opt_parser.add_option("--speed", type=float, default=0.3, help="Specify walking speed in [m/s]")
        opt_parser.add_option("--step", type=int, help="Specify time step (otherwise world time step is used)")
        options, args = opt_parser.parse_args()
        
        if not options.trajectory or len(options.trajectory.split(',')) < 2:
            print("You should specify the trajectory using the '--trajectory' option.")
            print("The trajectory should have at least 2 points.")
            return
        if options.speed and options.speed > 0:
            self.speed = options.speed
        if options.step and options.step > 0:
            self.time_step = options.step
        else:
            self.time_step = int(self.getBasicTimeStep())
        point_list = options.trajectory.split(',')
        self.number_of_waypoints = len(point_list)
        self.waypoints = []
        for i in range(0, self.number_of_waypoints):
            self.waypoints.append([])
            self.waypoints[i].append(float(point_list[i].split()[0]))
            self.waypoints[i].append(float(point_list[i].split()[1]))
        self.root_node_ref = self.getSelf()
        self.root_translation_field = self.root_node_ref.getField("translation")
        self.root_rotation_field = self.root_node_ref.getField("rotation")
        for i in range(0, self.BODY_PARTS_NUMBER):
            self.joints_position_field.append(self.root_node_ref.getField(self.joint_names[i]))

        # compute waypoints distance
        self.waypoints_distance = []
        for i in range(0, self.number_of_waypoints):
            x = self.waypoints[i][0] - self.waypoints[(i + 1) % self.number_of_waypoints][0]
            y = self.waypoints[i][1] - self.waypoints[(i + 1) % self.number_of_waypoints][1]
            if i == 0:
                self.waypoints_distance.append(math.sqrt(x * x + y * y))
            else:
                self.waypoints_distance.append(self.waypoints_distance[i - 1] + math.sqrt(x * x + y * y))

        while not self.step(self.time_step) == -1:
            time = self.getTime()

            current_sequence = int(((time * self.speed) / self.CYCLE_TO_DISTANCE_RATIO) % self.WALK_SEQUENCES_NUMBER)
            ratio = (time * self.speed) / self.CYCLE_TO_DISTANCE_RATIO - \
                int(((time * self.speed) / self.CYCLE_TO_DISTANCE_RATIO))

            for i in range(0, self.BODY_PARTS_NUMBER):
                current_angle = self.angles[i][current_sequence] * (1 - ratio) + \
                    self.angles[i][(current_sequence + 1) % self.WALK_SEQUENCES_NUMBER] * ratio
                self.joints_position_field[i].setSFFloat(current_angle)

            # adjust height
            self.current_height_offset = self.height_offsets[current_sequence] * (1 - ratio) + \
                self.height_offsets[(current_sequence + 1) % self.WALK_SEQUENCES_NUMBER] * ratio

            # move everything
            distance = time * self.speed
            relative_distance = distance - int(distance / self.waypoints_distance[self.number_of_waypoints - 1]) * \
                self.waypoints_distance[self.number_of_waypoints - 1]

            for i in range(0, self.number_of_waypoints):
                if self.waypoints_distance[i] > relative_distance:
                    break

            distance_ratio = 0
            if i == 0:
                distance_ratio = relative_distance / self.waypoints_distance[0]
            else:
                distance_ratio = (relative_distance - self.waypoints_distance[i - 1]) / \
                    (self.waypoints_distance[i] - self.waypoints_distance[i - 1])
            x = distance_ratio * self.waypoints[(i + 1) % self.number_of_waypoints][0] + \
                (1 - distance_ratio) * self.waypoints[i][0]
            y = distance_ratio * self.waypoints[(i + 1) % self.number_of_waypoints][1] + \
                (1 - distance_ratio) * self.waypoints[i][1]
            root_translation = [x, y, self.ROOT_HEIGHT + self.current_height_offset]
            angle = math.atan2(self.waypoints[(i + 1) % self.number_of_waypoints][1] - self.waypoints[i][1],
                               self.waypoints[(i + 1) % self.number_of_waypoints][0] - self.waypoints[i][0])
            rotation = [0, 0, 1, angle]

            self.root_translation_field.setSFVec3f(root_translation)
            self.root_rotation_field.setSFRotation(rotation)

            # Calculate velocity (twist)
            velocity_x = (x - self.previous_position[0]) / (time - self.previous_time) if time != self.previous_time else 0
            velocity_y = (y - self.previous_position[1]) / (time - self.previous_time) if time != self.previous_time else 0
            angular_z = (angle - self.previous_angle) / (time - self.previous_time) if time != self.previous_time else 0

            twist_msg = Twist()
            twist_msg.linear.x = velocity_x
            twist_msg.linear.y = velocity_y
            twist_msg.angular.z = angular_z

            # Publish Odometry message
            odom_msg = Odometry()
            odom_msg.header.stamp = Time()  # Create a Time message
            odom_msg.header.stamp.sec = int(time)  # Set seconds
            odom_msg.header.stamp.nanosec = int((time - int(time)) * 1e9)  # Set nanoseconds
            odom_msg.header.frame_id = "world"
            
            # Correct initialization of Pose and Quaternion
            odom_msg.pose.pose.position.x = x
            odom_msg.pose.pose.position.y = y
            odom_msg.pose.pose.orientation.z = math.sin(angle / 2)
            odom_msg.pose.pose.orientation.w = math.cos(angle / 2)
            
            odom_msg.twist.twist = twist_msg

            self.publisher.publish(odom_msg)

            # Store previous values for next iteration
            self.previous_position = [x, y]
            self.previous_time = time
            self.previous_angle = angle  # Store the angle for next iteration

        # Shutdown ROS 2 node when controller ends
        self.node.destroy_node()
        rclpy.shutdown()
# Run the controller
controller = Pedestrian()
controller.run()

这个程序是基于上面的默认‘‘pedestrain’控制器修改的。主要修改了:

  • ROS 2 初始化:在 init 构造函数中初始化 ROS 2 节点和发布器。
    – 使用 rclpy.init() 来初始化 ROS 2。
    – 创建 rclpy.create_node(‘pedestrian_controller’) 来创建节点,并使用 node.create_publisher() 创建发布器

  • 发布位置:在 run() 方法的 while 循环中,每次更新位置后,通过 self.publisher.publish(pose_msg) 将行人的位置信息发布到 ROS 2 的 /human_pose 话题。

  • 控制器结束时,通过 self.node.destroy_node() 销毁 ROS 2 节点,并通过 rclpy.shutdown() 关闭 ROS 2。

参考链接

https://www.youtube.com/watch?v=_bVGHFBEOOA&t=9s


http://www.kler.cn/a/533053.html

相关文章:

  • Ubuntu下Tkinter绑定数字小键盘上的回车键(PySide6类似)
  • github下载失败网页打开失败 若你已经知道github地址如何cmd下载
  • Windows图形界面(GUI)-QT-C/C++ - QT MDI Area
  • JavaScript模块化
  • RK3566-移植5.10内核Ubuntu22.04
  • 神经网络参数量和运算量的计算- 基于deepspeed库和thop库函数
  • ES6-代码编程风格(数组、函数)
  • 2. K8S集群架构及主机准备
  • 物理群晖SA6400核显直通win10虚拟机(VMM)
  • Swift 进阶:Observation 框架中可观察(@Observable)对象的高级操作(上)
  • 路由器考研讲解
  • 34.Word:公积金管理中心文员小谢【35】
  • 九. Redis 持久化-AOF(详细讲解说明,一个配置一个说明分析,步步讲解到位 2)
  • 4.增强输入与玩家视角
  • 2.攻防世界PHP2及知识点
  • Nginx的配置文件 conf/nginx.conf /etc/nginx/nginx.conf 笔记250203
  • Vue3 完整学习笔记 - 第四部分
  • TCP 丢包恢复策略:代价权衡与优化迷局
  • LeetCode:583.两个字符串的删除操作
  • [leetcode·回溯算法]回溯算法解题套路框架
  • Kubernetes学习之网络
  • Github 2025-02-04 Python开源项目日报 Top10
  • Rust语言的并发编程
  • 青少年编程与数学 02-008 Pyhon语言编程基础 18课题、标准模块
  • 详解u3d之AssetBundle
  • CCF-GESP 等级考试 2023年12月认证C++八级真题解析