基于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_scale
    def 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*h
        new_w=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))
  • 同时我们继续书写整个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``激光雷达的仿真
  • 如有错误,欢迎指出~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++实现——红黑树

目录 1.红黑树 1.1红黑树的概念 1.2红黑树的性质 1.3红黑树节点的定义 1.4红黑树的插入操作 1.5红黑树的验证 1.6红黑树的删除 1.7红黑树与AVL树的比较 1.8红黑树的应用 1.红黑树 1.1红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位…

leetcode67. 二进制求和,简单模拟

leetcode67. 二进制求和 给你两个二进制字符串 a 和 b &#xff0c;以二进制字符串的形式返回它们的和。 示例 1&#xff1a; 输入:a “11”, b “1” 输出&#xff1a;“100” 示例 2&#xff1a; 输入&#xff1a;a “1010”, b “1011” 输出&#xff1a;“10101” …

网络如何发送一个数据包

网络如何发送一个数据包 网络消息发送就是点一点屏幕。 骚瑞&#xff0c;这一点都不好笑。&#xff08;小品就是我的本质惹&#xff09; 之前我就是会被这个问题搞的不安宁。是怎么知道对方的IP地址的呢&#xff1f;怎么知道对方的MAC呢&#xff1f;世界上计算机有那么多&…

SQL每日一练-0816

今日SQL题&#xff1a;计算每个项目的年度收入增长率 难度系数&#xff1a;&#x1f31f;☆☆☆☆☆☆☆☆☆ 1、题目要求 计算每个项目每年的收入总额&#xff0c;并计算项目收入环比增长率。找出每年收入增长率最高的项目。输出结果显示年份、项目ID、项目名称、项…

【走迷宫】

题目 DFS代码 #include<bits/stdc.h> using namespace std; const int N 110; int matrix[N][N]; int n, m; int dx[4] {-1, 0, 1, 0}, dy[4] {0, 1, 0, -1}; int dis[N][N]; void dfs(int x, int y, int cnt) {if(cnt > dis[n-1][m-1]) return;if(x n-1 &&a…

[AHK V2] 转换乱码“涓浗”为“中国”

想还原乱码字符串:涓浗 用乱码恢复工具 乱码恢复 可以查看到,该乱码的现在编码是gbk,原来编码是utf8 (也就是说原来是UTF-8编码的字符串,用GBK编码解析导致产生乱码,那么解析思路就是将GBK编码还原成UTF-8即可 ) 编码标识可以在这查阅 代码页标识符 GBK就是cp936 U…

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(一)---UnrealCV获取深度+分割图像

前言 本系列教程旨在使用UE5配置一个具备激光雷达深度摄像机的仿真小车&#xff0c;并使用通过跨平台的方式进行ROS2和UE5仿真的通讯&#xff0c;达到小车自主导航的目的。本教程使用的环境&#xff1a; ubuntu 22.04 ros2 humblewindows11 UE5.4.3python8 本系列教程将涉及以…

解决旧版CMS内容管理无法登录的问题

最近遇到了输入正确的账户密码&#xff0c;旧版的CMS内容管理的平台提示登录成功却无法跳转的问题 遇到这种情况请不要慌&#xff01;&#xff01;&#xff01; 请按照下面的步骤解决问题&#xff1a; 1.点击账号管理 2.点击右上角的返回旧版控制台 3.点击cloud1环境 4.点击扩…

leetcode13. 罗马数字转整数,流程图带你遍历所有情况

leetcode13. 罗马数字转整数 示例 1: 输入: s “III” 输出: 3 示例 2: 输入: s “IV” 输出: 4 示例 3: 输入: s “IX” 输出: 9 示例 4: 输入: s “LVIII” 输出: 58 解释: L 50, V 5, III 3. 示例 5: 输入: s “MCMXCIV” 输出: 1994 解释: M 1000, CM 900, XC…

RK3588J正式发布Ubuntu桌面系统,丝滑又便捷!

本文主要介绍瑞芯微RK3588J的Ubuntu系统桌面演示&#xff0c;开发环境如下&#xff1a; U-Boot&#xff1a;U-Boot-2017.09 Kernel&#xff1a;Linux-5.10.160 Ubuntu&#xff1a;Ubuntu20.04.6 LinuxSDK&#xff1a; rk3588-linux5.10-sdk-[版本号] &#xff08;基于rk3…

Kubectl 常用命令汇总大全

kubectl 是 Kubernetes 自带的客户端&#xff0c;可以用它来直接操作 Kubernetes 集群。 从用户角度来说&#xff0c;kubectl 就是控制 Kubernetes 的驾驶舱&#xff0c;它允许你执行所有可能的 Kubernetes 操作&#xff1b;从技术角度来看&#xff0c;kubectl 就是 Kubernetes…

力扣面试经典算法150题:找出字符串中第一个匹配项的下标

找出字符串中第一个匹配项的下标 今天的题目是力扣面试经典150题中的数组的简单题: 找出字符串中第一个匹配项的下标 题目链接&#xff1a;https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/?envTypestudy-plan-v2&envIdto…

知识改变命运 数据结构【栈和队列面试题】

1.最小栈 class MinStack {Stack <Integer>stack;Stack <Integer>minStack; public MinStack() {stacknew Stack<>();minStacknew Stack<>();}public void push(int val) {stack.push(val);if(minStack.empty()) {minStack.push(val);} else {int top…

算法-IMM

trajectory-prediction程序的imm.cc中的以下代码的对应的算法原理在后面 void IMM_UKF::InputInteract() {if (std::isnan(model_pro_(0)) || std::isnan(model_pro_(1)) || std::isnan(model_pro_(2)))std::abort();if (model_pro_.sum() ! 0)model_pro_ / model_pro_.sum();…

Android 12系统源码_多屏幕(二)模拟辅助设备功能开关实现原理

前言 上一篇我们通过为Android系统开启模拟辅助设备功能开关&#xff0c;最终实现了将一个Activity显示到多个屏幕的效果。 本篇文章我们具体来分析一下当我们开启模拟辅助设备功能开关的时候&#xff0c;Android系统做了什么哪些操作。 一、模拟辅助设备功能开关应用位置 …

基于web框架的协同过滤的美食推荐系统【数据爬虫、管理系统、数据可更新、样式可调整】

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍研究背景研究的目的与意义协同过滤算法基于用户的协同过滤算法定义基于物品的协同过滤算法的定义 数据库设计db_food&#xff08;美食信息表&#xff09;db_collect&#xff08;美食…

【Kubernetes】k8s集群图形化管理工具之rancher

目录 一.Rancher概述 1.Rancher简介 2.Rancher与k8s的关系及区别 3.Rancher具有的优势 二.Rancher的安装部署 1.实验准备 2.安装 rancher 3.rancher的浏览器使用 一.Rancher概述 1.Rancher简介 Rancher 是一个开源的企业级多集群 Kubernetes 管理平台&#xff0c;实…

【Linux网络】select函数

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 select函数介绍select函数参数介绍select函数返回值select的工作流程TCP服务器【多路复用版】 select函数介绍 在Linux网络编程中&#xff0c;select 函数是一种非常有用的IO多路复用技术&#xff0…

基于springboot的车辆违章信息管理系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

鸿萌数据恢复服务:SQL Server 中的“PFS 可用空间信息不正确”错误

天津鸿萌科贸发展有限公司从事数据安全服务二十余年&#xff0c;致力于为各领域客户提供专业的数据恢复、数据备份、网络及终端数据安全等解决方案与服务。 同时&#xff0c;鸿萌是国际主流数据恢复软件(Stellar、UFS、R-Studio、ReclaiMe Pro 等)的授权代理商&#xff0c;为专…