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

news2024/11/17 13:41:25

前言

  • 本系列教程旨在使用UE5配置一个具备激光雷达+深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2UE5仿真的通讯,达到小车自主导航的目的。
  • 本教程默认有ROS2导航及其gazebo仿真相关方面基础,Nav2相关的学习教程可以参考本人的其他博客Nav2代价地图实现和原理–Nav2源码解读之CostMap2D(上)-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``激光雷达的仿真
  • 如有错误,欢迎指出~

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

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

相关文章

HarmonyOS-MPChart以X轴或y轴为区间设置不同颜色

本文是基于鸿蒙三方库mpchart OpenHarmony-SIG/ohos-MPChart 的使用&#xff0c;以X轴为区间设置不同的曲线颜色。 mpchart本身的绘制功能是不支持不同区间颜色不同的曲线的&#xff0c;那么当我们的需求曲线根据x轴的刻度区间绘制不同颜色&#xff0c;就需要自定义绘制方法了。…

LVS (Linux virual server)

LVS简介 LVS&#xff08;Linux Virtual Server&#xff09;是一个基于Linux平台的开源负载均衡系统。它通过将多个服务器组成一个虚拟服务器集群&#xff0c;实现了高效的负载均衡和流量分发。 LVS的核心思想是利用IP负载均衡技术和内容请求分发机制&a…

传知代码-【CLIP】文本也能和图像配对

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 概述 模态&#xff1a;数据的一种形式&#xff0c;如图像、文本、声音、点云等。 多模态学习&#xff0c;就是利用模型同时处理多个模态数据&#xff0c;有助于提高模型的准确性和泛化能力。在自动驾驶场景中&am…

利用住宅代理应对机器人流量挑战:识别、使用与检验指南

引言 什么是机器人流量&#xff1f;其工作原理是什么&#xff1f; 机器人流量来自哪里&#xff1f; 合法使用机器人时如何避免被拦截&#xff1f; 如何检验恶意机器人流量&#xff1f; 总结 引言 你是否曾经遇到过访问某个网站时&#xff0c;被要求输入验证码或完成一些其…

源代码加密的意义和办法?

一、源代码加密的意义1、防止恶意修改&#xff1a;未加密的源代码容易被恶意用户或竞争对手获取并修改&#xff0c;以植入恶意代码或病毒&#xff0c;损害软件的功能性和安全性。加密后的源代码即使被非法获取&#xff0c;也无法修改或理解&#xff0c;从而防止了被破坏的风险。…

品味白酒的四大步骤,体验不一样的美酒人生

在华夏千年的文化传承中&#xff0c;白酒如同一部厚重的历史长卷&#xff0c;每一滴都蕴含着丰富的故事与智慧。豪迈白酒&#xff08;HOMANLISM&#xff09;&#xff0c;作为这长卷中的璀璨篇章&#xff0c;更是以其不同的魅力&#xff0c;吸引着无数品鉴者去探寻其中的奥秘。今…

android13 禁用wifi

总纲 android13 rom 开发总纲说明 目录 1.前言 2.情况分析 3.代码分析 3.1 代码位置1 3.2 代码位置2 3.3 代码位置3 4.代码修改 5. 彩蛋 1.前言 这个文章讲的是,在frameworks里面禁止打开wifi。 2.情况分析 我们打开wifi一般是 public static void turnOnWifi(Co…

Linux修改ssh默认端口22为其他端口2024

一、修改配置文件 修改ssh服务的配置文件&#xff1a; /etc/ssh/sshd_config 将Port 22放开注释&#xff0c;并将22修改为2024&#xff0c;并保存 二、重启sshd服务 systemctl restart sshd 三、重启服务失败 如果重启服务失败&#xff0c;可以执行以下命令&#xff1a; …

web 自动化测试,一定得掌握的 8 个核心知识点

使用 cypress 进行端对端测试&#xff0c;和其他的一些框架有一个显著不同的地方&#xff0c;它使用 javascript 作为编程语言。传统主流的 selenium 框架是支持多语言的&#xff0c;大多数 QA 会的python 和 java 语言都可以编写 selenium 代码&#xff0c;遇到需要编写 js 代…

HoloLens 和 Unity 空间坐标系统 Coordinate systems

坐标系统 Spatial coordinate systems 所有的 3D 图形应用程序都使用笛卡尔坐标系统来推理虚拟物体的位置和朝向。 这些坐标系建立三个垂直轴&#xff1a;X、Y 和 Z。 添加到场景的每个对象在其坐标系中都有一个 XYZ 位置。 Windows 调用在物理世界中具有实际意义的坐标系统…

Win10系统配置JDK和Maven环境变量

目录 一、Win10系统配置JDK和Maven环境变量 二、测试 配置环境变量可以不用cd到应用程序的bin目录&#xff0c;就可以运行。配置环境变量可以方便IDE开发工具识别JDK和Maven 省去了手动选择。 一、Win10系统配置JDK和Maven环境变量 1、右键我的电脑>属性&#xff0c;弹出…

【Java 并发编程】(二) 从对象内存布局开始聊 synchronized

对象的内存布局 首先抛出一个经典面试题: 一个 Object 对象占多大? 这里我用工具打印了出来, 发现是 “16bytes”, 也就是 16B; 为什么? 请继续往下看; 普通对象(除了数组), 由markword, 类型指针, 实例数据(就是对象里的成员), 对齐填充(整个对象大小要能被8B整数, 方便6…

谷歌前CEO施密特放飞自我:斯坦福课堂上的AI洞见

谷歌前CEO施密特放飞自我&#xff1a;斯坦福课堂上的AI洞见 曾经担任谷歌CEO长达10年之久的Eric Schmidt&#xff0c;近日在斯坦福大学计算机学院的会议上发表了一场引人深思的演讲。在这场讲座中&#xff0c;他全程“放飞自我”&#xff0c;甚至在讲话中提醒台下学生&#xf…

将 PDF 转换为 JPG 的 3 种简便方法

PDF&#xff08;Portable Document Format&#xff09;是Adobe公司开发的一种用于呈现文档的常用文件格式。PDF文件可以包含图像和文本。它承载着固定布局平面文档的完整描述&#xff0c;包括文本、字体、图像等信息。但很多时候&#xff0c;你需要将PDF转换为JPG。 您想将PDF…

The Science of Procrastination - And How To Manage It

img&#xff1a;Perseid Meteors over Stonehenge 一场英仙座流星雨 虽然英仙座流星雨在昨晚达到了顶峰&#xff0c;但一些英仙座流星雨在接下来的几个晚上仍然可以看到 Lets face it. Youre likely reading this article in an effort to avoid some other tasks youre pro…

UART通信实现与验证(RS485)

前言 UART是一种常用的串行通信协议&#xff0c;RS485则是一种用于长距离和抗干扰的物理层标准。结合UART和RS485可以实现可靠的数据传输&#xff0c;特别是在多点通信和长距离应用中。通过合适的硬件连接、软件配置和验证测试&#xff0c;可以确保这一通信系统的稳定性和数据完…

达美航空运营中断造成重大财务损失

达美航空遇运营中断 达美航空公司&#xff08;Delta Air Lines&#xff0c;股票代码&#xff1a;DAL&#xff09;周四宣布&#xff0c;由于CrowdStrike引发的系统故障&#xff0c;其运营受到了严重影响。本季度&#xff0c;该公司预计收入将减少3.8亿美元。这次故障导致达美航…

stm32入门学习14-电源控制

有时候我们的程序中有些触发执行条件&#xff0c;有时这些触发频率很少&#xff0c;我们的程序就一直在循环&#xff0c;这样就很浪费电&#xff0c;我们可以通过PWR电源控制来实现低功耗模式&#xff0c;即只有在触发时才执行程序&#xff0c;其余时间可以关闭一些没必要的设备…

zdppy+vue3+onlyoffice文档管理系统项目实战 20240812上课笔记

遗留问题 1、增加新建和导入按钮&#xff0c;有按钮了&#xff0c;但是还没有完善&#xff0c;图标还不对&#xff0c;需要解决 2、登录功能 3、用户管理 4、角色管理 5、权限管理 6、分享功能 解决新建和导入的图标问题 解决代码&#xff1a; <a-button type"prim…

数据中台之数据开发-算法开发

目录 一、数据智能化挑战 二、算法开发的作用 三、算法架构与算法使用场景 3.1 算法架构总览 3.2 算法的适用场景 3.2.1 金融风控和反欺诈 3.2.2 文本挖掘分析 3.2.3 广告精准营销 3.2.4 个性化推荐 四、 算法开发涉及的内容 4.1 建模 4.1.1 可视化建模 4.1.1.1 可…