拓冰建站拓冰建站
首页 / 资讯中心 / 正文

ROS Noetic下RVIZ与Qt5.12.8兼容性实战指南

简介本资源是一个面向ROS初学者与机器人开发者的QtRVIZ跨平台可视化集成DEMO聚焦Linux环境下Qt GUI与ROS可视化工具RVIZ的协同开发实践解决自定义界面控制3D传感器数据展示、节点通信调试等典型工程问题。压缩包共14个文件含4个核心CPP源码如rvizwidget.cpp、qnode.cpp、3个头文件h、2个UI界面设计文件main_window.ui、rvizwidget.ui辅以QRC资源描述、DOX文档及CMake构建配置整体仅29KB轻量易导入结构清晰便于理解Qt Widgets与ROS节点生命周期管理、话题发布/订阅机制及RVIZ插件嵌入逻辑。目前已有575人学习下载提供完整可编译工程框架涵盖环境初始化、ROS节点封装、RVIZ Widget嵌入、UI交互事件绑定等关键实现是掌握ROS人机交互界面开发的实用入门范例。1. RVIZ Qt ROS Linux 环境下跑通一个可交互的可视化 Demo为什么你 clone 下来却打不开、报错 fatal、界面空白或 VNC 里根本启动失败这不是一个“照着教程点几下就能跑”的玩具 Demo。当你在 Linux尤其是 Ubuntu 20.04/22.04上 git clone 一个名为rviz_demo_qtroslinux_rviz_DEMO_ROSRviz_ROS_的仓库执行catkin_make后source devel/setup.bash再敲rosrun rviz_demo demo_node—— 却只看到终端刷出qt.qpa.plugin: could not find the qt platform plugin linuxfb、fatal: cannot mix incompatible qt library (version ex50601)或者rviz: symbol lookup error: ... libQt5Core.so.5: undefined symbol: _ZNK7QString8toLatin1Ev甚至 VNC 桌面里 rviz 窗口一闪而没……说明你正踩进 ROSQt 双版本混用的黑匣子深坑。这个标题不是炫技组合词而是真实工程中高频复现的三重耦合故障现场RVIZ 作为 ROS 官方可视化前端依赖 Qt5但 ROS Noetic 默认链接系统 Qt5而你本地编译的 Qt 插件、或鱼香 ROS 一键安装脚本预装的 Qt、或自己从官网下载的 Qt Creator 二进制包极大概率自带另一套 Qt5 运行时——它们彼此不兼容且 Linux 下无 Windows 那种 DLL 路径优先级机制一加载就崩。本文不讲抽象原理只拆解如何在标准 Ubuntu ROS Noetic 环境下从零构建一个带 Qt 自定义控件、能实时驱动 RVIZ 场景、且不翻车的最小可运行 Demo。适合正在调试 URDF 显示异常、想加按钮控制 TF 坐标系、或需要把 Fast-LIO 定位结果用 Qt 滑块手动微调后再发给 RVIZ 的一线开发者。2. 为什么必须用 Qt5.12.8 ROS Noetic 组合不是 Qt6、不是 Qt5.15、更不是系统默认 Qt2.1 ROS Noetic 的 Qt ABI 锁死机制源码级绑定不可绕过ROS Noetic2020 年发布LTS 至 2025的rviz包在 CMakeLists.txt 中硬编码了find_package(Qt5 REQUIRED COMPONENTS Core Widgets OpenGL)且其rviz二进制在编译时通过-DQT_QMAKE_EXECUTABLE/usr/lib/x86_64-linux-gnu/qt5/bin/qmake强制指向系统 Qt5。关键在于Noetic 的 rviz.so 是用 Qt5.12.8 编译的Ubuntu 20.04 默认、Noetic 的 roscpp 和 rosconsole 也链接同一套 Qt 符号表。如果你强行用 Qt5.15 编译你的 Qt 控件比如用 Qt Creator 4.15 自带的 Qt5.15.2哪怕只是#include QMainWindow生成的.so也会导出_ZN7QWidgetD1EvQt_5.15这类符号而 rviz 运行时只认_ZN7QWidgetD1EvQt_5.12.8—— 动态链接器直接报undefined symbol。这不是版本号显示问题是 ABIApplication Binary Interface层面断裂。我曾用objdump -T your_plugin.so | grep QWidget对比过Qt5.12.8 和 Qt5.15.2 的虚函数表偏移、RTTI 结构体布局全不同。2.2 鱼香 ROS 一键安装的隐性陷阱它帮你装了 Qt但没帮你隔离环境“鱼香 ROS 一键安装”如curl -s https://fishros.com/install | bash确实省去 ROS 安装步骤但它默认会apt install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools—— 这是系统 Qt5.12.8。但很多用户后续为开发 Qt 界面又从 qt.io 下载离线安装包如Qt 5.15.2 for Linux Desktop并执行./qt-unified-linux-x64-4.4.2-online.run。该安装器默认把 Qt5.15.2 装到~/Qt/5.15.2/gcc_64/并在~/.profile里追加export PATH/home/user/Qt/5.15.2/gcc_64/bin:$PATH export LD_LIBRARY_PATH/home/user/Qt/5.15.2/gcc_64/lib:$LD_LIBRARY_PATH这导致qmake -v显示 5.15.2which qmake指向新路径但rosrun rviz rviz启动时rviz 本身仍用/usr/lib/x86_64-linux-gnu/libQt5Core.so.55.12.8而你的插件.so却链接了~/Qt/5.15.2/gcc_64/lib/libQt5Core.so.55.15.2——dlopen()时动态链接器发现两个libQt5Core.so.5版本共存直接触发fatal: cannot mix incompatible qt library。这是 Linux ELF 加载器的保护机制非 bug是 feature。2.3 正确选型锁定 Qt5.12.8 Ubuntu 20.04/22.04 ROS Noetic 三件套组件推荐版本理由验证命令OSUbuntu 20.04 LTS 或 22.04 LTS内核与 Mesa 驱动对 OpenGL 兼容性最佳避免rviz: GLXBadContextlsb_release -aROSNoetic (1.15.15)官方支持 Qt5.12.8rosdep install自动解析正确依赖rosversion -dQt系统自带 Qt5.12.8apt install qtbase5-dev与 rviz ABI 完全一致无需额外配置qmake -v dpkg -l构建工具catkin_tools非catkin_make支持--cmake-args -DQT_QMAKE_EXECUTABLE/usr/lib/x86_64-linux-gnu/qt5/bin/qmake精确指定 qmakecatkin build --help提示不要卸载系统 Qt5也不要试图用update-alternatives切换 qmake。ROS 工作空间必须全程使用/usr/lib/x86_64-linux-gnu/qt5/bin/qmake这是唯一安全路径。3. 从零构建可交互 RVIZ DemoQt 控件嵌入 RVIZ ROS Topic 双向通信3.1 创建 catkin 工作空间与基础包结构我们不 clone 那个名字冗长的仓库它大概率混用了 Qt5.15而是手写一个最小可验证结构。目标一个 Qt Widget带一个QPushButton和一个QSlider点击按钮发送/demo/cmd_vel拖动滑块实时更新 RVIZ 中的 Arrow Marker。mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src catkin_create_pkg rviz_demo std_msgs geometry_msgs sensor_msgs roscpp rospy rviz qtbase5-dev cd ~/catkin_ws关键点catkin_create_pkg显式声明qtbase5-dev为依赖这会让rosdep install自动安装系统 Qt5.12.8 头文件和库且CMakeLists.txt会自动添加find_package(Qt5 REQUIRED COMPONENTS Core Widgets OpenGL)。3.2 编写 Qt 主窗口继承 QMainWindow但不直接 new QCoreApplicationRVIZ 是一个 Qt 应用它自己管理QApplication实例。你的插件绝不能在main()里new QApplication(argc, argv)否则双 QApplication 冲突。正确做法将 Qt 界面封装为QWidget子类并在 ROS Node 中以QWidget::createWindowContainer()方式嵌入适用于独立窗口或直接作为 RVIZ Plugin需继承rviz::Panel。此处采用更轻量的独立窗口方案// src/rviz_demo/include/rviz_demo/demo_window.h #pragma once #include QWidget #include QPushButton #include QSlider #include QVBoxLayout #include ros/ros.h #include geometry_msgs/Twist.h namespace rviz_demo { class DemoWindow : public QWidget { Q_OBJECT public: explicit DemoWindow(QWidget *parent nullptr); ~DemoWindow() override default; private slots: void onButtonClicked(); void onSliderValueChanged(int value); private: QPushButton* button_; QSlider* slider_; ros::Publisher cmd_vel_pub_; ros::NodeHandle nh_; }; } // namespace rviz_demo// src/rviz_demo/src/demo_window.cpp #include rviz_demo/demo_window.h #include QVBoxLayout #include QHBoxLayout #include QLabel #include geometry_msgs/Twist.h namespace rviz_demo { DemoWindow::DemoWindow(QWidget *parent) : QWidget(parent), nh_(~) { setWindowTitle(RVIZ Demo Control Panel); resize(300, 150); // 布局 QVBoxLayout* main_layout new QVBoxLayout(this); QHBoxLayout* top_layout new QHBoxLayout(); button_ new QPushButton(Send Twist, this); top_layout-addWidget(button_); main_layout-addLayout(top_layout); QHBoxLayout* bottom_layout new QHBoxLayout(); bottom_layout-addWidget(new QLabel(Linear X:, this)); slider_ new QSlider(Qt::Horizontal, this); slider_-setRange(-100, 100); slider_-setValue(0); bottom_layout-addWidget(slider_); main_layout-addLayout(bottom_layout); // ROS Publisher cmd_vel_pub_ nh_.advertisegeometry_msgs::Twist(/demo/cmd_vel, 10); // 连接信号 connect(button_, QPushButton::clicked, this, DemoWindow::onButtonClicked); connect(slider_, QSlider::valueChanged, this, DemoWindow::onSliderValueChanged); } void DemoWindow::onButtonClicked() { geometry_msgs::Twist msg; msg.linear.x 0.5; // 固定值测试 cmd_vel_pub_.publish(msg); ROS_INFO(Published cmd_vel: linear.x 0.5); } void DemoWindow::onSliderValueChanged(int value) { double linear_x value / 100.0; // -1.0 ~ 1.0 geometry_msgs::Twist msg; msg.linear.x linear_x; cmd_vel_pub_.publish(msg); ROS_INFO_STREAM(Slider updated: linear.x linear_x); } } // namespace rviz_demo3.3 编写 ROS Node 主入口启动 Qt 窗口并维持 ROS spin// src/rviz_demo/src/demo_node.cpp #include ros/ros.h #include QApplication #include rviz_demo/demo_window.h int main(int argc, char** argv) { // 注意这里 QApplication 必须在 ros::init 之后、且仅创建一次 ros::init(argc, argv, rviz_demo_node); if (!ros::ok()) return -1; // 关键QApplication 必须传入 argc/argv且不能重复创建 QApplication app(argc, argv); app.setAttribute(Qt::AA_EnableHighDpiScaling); // 高分屏适配 rviz_demo::DemoWindow window; window.show(); // 启动 ROS spinner 在后台线程避免阻塞 Qt 事件循环 ros::AsyncSpinner spinner(2); // 2 个线程处理 callback spinner.start(); // 进入 Qt 事件循环替代 ros::spin() int result app.exec(); spinner.stop(); return result; }3.4 配置 CMakeLists.txt强制使用系统 Qt5.12.8# CMakeLists.txt cmake_minimum_required(VERSION 3.0.2) project(rviz_demo) find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs geometry_msgs sensor_msgs rviz qtbase5-dev # ← 显式声明触发 rosdep 安装 ) # 查找 Qt5必须指定路径防止找到其他 Qt find_package(Qt5 REQUIRED COMPONENTS Core Widgets OpenGL) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD 14) # 添加头文件目录 include_directories( ${catkin_INCLUDE_DIRS} ${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5OpenGL_INCLUDE_DIRS} ) # 构建可执行文件 add_executable(demo_node src/demo_node.cpp src/demo_window.cpp) target_link_libraries(demo_node ${catkin_LIBRARIES} ${Qt5Core_LIBRARIES} ${Qt5Widgets_LIBRARIES} ${Qt5OpenGL_LIBRARIES} ) # 关键强制 qmake 路径即使系统只有一个 Qt5也显式指定 set(QT_QMAKE_EXECUTABLE /usr/lib/x86_64-linux-gnu/qt5/bin/qmake CACHE STRING )注意set(QT_QMAKE_EXECUTABLE ...)这行必须存在。它确保catkin build时 CMake 不会去$PATH里乱找 qmake而是锁定系统 Qt5.12.8 的路径。漏掉这一行catkin build可能静默使用你~/Qt/5.15.2/.../qmake编译通过但运行时报错。4. 编译与运行避开 90% 的 fatal 报错和界面空白4.1 严格按顺序执行的编译命令cd ~/catkin_ws # 1. 确保 rosdep 已初始化首次运行 sudo rosdep init rosdep update # 2. 安装所有依赖含 qtbase5-dev rosdep install --from-paths src --ignore-src -r -y # 3. 使用 catkin_tools 构建推荐比 catkin_make 更稳定 catkin build rviz_demo --cmake-args \ -DQT_QMAKE_EXECUTABLE/usr/lib/x86_64-linux-gnu/qt5/bin/qmake \ -DCMAKE_BUILD_TYPERelWithDebInfo # 4. source 环境 source devel/setup.bash验证 Qt 路径是否生效catkin config --cmake-args | grep qmake # 应输出-DQT_QMAKE_EXECUTABLE/usr/lib/x86_64-linux-gnu/qt5/bin/qmake4.2 运行前必做的三项环境检查检查 LD_LIBRARY_PATH 是否污染echo $LD_LIBRARY_PATH # 如果包含 ~/Qt/5.15.2/gcc_64/lib 或 /opt/Qt/...立即清空 unset LD_LIBRARY_PATH # 并从 ~/.bashrc 中注释掉相关 export 行确认 rviz 本身能独立启动rosrun rviz rviz -h # 应正常打印帮助 # 若报错 qt.qpa.plugin说明系统 Qt 缺失 sudo apt install qt5-default libqt5opengl5-dev验证 Qt 插件路径RVIZ 启动时需加载libqxcb.so等平台插件。若报could not find the qt platform plugin xcb执行# 查看 rviz 依赖的 Qt 库路径 ldd $(rospack find rviz)/lib/rviz/rviz | grep Qt5 # 输出应类似libQt5Core.so.5 /usr/lib/x86_64-linux-gnu/libQt5Core.so.5 (0x...) # 然后确认插件存在 ls /usr/lib/x86_64-linux-gnu/qt5/plugins/platforms/libqxcb.so # 若不存在安装 sudo apt install qt5ct4.3 启动 Demo 并联调 RVIZ# 终端1启动 RVIZ加载一个空配置 rosrun rviz rviz -d $(rospack find rviz)/launch/empty.rviz # 终端2运行我们的 Qt 控件 rosrun rviz_demo demo_node此时应看到独立弹出 Qt 窗口非嵌入 RVIZ便于调试点击按钮RVIZ 中应出现ArrowMarker需提前在 RVIZ 中 Add → By Topic →/demo/cmd_vel→geometry_msgs/Twist设置Arrow类型拖动滑块Arrow 长度实时变化提示RVIZ 的TwistDisplay 默认不显示 Arrow需右键Twistpanel →Edit→ 将Shape改为ArrowLength设为1.0Scale设为0.1。这是新手最常卡住的一步。5. 避坑5 条血泪经验总结每一条都来自真实翻车现场5.1 现象fatal: cannot mix incompatible qt library (version ex50601)原因ex50601是 Qt5.12.8 的内部 ABI 标签ex表示extended50601是 Qt5.12.8 的 magic number。此错误表明你的可执行文件demo_node链接了 Qt5.12.8 的libQt5Core.so.5但某个动态库如你写的.so插件、或第三方库链接了 Qt5.15.x 的同名库。Linux 加载器拒绝混合。解决ldd ./devel/lib/rviz_demo/demo_node | grep Qt5查看所有 Qt 库路径确保全部指向/usr/lib/x86_64-linux-gnu/nm -D ./devel/lib/rviz_demo/demo_node | grep -i qt检查导出符号确认无Qt_5.15字样彻底删除~/Qt/目录注释~/.bashrc中所有QT_*和LD_LIBRARY_PATH相关行5.2 现象Qt 窗口弹出即消失终端无报错原因QApplication构造后未调用show()或exec()前ros::init()失败导致app.exec()返回 0。解决在DemoWindow构造函数末尾加show();代码中已有在main()中ros::init()后加ROS_ASSERT(ros::ok());确保 ROS 初始化成功若用 VNC需设置export QT_QPA_PLATFORMxcb而非linuxfb5.3 现象RVIZ 中 Twist Marker 不动但rostopic echo /demo/cmd_vel有数据原因RVIZ 的TwistDisplay 默认订阅/cmd_vel而非/demo/cmd_vel。Topic 名不匹配。解决在 RVIZ 中右键Twistpanel →Edit→ 修改Topic字段为/demo/cmd_vel或在代码中改nh_.advertise...(/cmd_vel, 10)保持与 RVIZ 默认一致5.4 现象滑块拖动时 RVIZ Marker 跳变剧烈不平滑原因QSlider::valueChanged信号在拖动过程中高频触发每毫秒发一次TwistRVIZ 渲染跟不上。解决在onSliderValueChanged中加防抖// 在头文件中声明 QTimer* timer_; // 构造函数中timer_ new QTimer(this); connect(timer_, QTimer::timeout, this, DemoWindow::publishTwist); void DemoWindow::onSliderValueChanged(int value) { current_value_ value; // 成员变量缓存 timer_-start(50); // 50ms 延迟后才发 } void DemoWindow::publishTwist() { timer_-stop(); // ... 发布逻辑 }5.5 现象VNC 桌面无法启动 RVIZ报GLXBadContext原因VNC 默认不启用 OpenGL 硬件加速RVIZ 需要GLX扩展。解决启动 VNC 时加参数vncserver :1 -localhost no -geometry 1920x1080 -depth 24在 VNC 会话中运行export LIBGL_ALWAYS_INDIRECT1或改用x11vnc替代tightvnc它对 OpenGL 支持更好6. 进阶技巧让 Qt 控件真正嵌入 RVIZ 界面而非独立窗口6.1 为什么需要嵌入独立窗口的三大硬伤焦点冲突Qt 窗口和 RVIZ 窗口互相抢夺键盘输入如 WASD 移动视角时Qt 滑块意外响应主题不一致RVIZ 是深色主题Qt 默认浅色视觉割裂部署麻烦用户需同时启动两个进程易遗漏嵌入方案本质是将你的 Qt Widget 注册为 RVIZ 的 Panel Plugin它会出现在 RVIZ 右侧Panels区域与Displays、Views并列共享 RVIZ 的QApplication和主题。6.2 创建 RVIZ Plugin 的最小骨架RVIZ Plugin 必须继承rviz::Panel且需注册为PLUGINLIB_EXPORT_CLASS。结构如下src/rviz_demo/ ├── include/rviz_demo/ │ ├── demo_panel.h # 继承 rviz::Panel │ └── demo_panel_widget.h # 纯 Qt Widget不含 ROS ├── src/ │ ├── demo_panel.cpp # 实现 Panel 接口 │ └── demo_panel_widget.cpp # 实现 UI 逻辑 └── plugin_description.xml # RVIZ 插件描述文件plugin_description.xml必须library pathlibrviz_demo class namerviz_demo/DemoPanel typerviz_demo::DemoPanel base_class_typerviz::Panel descriptionA Qt-based control panel embedded in RVIZ./description /class /librarydemo_panel.h#pragma once #include rviz/panel.h #include rviz_demo/demo_panel_widget.h namespace rviz_demo { class DemoPanel : public rviz::Panel { Q_OBJECT public: explicit DemoPanel(QWidget* parent nullptr); ~DemoPanel() override default; // 必须实现RVIZ 调用此函数创建 widget void load(const rviz::Config config) override; void save(rviz::Config config) const override; private: DemoPanelWidget* widget_; // 真正的 Qt Widget }; } // namespace rviz_demodemo_panel.cpp#include rviz_demo/demo_panel.h #include rviz/properties/property.h #include rviz/properties/property_tree_widget.h namespace rviz_demo { DemoPanel::DemoPanel(QWidget* parent) : rviz::Panel(parent) { widget_ new DemoPanelWidget(this); QVBoxLayout* layout new QVBoxLayout(this); layout-addWidget(widget_); setLayout(layout); } void DemoPanel::load(const rviz::Config config) { rviz::Panel::load(config); // 加载配置如滑块初始值 int init_val; if (config.mapGetString(initial_slider_value, init_val)) { widget_-setSliderValue(init_val); } } void DemoPanel::save(rviz::Config config) const { rviz::Panel::save(config); // 保存配置 config.mapSetValue(initial_slider_value, widget_-getSliderValue()); } } // namespace rviz_demo #include pluginlib/class_list_macros.h PLUGINLIB_EXPORT_CLASS(rviz_demo::DemoPanel, rviz::Panel)6.3 CMakeLists.txt 中的关键修改# 添加 plugin 目标 add_library(rviz_demo SHARED src/demo_panel.cpp src/demo_panel_widget.cpp ) target_link_libraries(rviz_demo ${catkin_LIBRARIES} ${Qt5Core_LIBRARIES} ${Qt5Widgets_LIBRARIES} ${Qt5OpenGL_LIBRARIES} ) set_target_properties(rviz_demo PROPERTIES PREFIX ) # 安装 plugin 和 xml install(TARGETS rviz_demo ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} ) install(FILES plugin_description.xml DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} )6.4 编译后启用 Plugin 的操作流程编译catkin build rviz_demoSourcesource devel/setup.bash启动 RVIZrosrun rviz rvizPanels → Add New Panel → rviz_demo/DemoPanel面板即出现在右侧与 RVIZ 原生 Panel 无异我的习惯是开发阶段用独立窗口快速迭代 UI交付前切到 Plugin 模式专业感免维护。每次切换只需改demo_node.cpp的main()函数把QApplication和show()替换为rviz::VisualizationManager::getInstance()-getPanelFactoryManager()-addPanel(...)—— 但实际项目中我直接维护两套main()用#ifdef PLUGIN_MODE切换省得反复改。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门