基于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.rhkb.cn/news/404444.html

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

相关文章

系规学习第13天

1、规划设计的主要目的不包括() A、设计满足业务需求的IT服务 B、设计SLA、测量方法和指标。 C、设计服务过程及其控制方 D、设计实施规划所需要的进度管理过程 [答案] D [解析]本题考察的是规划设计的目的&#xff0c;建议掌握。 (1)设计满足业务需求的IT服务。 (2)设…

Axios请求使用params参数导致后端获取数据嵌套

问题重述&#xff1a; 首先看前端的axios请求这里我使用params参数将data数据传给后端 let data JSON.stringify(this.posts);axios.post("/blog_war_exploded/insertPost", {params: {data: data}}).then((res) > {if (res.data "success") {alert(…

大杂烩!注意力机制+时空特征融合!组合模型集成学习预测!CNN-LSTM-Attention-Adaboost多变量负荷预测

大杂烩&#xff01;注意力机制时空特征融合&#xff01;组合模型集成学习预测&#xff01;CNN-LSTM-Attention-Adaboost多变量负荷预测 目录 大杂烩&#xff01;注意力机制时空特征融合&#xff01;组合模型集成学习预测&#xff01;CNN-LSTM-Attention-Adaboost多变量负荷预测…

银河麒麟V10如何安装本地deb软件包?(以安装wps为例)

银河麒麟V10如何安装本地deb软件包&#xff1f;&#xff08;以安装wps为例&#xff09; 一、准备二、安装三、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在银河麒麟V10中安装本地.deb软件包&#xff0c;虽然apt主要用于管理仓库中…

LeetCode:3148. 矩阵中的最大得分(DP Java)

目录 3148. 矩阵中的最大得分 题目描述&#xff1a; 实现代码与解析&#xff1a; DP 原理思路&#xff1a; 3148. 矩阵中的最大得分 题目描述&#xff1a; 给你一个由 正整数 组成、大小为 m x n 的矩阵 grid。你可以从矩阵中的任一单元格移动到另一个位于正下方或正右侧…

删除微博博文js脚本实现

我当前的时间&#xff1a;2024.8.18 脚本可以直接使用&#xff0c;随着时间推移&#xff0c;微博页面元素可能会有变动。 思路&#xff1a;javascript 模拟手动点击&#xff0c;下滑&#xff0c;并且删除博文 首先登录微博&#xff0c;进入自己的博文界面如下&#xff1a; 进…

Git使用方法(三)---简洁版上传git代码

1 默认已经装了sshWindows下安装SSH详细介绍-CSDN博客 2 配置链接github的SSH秘钥 1 我的.ssh路径 2 进入路径cd .ssh 文件 3 生成密钥对 ssh-keygen -t rsa -b 4096 (-t 秘钥类型 -b 生成大小) 输入完会出现 Enter file in which to save the key (/c/Users/Administrator/…

使用DOM破坏启动xss

目录 实验环境&#xff1a; 分析&#xff1a; 找破坏点&#xff1a; 查看源码找函数&#xff1a; 找到了三个方法&#xff0c;loadComments、escapeHTM 、displayComments loadComments escapeHTM displayComments&#xff1a; GOGOGO 实验环境&#xff1a; Lab: Exp…

MySQL库表的基本操作

目录 1.库的操作1.1 创建数据库1.2字符集和校验规则①查看系统默认字符集以及校验规则②查看数据库支持的字符集③查看数据库支持的字符集校验规则④校验规则对数据库的影响 1.3操纵数据库①查看数据库②显示创建的数据库的语句③修改数据库④数据库删除⑤备份和恢复⑥还原注意…

C库函数signal()信号处理

signal()是ANSI C信号处理函数&#xff0c;原型如下&#xff1a; #include <signal.h>typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); signal()将信号signum的处置设置为handler&#xff0c;该handler为SIG_IGN&#xff…

物联网(IoT)详解

物联网&#xff08;IoT&#xff09;详解 1. IoT定义简介2. IoT工作原理3. IoT关键技术4. 物联网与互联网区别5. IoT使用场景6. 开源物联网平台7. 参考资料 1. IoT定义简介 首先第一个问题&#xff0c;什么是物联网&#xff08;IoT&#xff09;? 物联网&#xff08;英文&#…

LabVIEW光纤水听器闭环系统

开发了一种利用LabVIEW软件开发的干涉型光纤水听器闭环工作点控制系统。该系统通过调节光源频率和非平衡干涉仪的光程差&#xff0c;实现了工作点的精确控制&#xff0c;从而提高系统的稳定性和检测精度&#xff0c;避免了使用压电陶瓷&#xff0c;使操作更加简便。 项目背景 …

thinkphp5实现弹出框(下拉框选项动态赋值)

效果图 原理 先执行接口获取动态数据&#xff0c;然后在 layer.open的success回调函数中动态添加html代码片段&#xff0c;通过如下方法&#xff0c;将动态生成的代码插入指定的div中&#xff0c;实现动态赋值的效果。 // 动态获取的数据 var data ......;// 弹出框配置 lay…

【BUU】[NewStarCTF 2023 公开赛道]Final -CP读取文件内容

漏洞检测 访问首页发现是ThinkPHP5 的站点 用工具扫描一下,发现存在ThinkPHP5.0.23 RCE漏洞 访问验证,写入shell 成功写入shell. 根目录发现flag,但是权限不足 提权获取flag 准备提权,这里一开始尝试了find,但是find权限不足 尝试采用cp命令,移动到web目录,发现访问还是…

基于web的物流管理系统--论文pf

TOC springboot473基于web的物流管理系统--论文pf 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不可…

基于深度学习的图像特征优化识别复杂环境中的果蔬【多种模型切换】

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍图像特征优化方法模型原理及实验对比模型训练每文一语 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 项目介绍 基于深度学习的图像识别技术广泛应…

清影智能开源版CogVideox:开源文本到视频生成模型的探索

人工智能&#xff08;AI&#xff09;领域的创新一直在不断推进&#xff0c;而下一个前沿领域&#xff0c;很可能就是文本到视频生成模型。在不久的将来&#xff0c;我们将会看到许多中小型公司推出自己的文本到视频生成模型&#xff0c;这一技术将会迅速发展。而这正是为什么当…

Java | Leetcode Java题解之第350题两个数组的交集II

题目&#xff1a; 题解&#xff1a; class Solution {public int[] intersect(int[] nums1, int[] nums2) {Arrays.sort(nums1);Arrays.sort(nums2);int length1 nums1.length, length2 nums2.length;int[] intersection new int[Math.min(length1, length2)];int index1 …

建筑工程项目管理系统-计算机毕设Java|springboot实战项目

&#x1f34a;作者&#xff1a;计算机毕设匠心工作室 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目…

Java——反射(4/4):反射的作用、应用场景(案例需求、实现步骤、代码实现)

目录 作用 应用场景 案例需求 实现步骤 代码实现 作用 基本作用&#xff1a;可以得到一个类的全部成分然后操作。可以破坏封装性。最重要的用途是&#xff1a;适合做Java的框架&#xff0c;基本上&#xff0c;主流的框架都会基于反射设计出一些通用的功能。 通过反射能够…