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

news2024/9/20 6:00:39

前言

  • 本系列教程旨在使用UE5配置一个具备激光雷达+深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2UE5仿真的通讯,达到小车自主导航的目的。
  • 本教程使用的环境:
    • ubuntu 22.04 ros2 humble
    • windows11 UE5.4.3
    • python8
  • 本系列教程将涉及以下内容:
    • UE仿真环境和简易小车的搭建
    • UE仿真雷达数据和RGBD深度相机数据的获取
    • 使用ROS2和UE5进行通讯
    • 使用ROS2导航Nav2及其相关模块对小车进行自主通讯
    • UE5部分C++编写和ROS2C++代码编写
    • 部分插件使用python进行编写
  • 本教程默认有ROS2导航及其gazebo仿真相关方面基础,Nav2相关的学习教程可以参考本人的其他博客Nav2代价地图实现和原理–Nav2源码解读之CostMap2D(上)-CSDN博客

UE介绍

请添加图片描述

  • UE5(Unreal Engine5)是由Epic Games开发的一款功能强大、跨平台的最新版的游戏开发引擎,以其高质量的实时渲染效果、多编程语言支持、丰富的市场资源和活跃的社区而闻名,广泛应用于游戏、影视、建筑等多个领域,不断推动着数字内容产业的发展。
  • 目前Unreal Engine(UE)主要支持以下编程语言:
      1. C++
      • C++是UE的核心编程语言,用于编写引擎的底层架构和游戏逻辑。
      • 它提供了最高的性能和灵活性,允许开发者直接访问和修改引擎的各个组件。
      • C++在UE中通过自定义的反射系统与蓝图系统紧密集成,使得C++类和函数可以在蓝图可视化编程环境中被使用。
    1. 蓝图(Blueprint)
      • 蓝图是UE的一种可视化脚本系统,它允许开发者通过拖拽连接节点的方式来创建逻辑和功能,无需编写传统的代码。
      • 蓝图降低了编程的入门门槛,使得非程序员也能参与到游戏和应用的开发中。
      • 蓝图可以与C++代码无缝交互,使得开发者可以根据需要选择最适合的编程方法。
安装
  • 本系列教程将使用最新版的UE5.4.3作为教程使用

  • 在下载UE需要先安装Epic Games Launcher,下载链接请添加图片描述

  • 下载完后初次进入会要求注册Epic账号,注册好后选择虚幻引擎的库选项,选择“+”号在下拉的菜单栏中选择5.4.3即可完成下载请添加图片描述

  • 注意下载可能需要根据个人需求安装对应的编辑器,这里我们使用的是VisualStudio2022请添加图片描述

  • 完成安装后我们选择创建一个全新的C++空白模板项目请添加图片描述

  • 创建完项目打开我们会得到以下的画面,这样就完成UE5.4版本的安装请添加图片描述


UnrealCV插件

  • UnrealCV是一个开源项目,旨在将Unreal Engine(UE)与计算机视觉(CV)领域结合起来。它允许开发者利用UE的高质量渲染能力来生成用于机器学习和计算机视觉研究的数据集和场景。UnrealCV提供了一个Python接口,允许用户通过Python脚本控制UE引擎,从而实现自动化场景渲染、数据采集和交互。请添加图片描述

  • 更多内容->UnrealCV官网

安装
  • 这里来介绍以下如何为UE5配置UnrealCV插件插件

  • 首先我们先登录UnrealCV的github仓库,由于官方提供的二进制release目前只支持到UE4.16版本,所以这里我们选择源码安装请添加图片描述

  • 注意选择5.2分支,目前UnrealCV已经支持到UE5.4版本请添加图片描述

  • git clone后我们会得到一个unrealcv的文件夹,文件夹内容大致如下请添加图片描述

  • 找到你的项目文件夹(该文件夹下有一个以.uproject结尾的项目文件),创建一个名为Plugins的文件夹请添加图片描述

  • 把上述你下载的unrealcv文件夹整个移动到Plugins文件交下,然后重新打开这个项目,项目会弹出UnrealCV插件是否进行编译的提示,选择是,然后就会进行编译请添加图片描述

  • 安装好后,在UE5界面左上角的编辑,在下拉菜单找到插件,搜索UnrealCV请添加图片描述

  • 返回UE主界面,找到绿色箭头开始运行此关卡(请务必记得这一步!!!)请添加图片描述

  • 在左下方的控制台输入vget /unrealcv/status确认插件配置情况请添加图片描述

  • 在左侧输出日志得到如下输出则表示插件配置成功请添加图片描述

报错指南
  • 控制台输入vget /unrealcv/statusUE直接闪退请添加图片描述

  • 检查是否进行绿色箭头开始运行此关卡此步骤(八成问题)

  • 检查UE下载是否完全,检查UE版本是否为5.4.3,检查unrealcv下载的分支是否为5.2


使用python对Unreal客户端与服务器进行连接通讯

  • UnrealCV 实现了在游戏和计算机视觉算法之间的进程间通信 (IPC)。这种通信可以用下图来总结。由UE创建的游戏将通过加载UnrealCV服务器作为其模块来扩展。当游戏启动时,UnrealCV将启动一个TCP 服务器并等待命令。任何程序都可以使用UnrealCV客户端代码发送纯文本 UnrealCV 命令来控制场景或检索信息。请添加图片描述

  • 下面我们来简单的写一段代码进行通讯测试,默认的UnrealCV服务器运行在本地的9000端口,我们通过简单的配置完成下述代码

from unrealcv import Client  
client = Client(('localhost', 9000))  
  
# 连接到 UnrealCV 服务器  
if client.connect():  
    print('UnrealCV connected successfully')  
else:  
    print('UnrealCV is not connected')  
    exit()  
  
response = client.request('vget /unrealcv/status')  
print(response)  
# 断开连接  
client.disconnect()
  • 上述运行结果将会输出和我们在UE控制台同样的内容输出,即视为通讯连接成功,此过程中确保UE允许了UnrealCV插件且UE处于运行此关卡中请添加图片描述

使用UnrealCV进深度和分割图像的获取

  • 这里我们介绍UnrealCV的一些基础用法,首先我们使用UE简单在摄像机视角前搭建一些简单物体请添加图片描述

  • 为相机添加初始位置,这里我们假定设置相机的位置为0,0,0(运行此关卡默认的摄像头视角就是0,0,0),注意这里必须设置,否则后面拍摄的深度和分割图像可能无法正确拍摄。

camera_settings = {  
    'location': {'x': 0, 'y': 0, 'z': 0},  # 相机位置  
    'rotation': {'pitch': 0, 'yaw': 0, 'roll': 0}  # 相机旋转  
}  
# 使用 vset 命令设置相机的位置  
client.request('vset /camera/0/location {x} {y} {z}'.format(**camera_settings['location']))  
  
# 使用 vset 命令设置相机的旋转  
client.request('vset /camera/0/rotation {pitch} {yaw} {roll}'.format(**camera_settings['rotation']))
  • 然后我们在上述连接的基础上添加如下代码,下述的代码将保存
    • lit:摄像机原始图像
    • object_mask:物体分割图像
    • depth:深度图像(注意这里深度图像保存为npy格式)
client.request('vget /camera/0/lit C:/Users/lzh/Desktop/UE5_ROS2_project/camera/lit.png')  
client.request('vget /camera/0/object_mask C:/Users/lzh/Desktop/UE5_ROS2_project/camera/object_mask.png')  
client.request('vget /camera/0/depth C:/Users/lzh/Desktop/UE5_ROS2_project/camera/depth.npy')
  • 我们简单对图像进行展示,便得到以下图像请添加图片描述
# 加载图像  
lit_img = plt.imread(r'C:/Users/lzh/Desktop/UE5_ROS2_project/camera/lit.png')  
object_mask_img = plt.imread(r'C:/Users/lzh/Desktop/UE5_ROS2_project/camera/object_mask.png')  
depth_img = np.load(r'C:\Users\lzh\Desktop\UE5_ROS2_project\camera\depth.npy')  
  
# 创建图形和子图  
fig, axes = plt.subplots(1, 3, figsize=(15, 5))  
  
# 显示 lit 图像  
axes[0].imshow(lit_img)  
axes[0].set_title('Lit Image')  
axes[0].axis('off')  
  
# 显示 object_mask 图像  
axes[1].imshow(object_mask_img, cmap='gray')  
axes[1].set_title('Object Mask')  
axes[1].axis('off')  
  
# 显示 depth 图像  
axes[2].imshow(depth_img, cmap='gray', vmin=0, vmax=1300)  
axes[2].set_title('Depth Image')  
axes[2].axis('off')  
  
# 显示图形  
plt.tight_layout()  
plt.show()

实时进行图像读取与显示

  • 上面我们进行了单张文件的保存,下面我们来介绍如何进行实时读取,这里我们介绍核心函数,通过下述三个函数,我们就能实时返回读取到的图像数据,为此我们需要进行解码
data=client.request('vget /camera/0/lit png')
data=client.request('vget /camera/0/object_mask png')
data=client.request('vget /camera/0/depth npy')
  • 对于普通图像和分割图像,我们只需要
lit_img = cv2.imdecode(np.frombuffer(self.data, np.uint8), cv2.IMREAD_COLOR)
object_mask_img = cv2.imdecode(np.frombuffer(self.data, np.uint8), cv2.IMREAD_COLOR)
  • 对于深度图像,如果我们希望使用opencv-python进行展示,我们需要进行转换
import io
def read_npy(self,res):  
    return np.load(io.BytesIO(res))
depth_img = self.read_npy(self.data)  
gray_depth = cv2.convertScaleAbs(depth_img, alpha=(255.0 / 1300.0))  
# 使用cv2.applyColorMap将灰度图转换为伪彩色图  
colored_depth = cv2.applyColorMap(gray_depth, cv2.COLORMAP_JET)  
  • 通过上述核心代码,我们只需要简单写一个循环,进行展示即可
  • 在不额外添加移动物体以及移动摄像机的前提下,我们移动观测者小球,便能得到以下实时画面
    在这里插入图片描述
    在这里插入图片描述

封装

  • 为了后续教程我们方便使用,我们封装以下整个流程
from unrealcv import Client  
import cv2  # OpenCV  
import numpy as np  
import io  
import time  
class UE5CameraCenter:  
    def __init__(self):  
        self._client = Client(('localhost', 9000))  
        self._connection_check()  
        self._camera_init()  
  
        self._lit_image=LitImage()  
        self._object_mask=ObjectMaskImage()  
        self._depth_mask=DepthImage()  
    def __del__(self):  
        self._client.disconnect()  
    def _connection_check(self):  
        '''检查是否连接'''  
        if self._client.connect():  
            print('UnrealCV connected successfully')  
        else:  
            print('UnrealCV is not connected')  
            exit()  
    def set_camera_pose(self,x,y,z,pitch,yaw,roll):  
        '''设置摄像头位置'''  
        camera_settings = {  
            'location': {'x': x, 'y': y, 'z': z},  # 相机位置  
            'rotation': {'pitch': pitch, 'yaw': yaw, 'roll': roll}  # 相机旋转  
        }  
        # 设置相机的位置  
        self._client.request('vset /camera/0/location {x} {y} {z}'.format(**camera_settings['location']))  
  
        # 设置相机的旋转  
        self._client.request('vset /camera/0/rotation {pitch} {yaw} {roll}'.format(**camera_settings['rotation']))  
  
    def _camera_init(self):  
        '''摄像头初始化'''  
        self.set_camera_pose(0,0,0,0,0,0)  
  
    def get_camera_data(self, camera_type):  
        valid_types = {'lit', 'object_mask', 'depth'}  
        # 检查 camera_type 是否在有效类型中  
        if camera_type not in valid_types:  
            raise ValueError(f"Invalid camera type. Expected one of {valid_types}, but got '{camera_type}'.")  
        # 根据camera_type获取相应的数据  
        if camera_type == 'lit':  
            return self._lit_image.get_image(self._client)  
        elif camera_type == 'object_mask':  
            return self._object_mask.get_image(self._client)  
        elif camera_type == 'depth':  
            return self._depth_mask.get_image(self._client)  
    def test(self):  
        while True :  
  
            self._lit_image.get_image(self._client)  
            self._object_mask.get_image(self._client)  
            self._depth_mask.get_image(self._client)  
  
            self._lit_image.display()  
            self._object_mask.display()  
            self._depth_mask.display()  
  
  
class Image:  
    def __init__(self):  
        self.data_np = None  
    def get_image(self,client):  
        pass  
    def display(self):  
        pass  
  
class LitImage(Image):  
    def __init__(self):  
        self.data_np = None  
    def get_image(self,client):  
        data = client.request('vget /camera/0/lit png')  
        self.data_np=cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR)  
    def display(self):  
        cv2.imshow("lit_img", self.data_np)  
        key = cv2.waitKey(1)  
class ObjectMaskImage(Image):  
    def __init__(self):  
        self.data_np = None  
    def get_image(self,client):  
        data=client.request('vget /camera/0/object_mask png')  
        self.data_np=cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR)  
    def display(self):  
        cv2.imshow("object_mask_data", self.data_np)  
        key = cv2.waitKey(1)  
class DepthImage(Image):  
    def __init__(self):  
        self.data_np = None  
    def get_image(self,client):  
        data=client.request('vget /camera/0/depth npy')  
        self.data_np = self.read_npy(data)  
    def display(self):  
        gray_depth = cv2.convertScaleAbs(self.data_np, alpha=(255.0 / 1300.0))  
        # 使用cv2.applyColorMap将灰度图转换为伪彩色图  
        colored_depth = cv2.applyColorMap(gray_depth, cv2.COLORMAP_JET)  
        cv2.imshow("depth_img", colored_depth)  
    def read_npy(self,res):  
        return np.load(io.BytesIO(res))  
  
def main():  
    ue5_cam_center=UE5CameraCenter()  
    ue5_cam_center.test()  
if __name__ =='__main__':  
   main()
  • 并预留三个接口
    • 设置摄像机位置def set_camera_pose(self,x,y,z,pitch,yaw,roll):
    • 获取摄像机图像def get_camera_data(self, camera_type):
    • 测试(显示摄像机画面)def test(self):

UnrealCV指令大全

通用命令
  • vget /unrealcv/status:获取 UnrealCV 插件的状态。
  • vget /unrealcv/help:列出所有可用的命令及其帮助信息。
  • vget /unrealcv/version:获取 UnrealCV 的版本信息。
  • vget /scene/name:获取当前场景的名称。
  • vget /level/name:获取当前级别的名称。
对象命令
  • vget /objects:获取场景中所有对象的名称。
  • vset /objects/spawn_cube:在场景中生成一个用于调试的立方体。
  • vget /object/[str]/location:获取指定对象的坐标位置。
  • vset /object/[str]/location [float] [float] [float]:设置指定对象的坐标位置。
  • vget /object/[str]/rotation:获取指定对象的旋转。
  • vset /object/[str]/rotation [float] [float] [float]:设置指定对象的旋转。
  • vset /object/[str]/color [uint] [uint] [uint]:设置指定对象的颜色。
  • vset /object/[str]/destroy:销毁指定对象。
相机命令
  • vget /cameras:列出场景中所有传感器(相机)。
  • vset /camera/[uint]/location [float] [float] [float]:设置指定相机的位置。
  • vset /camera/[uint]/rotation [float] [float] [float]:设置指定相机的旋转。
  • vget /camera/[uint]/lit [str]:从指定相机获取光照图像。
  • vget /camera/[uint]/depth [str]:从指定相机获取深度图像。
  • vget /camera/[uint]/normal [str]:从指定相机获取表面法线图像。
  • vget /camera/[uint]/object_mask [str]:从指定相机获取对象掩码图像。
  • vset /camera/[uint]/fov [float]:设置指定相机的视场角。
视图模式命令
  • vset /viewmode [str]:设置视图模式,如 lit(光照)、normal(法线)、depth(深度)、object_mask(对象掩码)等。
  • vget /viewmode:获取当前的视图模式。
其他命令
  • vrun [str]:运行 Unreal 引擎内置的命令。
  • vexec [str]:运行 Unreal 引擎蓝图函数。
  • vbp [str]:运行 Unreal 引擎蓝图函数。

小结

  • 本节介绍了如何使用UnrealCV插件在UE中获取原始,深度,分割图像,并实时进行读取
  • 下一节我们将进行跨平台实现图像数据的传输
  • 感谢大家对本教程的支持,如有错误,欢迎指出~

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

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

相关文章

04-正弦波,衰减正弦波,正弦波脉冲冲串的产生

1.设置波形线宽 点击Waveforms a 2.添加Comment 3.添加正弦波 3.1先添加一个电压源 3.2增加波形窗口 3.3右键选择Advanced 3.31原始正弦波 名称含义①DC offset直流偏置②Amplitude幅值③Freq频率④Tdelay延迟⑤Theta衰减⑥Phi相位⑦Ncycles产生正弦波的个数 设置完成后&am…

数据结构与算法——BFS(广度优先搜索)

算法介绍: 广度优先搜索(Breadth-First Search,简称BFS)是一种遍历或搜索树和图的算法,也称为宽度优先搜索,BFS算法从图的某个节点开始,依次对其所有相邻节点进行探索和遍历,然后再…

第T8周:使用TensorFlow实现猫狗识别

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 文章目录 一、前期工作1.设置GPU(如果使用的是CPU可以忽略这步)2. 导入数据 二、数据预处理1、加载数据2、再次检查数据3. 配置数据集4…

低代码开发的崛起:机遇与挑战

近年来,“低代码”开发平台的迅速崛起,已经成为IT行业中不可忽视的趋势。这些平台承诺让非专业人士也能快速构建应用程序,通过减少代码编写的需求,大幅提高开发效率。对于许多企业而言,低代码开发工具成为了一个加速数…

Real-Time Open-Vocabulary Object Detection:使用Ultralytics框架进行YOLO-World目标检测

Real-Time Open-Vocabulary Object Detection:使用Ultralytics框架进行YOLO-World目标检测 前言相关介绍前提条件实验环境安装环境项目地址LinuxWindows 使用Ultralytics框架进行YOLO-World目标检测进行训练进行预测进行验证 扩展目标跟踪设置提示 参考文献 前言 由…

windows核心编程 第14章,虚拟内存:获取系统信息

windows核心编程 第14章,虚拟内存:获取系统信息 14,获取系统消息 文章目录 windows核心编程 第14章,虚拟内存:获取系统信息14.1 系统信息 14.1 系统信息 许多操作系统的值是根据主机而定的,比如页面的大小&#xff0…

无人机挂载垂直抛投灭火弹技术详解

随着城市化进程的加快,高层建筑、森林、化工园区等区域火灾防控难度日益增大。传统消防手段在面对复杂地形或高层火灾时,往往存在响应速度慢、作业难度大、人员安全风险高等问题。无人机挂载垂直抛投灭火弹技术的出现,为高效、安全、精准的火…

conda install vs pip install

1背景 最近使用pyinstaller打包python程序,启动程序的时候,发现了以下的报错信息 Failed to execute script "pyi_rth_pkgres" due to unhandled dll load failed while importing pyexpat后面查阅了相关文档,比如根据stackoverf…

Vue3+Ts封装下拉懒加载自定义指令

文件夹目录如下: 使用方式: <template><divclass="time-line"v-infinite-scroll="{loadMore: loadMoreItems,threshold: 100 // 当滚动到距离底部 100 像素时触发加载}"> </div> </template><script lang="ts" se…

7次多项式对若干个点进行拟合,并生成图像|MATLAB实现

文章目录 拟合运行结果完整代码拟合 MATLAB对数据进行拟合的意义是通过数学模型和统计方法对实际数据进行分析和预测。拟合可以帮助我们理解数据背后的规律和趋势,从而做出科学决策。 拟合的意义 揭示数据的规律 预测未来趋势 数据修正和异常检测 数据分析和模型验证 总之,…

Prometheus+Grafana保姆笔记(2)——监控Spring Boot微服务程序

Prometheus Grafana 的组合在微服务项目中可以完成许多DevOps任务&#xff0c;它们共同提供了强大的监控和可视化功能。 我们陆续介绍Prometheus Grafana 的相关用法。 上一期&#xff0c;我们介绍了PrometheusGrafana的安装&#xff0c; PrometheusGrafana保姆笔记&#…

javaFx桌面程序开发代码示例

程序效果&#xff1a; 弹窗内容&#xff1a; 1.启动类 HelloJavaFX&#xff1a; import javafx.application.Application; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene…

赵本山:你把这玩意借我带上,黄晓娟:驴蒙脸你要带这个?--小品《麻将豆腐》(中3)的台词与解说

赵本山&#xff1a;你把这玩意借我带上&#xff0c;黄晓娟&#xff1a;驴蒙脸你要带这个&#xff1f; --小品《麻将豆腐》&#xff08;中3&#xff09;的台词与解说 &#xff08;接上&#xff09; 黄晓娟&#xff08;饰演小姨子&#xff09;&#xff1a;忘了麻将 赵本山&…

车载客流计数器在公交系统中的应用

车载客流计数器在公交系统中的应用&#xff0c;已成为提升运营效率与服务质量的关键工具。随着大数据概念的深入人心&#xff0c;人们愈发认识到数据在决策中的核心地位。公交车客流统计的引入&#xff0c;正是基于这一认识&#xff0c;旨在为公交车辆的运营组织提供详实、精准…

MySQL:表的设计原则和聚合函数

所属专栏&#xff1a;MySQL学习 &#x1f48e;1. 表的设计原则 1. 从需求中找到类&#xff0c;类对应到数据库中的实体&#xff0c;实体在数据库中表现为一张一张的表&#xff0c;类中的属性对应着表中的字段 2. 确定类与类的对应关系 3. 使用SQL去创建具体的表 范式&#xff1…

【Linux多线程】线程同步 与 生产者消费者模型(无锁化模型)

文章目录 1. Linux线程同步1.1 条件变量1.2 同步概念与竞态条件1.3 条件变量函数示例代码1:示例代码2 1.4 为什么 pthread_ cond_ wait 需要互斥量1.5 条件变量使用规范 2. 生产者消费者模型3. 读者 写者 问题3.1 读写锁3.2 读写锁的相关接口 4. 扩展&#xff1a;无锁化模型4.1…

Python 如何创建和解析 XML 文件

XML&#xff08;可扩展标记语言&#xff09;是一种广泛使用的标记语言&#xff0c;主要用于存储和传输数据。它具有结构化、层次化的特点&#xff0c;常被用作数据交换格式。Python 提供了多种工具和库来处理 XML 文件&#xff0c;包括创建、解析和操作 XML 文档。 一、XML 简…

免费webp转jpeg或gif工具

1、”“添加webp文件&#xff1b;”-“移除webp文件&#xff1b;”>>“开始转换&#xff1b;”X“清空内容。 也可以把想要转换的文件全选&#xff0c;拖进窗口里。 2、默认将webp文件转换成同名的png文件放在原来的文件夹里。如果不是静态图片&#xff0c;则自动尝试转…

案例:ZooKeeper + Kafka消息队列集群部署

目录 消息队列 概念 使用场景 不适宜 适宜 消息队列的特征 存储 异步 异步的优点 同步 为什么需要消息队列 解耦 作用 冗余 扩展性 灵活性 峰值处理能力 可恢复性 顺序保证 Kafka 概念 Kafka技术名词 &#xff08;1&#xff09;Broker &#xff08;2&a…

基于Orangepi全志H616智能视觉垃圾分类系统

目录 一、功能需求 二、Python的安装和环境搭建 三、Python基础 3.1 Python的特点&#xff1a; 3.2 Python的基础学习&#xff1a; 3.3 字典的多层嵌套&#xff1a; 四、C语言调用Python 4.1 搭建编译环境&#xff1a; 4.2 C语言执行Python语句&#xff1a; 4.3 C语言…