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

前言

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

UE介绍

请添加图片描述

  • UE5(Unreal Engine5)是由Epic Games开发的一款功能强大、跨平台的最新版的游戏开发引擎,以其高质量的实时渲染效果、多编程语言支持、丰富的市场资源和活跃的社区而闻名,广泛应用于游戏、影视、建筑等多个领域,不断推动着数字内容产业的发展。

  • 更多相关内容可以移步自我的教程UE5-C++入门教程(一):使用代码创建一个指定目标的移动小球-CSDN博客


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.rhkb.cn/news/405134.html

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

相关文章

Leetcode JAVA刷刷站(58)最后一个单词的长度

一、题目概述 二、思路方向 要解决这个问题,你可以通过遍历字符串 s 并从后往前计数的方式来实现。但更简洁且易于理解的方法是,首先去除字符串尾部的空格(如果有的话),然后找到最后一个单词的起始位置,并计…

XSS反射型和DOM型+DOM破坏

目录 第一关 源码分析 payload 第二关 源码分析 payload 第三关 源码分析 payload 第四关 源码分析 payload 第五关 源码分析 payload 第六关 源码分析 第七关 源码分析 方法一:构造函数 方法二:parseInt 方法三:locat…

【C语言】冒泡排序保姆级教学

C语言冒泡排序保姆级教学 直奔主题&#xff1a; 拿排升序举例子 第一步&#xff1a; 将想要排序的数组中数值最大的那个数排到该数组的最后 具体实现如下图&#xff1a; 第一步代码实现 for (int i 1; i < n ; i)//n为数组大小此处为4 {if (a[i - 1] > a[i])//注意越…

【java基础】IDEA 的断点调试(Debug)

目录 1.为什么需要 Debug 2.Debug的步骤 2.1添加断点 2.2单步调试工具介绍 2.2.1 Step Over 2.2.2 Step Into 2.2.3 Force Step Into 2.2.4 Step Out 2.2.5 Run To Cursor 2.2.6 Show Execution Poiint 2.2.7 Resume Program 3.多种 Debug 情况介绍 3.1行断点 3.2方…

[数据集][目标检测]锤子检测数据集VOC+YOLO格式1510张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1510 标注数量(xml文件个数)&#xff1a;1510 标注数量(txt文件个数)&#xff1a;1510 标注…

美团外卖新版 web mtgsig 1.2 分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、 敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业 用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间头像或私信联…

Pcie学习笔记(24)

Ordering and Receive Buffer Flow Control 流量控制(FC)用于防止接收端缓冲区溢出&#xff0c;并使其符合定义的排序规则。请注意&#xff0c;请求者使用流量控制机制来跟踪代理中可用的队列/缓冲区空间&#xff0c;如图2-48所示。也就是说&#xff0c;流控制是点对点的(跨一…

集团数字化转型方案(六)

集团数字化转型方案旨在通过引入前沿技术&#xff0c;如人工智能&#xff08;AI&#xff09;、大数据分析、云计算和物联网&#xff08;IoT&#xff09;&#xff0c;全面提升业务运营效率和市场竞争力。该方案首先实现业务流程的自动化&#xff0c;减少人工干预&#xff0c;通过…

学习C语言 第十八天

第一项 C 强制类型转换 强制类型转换是把变量从一种类型转换为另一种数据类型。可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型 (type_name) expression 一个整数变量除以另一个整数变量&#xff0c;得到一个浮点数&#xff1a; eg: #include <st…

AI在线免费数学工具:Qwen2-Math

1、Qwen2-Math https://huggingface.co/spaces/Qwen/Qwen2-Math-Demo

Python爬虫——简单网页抓取(实战案例)小白篇

Python 爬虫是一种强大的工具&#xff0c;用于从网页中提取数据。这里&#xff0c;我将通过一个简单的实战案例来展示如何使用 Python 和一些流行的库&#xff08;如 requests 和 BeautifulSoup&#xff09;来抓取网页数据。 实战案例&#xff1a;抓取一个新闻网站的头条新闻标…

【Qt】 常用控件QLCDNumber

常用控件QLCDNumber QLCDNumber是一个专门用来显示数字的控件&#xff0c;类似于“老式计算机”的效果。 QLCDNumber的属性 属性说明 intValue QLCDNumber 显⽰的数字值(int). value QLCDNumber 显⽰的数字值(double). 和 intValue 是联动的. 例如给 value 设为 1.5, i…

Docker 存储空间不足无法导入加载镜像

问题&#xff1a;在载入镜像时&#xff0c;发现docker没有空间了 解决办法&#xff1a; 更改docker的存储路径 1.添加新的硬盘 docker info #查看docker的存储位置 df -Th #查看占用以及挂载情况 发现没有可用的剩余空间&#xff0c;我们可以添加一个新的硬盘 在linu…

Java之HashMap的底层实现

Java之HashMap的底层实现 摘要HashMap的底层原理哈希值转换为数组下标节点初始化put(Object key, Object value)重写toString()get(Object key)增加泛化remove(K key) 摘要 本博客主要讲述了Java的HashMap的底层实现 HashMap的底层原理 底层原理&#xff1a;数组链表 过程…

Golang | Leetcode Golang题解之第352题将数据流变为多个不相交区间

题目&#xff1a; 题解&#xff1a; type SummaryRanges struct {*redblacktree.Tree }func Constructor() SummaryRanges {return SummaryRanges{redblacktree.NewWithIntComparator()} }func (ranges *SummaryRanges) AddNum(val int) {// 找到 l0 最大的且满足 l0 < val…

Elasticsearch 使用误区之四——不合理的使用 track_total_hits

0、企业级实战问题 在使用 Elasticsearch 进行搜索时&#xff0c;我们常常关心匹配查询的文档总数而将 track_total_hits 设置为 true&#xff0c;如下截图所示&#xff0c;在数据量非常大的情况下这种检索导致的问题是&#xff1a;查询特别慢&#xff0c;聚合会更慢&#xff0…

机器学习:逻辑回归实现下采样和过采样

1、概述 逻辑回归本身是一种分类算法&#xff0c;它并不涉及下采样或过采样操作。然而&#xff0c;在处理不平衡数据集时&#xff0c;这些技术经常被用来改善模型的性能。下采样和过采样是两种常用的处理不平衡数据集的方法。 2、下采样 1、概念 下采样是通过减少数量较多的类…

MaxKB(二):Ubuntu24.04搭建maxkb开发环境

接上文&#xff1a;windows10搭建maxkb开发环境&#xff08;劝退指南&#xff09; 上文在windows10环境搭建maxkb开发环境遇到各种坑&#xff0c;后面就转战ubuntu平台&#xff0c;果然比较顺利的完成开发环境搭建。当然遇到相关的问题还是可以参考上文《windows10搭建maxkb开发…

Stable Diffusion赋能“黑神话”——助力悟空走进AI奇幻世界

《黑神话&#xff1a;悟空》是由游戏科学公司制作的以中国神话为背景的动作角色扮演游戏&#xff0c;将于2024年8月20日发售。玩家将扮演一位“天命人”&#xff0c;为了探寻昔日传说的真相&#xff0c;踏上一条充满危险与惊奇的西游之路。 同时&#xff0c;我们还可以借助AI绘…

向量数据库Faiss的搭建与使用

​ ​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 向量数据库在处理大量高维数据时非常有用&#xff0c;尤其在机器学习、推荐系统、图像检索和自然语言处理等领域。Faiss是 Facebook AI Research (FAIR) 开发的一款高效的开源向量数据库&#xff0c;专注于大规模、高维…