ROS理论与实践(以移动机器人为例)连载(五) ——构建机器人仿真平台

在这里插入图片描述
O(∩_∩)O哈哈哈~ 特想放张伟!我是什么颜色???



参考功能包链接

https://github.com/XFFer/ROS_Resource

1. 优化物理仿真模型

使用xacro文件优化URDF模型

连载(四) 中我们使用URDF建模遇到了很多问题,比如多个轮子需要重复书写代码;不能进行参数计算,各个link的位置需要逐个输入参数。
在这里插入图片描述

URDF模型的进化版本——xacro模型文件

官方Wiki地址跳转

  • 精简模型代码
    • 创建宏定义
    • 文件包含
  • 提供可编程接口
    • 常量
    • 变量
    • 数学计算
    • 条件语句

① 常量的定义与使用

常量定义

<xacro:property name="M_PI" value="3.14159" />

举例:

<!-- PROPERTY LIST -->
<xacro:property name="M_PI" value="3.1415926" />
<xacro:property name="base_radius" value="0.20" />
<xacro:property name="base_length" value="0.16" />

<xacro:property name="wheel_radius" value="0.06" />
<xacro:property name="wheel_length" value="0.025" />
<xacro:property name="wheel_joint_y" value="0.19" />
<xacro:property name="wheel_joint_z" value="0.05" />

<xacro:property name="caster_radius" value="0.015" />
<xacro:property name="caster_joint_x" value="0.18" />

常量使用

<origin xyz="0 0 0" rpy="${M_PI/2} 0 0"/>

举例:

<joint name="base_footprint_joint" type="fixed">
	<origin xyz="0 0 ${base_length/2 + caster_radius*2}" rpy="0 0 0" />
	<parent link="base_footprint" />
	<child link="base_link" />
</joint>

<link name="base_link">
	<visual>
		<origin xyz="0 0 0" rpy="0 0 0" />
		<geometry>
			<cylinder length="${base_length}" radius="${base_radius}" />
		</geometry>
		<material name="yellow" />
	</visual>
</link>

② 数学计算

所有数学运算都会转换成浮点数进行,以保证运算精度。

<origin xyz="0 ${(motor_length+wheel_length)/2} 0" rpy="0 0 0" />

举例:

<joint name="base_footprint_joint" type="fixed">
	<origin xyz="0 0 ${base_length/2 + caster_radius*2}" rpy="0 0 0" />
	<parent link="base_footprint" />
	<child link="base_link" />
</joint>

③ 宏

宏定义

类似于函数,可以实现创建一个轮子的模型,通过再次调用修改参数,创建相同形状,多个轮子模型。

<xacro:macro name="name" params="A B C">
......
</xacro:macro>

举例:

<!--Macro for robot wheel-->
<xacro:macro name="wheel" param="prefix reflect">
	<joint name="${prefix}_wheel_joint" type="continuous">
		<origin xyz="0 ${reflect*wheel_joint_y} ${-wheel_joint_z}" rpy="0 0 0" />
		<parent link="base_link" />
		<child link="${prefix}_wheel_link" />
		<axis xyz="0 1 0" />
	</joint>

	<link name="${prefix}_wheel_link">
		<visual>
			<origin xyz="0 0 0" rpy="${M_PI/2} 0 0" />
			<geometry>
				<cylinder radius="${wheel_radius}" length="${wheel_length}" />
			</geometry>
			<material name="gray" />
		</visual>
	</link>
</xacro:macro>

宏调用

<name A="A_value" B="B_value" C="C_value" />

举例:

<wheel prefix="left" reflect="1" />
<wheel prefix="right" reflect="-1" />

④ 文件包含

<xacro:include filename="$(find mbot_description)/urdf/mbot_base_gazebo.xacro" />

举例:

<xacro:include filename="$(find mbot_description)/urdf/sensors/camera_gazebo.xacro" />
<xacro:include filename="$(find mbot_description)/urdf/mbot_base_gazebo.xacro" />

ros_control

  • ros_control是ROS为开发者提供的机器人控制中间件
  • 包含一系列控制器接口、传动装置接口、硬件接口、控制器工具箱等
  • 可以帮助我们将上层功能包里的指令发送给机器人

在这里插入图片描述
由于硬件包括各种有刷无刷电机、机械臂、激光雷达、视觉传感器,驱动方式都有很大的差别。故硬件抽象层是对硬件进行包装,使它更容易能够和上层控制层绑定在一起。

  • 控制器管理器 Controller Manager
    • 提供一种通用的接口来管理不同的控制器。
  • 控制器 Controller
    • 读取硬件状态,发送控制命令,完成每个joint的控制。
  • 硬件资源 Hardware Resource Interface Layer
    • 为上下两层提供硬件资源的接口。
  • 机器人硬件抽象 DefaultRobotHWSim/hardware_interface::RobotHW
    • 机器人硬件抽象和硬件资源直接打交道,通过write和read方法完成硬件操作。
  • 真实机器人 Reality
    • 执行接收到的命令。

在这里插入图片描述

上图中包含了上述的几个模块,Controller Manager可以帮助管理不同的控制器,如:底盘控制器,机械臂等的启动、暂停、运行、停止;具体的是一个PID Loops,这个闭环PID更像在应用层面,会向下发指令会通过Hardware Resource Interface Layer这个硬件资源的接口,以不同的形式发布;在仿真器中会有DefaultRobotHWSim默认硬件抽象,但对真实机器人,就需要自己去构建这个硬件抽象RobotHW,这里也存在一个PID闭环控制,但偏向于控制板,如STM32对某个电机的控制,再通过解码器Encoders得到关节的状态信息反馈给Controller

前面MoveIt的插入课,我们用到过joint_position_controller,对机械臂进行MotionPlanning。这里我们列举控制器的所有类型,可参考 https://github.com/ros-controls/ros_control/wiki/controller_interface

  • joint_state_controller
  • joint_effort_controller
  • joint_position_controller
  • joint_velocity_controller

在这里插入图片描述

第一步:为link添加惯性参数和碰撞属性

转动惯量是大学物理的内容研究的是2D平面的,公式为: J = m r 2 J=\sum mr^2 M = J ω M=J\omega ω \omega 是角速度,而这里的惯性参数是三维的,用一个惯性矩阵来描述。推了一下圆柱体的公式,同样长方体和球体也可以使用三次积分的方式求出。
在这里插入图片描述

<xacro:macro name="cylinder_inertial_matrix" params="m r h">
	<inertial>
		<mass value="${m}" />
		<inertia ixx="${m*(3*r*r+h*h)/12}" ixy="0" ixz="0"
				 iyy="${m*(3*r*r+h*h)/12}" iyz="0"
				 izz="${m*r*r/2}" />
	</inertial>
</xacro:macro>

<link name="base_link">
	<visual>
		<origin xyz="0 0 0" rpy="0 0 0" />
		<geometry>
			<cylinder length="${base_length}" radius="{base_radius}" />
		</geometry>
		<material name="yellow" />
	</visual>
	<collison>
		<origin xyz="0 0 0" rpy="0 0 0" />
		<geometry>
			<cylinder length="${base_length}" radius="{base_radius}" />
		</geometry>
	</collision>
	<cylinder_inertial_matrix m="${base_mass}" r="${base_radius}" h="${base_length}" />
</link>

第二步:为link添加gazebo标签

Gazebo中需要添加色彩标签。由于rviz和gazebo的色彩空间不同,描述形式不一样,不能通用,如果不标记标签,Gazebo内的模型都为灰色。

<gazebo reference="base_link">
	<material>Gazebo/Blue</material>
</gazebo>
<gazebo reference="${prefix}_wheel_link">
	<material>Gazebo/Gray</material>
</gazebo>
<gazebo reference="base_footprint">
	<turnGravityoff>false</turnGravityoff>
</gazebo>

“base_footprint”是对底盘在地面的一个映射,这里需要把这个影子的重力设置为无。

<gazebo reference="${prefix}_caster_link">
	<material>Gazebo/Black</material>
</gazebo>

第三步:为joint添加传动装置

为每一个joint添加虚拟电机(传动装置)。这里使用了硬件接口——速度控制接口hardware_interface/VelocityJointInterface。

<!-- Transmission is important to link the joints and the controller -->
<transmission name="${prefix}_wheel_joint_trans">
	<type>transmission_interface/SimpleTransmission</type>
	<joint name="${prefix}_name_wheel_joint">
		<hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
	</joint>
	<actuator name="${prefix}_wheel_joint_motor">
		<hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
		<mechanicalReduction>1</mechanicalReduction>
	</actuator>
</transmission>

第四步:添加gazebo控制器插件

  • <robotNamespace>:机器人的命名空间。
  • <leftJoint>和<rightJoint>:左右轮转动的关节joint。
  • <whellSeparation>和<wheelDiameter>:机器人模型的相关尺寸,在计算差速参数时需要用到。
  • <commandTopic>:控制器订阅的速度控制指令,生成全局命名时需要结合<robotNamespace>中设置的命名空间。
  • <odometryFrame>:里程计数据的参考坐标系,ROS中一般命名为odom。
<!-- controller -->
<gazebo>
	<!--控制器插件,差速控制libgazebo_ros_diff_drive.so-->
	<plugin name="differential_drive_controller"
			filename="libgazebo_ros_diff_drive.so">
		<rosDebugLevel>Debug</rosDebugLevel>
		<publishWheelTF>true</publishWheelTF>
		<!-- /代表无命名空间,属于全局命名空间 -->
		<robotNamespace>/</robotNamespace>
		<publishTf>1</publishTf>
		<publishWheelJointState>true</publishWheelJointState>
		<alwaysOn>true</alwaysOn>
		<updateRate>100.0</updateRate>
		<legacyMode>true</legacyMode>
		<leftJoint>left_wheel_joint</leftJoint>
		<rightJoint>right_wheel_joint</rightJoint>
		<!--两个轮子的轮距-->
		<wheelSeparation>${wheel_joint_y*2}</wheelSeparation>
		<wheelDiameter>${wheel_radius*2}</wheelDiameter>
		<broadcastTF>1</broadcastTF>
		<!--转矩Torque-->
		<wheelTorque>30</wheelTorque>
		<wheelAcceleration>1.8</wheelAcceleration>
		<!--需要发布的速度信息-->
		<commandTopic>cmd_vel</commandTopic>
		<!--使用里程计,计算仿真中机器人的位置-->
		<odometryFrame>odom</odometryFrame>
		<odometryTopic>odom</odometryTopic>
		<robotBaseFrame>base_footprint</robotBaseFrame>
	</plugin>
</gazebo>	

全向移动使用libgazebo_ros_planar_move这个插件,具体gazebo插件可以参考官方网站

第五步:编辑.launch文件

在gazebo中加载机器人模型需要首先将.xacro文件写入.launch文件中用来执行。

示例:

<launch>
	
	<!--设置launch文件的参数-->
	<arg name="paused" default="false" />
	<arg name="use_sim_time" default="true" />
	<arg name="gui" default="true" />	
	<arg name="headless" default="false" />
	<arg name="debug" default="false" />

	<!-- 运行gazebo仿真环境 -->
	<include file="$(find gazebo_ros)/launch/empty_world.launch">
		<arg name="debug" value="$(arg debug)" />
		<arg name="paused" value="$(arg paused)" />
		<arg name="use_sim_time" value="$(arg use_sim_time)" />
		<arg name="gui" value="$(arg gui)" />	
		<arg name="headless" value="$(arg headless)" />
	</include>

	<!-- 加载机器人模型描述参数 -->
	<!-- xacro --inorder解释器作用于.xacro文件 -->
	<param name="robot_description" command="$(find xacro)/xacro --inorder '$(find mobot_description)/urdf/mbot_gazebo.xacro'"/>
	
	<!-- 运行joint_state_publisher节点,发布机器人的关节状态 -->
	<node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" />

	<!-- 运行robot_state_publisher节点,发布tf -->
	<node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" output="screen" >
		<param name="publish_frequency" type="double" value="50.0" />
	</node>

	<!-- 在gazebo中加载机器人模型 -->
	<!-- spawn_model功能包,-urdf说明模型由URDF格式构建,-model后跟机器人名,-param参数为上面加载的模型描述参数 -->
	<node name="urdf_spawner" pkg="gazebo_ros" type="spawn_model" respawn="false" output="screen"
		  args="-urdf -model mrobot -param robot_description" />
	
</launch>

运行

$ roslaunch mbot_gazebo view_mbot_gazebo_empty_world.launch

在这里插入图片描述
终端输入

$ rostopic list

会发现/cmd_vel,那么就可以通过

$ rostopic pub /cmd_vel geometry_msgs/Twist "linear:       
  x: 0.6
  y: 0.0
  z: 0.0
angular:
  x: 0.0
  y: 0.0
  z: 0.5"

发布运动指令了。

2. 创建物理仿真环境

第一种方法

https://bitbucket.org/osrf/gazebo_models/downloads/下载,提前放到模型~/.gazebo/models下,.gazebo文件夹一般是隐藏的,点击ctrl+h可以显示隐藏文件夹。下载的模型包解压后需要把文件内的所有文件散开放,每个文件夹都是一个模型。

在Gazebo仿真环境,点击左上角的Insert插入,就可以放置物理环境了,通过左上角的Save World As可以保存当前设置的仿真环境。

第二种方法:使用Building Editor

首先需要打开一个Gazebo World。可以使用

$ roslaunch mbot_gazebo view_mbot_gazebo_empty_world.launch

但是要事先把功能包放置到工作空间下。(功能包已经在全文开头给出了github地址)

点击File中的Building Editor在这里插入图片描述
通过左边的墙体、窗户、门,颜色,质感等,构建一个虚拟的物理环境。

下面我们运行一个gazebo提供的仿真环境,只不过也就是放了几个东西而已。

$ roslaunch mbot_gazebo view_mbot_gazebo_play_ground.launch

然后运行一个gazebo提供的键盘控制小车移动的脚本。

$ roslaunch mbot_teleop mbot_teleop.launch

你会发现使用i前进(红色X轴),使用,后退,使用j向左转,l右转,q加速等等,kinetic中左右是相反的,你会发现小车是具有惯性的,这就是惯性矩阵的作用,同样小车碰撞物体时,可能推动物体前进(在物体质量较轻时),小车对物体有力的作用。

3. 传感器仿真及应用

①摄像头仿真

  • <sensor>标签:描述仿真器
    • type:传感器类型,camera
    • name:摄像头命名,自由设置
  • <camera>标签:描述摄像头参数
    • 分辨率,编码格式,图像范围,噪音参数等
  • <plugin>标签:加载摄像头仿真插件
    • 设置插件的命名空间、发布图像的话题、参考坐标系等

示例:(示例中仅包含传感器设置,并不包含其他模型构建,不是完整的)

<gazebo reference="${prefix}_link">
	<sensor type="camera" name="camera_node">
		<!--每秒30帧-->
		<update_rate>30.0</update_rate>
		<camera name="head">
			<horizontal_fov>1.3962634</horizontal_fov>
			<image>
				<!--分辨率-->
				<width>1280</width>
				<height>720</height>
				<format>R8G8B8</format>
			</image>
			<clip>
				<!--最近和最远(m)-->
				<near>0.02</near>
				<far>300</far>
			</clip>
			<noise>
				<type>gaussian</type>
				<mean>0.0</mean>
				<stddev>0.007</stddev>
			</noise>
		</camera>
		<plugin name="gazebo_camera" filename="libgazebo_ros_camera.so">
			<alwaysOn>true</alwayOn>
			<updateRate>0.0</updateRate>
			<!--命名空间-->
			<cameraName>/camera</cameraName>
			<!--对外发布话题名为image_raw-->
			<imageTopicName>image_raw</imageTopicName>
			<cameraInfoTopicName>camera_info</cmaeraInfoTopicName>
			<frameName>camera_link</framName>
			<hackBaseline>0.07</hackBaseline>
			<distortionK1>0.0</distortionK1>
			<distortionK2>0.0</distortionK2>
			<distortionK3>0.0</distortionK3>
			<distortionT1>0.0</distortionT1>
			<distortionT2>0.0</distortionT2>
		</plugin>
	</sensor>
</gazebo>

可以运行一下给出功能包里的程序

$ roslaunch mbot_gazebo view_mbot_with_camera_gazebo.launch

随后运行

$ rostopic list

可以看到多出很多由camera开头的话题,我们用rqt_image_view显示话题仿真的图像

$ rqt_image_view

②RGB-D摄像头仿真(Kinect)

Kinect通过红外感知周围物体的深度信息。

示例:(示例中仅包含传感器设置,并不包含其他模型构建,不是完整的)

<gazebo reference="${prefix}_link">
	<sensor type="depth" name="${prefix}">
		<always_on>true</always_on>
		<update_rate>20.0</update_rate>
		<camera>
			<!--俯仰角-->
			<horizontal_fov>${60.0*M_PI/180.0}</horizontal_fov>
			<image>
				<width>640</width>
				<height>480</height>
				<format>R8G8B8</format>
			</image>
			<clip>
				<!--最近和最远(m)-->
				<near>0.05</near>
				<far>8.0</far>
			</clip>
		</camera>
		<plugin name="kinect_${prefix}_controller" filename="libgazebo_ros_openni_kinect.so">
			<alwaysOn>true</alwayOn>
			<updateRate>10</updateRate>
			<!--命名空间-->
			<cameraName>${prefix}</cameraName>
			<!--创建rgb彩色图像话题-->
			<imageTopicName>rgb/image_raw</imageTopicName>
			<!--创建深度图像话题-->
			<depthImageTopicName>depth/image_raw</depthImageTopicName>
			<!--创建点云图像话题-->
			<pointCloudTopicName>depth/points</pointCloudTopicName>			
			<depthImageCameraInfoTopicName>depth/camera_info</depthImageCameraInfoTopicName>
			<cameraInfoTopicName>rgb/camera_info</cmaeraInfoTopicName>
			<frameName>${prefix}_frame_optical</framName>
			<baseline>0.1</beseline>
			<distortionk1>0.0</distortionk1>
			<distortionk2>0.0</distortionk2>
			<distortionk3>0.0</distortionk3>
			<distortiont1>0.0</distortiont1>
			<distortiont2>0.0</distortiont2>
			<pointCloudCutoff>0.4</pointCloudCutoff>
		</plugin>
	</sensor>
</gazebo>

同样可以执行给出功能包中的.launch文件

$ roslaunch mbot_gazebo view_mbot_with_kinect_gazebo.launch

使用rostopic list输出的话题中由kinect开头的都是该仿真摄像头提供的话题。我们可以通过rviz进行显示。

$ rosrun rviz rviz

添加PointCloud2和Image选择Kinect发布的话题,就可以在rviz中显示出来。
在这里插入图片描述

③激光雷达仿真

示例:这里也只列出仿真传感器的设置,其他设置大差不差。

这里是一个二维的雷达,可以得到二维的深度信息。

<gazebo reference="${prefix}_link">
	<sensor type="ray" name="rplidar">
		<pose>0 0 0 0 0 0</pose>
		<visualize>false</visualize>
		<update_rate>5.5</update_rate>
		<ray>
			<scan>
				<horizontal>
					<!--每一帧360个点-->
					<samples>360</samples>
					<!--每一度一个点-->
					<resolution>1</resolution>
					<min_angle>-3</min_angle>
					<max_angle>3</max_angle>
				</horizontal>
			</scan>
			<range>
				<!--最近0.10m,最远6.0m-->
				<min>0.10</min>
				<max>6.0</max>
				<resolution>0.01</resolution>
			</range>
			<noise>
				<type>gaussian</type>
				<mean>0.0</mean>
				<stddev>0.01</stddev>
			</noise>
		</ray>
		<plugin name="gazebo_rplidar" filename="libgazebo_ros_laser.so">
			<topicName>/scan</topicName>
			<!--雷达信息都在laser_link下做描述-->
			<frameName>laser_link</frameName>
		</plugin>
	</sensor>
</gazebo>

在这里插入图片描述
推荐阅读

  1. ROS xacro使用方法
  2. rviz和gazebo的区别
  3. Gazebo仿真官方教程
  4. 古月居——ros_control
  5. 古月居——gazebo插件plugin
发布了27 篇原创文章 · 获赞 60 · 访问量 6578

猜你喜欢

转载自blog.csdn.net/qq_44455588/article/details/105163024