点云标注工具开发记录(五)之点云文件加载、视角转换

Open3D中,通过read方法,我们可以读取不同格式的点云数据,那么,在不使用Open3D的相关接口时,我们就需要自己重写文件读入、加载、渲染展示方法,效果如下:

在这里插入图片描述

点云文件读入

首先,我们要绑定事件:

main.py中启动点云读入线程

def point_cloud_read_thread_start(self, file_path):if file_path.endswith('.las') or file_path.endswith('.ply') or file_path.endswith('.txt'):self.point_cloud_read_thread.set_file_path(file_path)   # 传递文件名self.point_cloud_read_thread.start()                    # 线程读取文件else:QtWidgets.QMessageBox.warning(self, 'Warning', "Only support file endwith '.txt', '.ply', '.las'")

pointcloud.py文件中我们可以看到,其继承了QThread,并重写了run方法,当我们的线程启动时,其会执行run方法的内容,

from multiprocessing import Poolclass PointCloudReadThread(QThread):message = pyqtSignal(str, int)tag = pyqtSignal(bool)def __init__(self):super(PointCloudReadThread, self).__init__()self.file_path = Noneself.callback = Noneself.pointcloud = Nonedef run(self):#在状态栏加载这段话self.message.emit("Open file | Loading ...", 10000000)    # 一直显示pool = Pool()#一个线程池,其大小为20 <multiprocessing.pool.Pool state=RUN pool_size=20>#pool.apply_async:这是 Pool 类的一个方法,用于异步地执行一个函数。与 pool.apply 不同,apply_async 是非阻塞的,这意味着它会立即返回一个 AsyncResult 对象(在您的例子中是 p),而不会等待函数执行完成。其余的则是需要指定的传入参数,这里其要异步执行的是read方法,p = pool.apply_async(func=self.read, args=(self.file_path,), callback=self.callback)pool.close()#关闭线程池pool.join()self.pointcloud = p.get()self.message.emit("Open file | Load point cloud finished.", 1000)self.tag.emit(True)#read方法,根据传入的文件个数,选择不同的文件加载方法,这里我们使用的是ply格式的文件@staticmethoddef read(file_path:str):if file_path.endswith('.las'):xyz, rgb, size, offset = las_read(file_path)elif file_path.endswith('.ply'):xyz, rgb, size, offset = ply_read(file_path)elif file_path.endswith('.txt'):xyz, rgb, size, offset = txt_read(file_path)else:return Nonepointcloud = PointCloud(file_path, xyz, rgb, size, offset)return pointcloud

ply文件读取代码如下:

def ply_read(file_path):#PlyData是提供的一种PLY文件格式读取接口ply_data = PlyData.read(file_path)#PlyData的值如下:((PlyElement('vertex', (PlyProperty('x', 'double'), PlyProperty('y', 'double'), PlyProperty('z', 'double'), PlyProperty('red', 'uchar'), PlyProperty('green', 'uchar'), PlyProperty('blue', 'uchar')), count=1178312, comments=[]),), text=False, byte_order='<', comments=['Created by Open3D'], obj_info=[])if 'vertex' not in ply_data:return np.array([]), np.array([]), np.array([0, 0, 0]), np.array([0, 0, 0])#下面这段代码则将ply_data的内容读取处理#ply_data['vertex']['x']#Out[5]: memmap([ 7.90625,  7.65625,  8.     , ..., 34.78125, 34.71875, 30.1875 ])vertices = np.vstack((ply_data['vertex']['x'],ply_data['vertex']['y'],ply_data['vertex']['z'])).transpose()if 'red' in ply_data['vertex']:colors = np.vstack((ply_data['vertex']['red'],ply_data['vertex']['green'],ply_data['vertex']['blue'])).transpose()else:colors = np.ones(vertices.shape)vertices = vertices.astype(np.float32)#接下来这个操作便是计算一些点云属性,入最值,大小、偏移等xmin, ymin, zmin = min(vertices[:, 0]), min(vertices[:, 1]), min(vertices[:, 2])xmax, ymax, zmax = max(vertices[:, 0]), max(vertices[:, 1]), max(vertices[:, 2])size = np.array((xmax - xmin, ymax - ymin, zmax - zmin))#偏移值即xyz的最小值offset = np.array([xmin, ymin, zmin])vertices -= offsetcolors = colors.astype(np.float32)#颜色归一化colors = colors / 255return vertices, colors, size, offset

使用PlyData读取的ply文件内容如下:

在这里插入图片描述

将其转换为numpy格式查看,我们看到其内容如下,即表示xyzrgb

在这里插入图片描述

生成点云对象

将点云信息获得后,生成点云对象(这个PointCloud是我们自己定义的)

#生成点云对象
pointcloud = PointCloud(file_path, xyz, rgb, size, offset)class PointCloud:def __init__(self, file_path:str, xyz, rgb, size, offset):self.file_path:str = file_pathself.xyz:np.ndarray = xyz if xyz.dtype == np.float32 else xyz.astype(np.float32)self.offset:np.ndarray = offsetself.size:np.ndarray = sizeself.num_point = self.xyz.shape[0]self.rgb:np.ndarray = rgb if rgb.dtype == np.float32 else rgb.astype(np.float32)def __str__(self):return "<PointCloud num_point: {} | size: ({:.2f}, {:.2f}, {:.2f}) | offset: ({:.2f}, {:.2f}, {:.2f}) >".format(self.num_point, self.size[0], self.size[1], self.size[2], self.offset[0], self.offset[1], self.offset[2])

点云渲染生成

最终,我们也就得到了点云文件,接下来将送消息通知,调用point_cloud_read_thread_finished方法,这个方法主要是初始化一些点云信息,用于将来保存,在这里面,真正在屏幕上生成点云图像的是self.openGLWidget.load_vertices(pointcloud, categorys, instances)方法

#线程绑
self.point_cloud_read_thread.tag.connect(self.point_cloud_read_thread_finished)
def point_cloud_read_thread_finished(self, tag:bool):if tag:#self.tag.emit(True)这个tag是在这里pointcloud = self.point_cloud_read_thread.pointcloud#获得我们生成的PointCloud对象if pointcloud is None:return#label_file = '.'.join(self.current_file.split('.')[:-1]) + '.json'#当我们点击保存时,会生成json的label标签文件,目录与我们的点云文件相同,该文件设置为仅可读模式categorys = Noneinstances = None#判断这个文件是否存在,继续写入,这个是在第二次打开时才会生效,然后判断点云文件与先前的点云是否相同,并且加载先前我们对点云的分类信息if os.path.exists(label_file):with open(label_file, 'r') as f:datas = load(f)info = datas.get('info', '')if info == 'Laiease label file.':categorys = datas.get('categorys', [])instances = datas.get('instances', [])categorys = np.array(categorys, dtype=np.int16)instances = np.array(instances, dtype=np.int16)if categorys.shape[0] != pointcloud.xyz.shape[0] or instances.shape[0] != pointcloud.xyz.shape[0]:QtWidgets.QMessageBox.warning(self, 'Warning', 'Point cloud size does not match label size!')if categorys.shape[0] != pointcloud.xyz.shape[0]:categorys = Noneif instances.shape[0] != pointcloud.xyz.shape[0]:instances = Noneif pointcloud.num_point < 1:return#展示点云self.openGLWidget.load_vertices(pointcloud, categorys, instances)#侧边标签显示点云信息self.label_num_point.setText('{}'.format(pointcloud.num_point))self.label_size_x.setText('{:.2f}'.format(pointcloud.size[0]))self.label_size_y.setText('{:.2f}'.format(pointcloud.size[1]))self.label_size_z.setText('{:.2f}'.format(pointcloud.size[2]))self.label_offset_x.setText('{:.2f}'.format(pointcloud.offset[0]))self.label_offset_y.setText('{:.2f}'.format(pointcloud.offset[1]))self.label_offset_z.setText('{:.2f}'.format(pointcloud.offset[2]))self.setWindowTitle(pointcloud.file_path)#开启点云框选、取消框选按钮self.actionPick.setEnabled(True)self.actionCachePick.setEnabled(True)

展示点云调用的是load_vertices(pointcloud, categorys, instances)方法,该方法将计算点云的一些初始化参数

    def load_vertices(self, pointcloud, categorys:np.ndarray=None, instances:np.ndarray=None):self.reset()#清空先前的信息self.pointcloud = pointcloud#这里是对视角进行转换,如平移旋转等self.vertex_transform.setTranslation(-pointcloud.size[0]/2, -pointcloud.size[1]/2, -pointcloud.size[2]/2)#添加掩膜,用于根据标签展示点云self.mask = np.ones(pointcloud.num_point, dtype=bool)self.category_display_state_dict = {}self.instance_display_state_dict = {}#这里会读取categorys的内容,即在point_cloud_read_thread_finished方法中加载的json里面的类别信息self.categorys = categorys if categorys is not None else np.zeros(pointcloud.num_point, dtype=np.int16)#这个是不同展示模式,设计了rgb、categorys以及instances三种模式self.instances = instances if categorys is not None else np.zeros(pointcloud.num_point, dtype=np.int16)#计算缩放系数,这个值用于后面屏幕点击坐标转换self.ortho_change_scale = max(pointcloud.size[0] / (self.height() / 5 * 4),pointcloud.size[1] / (self.height() / 5 * 4))self.current_vertices = self.pointcloud.xyz#这个是要通过vob渲染展示到屏幕的点云self.current_colors = self.pointcloud.rgbself.init_vertex_vao()#渲染展示self.resizeGL(self.width(), self.height())self.update()

在这里插入图片描述

视角转换

在点云标注过程中,我们经常要进行点云视角切换,如前视角、后视角等。
视角切换代码如下,我们以左视角为例:
首先,需要绑定视角切换按钮和事件

self.actionLeft_view.triggered.connect(self.openGLWidget.set_left_view)

openglwidget.py文件中,定义了set_left_view方法

		self.vertex_transform = Transform()self.circle_transform = Transform()self.axes_transform = Transform()self.keep_transform = Transform()self.projection = QMatrix4x4()self.camera = Camera()def set_left_view(self):self.vertex_transform.setRotation(QQuaternion.fromAxisAndAngle(QVector3D(1, 0, 0), 270))self.circle_transform.setRotation(QQuaternion.fromAxisAndAngle(QVector3D(1, 0, 0), 270))self.axes_transform.setRotation(QQuaternion.fromAxisAndAngle(QVector3D(1, 0, 0), 270))self.vertex_transform.rotate(QVector3D(0, 1, 0), 90)self.circle_transform.rotate(QVector3D(0, 1, 0), 90)self.axes_transform.rotate(QVector3D(0, 1, 0), 90)self.update()

Transform()类的定义如下:

from PyQt5.QtGui import QMatrix4x4, QVector3D, QQuaternion
#视角转换class Transform(object):def __init__(self):self.m_translation = QVector3D()self.m_scale = QVector3D(1, 1, 1)self.m_rotation = QQuaternion()self.m_world = QMatrix4x4()self.localforward = QVector3D(0.0, 0.0, 1.0)self.localup = QVector3D(0.0, 1.0, 0.0)self.localright = QVector3D(1.0, 0.0, 0.0)def forward(self):return self.m_rotation.rotatedVector(self.localforward)def up(self):return self.m_rotation.rotatedVector(self.localup)def right(self):return self.m_rotation.rotatedVector(self.localright)def translate(self, dx, dy, dz):self.m_translation += QVector3D(dx, dy, dz)def scale(self, sx, sy, sz):self.m_scale *= QVector3D(sx, sy, sz)def rotate(self, axis, angle):dr = QQuaternion.fromAxisAndAngle(axis, angle)self.m_rotation = dr * self.m_rotationself.m_translation = dr.rotatedVector(self.m_translation)def rotate_in_place(self, axis, angle):dr = QQuaternion.fromAxisAndAngle(axis, angle)self.m_rotation = dr * self.m_rotationdef setTranslation(self, dx, dy, dz):self.m_translation = QVector3D(dx, dy, dz)def setTranslationwithRotate(self, dx, dy, dz):self.m_translation = self.m_rotation.rotatedVector(QVector3D(dx, dy, dz))def setScale(self, sx, sy, sz):self.m_scale = QVector3D(sx, sy, sz)def setRotation(self, r:QQuaternion):dr = r * self.m_rotation.inverted()self.m_translation = dr.rotatedVector(self.m_translation)self.m_rotation = rdef toMatrix(self):self.m_world.setToIdentity()self.m_world.translate(self.m_translation)self.m_world.scale(self.m_scale)self.m_world.rotate(self.m_rotation)return self.m_world

当然,这里的视角切换是切换到固定视角,当我们的鼠标在拖动时,点云也会随着切换视角,此时,其实现方法与之类似:

def mouse_rotate(self, xoffset, yoffset):# 点云旋转self.vertex_transform.rotate(self.vertex_transform.localup, xoffset * 0.5)self.vertex_transform.rotate(self.vertex_transform.localright, yoffset * 0.5)# 坐标旋转self.circle_transform.rotate_in_place(self.circle_transform.localup, xoffset * 0.5)self.circle_transform.rotate_in_place(self.circle_transform.localright, yoffset * 0.5)self.axes_transform.rotate_in_place(self.axes_transform.localup, xoffset * 0.5)self.axes_transform.rotate_in_place(self.axes_transform.localright, yoffset * 0.5)self.update()

为了将坐标从一个坐标系变换到另一个坐标系,需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。顶点坐标起始于局部空间(Local Space),在这里它称为局部坐标(Local Coordinate),它在之后会变为世界坐标(World Coordinate),观察坐标(View Coordinate),裁剪坐标(Clip Coordinate),并最后以屏幕坐标(Screen Coordinate)的形式结束。下面的这张图展示了整个流程以及各个变换过程做了什么:

在这里插入图片描述

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

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

相关文章

vue开发的一个小插件vue.js devtools

可打开谷歌商城的情况下&#xff0c;不可打开的可以到极简插件里面去下载 极简插件官网_Chrome插件下载_Chrome浏览器应用商店 搜索vue即可

Flutter仿京东商城APP实战 用户中心基础布局

用户中心界面 pages/tabs/user/user.dart import package:flutter/material.dart; import package:jdshop/utils/zdp_screen.dart; import package:provider/provider.dart;import ../../../store/counter_store.dart;class UserPage extends StatefulWidget {const UserPage…

Maven入门到实践:从安装到项目构建与IDEA集成

目录 1. Maven的概念 1.1 什么是Maven 1.2 什么是依赖管理 1.3 什么是项目构建 1.4 Maven的应用场景 1.5 为什么使用Maven 1.6 Maven模型 2.初识Maven 2.1 Maven安装 2.1.1 安装准备 2.1.2 Maven安装目录分析 2.1.3 Maven的环境变量 2.2 Maven的第一个项目 2.2.1…

古埃及象形文字在线字典

我在个人网站“小孔的埃及学站点”上推出了在线的象形文字字典&#xff0c;总共收罗了将近700条的象形文字&#xff08;词&#xff09;。在线字典的使用方法很简单&#xff0c;在网站各大版块首页的右上方会有如下图所示的查询入口。 点击文本框&#xff0c;输入中文或英文关键…

公交IC卡收单管理系统 assets 信息泄露

0x01 产品描述&#xff1a; 公交IC卡系统是公交一卡通系统核心建设部分&#xff0c;是高时尚、高科技的管理系统&#xff0c;大大提升了公交行业的服务&#xff0c;能让公交企业信息化和电子化打下一个良好的硬件基础和软件基0x02 漏洞描述&#xff1a; 公交IC卡系统在/assets/…

FRIDA-JSAPI:Instruction使用

官方API文档介绍 Instruction.parse(target) 解析内存中 target 地址处的指令。 返回的对象具有的字段&#xff1a; address: 此指令的地址&#xff08;EIP&#xff09;&#xff0c;类型为 NativePointernext: 指向下一条指令的指针&#xff0c;您可以使用 parse() 解析它size…

详解如何使用WGCLOUD监测日志文件

WGCLOUD可以监控日志文件&#xff0c;包括.log、.txt、.out等类型的文件 WGCLOUD既可以监测文件夹下按天生成的日志文件&#xff0c;也可以监控指定的日志文件&#xff0c;非常灵活 我们只需要设置好日志中出现什么关键字符&#xff0c;那么WGCLOUD就可以自动进行这些监控工作…

【react 和 vue】 ---- 实现组件的递归渲染

1. 需求场景 今天遇到了一个需求&#xff0c;就是 HTML 的递归渲染。问题就是商品的可用时间&#xff0c;使用规则等数据是后端配置&#xff0c;然后配置规则则是可以无限递归的往下配置&#xff0c;可以存在很多级。后端实现后&#xff0c;数据返回前端&#xff0c;就需要前端…

一招教你解决Facebook广告账号问题

这段时间&#xff0c;我们写了很多文章来探讨Facebook的广告账户问题&#xff1a;《Facebook被封号该怎么办》《Facebook二不限、三不限账号是什么》《Facebook海外户&#xff08;三不限&#xff09;和账单户该如何选择》《如何区分真假Facebook三不限海外户》相信看过这些文章…

【传知代码】智能推荐与隐私保护的融合(论文复现)

本文将深入探讨这样一种系统的设计理念、关键技术以及其在实际应用中的潜力和优势。通过探索如何在保证个性化推荐效果的同时&#xff0c;有效保护用户隐私&#xff0c;我们将揭示出一种新兴的技术趋势&#xff0c;为未来智能化应用的发展开辟新的可能性。 目录 概述 项目设计…

基于SSM+小程序的就业管理系统(就业1)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 学生实习与就业管理系统的设计与实现管理员、辅导员管理、企业管理、工作管理人、用户管理5个角色。 1、管理员实现了基础数据管理、辅导员管理、企业管理、工作管理人管理、公告信息管理…

js 填充数组

let arr Array.from({ length: 10 }, (_, index) > index)console.log(arr) 人工智能学习网站 https://chat.xutongbao.top

如何用示波器检测次级点火系统(一)

写在最前面&#xff1a; 单看标题可能会让你觉得这篇文章的主题是关于检测线圈&#xff0c;火花塞和火花塞插头电线。但我们指的是分析燃烧室内电子的行为。目标是看燃料混合物&#xff0c;阀座&#xff0c;压缩&#xff0c;积碳和其它影响这种特性的症状。最终目的是要学会分…

FIR数字滤波器在MATLAB中的实现

摘要 数字滤波器是由数字乘法器、加法器和延时单元组成的一种装置。数字滤波器的功能是对输入离散信号的数字代码进行运算处理&#xff0c;以达到改变信号频谱的目的。近年来数字滤波在通信、图像编码、语言编码、雷达等许多领域中有着十分广泛的应用。 本文首先介绍了数字滤波…

为什么诺贝尔物理学奖颁给了 AI 大神

瑞典皇家科学院刚宣布&#xff0c;科学家约翰霍普菲尔德&#xff08;John J. Hopfield) 和杰弗里辛顿 (Geoffrey E. Hinton) 荣膺 2024年诺贝尔物理学奖&#xff0c;以表彰他们通过人工神经网络 (ANN) 实现机器学习而作出的基础性发现和发明 (for foundational discoveries and…

程序员:代码世界的探险家与日常“救火队员”

在这个被数字与代码编织的时代&#xff0c;程序员&#xff0c;这一群看似平凡却又不凡的“数字工匠”&#xff0c;正用他们的智慧与汗水&#xff0c;构建着我们生活的每一个角落。值此1024程序员节之际&#xff0c;让我们以轻松幽默的方式&#xff0c;一同走进程序员的世界&…

8轴/4轴的EtherCAT轴模块EIO24088G-V2及EIO16084G的使用(一):TwinCAT总线配置与使用

上节课给大家介绍了 EIO24088-V2及EIO16084结合RTSys进行总线配置与使用&#xff0c;详情请点击→8轴/4轴的EtherCAT轴模块EIO24088-V2及EIO16084的使用&#xff08;一&#xff09;&#xff1a;RTSys总线配置与使用。 今天正运动给大家分享一下EIO24088G-V2及EIO16084G如何用T…

DNS安全概述

一、DNS的解析过程 1.递归解析 递归解析是一种由DNS客户端&#xff08;通常是用户的应用程序&#xff0c;如一个浏览器&#xff09;向本地DNS解析器发出解析请求&#xff0c;然后本地DNS解析器负责查询最终结果并将结果返回给客户端&#xff0c;而中间的所有查询请求都由本地D…

Unity之如何在物体空间中制作马赛克

文章目录 前言屏幕空间马赛克着色器对象空间中的马赛克着色器最后前言 GrabPass 允许您创建应用马赛克叠加的着色器。如果你想在屏幕空间中应用马赛克,你可以通过使用片段着色器对其进行离散化来实现,但我在尝试将其应用到对象空间时遇到了问题,所以这是一个记录。 ▼ 原图…

proteus中没有STM32F103C8(已解决)

想在proteus找一个和开发板相同的芯片型号STM32F103C8T6&#xff0c;亲测proteus的7.8、8.6、8.9版本都没有STM32F103C8&#xff0c;然后在proteus8.15中找到了&#xff0c;M4内核的芯片也有。 M3内核&#xff1a; M4内核&#xff1a;