基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(二)---ROS2与UE5进行图像数据传输

前言

  • 本系列教程旨在使用UE5配置一个具备激光雷达+深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2UE5仿真的通讯,达到小车自主导航的目的。
  • 本教程默认有ROS2导航及其gazebo仿真相关方面基础,Nav2相关的学习教程可以参考本人的其他博客Nav2代价地图实现和原理–Nav2源码解读之CostMap2D(上)-CSDN博客
  • UE5系列教程:UE5-C++入门教程(一):使用代码创建一个指定目标的移动小球-CSDN博客
  • 第一期:基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(一)—UnrealCV获取深度+分割图像-CSDN博客
  • 本教程环境支持:
    • UE5.43
    • ubuntu 22.04 ros2 humble
  • 上一节我们以及获取到了深度和分割图像的图像数据,本节我们来看看如何使用rosbridge进行图像传输

ROSbrige-suite

请添加图片描述

  • rosbridge 是一个用于在 ROS (Robot Operating System) 和其他编程语言或框架之间进行通信的桥梁。它允许开发者使用不同的编程语言(如 Python、JavaScript、Java、MATLAB 等)来与 ROS 系统进行交互,而无需直接使用 ROS 的 C++ API。
  • rosbridge 主要由两部分组成:
    1. ROS 端:运行在 ROS 系统上的服务器,负责与 ROS 系统进行交互。它可以将 ROS 消息、服务、动作等转换为可以通过网络传输的格式。
    2. 客户端:运行在非 ROS 系统上的客户端,负责与 ROS 端通信,并将接收到的数据转换为客户端语言或框架可以理解的形式。
  • rosbridge 支持多种通信协议,包括 WebSocket、TCP 和 UDP。这使得它可以在不同的网络环境中工作,无论是本地网络还是互联网。
安装与基础使用
  • 这里推荐使用humble版本安装
sudo apt-get install ros-humble-rosbridge-suite
  • rosbridge的使用也是非常方便
source /opt/ros/humble/setup.bash
ros2 launch rosbridge_server rosbridge_websocket_launch.xml 
  • 运行成功后,终端会输出如下内容,这里rosbridge默认会打开9090端口进行监听,一会我们发送信息也只需要发送到这里即可请添加图片描述

尝试使用发送ROSbrige一张图片

  • 由于我们的UE5仿真及其数据捕获程序运行在windows11,而我们的只要Nav2导航处理程序在ubuntu端,这里我们就需要使用ROSbrige进行通讯
1. ip查询
  • 在进行ROSbrigewebsocket通讯之前,在保证win11和ubuntu处于同一局域网的前提下,我们需要知道ubuntu端的ip地址
  • 在ubuntu终端输入ip adrr show查看ip地址,这里我的虚拟机ip是192.168.137.129请添加图片描述
2. 消息类型确认
  • 在查询好ip地址后,我们需要确定传输的消息类型,这里我们传输的是图像类型,那我们就选择最常见的sensor_msgs/msg/image类型的图片,我们来查询这个消息下有什么
ros2 interface show sensor_msgs/msg/Image
  • 我们会得到以下输出:请添加图片描述

  • 玩过ROS2的朋友都不陌生吧,那我简单说明一下

    • uint32 height:图像的高度
    • uint32 width:图像的宽度
    • string encoding:像素的编码方式,包括通道的含义、顺序和大小。
    • uint8 is_bigendian:表示图像数据是否使用大端字节序。在大多数现代系统中,y一般是 0(小端字节序)。
    • uint32 step:图像的完整行长度(以字节为单位)。这通常是 width * channels * bytes_per_channel。例如,对于宽度为 640 像素、3 个通道(如 RGB 图像)的图像,步长将是 640 * 3 = 1920 字节。
    • uint8[] data:实际的图像数据矩阵。其大小是 step * rows,即步长乘以行数。
编写Win11发送端
  • 那我们我们就来根据上述sensor_msgs/msg/image所需要的消息类型,我们来编写发送端
import websocket  
import cv2  
import base64  
import json  
import numpy as np  
# Ubuntu的IP地址  
ubuntu_ip = "192.168.137.129"  # 创建WebSockets连接  
ws = websocket.create_connection(f"ws://{ubuntu_ip}:9090")  image_path = r"C:\Users\lzh\Desktop\UE5_ROS2_project\camera\lit.png"  
img=cv2.imread(image_path)  
print(img.shape)  
img=img.astype(np.uint8)  
# 将图像数据转换为Base64编码的字符串  
encoded_string = base64.b64encode(img).decode('utf-8')  
# sensor_msgs/msg/image的JSON表示  
msg = {  "op": "publish",  "topic": "/image",  "msg": {  "data": encoded_string,  "height": 480,  "width": 640,  "step": 640 * 3 ,  "encoding": "bgr8"  }  
}  
while True:  
# 发送消息  ws.send(json.dumps(msg))  # 关闭连接  
ws.close()
  • 上述代码很简单,相信大家都能看得懂,需要注意的是data需要是json可迭代对象,所以这里转换为base64
编写ubuntu接受端data
  • 接收端我采用cpp,创建了一个ament_cmake的功能包
  • 那么接收端就更简单了,这里我们只创建一个订阅,用于广播image的类型,我们把显示交给rviz2,直接看代码
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/image.hpp>class ImageSubscriber : public rclcpp::Node
{
public:ImageSubscriber(): Node("image_subscriber"){subscription_ = this->create_subscription<sensor_msgs::msg::Image>("image", 10, std::bind(&ImageSubscriber::image_callback, this, std::placeholders::_1));}private:void image_callback(const sensor_msgs::msg::Image::SharedPtr msg) const{RCLCPP_INFO(this->get_logger(), "Received image");}rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr subscription_;
};int main(int argc, char * argv[])
{rclcpp::init(argc, argv);auto node = std::make_shared<ImageSubscriber>();rclcpp::spin(node);rclcpp::shutdown();return 0;
}
运行与展示
  • 值得一提的是,如果你只运行了win11的发送端和ubuntu的rosbridge,不运行接收端,那么你可能会得到一下错误请添加图片描述

  • 这是由于在 ROS 中,主题必须先被广告,然后才能接收消息。这意味着在尝试发布消息之前,需要确保有一个节点正在监听 /image 主题,并且已经广告了该主题。

  • 我们打开rviz2,选择Add,并根据话题选择图像显示插件请添加图片描述

  • 我们就得到了以下画面请添加图片描述


UE5核心类创建及实时数据传输

  • 为了更好的数据传输,我们这里进行封装,这里我们写一个发布者Publisher基类
  • connection将传入外部的websocket对象,非常常见的设计模式运用,这里就不多说明了
class Publisher:  def __init__(self,connection,topic):  self.connection=connection  self.topic = topic  def publish(self,msg):  pub_msg = {  "op": "publish",  "topic": self.topic,  "msg":msg  }  self.connection.send(json.dumps(msg))  
class ImagePublisher(Publisher):  def __init__(self,connection,topic,compressed_scale):  self.connection=connection  self.topic = topic  self.compressed_scale=compressed_scaledef publish(self,image):  if image is None:  print('image is None!')  return  h,w=image.shape[0],image.shape[1]  new_h=self.compressed_scale*hnew_w=self.compressed_scale*wimage=cv2.resize(image,(new_w,new_h))print('h:',new_h,',w:',new_w,'image is publishing')  image = image.astype(np.uint8)  # 将图像数据转换为Base64编码的字符串  encoded_string = base64.b64encode(image).decode('utf-8')  msg = {  "op": "publish",  "topic": self.topic,  "msg": {  "data": encoded_string,  "height": new_h,  "width": new_w,  "step": new_w * 3,  "encoding": "bgr8"  }  }  self.connection.send(json.dumps(msg))
  • 同时我们继续书写整个Win11端的核心类UE5MsgCenter,这里我们先测试一下功能
class UE5MsgCenter:  def __init__(self,ubuntu_remote_ip_):  self.ws = websocket.create_connection(f"ws://{ubuntu_remote_ip_}:9090")  self.ue5_cam_center=UE5CameraCenter()  self.image_pub=ImagePublisher(self.ws,topic='/image',compressed_scale=0.5)  self.object_mask_image_pub=ImagePublisher(self.ws,topic='/object_mask_image',compressed_scale=0.5)  def __del__(self):  self.ws.close()  def run(self):  while True:  self.image_pub.publish(self.ue5_cam_center.get_camera_data('lit'))  self.object_mask_image_pub.publish(self.ue5_cam_center.get_camera_data('object_mask'))
  • 然后我们再次修改ubuntu订阅端的cpp代码
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/image.hpp>class ImageSubscriber : public rclcpp::Node
{
public:ImageSubscriber(): Node("image_subscriber"){rawImageSub = this->create_subscription<sensor_msgs::msg::Image>("image", 10, std::bind(&ImageSubscriber::rawImageCB, this, std::placeholders::_1));objMaskImageSub= this->create_subscription<sensor_msgs::msg::Image>("object_mask_image", 10, std::bind(&ImageSubscriber::objMaskImageCB, this, std::placeholders::_1));}private:void rawImageCB(const sensor_msgs::msg::Image::SharedPtr msg) const{RCLCPP_INFO(this->get_logger(), "Received image");}void objMaskImageCB(const sensor_msgs::msg::Image::SharedPtr msg) const{RCLCPP_INFO(this->get_logger(), "Received image");}rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr rawImageSub;rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr objMaskImageSub;
};int main(int argc, char * argv[])
{rclcpp::init(argc, argv);auto node = std::make_shared<ImageSubscriber>();rclcpp::spin(node);rclcpp::shutdown();return 0;
}
启动顺序
  • 这里有必要说明一下各个平台程序和软件的开启顺序:
    1. Win11的UE5记得按下开始仿真本关卡!!!
    2. ubuntu的cpp接收端(用于广播消息类型)
    3. ubuntu的rosbridge服务器
    4. Win11的python的UE5MsgCenter
    5. ubuntu的`rviz2
结果展示
  • 移动我们在UE5中的观测者小球,同样我们收到消息请添加图片描述

  • 请添加图片描述

深度图像特殊处理
  • 这里深度图像Win11发送端我们需要进行特殊处理,我们重新写一个新的类
  • 深度图像为灰度图像,是单通道,故设置
    • step: new_w,
    • encoding: “mono8” # 8位灰度图的编码
class DepthImagePublisher(Publisher):  def __init__(self,connection,topic,compressed_scale):  self.connection=connection  self.topic = topic  self.compressed_scale=compressed_scale  def publish(self,image):  if image is None:  print('image is None!')  return  h,w=image.shape[0],image.shape[1]  new_h=int(self.compressed_scale*h)  new_w=int(self.compressed_scale*w)  image=cv2.resize(image,(new_w,new_h))  print('h:',new_h,',w:',new_w,'image is publishing')  image = image.astype(np.uint8)  # 将图像数据转换为Base64编码的字符串  encoded_string = base64.b64encode(image).decode('utf-8')  msg = {  "op": "publish",  "topic": self.topic,  "msg": {  "data": encoded_string,  "height": new_h,  "width": new_w,  "step": new_w,  "encoding": "mono8"  }  }  self.connection.send(json.dumps(msg))
  • 我们得到如下
    请添加图片描述

完整代码

  • UE5MsgCenter.py
import websocket  
import cv2  
import base64  
import json  
import numpy as np  
from UE5CameraCenter import UE5CameraCenter  
class Publisher:  def __init__(self,connection,topic):  self.connection=connection  self.topic = topic  def publish(self,msg):  pub_msg = {  "op": "publish",  "topic": self.topic,  "msg":msg  }  self.connection.send(json.dumps(msg))  
class DepthImagePublisher(Publisher):  def __init__(self,connection,topic,compressed_scale):  self.connection=connection  self.topic = topic  self.compressed_scale=compressed_scale  def publish(self,image):  if image is None:  print('image is None!')  return  h,w=image.shape[0],image.shape[1]  new_h=int(self.compressed_scale*h)  new_w=int(self.compressed_scale*w)  image=cv2.resize(image,(new_w,new_h))  print('h:',new_h,',w:',new_w,'image is publishing')  image = image.astype(np.uint8)  # 将图像数据转换为Base64编码的字符串  encoded_string = base64.b64encode(image).decode('utf-8')  msg = {  "op": "publish",  "topic": self.topic,  "msg": {  "data": encoded_string,  "height": new_h,  "width": new_w,  "step": new_w,  "encoding": "mono8"  }  }  self.connection.send(json.dumps(msg))  
class ImagePublisher(Publisher):  def __init__(self,connection,topic,compressed_scale):  self.connection=connection  self.topic = topic  self.compressed_scale=compressed_scale  def publish(self,image):  if image is None:  print('image is None!')  return  h,w=image.shape[0],image.shape[1]  new_h=int(self.compressed_scale*h)  new_w=int(self.compressed_scale*w)  image=cv2.resize(image,(new_w,new_h))  print('h:',new_h,',w:',new_w,'image is publishing')  image = image.astype(np.uint8)  # 将图像数据转换为Base64编码的字符串  encoded_string = base64.b64encode(image).decode('utf-8')  msg = {  "op": "publish",  "topic": self.topic,  "msg": {  "data": encoded_string,  "height": new_h,  "width": new_w,  "step": new_w * 3,  "encoding": "bgr8"  }  }  self.connection.send(json.dumps(msg))  class UE5MsgCenter:  def __init__(self,ubuntu_remote_ip_):  self.ws = websocket.create_connection(f"ws://{ubuntu_remote_ip_}:9090")  self.ue5_cam_center=UE5CameraCenter()  self.image_pub=ImagePublisher(self.ws,topic='/image',compressed_scale=0.5)  self.object_mask_image_pub=ImagePublisher(self.ws,topic='/object_mask_image',compressed_scale=0.5)  self.depth_image_pub=DepthImagePublisher(self.ws,topic='/depth_image',compressed_scale=0.5)  def __del__(self):  self.ws.close()  def run(self):  while True:  self.image_pub.publish(self.ue5_cam_center.get_camera_data('lit'))  self.object_mask_image_pub.publish(self.ue5_cam_center.get_camera_data('object_mask'))  self.depth_image_pub.publish(self.ue5_cam_center.get_camera_data('depth'))  def main():  webs_server = UE5MsgCenter("192.168.137.129")  webs_server.run()  
if __name__ =='__main__':  main()

小结

  • 本节我们介绍了如何使用rosbridge对UE5和ROS2进行通讯
  • 下一小节我们将谈谈UE5``激光雷达的仿真
  • 如有错误,欢迎指出~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/877544.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL进阶之路】表的约束——主键,自增长,唯一键,外键

目录 主键 复合主键 自增长 唯一键 unique 外键 方案一 方案二 方案三 个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 主键 主键&#xff1a;primary key用来唯一的约束该字段里面的数据&#xff0c;不能重复&#xff0c;不能为空&#xff08;必须有非空约束&#xf…

微知-lspci如何查看pcie设备树状结构(-t)

对于查看pcie设备列表除了看是否存在 还需要看拓扑结构。如何看&#xff1f; lspci -t以减号为分割说明 第一列数字是域段 和 bus id。比如0000:00中0000是域 00是busid 第二列 01.2中01是device id。2是functionid 如果还有下游设备device还有一个指定busid的序号

Postman文件上传接口测试

接口介绍 返回示例 测试步骤 1.添加一个新请求&#xff0c;修改请求名&#xff0c;填写URL&#xff0c;选择请求方式 2.将剩下的media参数放在请求body里&#xff0c;选择form-data&#xff0c;选择key右边的类型为file类型&#xff0c;就会出现选择文件的按钮Select Files&a…

QT翻金币小游戏(含音频图片文件资源)

目录 QT翻金币小游戏 音频图片资源文件获取 效果展示 图片 视频 实现代码 main.cpp mymainwindow.h mymainwindow.cpp startscene.h startscene.cpp selectscene.cpp playscene.h playscene.cpp mypushbutton.h mypushbutton.cpp dataconfig.h dataconfig.cpp QT…

大模型时代,云南白药如何成为一家AI医药企业?|产业AI案例

作者|斗斗 编辑|皮爷 出品|产业家 中医药大模型发布&#xff1b;英伟达成立AI制药部门&#xff0c;发力生物制药领域&#xff1b;赛诺菲与百图生科达成战略合作&#xff0c;共同开发用于生物治疗药物发现的领先模型&#xff1b;京东发布医疗大模型&#xff1b;百度“产业级”…

读软件开发安全之道:概念、设计与实施04缓解

1. 缓解 1.1. 安全思维转换为有效行动的方法就是首先预判威胁&#xff0c;然后针对可能的漏洞加以保护 1.2. 主动响应的做法就叫做“缓解” 1.2.1. mitigation 1.2.2. 喂宝宝的时候给孩子围上围嘴&#xff0c;避免掉下来的食物粘在宝宝的衣服上&#xff0c;还有安全带、限速…

ID3算法详解:构建决策树的利器

目录 引言 ID3算法概述 算法基础 信息熵 ​编辑 信息增益 ID3算法步骤 决策树 概念: 核心&#xff1a; 节点 1. 根节点 2. 非叶子节点 3. 叶子节点 引言 在机器学习领域&#xff0c;决策树是一种非常流行的分类和回归方法。其中&#xff0c;ID3算法作为决策树算法…

jenkins最佳实践(二):Pipeline流水线部署springCloud微服务项目

各位小伙伴们大家好呀&#xff0c;我是小金&#xff0c;本篇文章我们将介绍如何使用Pipeline流水线部署我们自己的微服务项目&#xff0c;之前没怎么搞过部署相关的&#xff0c;以至于构建流水线的过程中中也遇到了很多自己以前没有考虑过的问题&#xff0c;特写此篇&#xff0…

使用 Python 进行 PDF 文件加密

使用 Python 解密加密的 PDF 文件-CSDN博客定义一个名为的函数&#xff0c;该函数接受三个参数&#xff1a;输入的加密 PDF 文件路径input_pdf、输出的解密 PDF 文件路径output_pdf和密码password。https://blog.csdn.net/qq_45519030/article/details/141256661 在数字化时代…

Linux驱动开发基础(设备树)

所学来自百问网 目录 1. 引入设备树的原因 2. 设备树语法 2.1 Devicetree格式 2.1.1 DTS文件格式 2.1.2 node的格式 2.1.3 properties的格式 2.1.4 dts 文件包含dtsi文件 2.2 常用属性 2.2.1 #address-cells、#size-cells 2.2.2 compatible 2.2.3 model 2.2.4 st…

一步解决Ubuntu中/mnt/hgfs无共享文件夹的问题

当我们启用了共享文件夹后&#xff0c;但是在终端/mnt/hgfs任然没有文件 在终端输入 sudo vmhgfs-fuse .host:/ /mnt/hgfs/ -o allow_other 之后&#xff0c;就可以查到共享文件了

复现 LET-NET

摘要 稀疏光流法是计算机视觉中的一项基本任务。然而&#xff0c;它依赖于恒定的假设限制了其在高动态范围&#xff08;HDR&#xff09;场景中的适用性。在本研究中&#xff0c;我们提出了一种新的方法&#xff0c;旨在通过学习一个对光照变化具有鲁棒性的特征映射来超越图像的…

KubeSphere核心实战_kubesphere部署redis01_为redis指定配置文件_指定存储卷_配置服务---分布式云原生部署架构搭建047

然后我们再来,部署一下redis,可以看到,首先去容器官网去找到对应的redis的镜像然后 可以看到镜像中都有说的,如何启动,以及 --appendonly yes 是指定持久化.然后 /data表示数据存储的位置. 可以看到数据存储位置 然后还有配置文件的位置. 可以看到,我们首先去创建配置文件,然后…

LNMP 架构(Linux+NGINX+memcache+PHP)

目录 1 源码编译PHP与NGINX 1.1 NGINX 源码编译 1.2 PHP 源码编译安装 2 实现PHP与NGINX的连接 2.1 php-fpm的详细介绍 2.2 LNMP与LAMP的区别 2.3 PHP配置文件的介绍 2.4 实例实现php-fpm 与 NGINX的连接 2.4.1 指定pid的存放位置 2.4.2 php-fpm设置监听自己端口与IP 2.4.3 主配…

配置 昇腾 Ascend C/C++ 开发环境

配置 昇腾 Ascend C/C 开发环境 flyfish 这里以Orange Pi Ai Pro 为例 先说如何配置MindStudio&#xff0c;然后再说如何查看Orange Pi Ai Pro的一些信息 Orange Pi AI Pro 开发板是香橙派联合华为精心打造的高性能AI 开发板&#xff0c;其搭载了昇腾 AI 处理器。Linux 桌面…

VSCode系列 - 如何用VSCode搭建C++高效开发环境(2)

1. 插件的用法 1.1. C/C 1.1.1. 插件介绍1.1.2. 插件配置 1.2. Clang-Format1.3. cpp-check-lint 1.3.1. cpplint1.3.2. cppcheck1.3.3. 插件的使用 1.4. C/C Advanced Lint 1.4.1. 插件介绍1.4.2. 插件配置 1.5. Bracket Pair Colorizer 1.5.1. 插件介绍1.5.2. 功能配置 1.6. …

【机器学习】探索机器学习在旅游业的革新之旅

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀目录 &#x1f50d;1. 引言&#x1f4d2;2. 机器学习在旅游需求分析中的应用&#x1f31e;用户行为数据分析&#x1f319;旅客偏好预测模型⭐…

Java面试八股之如何保证消息队列中消息不重复消费

如何保证消息队列中消息不重复消费 要保证消息队列中的消息不被重复消费&#xff0c;通常需要从以下几个方面来着手&#xff1a; 消息确认机制&#xff1a; 对于像RabbitMQ这样的消息队列系统&#xff0c;可以使用手动确认&#xff08;manual acknowledge&#xff09;机制来…

C++ 设计模式——策略模式

策略模式 策略模式主要组成部分例一&#xff1a;逐步重构并引入策略模式第一步&#xff1a;初始实现第二步&#xff1a;提取共性并实现策略接口第三步&#xff1a;实现具体策略类第四步&#xff1a;实现上下文类策略模式 UML 图策略模式的 UML 图解析 例二&#xff1a;逐步重构…

【c语言】整数在内存中的储存(大小端字节序)

整数在内存中的储存&#xff08;大小端字节序&#xff09; 1.整数在内存中的储存 2.大小端字节序 3.整数在内存中储存例子 4.字节序判断 5.死循环现象 文章目录 整数在内存中的储存&#xff08;大小端字节序&#xff09;整数在内存中的储存大小端字节序什么是大小端为什么会有…