ROS是目前最流行最全面的机器人操作系统,通过ROS上的编程学习,能够让我们切身体验各种机器人技术如何在现实中进行融合和使用。但是,一台支持ROS的机器人价格不菲,不像手机,说买就买。如果没有机器人,我们就没法学习ROS编程了吗?不可能!什么都不能阻挡我们拥抱ROS的热情和决心!这次就给同学们介绍一个开源的ROS机器人仿真项目,这个仿真项目集成了三维图形界面和物理惯性和碰撞引擎,脱离了物理世界的机器人实体,让我们坐着躺着就能把程序跑在一台ROS机器人上,看着它运动,看着它撞墙,看着它倒地……
一、开源项目网址
https://github.com/6-robot/wpr_simulation
注意:由于Indigo(Ubuntu 14.04)集成的Gazebo版本太过古老,已经失去维护无法使用。建议系统版本为Kinetic/Ubuntu 16.04以上版本。
二、开源项目的下载
在Ubuntu系统中,打开一个终端程序,输入如下指令下载项目源码:
cd catkin_ws/src git clone https://github.com/6-robot/wpr_simulation.git |
其中“catkin_ws”为ROS的工作空间,请根据电脑的环境设置进行修改。这个项目的源码是从Github网站下载的,所以下载过程需要连接互联网。
源码下载完成后,运行如下指令进行源码工程的编译:
cd ~/catkin_ws catkin_make |
这个开源工程,用到了Gazebo,如果之前安装的ROS是完整版本,则会很顺利的编译通过。如果编译过程中提示缺少某些依赖项,可以通过如下指令补充完整:
sudo apt-get install ros-kinetic-desktop-full |
三、开源项目的使用
项目编译完成后,可以通过如下指令启动一个简单的仿真场景:
roslaunch wpr_simulation wpb_simple.launch |
启动后,会弹出一个窗口,里面显示一台机器人,面对着一个柜子发呆:
这个就是仿真环境的主界面,可以看到界面的周围有很多的工具按钮和菜单列表,这些我们会在后续的教程中进行介绍,这次先跳过,直接进入ROS编程的正题。
四、和ROS程序的连接
仿真环境运行起来了,下面看看如何与ROS程序进行连接。这里准备了一个简单的速度控制程序作为例子,可以运行起来体验一下。保持刚才启动的仿真环境别关闭,在Ubuntu系统中再新打开一个终端,输入如下指令:
rosrun wpr_simulation demo_vel_ctrl |
运行之后,可以看到机器人开始向前缓慢移动,直到撞上柜子……
五、示例程序源码解析
这个demo_vel_ctrl就是一个简单的ROS机器人速度控制程序,它的源代码位置:
~/catkin_ws/src/wpr_simulation/src/ demo_vel_ctrl.cpp |
我们可以用Visual Studio Code之类的IDE打开这个源码文件:
#include <ros/ros.h> #include <geometry_msgs/Twist.h>
int main(int argc, char** argv) { ros::init(argc, argv, "demo_vel_ctrl");
ros::NodeHandle n; ros::Publisher vel_pub = n.advertise<geometry_msgs::Twist>("/cmd_vel", 10);
while (ros::ok()) { geometry_msgs::Twist vel_cmd; vel_cmd.linear.x = 0.1; vel_cmd.linear.y = 0.0; vel_cmd.linear.z = 0.0; vel_cmd.angular.x = 0; vel_cmd.angular.y = 0; vel_cmd.angular.z = 0; vel_pub.publish(vel_cmd); ros::spinOnce(); }
return 0; } |
(1) 代码的开始部分,先include了两个头文件,一个是ros的系统头文件,另一个是运动速度结构体类型geometry_msgs::Twist的定义文件;
(2) ROS节点的主体函数是int main(int argc, char** argv),其参数定义和其他C++程序一样;
(3) main函数里,首先调用ros::init(argc, argv, "demo_vel_ctrl");进行该节点的初始化操作,函数的第三个参数是节点名称;
(4) 接下来声明一个ros::NodeHandle对象n,并用n生成一个广播对象vel_pub,调用的参数里指明了vel_pub将会在主题“/cmd_vel”里广播geometry_msgs::Twist类型的数据。我们对机器人的控制,就是通过这个广播形式实现的。这里就有一个疑问:为什么是往主题“/cmd_vel”里广播数据而不是其他的主题?机器人怎么知道哪个主题里是要执行的速度?
答案是:在ROS里有很多约定俗成的习惯,比如激光雷达数据发布主题通常是“/scan”,坐标系变换关系的发布主题通常是“/tf”,所以这里的机器人速度控制主题“/cmd_vel”也是这样一个约定俗成的情况。
(5) 为了连续不断的发送速度,使用一个while(ros::ok())循环,以ros::ok()返回值作为循环结束条件可以让循环在程序关闭时正常退出。
(6) 为了发送速度值,声明一个geometry_msgs::Twist类型的对象vel_cmd,并将速度值赋值到这个对象里。其中:
(7) vel_cmd.linear.x是机器人前后平移运动速度,正值往前,负值往后,单位是“米/秒”;
(8) vel_cmd.linear.y是机器人左右平移运动速度,正值往左,负值往右,单位是“米/秒”;
(9) vel_cmd.angular.z(注意angular)是机器人自转速度,正值左转,负值右转,单位是“弧度/秒”;
(10) 其他值对启智机器人来说没有意义,所以都赋值为零。
(11) vel_cmd赋值完毕后,使用广播对象vel_pub将其发布到主题“/cmd_vel”上去。机器人的核心节点会从这个主题接收我们发过去的速度值,并转发到硬件底盘去执行。
调用ros::spinOnce()函数给其他回调函数得以执行。
可以看到这是一个标准的ROS程序,在实体机器人上,它实现的是让机器人以0.1米/秒的速度往前推进,在这个仿真环境里,机器人也是执行了相同的行为。
六、和ROS系统工具的连接
在这个仿真环境里,是否可以正常的使用ROS的系统工具?我们来实践一下,启动一个Rviz工具,显示机器人采集到的传感器数据。为了省去繁琐的配置工作,这里我们直接下载一个现成的ROS机器人开源代码。打开一个新的终端程序,输入如下指令:
cd ~/catkin_ws git clone https://github.com/6-robot/wpb_home.git |
下载完成后,再次进行编译:
cd ~/catkin_ws catkin_make |
保持刚才启动的仿真环境别关闭,在Ubuntu系统新打开一个终端,输入如下指令:
roslaunch wpr_simulation wpb_rviz.launch |
运行指令后,就会弹出ROS标配的图形显示界面Rviz,里面显示的就是仿真环境中的虚拟机器人所感知到的各种数据:
右侧主界面里显示的是机器人头部的立体相机采集到的三维点云。
柜子底部的红色点阵是机器人底盘激光雷达扫描到的障碍物边缘。
左下角的视频图像是机器人头部彩色相机采集到的数据。
在机器人运动的过程中,上述数据都会实时的变化,可以很直观的测试我们编写的机器人控制程序。
七、基于传感器数据的闭环控制
下面我们再看看一个复杂一点的例子,在一个标准的ROS程序里,获取机器人从仿真环境里采集到的数据,再反馈回去控制仿真环境里的虚拟机器人。整个程序的实现思路:
从如下地址可以找到这个程序的源代码文件:
~/catkin_ws/src/wpb_home/wpb_home_tutorials/ src/wpb_home_lidar_behavior.cpp |
我们可以用Visual Studio Code之类的IDE打开这个源码文件:
#include <ros/ros.h> #include <std_msgs/String.h> #include <sensor_msgs/LaserScan.h> #include <geometry_msgs/Twist.h>
ros::Publisher vel_pub; static int nCount = 0;
void lidarCallback(const sensor_msgs::LaserScan::ConstPtr& scan) { int nNum = scan->ranges.size();
int nMid = nNum / 2; float fMidDist = scan->ranges[nMid]; ROS_INFO("Point[%d] = %f", nMid, fMidDist);
if (nCount > 0) { nCount--; return; }
geometry_msgs::Twist vel_cmd; if (fMidDist > 1.5f) { vel_cmd.linear.x = 0.05; } else { vel_cmd.angular.z = 0.3; nCount = 50; } vel_pub.publish(vel_cmd); }
int main(int argc, char** argv) { ros::init(argc, argv, "wpb_home_lidar_behavior");
ROS_INFO("wpb_home_lidar_behavior start!");
ros::NodeHandle nh; ros::Subscriber lidar_sub = nh.subscribe("/scan", 10, &lidarCallback); vel_pub = nh.advertise<geometry_msgs::Twist>("/cmd_vel", 10);
ros::spin(); } |
源码解析:
(1) 代码的开头include了四个头文件:ros.h是ros的系统头文件;String.h是字符格式的定义文件,用来做文字输出;LaserScan.h是激光雷达的数据格式定义文件,用来装载雷达数据;Twist.h是机器运动速度消息包的格式定义文件,对应的Twist消息包格式如下:
其中linear是运动控制的线性分量,也就是机器人直线移动的分量,angular是机器人旋转运动的分量。这两个分量都是Vector3类型,其结构定义如下:
可见Vector3类型包含三个浮点数:x、y和z。对于linear来说,x、y和z对应的是沿X轴、Y轴和Z轴方向上的速度分量,分量数值单位为“米/秒”。对于angular来说,x、y和z对应的是以X轴、Y轴和Z轴为旋转轴的旋转速度分量,分量数值单位为“弧度/秒”。
(2) 程序接下来定义一个消息发布对象vel_pub,后面会用这个发布对象向机器人核心节点发送速度控制消息包。因为机器人转向行为需要维持一段时间,才能转到完全避开障碍物的方向,所以这里定义了一个int型变量nCount,用来调整机器人转向动作的时长。
(3) 定义一个回调函数void lidarCallback(),用来处理激光雷达数据。ROS每接收到一帧激光雷达数据,就会自动调用一次回调函数。雷达的测距数值会以参数的形式传递到这个回调函数里。
(4) 在回调函数void lidarCallback()中,参数scan是一个sensor_msgs::LaserScan格式的数据包,其数据格式定义如下:
其中float32[] ranges数组存放的就是激光雷达的测距数值。启智ROS机器人使用的是RPLidar A2型号激光雷达,其旋转一周测量360个距离值,所以在我们的代码里,ranges是一个360个成员的距离数组。
按照程序逻辑,我们需要的是机器人正前方的测距数值,根据激光雷达在机器人上的安装位置,激光雷达的扫描角度如下图所示:
从图中可知机器人正前方的激光射线角度为扫描角度范围的中间值,我们定义一个变量nNum,用来获取ranges数组的成员个数。再定义一个变量nMid,值为nNum的一半,由上图可知,nMid对应的激光雷达扫描线序即为机器人正前方的扫描线,以nMid作为下标从ranges数组里取到的值即为机器人正前方的雷达测距数值,我们将这个测距值保存到变量fMidDist中。fMidDist中的数值为一个小数,数值单位为“米”。我们使用ROS_INFO()将机器人正前方的雷达测距数值fMidDist显示到终端程序里,方便我们观察和调试。
接下来是对nCount的一个数值判断:如果nCount大于0,则将nCount减一,并直接return中断这个回调函数,让机器人维持之前的运动状态直到nCount递减到0。这就实现了通过给nCount赋值来控制机器人转向动作时长的功能。
程序里定义了一个geometry_msgs::Twist消息包vel_cmd,用来装载我们要发送的机器人运动控制量,然后根据fMidDist的数值大小来对vel_cmd进行不同的赋值。当fMidDist大于1.5时,也就是机器人正前方的障碍物距离大于1.5米的时候,我们给vel_cmd的x赋值0.05,控制机器人以0.05米/秒的速度缓慢向前移动;当fMidDist不大于1.5时,也就是机器人正前方的障碍物距离小于或等于1.5米的时候,我们给vel_cmd的z赋值0.3,控制机器人以0.3弧度/秒的速度原地向左旋转,同时对nCount赋值50,让机器人在后面的50次回调函数执行过程中都维持这个旋转动作。对vel_cmd赋值完毕后,通过vel_pub将其publish发布到相关主题上,启智ROS的核心节点会从主题中获得这个数据包,并按照我们赋值的速度对机器人底盘进行运动控制。
(5) 在主函数main()中,调用ros::init(),对这个节点进行初始化。
(6) 调用ROS_INFO()向终端程序输出字符串信息,以表明节点正常启动了。
(7) 定义一个ros::NodeHandle节点句柄nh,并使用这个句柄向ROS核心节点订阅“/scan”主题的数据,回调函数设置为之前定义的lidarCallback()。
(8) 使用节点句柄nh对vel_pub进行初始化,让其在主题“/cmd_vel”发布速度控制消息,启智ROS的核心节点会从这个主题获取vel_pub发布的消息,并控制机器人底盘执行消息包里的速度值。
(9) 调用ros::spin()对main()函数进行阻塞,保持这个节点程序不会结束退出。
这个程序在前面我们已经编译过了,这里直接执行即可。保持刚才启动的仿真环境别关闭,在Ubuntu系统新打开一个终端,输入如下指令:
rosrun wpb_home_tutorials wpb_home_lidar_behavior |
这条指令会启动刚才的wpb_home_lidar_behavior程序。按照程序逻辑,会从激光雷达的“/scan”主题里不断获取激光雷达数据包,并把机器人正前方的激光雷达测距数值显示在终端程序里。终端里显示“Point[180] = xxxx”其中xxxx为一个浮点数,单位是“米”。比如“Point[180] = 2.626860”表示机器人正前方的激光雷达测距值为2.626860米。
这时切换到仿真环境界面,可以观察机器人的运行效果:
(1) 程序启动后,机器人开始以0.05米/秒的速度向前移动。
(2) 当机器人前方1.5米处出现障碍物时,机器人停止移动,以0.3弧度/秒的速度原地转动。
(3) 当机器人转到前方1.5米范围内没有障碍物时,停止转动,继续以0.05米/秒的速度向前移动。
八、问题反馈
至此,关于这个仿真项目的简单体验就介绍完了,如果同学们在安装和使用过程中遇到问题,可以在这个项目的Github主页上提交issuse,我们会及时回复,让其他遇到类似问题的小伙伴也能看到。提交issuse的方法:
(1) 在浏览器中打开项目的Github主页:
https://github.com/6-robot/wpr_simulation
(2) 点击分页栏的第二项“Issues”切换页面:
(3) 在“Issues”页面中点击“New issue”即可提交新的issuse:
九、后续更新
在这个系列后续的文章里,我们还会详细介绍Gazebo这个仿真环境的使用。同时这个开源项目也会继续持续更新,添加越来越多的新功能,比如SLAM环境建图、路径规划与导航、语音识别、人脸识别、物品识别、物品抓取……等等等等,也欢迎各位同学在“Issues”中给我们新的想法和反馈。
如果你喜欢这个项目,麻烦不要吝啬手中的鼠标,素质三连!赏给我们一颗小星星!您的满意是我们持续更新的最大动力!谢谢~~我们下次再见!(比心❤)
更多产品了解
欢迎扫码加入云巴巴企业数字化交流服务群
产品交流、问题咨询、专业测评
都在这里!
2020-04-14 17:43:44
2020-04-29 17:12:20
2022-11-23 16:58:11
2022-11-21 13:49:04
2024-03-27 14:25:40
2022-11-24 10:01:04
甄选10000+数字化产品 为您免费使用
申请试用
评论列表