Roomba, Xtion Pro live 实现360度全景照片panorama 无法启动的解决方案

http://blog.csdn.net/crazyquhezheng/article/details/47065811Roomba, Xtion Pro机器人制作地图在Android手机无法实时观测地图   

Roomba, Xtion Pro live 实现360度全景照片panorama 无法启动的解决方案

turtlebot 有个实现iPhone360全景照相功能的应用 panorama.  官方使用Create底座和Kinnect,  在使用Roomba底座和Xtion Pro Live配套时发现,按照教程的方式启动不了。


1.  启动

  1. roslaunch turtlebot_bringup minimal.launch       \\加载轮子驱动  
  2. <pre>roslaunch turtlebot_panorama panorama.launch     \\启动 panorama  
roslaunch turtlebot_bringup minimal.launch       \\加载轮子驱动
<pre>roslaunch turtlebot_panorama panorama.launch     \\启动 panorama

 

打开另一个shell窗口

  1. rosservice call turtlebot_panorama/take_pano 0 360.0 30.0 0.3   \\下令照相转圈  
rosservice call turtlebot_panorama/take_pano 0 360.0 30.0 0.3   \\下令照相转圈

这个时候发现,Roomba 驱动的turtlebot根本没有任何反应,接下来一步剖析问题。


2. 问题分析

打开源代码panorama.cpp, 看到有大量的 log("...") 输出调试信息:

  1. //*************  
  2. // Logging  
  3. //*************  
  4. void PanoApp::log(std::string log)  
  5. {  
  6.   std_msgs::String msg;  
  7.   msg.data = log;  
  8.   pub_log.publish(msg);  
  9.   ROS_INFO_STREAM(log);  
  10. }  
  11.   
  12. PanoApp::PanoApp() : nh(), priv_nh("~")  
  13. {  
  14.   std::string name = ros::this_node::getName();  
  15.   
  16.   ros::param::param<int>("~default_mode", default_mode, 1);  
  17.   ros::param::param<double>("~default_pano_angle", default_pano_angle, (2 * M_PI));  
  18.   ros::param::param<double>("~default_snap_interval", default_snap_interval, 2.0);  
  19.   ros::param::param<double>("~default_rotation_velocity", default_rotation_velocity, 0.3);  
  20.   
  21.   ros::param::param<std::string>("~camera_name", params["camera_name"], "/camera/rgb");  
  22.   ros::param::param<std::string>("~bag_location", params["bag_location"], "/home/turtlebot/pano.bag");  
  23.   
  24.   pub_log = priv_nh.advertise<std_msgs::String>("log", 100);  
  25. }  
//*************
// Logging
//*************
void PanoApp::log(std::string log)
{
  std_msgs::String msg;
  msg.data = log;
  pub_log.publish(msg);
  ROS_INFO_STREAM(log);
}

PanoApp::PanoApp() : nh(), priv_nh("~")
{
  std::string name = ros::this_node::getName();

  ros::param::param<int>("~default_mode", default_mode, 1);
  ros::param::param<double>("~default_pano_angle", default_pano_angle, (2 * M_PI));
  ros::param::param<double>("~default_snap_interval", default_snap_interval, 2.0);
  ros::param::param<double>("~default_rotation_velocity", default_rotation_velocity, 0.3);

  ros::param::param<std::string>("~camera_name", params["camera_name"], "/camera/rgb");
  ros::param::param<std::string>("~bag_location", params["bag_location"], "/home/turtlebot/pano.bag");

  pub_log = priv_nh.advertise<std_msgs::String>("log", 100);
}
从这里可以看出所有的信息都被发送到了    turtlebot\log 这个topic里面了,那就监视一些这个topic:

  1. rostopic  echo turtlebot\log   
rostopic  echo turtlebot\log 

再执行一遍启动命令:

  1. rosservice call turtlebot_panorama/take_pano 0 360.0 30.0 0.3   \\下令照相转圈  
rosservice call turtlebot_panorama/take_pano 0 360.0 30.0 0.3   \\下令照相转圈

这时候可以看到turtlebot\log 输出的日志:

  1. [well time xxxxx]: Starting panorama creation.  
  2. [well time xxxxx]: Pano ROS action goal sent.  
  3. <pre name="code" class="cpp">[well time xxxxx]: Pano action goal just went active.  
[well time xxxxx]: Starting panorama creation.
[well time xxxxx]: Pano ROS action goal sent.
<pre name="code" class="cpp">[well time xxxxx]: Pano action goal just went active.
  1. <pre name="code" class="cpp">[well time xxxxx]: snap  
<pre name="code" class="cpp">[well time xxxxx]: snap

 
 很明显,事情卡在了snap拍快照上面了,我们来看看这块代码: 

  1. void PanoApp::snap()  
  2. {  
  3.   log("snap");  
  4.   pub_action_snap.publish(empty_msg);  
  5. }  
  6.   
  7. void PanoApp::init()  
  8. {  
  9.   ......................  
  10.   
  11.   //***************************  
  12.   // pano_ros API  
  13.   //***************************  
  14.   pano_ros_client = new actionlib::SimpleActionClient<pano_ros::PanoCaptureAction>("pano_server"true);  
  15.   log("Waiting for Pano ROS server ...");  
  16.   pano_ros_client->waitForServer(); // will wait for infinite time  
  17.   log("Connected to Pano ROS server.");  
  18.   pub_action_snap = nh.advertise<std_msgs::Empty>("pano_server/snap", 100);  
  19.   pub_action_stop = nh.advertise<std_msgs::Empty>("pano_server/stop", 100);  
  20.   image_transport::ImageTransport it_pano(nh);  
  21.   sub_stitched = it_pano.subscribe("pano_server/stitch", 1, &PanoApp::stitchedImageCb, this);  
  22.   
  23.   ..............................  
  24. }  
void PanoApp::snap()
{
  log("snap");
  pub_action_snap.publish(empty_msg);
}

void PanoApp::init()
{
  ......................

  //***************************
  // pano_ros API
  //***************************
  pano_ros_client = new actionlib::SimpleActionClient<pano_ros::PanoCaptureAction>("pano_server", true);
  log("Waiting for Pano ROS server ...");
  pano_ros_client->waitForServer(); // will wait for infinite time
  log("Connected to Pano ROS server.");
  pub_action_snap = nh.advertise<std_msgs::Empty>("pano_server/snap", 100);
  pub_action_stop = nh.advertise<std_msgs::Empty>("pano_server/stop", 100);
  image_transport::ImageTransport it_pano(nh);
  sub_stitched = it_pano.subscribe("pano_server/stitch", 1, &PanoApp::stitchedImageCb, this);

  ..............................
}
抓图的功能放到了另外一个进程。"pano_server/snap" 的实现在  turtlebot_apps/software/pano/pano_ros/nodes/capture_server.py 里面:

  1. def pano_capture(self, goal):  
  2.        if self._capture_job is not None:  
  3.            raise Exception("cannot run multile capture jobs. TODO: pre-eempt existing job")  
  4.          
  5.        rospy.loginfo('%s: Pano Capture Goal: \n\tCamera [%s]'%(self._action_name, goal.camera_topic))  
  6.                               
  7.        pano_id = int(rospy.get_time())  
  8.        #TODO make this a parameter, bag, publisher, etc...  
  9.        capturer = self._capture_interface()#= BagCapture(pano_id,goal.bag_filename or None)  
  10.        capturer.start(pano_id,goal)  
  11.                  
  12.        self._snap_requested = False #reset  
  13.        capture_job = self._capture_job = PanoCaptureJob(pano_id, capturer )  
  14.        camera_topic = goal.camera_topic or rospy.resolve_name('camera')  
  15.        #TODO: FIX ONCE OPENNI API IS FIXED  
  16.        image_topic = rospy.names.ns_join(camera_topic, 'image_color')  
  17.        camera_info_topic = rospy.names.ns_join(camera_topic, 'camera_info')  
  18.   
  19.        rospy.loginfo('%s: Starting capture of pano_id %d.\n\tImage [%s]\n\tCamera Info[%s]'  
  20.                      %(self._action_name, pano_id, image_topic, camera_info_topic) )  
  21.        grabber = ImageGrabber(image_topic, camera_info_topic, self.capture_fn)  
  22.   
  23.        # local vars  
  24.        server = self._server  
  25.        preempted = False          
  26.   
  27.        rospy.loginfo('%s: Executing capture of pano_id %d' % (self._action_name, pano_id))    
  28.        # this will become true   
  29.        while capture_job.result is None and not preempted and not rospy.is_shutdown():  
  30.            if server.is_preempt_requested():  
  31.                rospy.loginfo('%s: Preempted' % self._action_name)  
  32.                server.set_preempted()  
  33.                capture_job.cancel()  
  34.                preempted = True  
  35.            else:  
  36.                rospy.sleep(0.001#let the node rest a bit  
  37.                  
  38.        result = capture_job.result  
  39.        grabber.stop()  
  40.          
  41.        if result:  
  42.            rospy.loginfo('%s: Succeeded, [%s] images.\nResult: %s' % (self._action_name, result.n_captures, result))  
  43.            server.set_succeeded(result)  
  44.    
  45.        self._capture_job = None  
 def pano_capture(self, goal):
        if self._capture_job is not None:
            raise Exception("cannot run multile capture jobs. TODO: pre-eempt existing job")
        
        rospy.loginfo('%s: Pano Capture Goal: \n\tCamera [%s]'%(self._action_name, goal.camera_topic))
                             
        pano_id = int(rospy.get_time())
        #TODO make this a parameter, bag, publisher, etc...
        capturer = self._capture_interface()#= BagCapture(pano_id,goal.bag_filename or None)
        capturer.start(pano_id,goal)
                
        self._snap_requested = False #reset
        capture_job = self._capture_job = PanoCaptureJob(pano_id, capturer )
        camera_topic = goal.camera_topic or rospy.resolve_name('camera')
        #TODO: FIX ONCE OPENNI API IS FIXED
        image_topic = rospy.names.ns_join(camera_topic, 'image_color')
        camera_info_topic = rospy.names.ns_join(camera_topic, 'camera_info')

        rospy.loginfo('%s: Starting capture of pano_id %d.\n\tImage [%s]\n\tCamera Info[%s]'
                      %(self._action_name, pano_id, image_topic, camera_info_topic) )
        grabber = ImageGrabber(image_topic, camera_info_topic, self.capture_fn)

        # local vars
        server = self._server
        preempted = False        

        rospy.loginfo('%s: Executing capture of pano_id %d' % (self._action_name, pano_id))  
        # this will become true 
        while capture_job.result is None and not preempted and not rospy.is_shutdown():
            if server.is_preempt_requested():
                rospy.loginfo('%s: Preempted' % self._action_name)
                server.set_preempted()
                capture_job.cancel()
                preempted = True
            else:
                rospy.sleep(0.001) #let the node rest a bit
                
        result = capture_job.result
        grabber.stop()
        
        if result:
            rospy.loginfo('%s: Succeeded, [%s] images.\nResult: %s' % (self._action_name, result.n_captures, result))
            server.set_succeeded(result)
  
        self._capture_job = None

第16, 17 行 显示 同时监听等待 “image_color” “camera_info”两个topic, 它们都是OPenNI2发布的图像数据,看ImageGrabber的代码发现,里面就是在同步等待两个 topic同时有数据时才触发拍照操作(  message_filters.TimeSynchronizer() )


这个时候我们查看一下这两个主题是否有图像和信息:

  1. $ rosrun image_view image_view image:=/camera/rgb/image_color  
  2.   
  3. $ rostopic echo /camera/rgb/camera_info  
$ rosrun image_view image_view image:=/camera/rgb/image_color

$ rostopic echo /camera/rgb/camera_info

可以看到在image_view里面是没有图像的,因此可以判断是图像的主题在kinnect和XtionPro的不一致造成的.


真正的图像主题在这里:

  1. $ rosrun image_view image_view image:=/camera/rgb/image_raw  
$ rosrun image_view image_view image:=/camera/rgb/image_raw
可以看到在image_view里面是有图像的。


结论

Xtion Pro Live 的图像数据被发布在 /camera/rgb/image_raw, 但是turtlebot_apps/software/pano/pano_ros/nodes/capture_server.py 里面一直在监听/camera/rgb/image_color主题,导致一直没有图像出现。


解决方案

remap topic 映射主题, 修改turtlebot_panorama/launch/panorama.launch。 如下,在第15行增加了remap, 映射两个主题,将程序指向/camera/rgb/image_raw

  1. <launch>  
  2.   <!-- 3d sensor; we just need RGB images -->  
  3.   <include file="$(find turtlebot_bringup)/launch/3dsensor.launch">  
  4.     <arg name="rgb_processing"                  value="true"/>  
  5.     <arg name="ir_processing"                   value="false"/>  
  6.     <arg name="depth_processing"                value="false"/>  
  7.     <arg name="depth_registered_processing"     value="false"/>  
  8.     <arg name="depth_registration"              value="false"/>  
  9.     <arg name="disparity_processing"            value="false"/>  
  10.     <arg name="disparity_registered_processing" value="false"/>  
  11.     <arg name="scan_processing"                 value="false"/>  
  12.   </include>  
  13.   
  14.   <node name="pano_server" pkg="pano_ros" type="capture_server.py" output="screen">  
  15.     <remap from="camera/rgb/image_color" to="camera/rgb/image_raw"/>  
  16.   </node>  
  17.   
  18.   <node name="turtlebot_panorama" pkg="turtlebot_panorama" type="panorama" output="screen">  
  19.     <param name="default_mode" value="1"/>  
  20.     <param name="default_pano_angle" value="6.28318530718"/> <!-- 2 * Pi -->  
  21.     <param name="default_snap_interval" value="2.0"/>  
  22.     <param name="default_rotation_velocity" value="0.3"/>  
  23.     <param name="camera_name" value="camera/rgb"/>  
  24.     <param name="bag_location" value="/tmp/turtlebot_panorama.bag"/>  
  25.     <remap from="cmd_vel" to="/cmd_vel_mux/input/navi"/>  
  26.     <remap from="odom" to="/odom"/>  
  27.   </node>  
  28. </launch>  
Roomba, Xtion Pro机器人制作地图在Android手机无法实时观测地图                         

turtlebot 配合Android应用Make a Map可以允许用户通过手机操纵机器人扫描室内地图并在手机上显示出来,效果图如下:
这里写图片描述
ROS机器人与安卓设备的通讯协作是通过rocon实现的,细节这里不作描述。本文适合已经对该技术非常熟悉的人阅读。


1. 问题描述

下载安卓rocon remoconMake a map并安装,在turtlebot上启动机

该命令可以启动turtlebot的轮子驱动并加在Android interaction 应用rapps. 该命令可以成功启动的前提条件是正确配置的硬件环境变量,把roomba和XTion pro Live指定给ROS.

接下来在安卓终端打开Rocon remocon应用,添加一个master URI,把turtlebot的IP输入,这时就可以看到该节点信息了,点击进入后选择角色”Android pair”. 就可以看到大量的应用,启动”Make a map”就可以看到操作界面了,摇控行走可以看到室内图片信息显示出来了,但是地图上一片空白。
无法显示地图但可以显示视频的app

2. 问题分析

首先查看地图数据到底生产没有,地图应该是由 /map 主题发布出来的,这时候我们看一些该主题:

 

上来就看到warning: No map received, 点击topic下拉框,看到/turtlebot/map被自动列出来了,选中后就看到地图确实正确显示出来了。
手工选择 turtlebot\map 主题

3. 问题确认

安卓的Make a map到底订阅了那个主题?我们可以通过rqt_graph来查看究竟:
只订阅到了图像数据的关系图
我们可以看出来,android那边只使用了一个主题”/teleop/compressed_image”,也就是图像数据,没有map数据被订阅的痕迹,看来就是这个”/turtlebot”前缀搞的鬼了。

怀疑是rocon通过 .rapp启动一个.launch文件的时候,这个缀搞被自动加上了,试一下手工启动. 这需要禁止ros在点击安卓应用后启动.launch文件。

 

这个时候在安卓终端打开rocon remocon启动Map a map,地图出来了,房间图像不见了!
只能显示地图的app

再次使用rqt_graph,可以看到 /map被Android节点成功订阅到了,而且不止一个主题,还有/scan
安卓终端与turtlebot的/map /scan 主题订阅关系图

4. 结论

安卓应用Make a map 需要订阅的主题为:
图像数据:/teleop/compressed_image
地图数据:/map
激光扫描: /scan

而通过remocon启动程序后真正的数据主题为:
图像数据:/teleop/compressed_image
地图数据:/turtlebot/map
激光扫描: /turtlebot/scan

5. 解决方案

需要完成下面的主题映射:
/turtlebot/map –> /map
/turtlebot/scan –> /scan

在 turtlebot_rapp 的make_a_map.launch 文件里面添加映射命令. 先找到该文件nano编辑

 

注意这里的参数千万不要带前缀’turtlebot’,而是使用相对路径’map’ ‘scan’ ,而映射目标主题要写明绝对路径’/map’ ‘/sacn’.

因为这个launch文件就被放到了’turtlebot’命名空间里面了,相对路径会被自动补全--前面加上命名空间

6. 后记--’turtlebot’命名空间哪里来的?

ROS默认就有一个环境变量 TURTLEBOTNAMEturtlebot.minimal.launch>standalone.launch> (find rocon_app_manager)/launch/includes/_app_manager.xml 一直传递进 rapp_manager.py 里面。该程序负责启动romocon对应的rapp launch文件,查看函数_process_start_app(self,


每次启动的所有程序都被放到了命名空间里面,这里的self._application_namespace就是robot_name参数的值。

rospy.loginfo()的内容可以通过查看 /rosout 这个主题观察,验证每次启动rapp所用的命名空间.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值