最近在需要做DDS的点云数据可视化,发现了一款功能很强大的ROS数据可视化工具foxglove,尝试将其集成进自己的theia扩展工程中,中途遇到很多问题,因此将过程中的学习心得记录如下。
foxglove项目地址:foxglove/studio: Robotics visualization and debugging (github.com)
其根目录中包含CONTRIBUTING.md文件,可作为开发贡献指南;
尝试直接使用Foxglove 3D窗口
代码路径:Foxglove 3D窗口的相关代码位置主要在packages/studio-base/src/panels/ThreeDeeRender路径下。
尝试1:Theia中集成Foxglove 3D代码
将Foxglove 3D可视化代码直接复制到我们的Theia扩展目录中,进行编译,即ThreeDeeRender下的所有代码复制到我们的扩展目录。
遇到的问题:
-
在运行corepack enable后使用yarn命令一直报错,原因是自装了yarn软件而不是使用的corepack自带的yarn,因此要先卸载掉yarn后再运行corepack enable以保障使用的是corepack的。
在win上,使用scoop安装的yarn需要通过scoop命令来删除;
npm安装的可以直接npm uninstall yarn -g。
-
Foxglove的package.json中存在如下依赖:
-
{ "devDependencies":{ ..., "three": "patch:three@0.149.0#../../patches/three.patch", ... } }
-
patch表示针对此依赖添加的补丁包,在复制代码编译的同时,也要把相应的补丁包添加进相关目录中。
- Foxglove的package.json中存在如下依赖:
{
"devDependencies":{
...,
"@foxglove/typescript-transformers": "workspace:*",
}
}
此依赖写法属于Yarn v2以后的新功能,使用这种解析协议时,Yarn 将拒绝解析本地工作区以外的任何其他内容,详情可见yarnpkg.com/features/wo…
解决思路是升级yarn到相应版本,或者将workspace:写法改为file:本地路径的写法,但是使用file:写法时,发现foxglove这些依赖中的package.json省略了很多字段,可能会报错或警告。
-
Foxglove代码中大量使用ReactNull类型错误,这是Foxglove自定义的类型,即null,可以在d.ts文件中添加相关的全局声明,如果使用了显式的webpack配置文档,也可以直接添加webpack配置。
-
typescript 报错:
- "TS1005: '?' expected";
- Unable to resolve signature of method decorator when called as an expression;
- Argument of type string | undefined is not assignable to parameter of type string
这些都是typescript版本问题,当前我们theia扩展的ts版本和foxglove存在冲突,很多报错,无法编译。
尝试解决思路是新建一个空扩展来集成,先规避掉这个问题。
尝试2:Theia空扩展集成foxglove 3D代码
新建了一个空theia扩展,来添加foxglove相关代码,依赖冲突依然很多,编译难以进行。
尝试3:普通的react项目集成
直接创建一个普通的react前端项目来集成。
- foxglove根文件夹下的package.json依赖项也要加入到项目中。
foxglove项目中包含大量的依赖项,不仅存在于studio-base目录下,有的依赖也直接放入了根目录,因此foxglove根目录下的package.json也要多留意。
- 类型检查eslint文件也要转移。
foxglove项目自定义了大量eslint,而且层层依赖,因此eslint文件也要转移,不然会出现大量eslint类型报错。
- “xxxx”不能用作 JSX 组件:
解决React中遇到的 “xxxx”不能用作 JSX 组件 问题 - 掘金
- " *** are only available when targeting ECMAScript 2015 and higher+":
tsconfig.json配置
"compilerOptions": {
...
"target": "es2022",
}
4. 依赖别名设置
因为复制的代码,依赖路径发生变化,通过配置依赖别名可以解决,方法是增加webpack配置项:
const path = require('path'); //引入path 要用到他的resolve 方法
const resolve = dir => path.resolve(__dirname, dir);
//直接引入const {resolve } = require('path') 也可
module.exports = {
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: resolve('dist')
},
resolve: {
// 设置别名
alias: {
// __dirname 可以获取被执行 js 文件的绝对路径
// 这样配置后 @ 可以指向 src 目录
'@': resolve('src')
//别名写更深层也可以
'api': resolve('src/api')// 这样配置后 api 可以指向 src 目录下的api目录
}
}
};
但foxglove的代码耦合性极强,文件的依赖层层递进,最终引入的文件越来越多,且看不到结束的迹象,直接使用foxglove代码的方案恐怕行不通。
foxglove的数据结构
Initalization
foxglove导入的文件(包括.db3,.bag,.ulog等),最终被其格式化为Initalization(文件目录存在于packages\studio-base\src\players\IterablePlayer\IIterableSource.ts):
type Initalization = {
start: Time;
end: Time;
topics: Topic[];
topicStats: Map<string, TopicStats>;
datatypes: RosDatatypes;
profile: string | undefined;
name?: string;
/** Publisher names by topic **/
publishersByTopic: Map<string, Set<string>>;
problems: PlayerProblem[];
};
WebAssembly
转换过程使用了WebAssembly
developer.mozilla.org/zh-CN/docs/…
即在web浏览器中调用其他语言编写的程序,以使得以各种语言编写的代码都可以以接近原生的速度在 Web 中运行。在这种情况下,以前无法以此方式运行的客户端软件都将可以运行在 Web 中。
PointCloud2
格式化后的点云数据为PointCloud2
export type PointCloud2 = {
header: Header;
height: number;
width: number;
fields: PointField[];
is_bigendian: boolean;
point_step: number;
row_step: number;
data: Uint8Array;
is_dense: boolean;
};
export type PointField = {
name: string;
offset: number;
datatype: number;
count: number;
};
export type Header = {
frame_id: string;
stamp: RosTime;
seq?: number;
};
Image
export type Image = {
header: Header;
height: number;
width: number;
encoding: string;
is_bigendian: boolean;
step: number;
data: Int8Array | Uint8Array;
};
Marker
export type Marker = {
header: Header;
ns: string;
id: number;
type: number;
action: number;
pose: Pose;
scale: Vector3;
color: ColorRGBA;
lifetime: RosDuration;
frame_locked: boolean;
points: Vector3[];
colors: ColorRGBA[];
text: string;
mesh_resource: string;
mesh_use_embedded_materials: boolean;
};
Pose
export type Point = {
x: number;
y: number;
z: number;
};
export type Orientation = {
x: number;
y: number;
z: number;
w: number;
};
export type Pose = {
position: Point;
orientation: Orientation;
};
CameraInfo
export type CameraInfo = {
header: Header;
height: number;
width: number;
distortion_model: string;
D: number[];
K: Matrix3 | [];
R: Matrix3 | [];
P: Matrix3x4 | [];
binning_x: number;
binning_y: number;
roi: RegionOfInterest;
};
LaserScan
export type LaserScan = {
header: Header;
angle_min: number;
angle_max: number;
angle_increment: number;
time_increment: number;
scan_time: number;
range_min: number;
range_max: number;
ranges: Float32Array;
intensities: Float32Array;
};
更多
有关于ros的大量结构化数据类型都存放于
文件中。
关于点云数据解析的其他探索
pnext/three-loader
GitHub - pnext/three-loader: Point cloud loader for ThreeJS, based on the core parts of Potree
此项目基于Potree的core/loading部分,转换为Typescript,可直接在基于ThreeJS的第三方应用程序中使用。
项目只专注于将点云加载到 ThreeJS 应用程序中,并不尝试提供 Potree 中可用的其他功能。
下面是一个使用示例:
import { Scene } from 'three';
import { PointCloudOctree, Potree } from '@pnext/three-loader';
const scene = new Scene();
// Manages the necessary state for loading/updating one or more point clouds.
const potree = new Potree();
// Show at most 2 million
points.potree.pointBudget = 2_000_000;
// List of point clouds which we loaded and need to update.
const pointClouds: PointCloudOctree[] = [];
potree.loadPointCloud(
// The name of the point cloud which is to be loaded.
'cloud.js',
// Given the relative URL of a file, should return a full URL (e.g. signed).
relativeUrl => `${baseUrl}${relativeUrl}`,).then(pco => {
pointClouds.push(pco);
scene.add(pco); // Add the loaded point cloud to your ThreeJS scene.
// The point cloud comes with a material which can be customized directly.
// Here we just set the size of the
points.pco.material.size = 1.0;
});
function update() {
// This is where most of the potree magic happens. It updates the visiblily of the octree nodes
// based on the camera frustum and it triggers any loads/unloads which are necessary to keep the
// number of visible points in check.potree.update
PointClouds(pointClouds, camera, renderer);
// Render your scene as normal
renderer.clear();
renderer.render(scene, camera);
}
其支持的数据类型如下:
export interface IPointCloudTreeNode {
id: number;
name: string;
level: number;
index: number;
spacing: number;
boundingBox: Box3;
boundingSphere: Sphere;
loaded: boolean;
numPoints: number;
readonly children: ReadonlyArray<IPointCloudTreeNode | null>;
readonly isLeafNode: boolean;
dispose(): void;
traverse(cb: (node: IPointCloudTreeNode) => void, includeSelf?: boolean): void;
}
nytimes/three-loader-3dtiles
GitHub - nytimes/three-loader-3dtiles: This is a Three.js loader module for handling OGC 3D Tiles, c
这是一个three.js加载器模块,用于处理由Cesium创建的OGC 3D Tiles。它目前支持两种主要格式:
- 批处理 3D 模型 (b3dm) - 基于 glTF(可以用于解析我们的小车模型,如果有)。
- 点云。
它使用的是 loaders.gl 库。
下面是一个基础使用示例:
import { Scene } from 'three';
import { PointCloudOctree, Potree } from '@pnext/three-loader';
const scene = new Scene();
// Manages the necessary state for loading/updating one or more point clouds.
const potree = new Potree();
// Show at most 2 million points.
potree.pointBudget = 2_000_000;
// List of point clouds which we loaded and need to update.
const pointClouds: PointCloudOctree[] = [];
potree
.loadPointCloud(
// The name of the point cloud which is to be loaded.
'cloud.js',
// Given the relative URL of a file, should return a full URL (e.g. signed).
relativeUrl => `${baseUrl}${relativeUrl}`,
)
.then(pco => {
pointClouds.push(pco);
scene.add(pco); // Add the loaded point cloud to your ThreeJS scene.
// The point cloud comes with a material which can be customized directly.
// Here we just set the size of the points.
pco.material.size = 1.0;
});
function update() {
// This is where most of the potree magic happens. It updates the visiblily of the octree nodes
// based on the camera frustum and it triggers any loads/unloads which are necessary to keep the
// number of visible points in check.
potree.updatePointClouds(pointClouds, camera, renderer);
// Render your scene as normal
renderer.clear();
renderer.render(scene, camera);
}
GuYufeng93/Pointcloud-to-Images
GitHub - GuYufeng93/Pointcloud-to-Images: An algorithm for projecting three-dimensional laser point
一种将三维激光点云数据投影到序列化二维图像中的算法。在点云数据的中心或数据的采集轨迹上选择一个视点,然后,将 3D 点云数据投影到以视点为中心的不同视角对应的平面上。然后使用三维激光点云的特性对图像进行染色。该算法总共给出了六种染色方法,读者可以根据需要选择其中一种或多种。