点云数据可视化:foxglove及其他开发笔记

2,941 阅读7分钟

最近在需要做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下的所有代码复制到我们的扩展目录。

遇到的问题:

  1. 在运行corepack enable后使用yarn命令一直报错,原因是自装了yarn软件而不是使用的corepack自带的yarn,因此要先卸载掉yarn后再运行corepack enable以保障使用的是corepack的。

    在win上,使用scoop安装的yarn需要通过scoop命令来删除;

    npm安装的可以直接npm uninstall yarn -g。

  2. Foxglove的package.json中存在如下依赖:

    1.    {
          "devDependencies":{
              ...,
              "three": "patch:three@0.149.0#../../patches/three.patch",
              ...
           }
         }
      

patch表示针对此依赖添加的补丁包,在复制代码编译的同时,也要把相应的补丁包添加进相关目录中。

  1. Foxglove的package.json中存在如下依赖:
{
 "devDependencies":{
     ...,
     "@foxglove/typescript-transformers": "workspace:*",
  }
}

此依赖写法属于Yarn v2以后的新功能,使用这种解析协议时,Yarn 将拒绝解析本地工作区以外的任何其他内容,详情可见yarnpkg.com/features/wo…

解决思路是升级yarn到相应版本,或者将workspace:写法改为file:本地路径的写法,但是使用file:写法时,发现foxglove这些依赖中的package.json省略了很多字段,可能会报错或警告。

  1. Foxglove代码中大量使用ReactNull类型错误,这是Foxglove自定义的类型,即null,可以在d.ts文件中添加相关的全局声明,如果使用了显式的webpack配置文档,也可以直接添加webpack配置。

  2. typescript 报错:

    1. "TS1005: '?' expected";
    2. Unable to resolve signature of method decorator when called as an expression;
    3. 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前端项目来集成。

  1. foxglove根文件夹下的package.json依赖项也要加入到项目中。

foxglove项目中包含大量的依赖项,不仅存在于studio-base目录下,有的依赖也直接放入了根目录,因此foxglove根目录下的package.json也要多留意。

  1. 类型检查eslint文件也要转移。

foxglove项目自定义了大量eslint,而且层层依赖,因此eslint文件也要转移,不然会出现大量eslint类型报错。

  1. “xxxx”不能用作 JSX 组件:

解决React中遇到的 “xxxx”不能用作 JSX 组件 问题 - 掘金

  1. " *** 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的大量结构化数据类型都存放于

github.com/foxglove/st…

文件中。

关于点云数据解析的其他探索

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。它目前支持两种主要格式:

  1. 批处理 3D 模型 (b3dm) - 基于 glTF(可以用于解析我们的小车模型,如果有)。
  2. 点云。

它使用的是 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 点云数据投影到以视点为中心的不同视角对应的平面上。然后使用三维激光点云的特性对图像进行染色。该算法总共给出了六种染色方法,读者可以根据需要选择其中一种或多种。