1.QT刷新ROS地图画面时,地图画面时不时卡住,甚至整个界面挂掉退出

一、问题背景和描述

最近用QT实现一个地图刷新的功能,主要的过程就是通过ros的发布订阅机制来接收cartographer发布的map数据,收到map数据后,利用qt中的绘图相关的类进行实时描绘。功能实现后,测试时发现一下几个问题:1、刷新地图的时候,地图画面出现闪烁的情况。2、地图刷新时,时不时程序直接退出,很不稳定。3、就算程序不退出,当建图区域过大时,会导致,地图刷新画面会卡死不动

 

二、分析过程

看到上述问题时,首先去看了一下ros系统提供的log,关键log如下:

[roslaunch][INFO] 2020-10-27 10:59:17,613: process[amcl-8]: cwd will be [/home/cuijidan/.ros]
[roslaunch][INFO] 2020-10-27 10:59:17,617: process[amcl-8]: started with pid [5776]
[roslaunch][INFO] 2020-10-27 10:59:17,617: ... successfully launched [amcl-8]
[roslaunch][INFO] 2020-10-27 10:59:17,617: ... launch_nodes complete
[roslaunch.pmon][INFO] 2020-10-27 10:59:17,617: registrations completed 
[roslaunch.parent][INFO] 2020-10-27 10:59:17,617: ... roslaunch parent running, waiting for process exit
[roslaunch][INFO] 2020-10-27 10:59:17,617: spin
[roslaunch][ERROR] 2020-10-27 10:59:24,319: [ui-5] process has died [pid 5760, exit code -11, cmd /home/cuijidan/pcws/devel_isolated/ui/lib/ui/ui __name:=ui __log:=/home/cuijidan/.ros/log/66d16404-1800-11eb-ab52-1c1bb53579f1/ui-5.log].
log file: /home/cuijidan/.ros/log/66d16404-1800-11eb-ab52-1c1bb53579f1/ui-5*.log
[roslaunch.pmon][INFO] 2020-10-27 10:59:24,319: ProcessMonitor.unregister[ui-5] starting
[roslaunch.pmon][INFO] 2020-10-27 10:59:24,320: ProcessMonitor.unregister[ui-5] complete
[roslaunch.pmon][INFO] 2020-10-27 11:04:18,296: ProcessMonitor.shutdown 
[roslaunch.pmon][INFO] 2020-10-27 11:04:18,328: ProcessMonitor._post_run 
[roslaunch.pmon][INFO] 2020-10-27 11:04:18,329: ProcessMonitor._post_run : remaining procs are [, , , , , , , ]
[roslaunch.pmon][INFO] 2020-10-27 11:04:18,330: ProcessMonitor exit: killing amcl-8
[roslaunch][INFO] 2020-10-27 11:04:18,331: [amcl-8] killing on exit
[roslaunch][INFO] 2020-10-27 11:04:18,332: process[amcl-8]: killing os process with pid[5776] pgid[5776]
[roslaunch.pmon][INFO] 2020-10-27 11:04:18,332: ProcessMonitor exit: killing robot_manager-7
[roslaunch.pmon][INFO] 2020-10-27 11:04:18,333: ProcessMonitor exit: killing stdr_server-6
[roslaunch][INFO] 2020-10-27 11:04:18,334: [amcl-8] sending SIGINT to pgid [5776]
[roslaunch][INFO] 2020-10-27 11:04:18,334: [robot_manager-7] killing on exit
[roslaunch][INFO] 2020-10-27 11:04:18,335: [stdr_server-6] killing on exit
[roslaunch.pmon][INFO] 2020-10-27 11:04:18,335: ProcessMonitor exit: killing base_to_laser-4
[roslaunch.pmon][INFO] 2020-10-27 11:04:18,336: ProcessMonitor exit: killing base_to_gyro-3
[roslaunch][INFO] 2020-10-27 11:04:18,336: [amcl-8] sent SIGINT to pgid [5776]

看到上面的log后,第一时间去查了下下面log中的exit code -11代表着什么

[roslaunch][ERROR] 2020-10-27 10:59:24,319: [ui-5] process has died [pid 5760, exit code -11, cmd /home/cuijidan/pcws/devel_isolated/ui/lib/ui/ui __name:=ui __log:=/home/cuijidan/.ros/log/66d16404-1800-11eb-ab52-1c1bb53579f1/ui-5.log].
log file: /home/cuijidan/.ros/log/66d16404-1800-11eb-ab52-1c1bb53579f1/ui-5*.log

经过网上的查找后发现,有些博客确实说到了这个退出码,但是具体代表什么并没有说明白,只给出了一个环境更换相关解决方案,不适合该问题,同时,也看到了log中出现大量的SIGINT,该信号是Ubuntu下特有的,当按下control c后,当前程序就会收到该信号,可以对该信号进行忽略,但是,问题发生时,并没有按下control c,所以,应该不是该信号直接导致程序退出的。

上述调查无果后,怀疑是访问无效指针的数据导致的程序退出,于是将该进程中,所有无关功能的指针全部注释掉,重新编译,发现还是出现该问题,证明,代码中指针的使用没问题

接着,重新捋了一遍代码,在接收地图的回调函数中添加了很多打印,但是,回调函数完整地执行结束后,程序依然会挂掉,退出码依然是-11。

无奈之下,就把这三次的挂掉时,ros的log都打开,发现log都是一致的,而且还发现,每次程序在挂掉之前,都会调用ros的函数spin,都会出现下面的这个log

[roslaunch][INFO] 2020-10-27 10:59:17,617: spin

但是当时只是注意到了这个现象,并没有怀疑是是spin的问题,因为这毕竟时ros框架自带的函数,如果有问题,是会查到的

于是乎接着测试,这次测试时没有挂掉,程序跑了很长时间,但是时间一长,地图卡主不动了,也不刷新了,看了一下,此时的地图数据

void MainWindow::receivemapcb(const nav_msgs::OccupancyGrid& msg)
{
	cout<

发现打印出的msg.data.size()竟然有九十多万个,怀疑会不会是数据太多,导致内存不够用,于是,把机器人开着,但是不让机器人移动,此时激光雷达扫描的地图数据不是很大,也就几千个,但是程序还是卡,有时候还会挂掉,所以说明不是地图数据太多的原因

 

因为ROS自带了一个叫做rviz的显示界面,这个界面也是开源的,很稳定。现在自己的显示程序出了问题,没什么头绪,那么就只能去看看人家rviz是如果做的,于是乎,看了一下rviz的源代码

rviz的地图显示的实现是在map_display.cpp中,里面实现了一个MapDisplay的类,当这个类初始化的时候,会订阅话题,代码如下

void MapDisplay::subscribe()
{
  //.......
    try
    {
      if (unreliable_property_->getBool())
      {
        map_sub_ = update_nh_.subscribe(topic_property_->getTopicStd(), 1, &MapDisplay::incomingMap,
                                        this, ros::TransportHints().unreliable());
      }
      else
      {
        map_sub_ = update_nh_.subscribe(topic_property_->getTopicStd(), 1, &MapDisplay::incomingMap,
                                        this, ros::TransportHints().reliable());
      }
      setStatus(StatusProperty::Ok, "Topic", "OK");
    }
    catch (ros::Exception& e)
    {
      setStatus(StatusProperty::Error, "Topic", QString("Error subscribing: ") + e.what());
    }
  //.......
}

那两个subscribe就是用来接受地图数据,并通过MapDisplay::incomingMap来渲染,接着,查看一下MapDisplay::incomingMap的实现

void MapDisplay::incomingMap(const nav_msgs::OccupancyGrid::ConstPtr& msg)
{
  current_map_ = *msg;
  // updated via signal in case ros spinner is in a different thread
  Q_EMIT mapUpdated();
  loaded_ = true;
}

可以看到,当接收到地图数据后,并没有马上解析地图数据,更新地图画面,而是发了一个信号,与之绑定的槽函数如下

connect(this, SIGNAL(mapUpdated()), this, SLOT(showMap()));

可见,更新地图的方法是通过信号槽来完成的,并不使收到地图数据后马上就开始刷新画面,而且,代码里还有一句注释

updated via signal in case ros spinner is in a different thread

翻译过来就是通过信号来更新地图数据,防止ros spinner不在主线程里

看到这里,我就在想,程序每次挂的时候,都会打印出

[roslaunch][INFO] 2020-10-27 10:59:17,617: spin

会不会真的和这个函数有关??

没想太多,就按照rviz的写法,把自己刷新地图的功能封装成一个函数,当收到地图数据时,也通过发一个信号来触发槽函数的方式来刷新地图。重新编译后,再次进行测试,见证奇迹的时刻到了。。。,地图不会再卡主,软件也没有再挂掉,测试了10几次后,上述问题也没有再出现,问题解决。

 

三、解决办法

到这里,解决办法就是:按照rviz的写法,把刷新地图的功能封装成一个函数,当收到地图数据时,也通过发一个信号来触发槽函数的方式来刷新地图

但是,为啥能这种写法能解决这个问题呢?重新又看了一下代码。原来,因为spin是一个死循环,而qt主函数中的exec也是一个死循环,为了使qt的主事件循环不被卡主,将ros::spin放在了分线程中执行

void spinThreadFunction(void)
{
	cout<<__func__<

但是,这样写,虽然不会导致qt的主事件循环卡主,但是,因为ros::spin();在分线程中执行,订阅话题的回调函数也会和ros::spin在同一个分线程中执行。这是因为ros::spin()内部就是处理订阅者的回调函数的,而原来收到地图数据后,马上渲染地图,这样就把UI的渲染部分挪到了分线程中执行,没有在主线程中,后来,上网上查到,所有的UI渲染部分都要放在主线程中执行,不要在分线程中执行。

而通过信号触发槽函数的方式修改后,只有信号在分线程中触发,而信号槽的连接方式默认是Qt::AutoConnection,所以此时的连接方式自动转化为Qt::QueuedConnection,此时,槽函数所在的线程是主线程,所以,当信号发出后,画面渲染的槽函数就会在主线程中执行,符合UI渲染部分都要放在主线程中执行,不要在分线程中执行。所以,问题就得到了解决。关于信号槽的连接方式见博客https://blog.csdn.net/Master_Cui/article/details/109228521

所以,归根结底,是因为把ros::spin放在了分线程中,导致画面渲染也在分线程中执行,这也就和rviz的注释对上了

updated via signal in case ros spinner is in a different thread

通过这种方式,无论ros spinner在主线程还是分线程,都能正确的将画面渲染部分运行在主线程中

你可能感兴趣的:(各种问题解决,linux,qt,ROS,slam,ros)