未来,该语言会从ROS独立出来,集成到Ubuntu官方包发布。必须承认,URDF 是个很难懂的语言,但有RViz的强大仿真功能,我们很容易调试它。今天我们就以经常见到智能四轮驱动小车为例介绍它的使用。
OS: Ubuntu 14.04
ROS: Indigo Full package
具体实际的物理小车的建立请看我的文章树莓派搭建 ROS 系统下四论驱动车,并用 IPad, 手机控制。该车有两层,4个轮子,头部还有一个hc-sr04超声波和舵机。
基座(base_link): 长 27cm 宽 15cm 每层板厚度3mm 下层高度4.5cm 上层高度7.8cm
轮子 直径6.8cm 这个尺寸必须非常精确,影响测速和轨迹运算。厚度2.5cm
该车名字 SP1S (顺跑1 Smart)
$ catkin_create_pkg sp1s std_msgs rospy roscpp urdf
$ cd sp1s/
$ mkdir urdf
catkin_create_pkg最后一个参数是引入urdf 库。创建urdf目录存放urdf文件。
**必须已经创建好了一个workspace的情况下,使用该命令创建package.
创建目录launch并新建文件dispaly.launch 建立好urdf文件后就使用该文件launch
该文件可以帮助启动RViz来观察机器人。其中有三个node,一个是rviz,另外两个joint_state_publisher robot_state_publisher是必须的node。
第一个输入参数 model 就是要启动的urdf文件路径。
第二个输入参数 gui 指定是否启用关节转动控制面板窗口。
ROS里面把每个零件称为link,作为基座的link统一称为base_link.在官方的所有事例文章里面到处可以看到base_link,所以最好也延续该名称。其它的link都要依附到base_link上。这里我们把下层的板作为base_link. 新建一个文件sp1s.urdf,描述代码如下:
link可以指定许多属性:
我们详细看一下
使用下面命令启动RViz来查看我们刚刚完成的机器人模型:
roslaunch sp1s display.launch model:=urdf/sp1s.urdf
这时只是显示了一个半透明的长方体,就是我们的主体部分。中间的黄色点代表了base_link的原点,也是整个RViz世界的原点。
下面我们开始创建四个轮子,它们的名字分别为tyer_front_left, tyer_front_right, tyer_back_left, tyer_back_right. 尺寸大小直径6.8cm 厚度2.5cm. 先以一个轮子为例:
roslaunch sp1s display.launch model:=urdf/sp1s.urdf
可以看到黄色的轮子显示到了车身的中央,因为它的原点origin和base_link的origin重合. 选中TFcheckbox,就会显示出XYZ坐标轴,红色为X, 绿色为Y,蓝色为Z。
下一篇文章将讲述如何使用joint将轮子平移到左上方并旋转。
ROS 学习系列 -- 程序发送点云PointCloud2到Rviz显示
方法1 直接加载PCD文件:
#include
// PCL specific includes
#include
#include
#include
#include
// Create a ROS publisher for the output point cloud
pub = nh.advertise ("filtered_plane", 1);
pcl::PCLPointCloud2::Ptr cloud2(new pcl::PCLPointCloud2);
pcl::io::loadPCDFile (argv[1], *cloud2);
// Convert to ROS data type
sensor_msgs::PointCloud2 output;
pcl_conversions::fromPCL(*cloud2, output);
output.header.frame_id = std::string("base_link");
// Publish the data
pub.publish (output);
方法2 代码组织点云数据
#include
#include
#include
#include
#include
#include
typedef pcl::PointCloud pclPointCloudXYZ;
typedef boost::shared_ptr pclPointCloudXYZPtr;
void main() {
pclPointCloudXYZPtr pcl_cloud( new pclPointCloudXYZ );
BuildPclPointCloud( pcl_cloud );
sensor_msgs::PointCloud2Ptr pROSCloud(new sensor_msgs::PointCloud2);
pcl::toROSMsg( *pcl_cloud, *pROSCloud );
pROSCloud->header.frame_id = frame;
pROSCloud->header.stamp = ros::Time::now();
}
template void
BuildPclPointCloud( boost::shared_ptr> pCloud )
{
PointCloudType target_pt;
int j = 0;
for( int u = 0; u < width; u++ )
{
for(int v = 0; v < height; v++ )
{
target_pt.x = x;
target_pt.y = y;
target_pt.z = z;
pCloud->points.push_back( target_pt );
}
}
pCloud->width = pCloud->points.size();
pCloud->header.stamp = pcl_conversions::toPCL( ros::Time::now());
pCloud->height = 1;
}
ROS 学习系列 -- 执行turtlebot navigation的方法
我们讲一下如何使用现有的turtlebot launch文件来启动navigation,并在PC上使用rviz指挥。
1. 网络环境布置
turtlebot单片机我们使用的是firefly,rviz运行在PC上,它们使用wifi通讯。master ros运行在firefly上,PC需要指向firefly。在PC修改IP到firefly主机的映射,修改/etc/hosts 文件,如:
sudo nano /etc/hosts
打开hosts后,我们从前面几行开始插入firefly单片机的ip地址和hostname,形式如下:
127.0.0.1 localhost
127.0.1.1 pc_hostname
192.168.1.16 firefly
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
完毕后保存并确认可以在PC上ping通firefly名字本身。
在firefly单片机上source下面的脚本内容,设定turtlebot为master ROS节点:
export TURTLEBOT_BASE=roomba
export TURTLEBOT_STACKS=circles
export TURTLEBOT_3D_SENSOR=asus_xtion_pro
export TURTLEBOT_SIMULATION=false
export TURTLEBOT_SERIAL_PORT=/dev/ttyUSB0
export ROS_IP=192.168.1.16
export TURTLEBOT_MAP_FILE=/home/firefly/room323.yaml
在PC上source下面的脚本内容,指向turtlebot为master ROS节点:
export ROS_MASTER_URI=http://firefly:11311
export ROS_HOSTNAME=pc_hostname
2. 安装必要软件包
在firefly单片机上执行下面的脚本安装:
sudo apt-get install ros-indigo-turtlebot ros-indigo-rocon-app-platform ros-indigo-rocon-multimaster ros-indigo-rocon-msgs ros-indigo-rocon-qt-gui ros-indigo-rocon-tools ros-indigo-std-capabilities ros-indigo-rqt-capabilities ros-indigo-rocon-remocon ros-indigo-rocon-apps ros-indigo-turtlebot-rapps
在PC上执行下面的脚本安装:
sudo apt-get install ros-indigo-turtlebot-rviz-launchers
当然RVIZ 是必须已经安装完了
3. 启动turtlebot和rviz
在firefly单片机上打开一个终端执行下面脚本:
roslaunch turtlebot_bringup minimal.launch
在firefly单片机上打开第二个终端执行下面脚本:
roslaunch turtlebot_app nav_map.launch
注意这里一定要设置环境变量 TURTLEBOT_MAP_FILE 注明地图yaml文件的位置。这个时候,turtlebot已经可以使用了,准备接受命令移动。
执行下面脚本在PC上执行脚本:
roslaunch turtlebot_rviz_launchers view_navigation.launch --screen
这时候就可以看到rviz上显示如下画面,点击按钮'2D Pose Estimate' '2D Nav Goal'就可以导航了:
4. 简化后的turtlebot启动脚本
在firefly单片机上只执行一个简化后的launch脚本也可以,脚本内容如下:
- {source_frame: base_footprint, target_frame: map}
- {source_frame: camera_depth_frame, target_frame: map}
include文件:
"code" class="cpp">// For transform support
// For transform support #include "tf/transform_broadcaster.h" #include "tf/transform_listener.h" #include "tf/message_filter.h" #include "tf/tf.h" #include "message_filters/subscriber.h"
某时刻机器人在地图上的位置:
当机器人在移动过程中,tf会不断接收 base_link->odom 的位置关系信息,这些信息是根据时间不断变化并被记录下来的。当其它节点需要获取某个时间点上的 base_link的位置时就可以通过下面的方法查询:
x, y, yaw 就是base_link 在t 时刻在地图上的位置:
bool getOdomPose(tf::Stamped& odom_pose,
double& x, double& y, double& yaw,
const ros::Time& t, const std::string& base_link)
{
// Get the robot's pose
tf::Stamped ident (tf::Transform(tf::createIdentityQuaternion(),
tf::Vector3(0,0,0)), t, base_link );
try
{
tf_ = new tf::TransformListener();
tf_->transformPose(odom_frame_id_, ident, odom_pose);
}
catch(tf::TransformException e)
{
ROS_WARN("Failed to compute odom pose, skipping scan (%s)", e.what());
return false;
}
x = odom_pose.getOrigin().x();
y = odom_pose.getOrigin().y();
double pitch,roll;
odom_pose.getBasis().getEulerYPR(yaw, pitch, roll);
return true;
}
机器人是矩形,四个角儿相对中心的位置已知,获取四个角相对map的位置
tf::Stamped corner1(
tf::Pose(tf::Quaternion(0, 0, 0, 1), tf::Vector3(0.30, -0.45, 0.0)),
ros::Time(0), "base_link");
tf::Stamped corner2(
tf::Pose(tf::Quaternion(0, 0, 0, 1), tf::Vector3(0.30, 0.45, 0.0)),
ros::Time(0), "base_link");
tf::Stamped corner3(
tf::Pose(tf::Quaternion(0, 0, 0, 1), tf::Vector3(-0.30, -0.45, 0.0)),
ros::Time(0), "base_link");
tf::Stamped corner4(
tf::Pose(tf::Quaternion(0, 0, 0, 1), tf::Vector3(-0.30, 0.45, 0.0)),
ros::Time(0), "base_link");
transform_listener = new tf::TransformListener();
tf::Stamped transformed_corner_1;
transform_listener.transformPose("map", corner_1, transformed_corner_1);
tf::Stamped transformed_corner_2;
transform_listener.transformPose("map", corner_2, transformed_corner_2);
tf::Stamped transformed_corner_3;
transform_listener.transformPose("map", corner_3, transformed_corner_3);
tf::Stamped transformed_corner_1;
transform_listener.transformPose("map", corner_4, transformed_corner_4);
随机器人移动点在t+1时刻的位置
已知 t 时刻的位置是 pose_old,求t+1 时刻的位置 pose_new
tf_ = new tf::TransformListener();
tf::StampedTransform tx_odom;
try
{
tf_->lookupTransform(base_frame_id_, ros::Time::now(),
base_frame_id_, msg.header.stamp,
global_frame_id_, tx_odom);
}
catch(tf::TransformException e)
{
ROS_WARN("Failed to transform initial pose in time (%s)", e.what());
tx_odom.setIdentity();
}
tf::Pose pose_old, pose_new;
tf::poseMsgToTF(msg.pose.pose, pose_old);
pose_new = tx_odom.inverse() * pose_old;
// Transform into the global frame
ROS_INFO("Setting pose (%.6f): %.3f %.3f %.3f",
ros::Time::now().toSec(),
pose_new.getOrigin().x(),
pose_new.getOrigin().y(),
getYaw(pose_new));
这里认为global_frame_id是不动的,pose_old和pose_new都是在global_frame_id坐标系下的坐标。但是pose_old描述的物体是随着base_frame_id同步移动的
关于fixed frame的解释:2.3 Transforms in Time
相对角度的转换Quaternion
当base_link代表机器人时,激光扫描仪laser_scan安装的角度与base_link不平行,即激光数据的零度不对应机器人的正前方零度。已知 laser_scan->angle_min 和 laser_scan->angle_increment 为激光数据信息,转换角度到base_link的位置代码如下,该算法可以考虑到激光器上下颠倒安装的情况导致angle_increment为负:
tf::Quaternion q;
q.setRPY(0.0, 0.0, laser_scan->angle_min);
tf::Stamped min_q(q, laser_scan->header.stamp,
laser_scan->header.frame_id);
q.setRPY(0.0, 0.0, laser_scan->angle_min + laser_scan->angle_increment);
tf::Stamped inc_q(q, laser_scan->header.stamp,
laser_scan->header.frame_id);
try
{
tf_->transformQuaternion(base_frame_id_, min_q, min_q);
tf_->transformQuaternion(base_frame_id_, inc_q, inc_q);
}
catch(tf::TransformException& e)
{
ROS_WARN("Unable to transform min/max laser angles into base frame: %s",
e.what());
return;
}
double angle_min = tf::getYaw(min_q);
double angle_increment = tf::getYaw(inc_q) - angle_min; //考虑到了激光器上下颠倒安装的情况导致为负数
已知 W->B 和B->A的坐标转换,求W->A的坐标转换
ROS 主动蒙特卡罗粒子滤波定位算法 AMCL 解析-- map与odom坐标转换的方法
有时间差的lookupTransform
ros上的详细教程
turtle1和turtle2都是 world 的child frame. turtle1->world 和turtle2->world 的tf都不断发布的,现在需要知道这样的一个transform转换关系:
5秒中之前turtle1相对与现在的turtle2的位置关系
try{
ros::Time now = ros::Time::now();
ros::Time past = now - ros::Duration(5.0);
listener.waitForTransform("/turtle2", now,
"/turtle1", past,
"/world", ros::Duration(1.0));
listener.lookupTransform("/turtle2", now,
"/turtle1", past,
"/world", transform);
得到的转换结果可以这样理解, ( transform.getOrigin().x(), transform.getOrigin().y() ) 是以turtle2为原点的XY平面上turtle1的坐标。
turtlebot 配合Android应用 Make a Map可以允许用户通过手机操纵机器人扫描室内地图并在手机上显示出来,效果图如下:
ROS机器人与安卓设备的通讯协作是通过rocon实现的,细节这里不作描述。本文适合已经对该技术非常熟悉的人阅读。
下载安卓rocon remocon和Make a map并安装,在turtlebot上启动机器人:
$ roslaunch turtlebot_bringup minimal.launch
该命令可以启动turtlebot的轮子驱动并加在Android interaction 应用rapps. 该命令可以成功启动的前提条件是正确配置的硬件环境变量,把roomba和XTion pro Live指定给ROS.
接下来在安卓终端打开Rocon remocon应用,添加一个master URI,把turtlebot的IP输入,这时就可以看到该节点信息了,点击进入后选择角色”Android pair”. 就可以看到大量的应用,启动”Make a map”就可以看到操作界面了,摇控行走可以看到室内图片信息显示出来了,但是地图上一片空白。
首先查看地图数据到底生产没有,地图应该是由 /map 主题发布出来的,这时候我们看一些该主题:
$ rostopic echo /map
发现居然没有任何数据,不甘心--列出所有主题:
$ rostopic list
诡异的结果出现了,你会发现大部分的topic都被加了一个前缀:”turtlebot”. 地图数据很可能发布到了”/turtlebot/map”上了:
rostopic echo /turtlebot/map
数据哗哗的不停的更新了,我的眼泪也哗哗的流啊,NND这是毛情况?先不管这个,我们要验证一下地图数据到底对不对,使用rviz:
$ roslaunch turtlebot_rviz_launchers view_navigation.launch
上来就看到warning: No map received, 点击topic下拉框,看到/turtlebot/map被自动列出来了,选中后就看到地图确实正确显示出来了。
安卓的Make a map到底订阅了那个主题?我们可以通过rqt_graph来查看究竟:
我们可以看出来,android那边只使用了一个主题”/teleop/compressed_image”,也就是图像数据,没有map数据被订阅的痕迹,看来就是这个”/turtlebot”前缀搞的鬼了。
怀疑是rocon通过 .rapp启动一个.launch文件的时候,这个缀搞被自动加上了,试一下手工启动. 这需要禁止ros在点击安卓应用后启动.launch文件。
$ roscd turtlebot_rapps
$ sudo gedit rapps/make_a_map/make_a_map.rapp
把launch项去掉后,保存退出:
display: Make A Map
description: Make a map by driving a TurtleBot from an Android device.
compatibility: rocon:/turtlebot
public_interface: make_a_map.interface
icon: make_a_map_bubble_icon.png
将安卓终端的Make a map点击”back”退出,在rocon中点击’leave’离开该Master节点, 关闭turtlebot上的Master,并重新启动:
$ roslaunch turtlebot_bringup minimal.launch
在另外一个terminal手工启动make a map:
$ roslaunch turtlebot_rapps make_a_map.launch
这个时候在安卓终端打开rocon remocon启动Map a map,地图出来了,房间图像不见了!
再次使用rqt_graph,可以看到 /map被Android节点成功订阅到了,而且不止一个主题,还有/scan
安卓应用Make a map 需要订阅的主题为:
图像数据:/teleop/compressed_image
地图数据:/map
激光扫描: /scan
而通过remocon启动程序后真正的数据主题为:
图像数据:/teleop/compressed_image
地图数据:/turtlebot/map
激光扫描: /turtlebot/scan
需要完成下面的主题映射:
/turtlebot/map –> /map
/turtlebot/scan –> /scan
在 turtlebot_rapp 的make_a_map.launch 文件里面添加映射命令. 先找到该文件nano编辑
roscd turtlebot_rapps/rapps/make_a_map/
sudo nano make_a_map.launch
加入以下两个指令:
"topic_tools" name="map_relay" type="relay" args="map /map" />
"topic_tools" name="scan_relay" type="relay" args="scan /scan" />
注意这里的参数千万不要带前缀’turtlebot’,而是使用相对路径’map’ ‘scan’ ,而映射目标主题要写明绝对路径’/map’ ‘/sacn’.
因为这个launch文件就被放到了’turtlebot’命名空间里面了,相对路径会被自动补全--前面加上命名空间
ROS默认就有一个环境变量 TURTLEBOT N AME=turtlebot.该环境变量被经由minimal.launch−−>standalone.launch−−> (find rocon_app_manager)/launch/includes/_app_manager.xml 一直传递进 rapp_manager.py 里面。该程序负责启动romocon对应的rapp launch文件,查看函数_process_start_app(self, req)
rospy.loginfo("Rapp Manager : starting app '" + req.name + "' underneath " + self._application_namespace)
resp.started, resp.message, subscribers, publishers, services, action_clients, action_servers = \
rapp.start(self._application_namespace,
self._gateway_name,
self._rocon_uri,
req.remappings,
req.parameters,
self._param['app_output_to_screen'],
self._param['simulation'],
self.caps_list)
每次启动的所有程序都被放到了命名空间里面,这里的self._application_namespace就是robot_name参数的值。
rospy.loginfo()的内容可以通过查看 /rosout 这个主题观察,验证每次启动rapp所用的命名空间.
版权声明:本文为博主原创文章,未经博主允许不得转载。