从零实现无序抓取(四)如何获取点云位姿估计真值

本文介绍了如何利用Bullet物理引擎进行3D仿真模拟,包括安装pybullet、导入模型、设置重力等步骤,并展示了如何通过仿真获取RGB图像和深度图像。同时,讲解了如何将深度图像转换为点云数据,为无序抓取等应用场景提供数据支持。

前言

这是一章预告。
上一章在进行点云位姿估计时,我们利用PCL库对原始点云进行了一个人工设定的变换矩阵,这个变换矩阵可以作为位姿估计的真值。但是这种方式只能很简单地将某个原始点云整体进行变换。也就是只能生成下图这种变化。
在这里插入图片描述
但是我们的实际的位姿估计算法不可能只在这种简单的场景下应用。一般来说,具体的抓取场景应该是有多个目标相互堆叠存在于场景中的。类似这样。
在这里插入图片描述
现实中我们可以利用点云相机采集这个场景的点云,然后估计目标的位姿,但是我们难以通过测量的方式得到每个零件的6DoF位姿,也就是说我们很难得到场景中目标的真值,没有真值我们就无法评价算法的误差,不能定量的评价误差,我们就没办法评估不同算法之间的精度,也就没办法发论文。那么,如何获得目标真值位姿呢?可以采用仿真的方式生成与实际场景类似的仿真数据。
我在这里采用了bullet物理仿真引擎进行了自由落体的仿真模拟。
先放上一段效果:

bullet自由落体仿真

然后是点云模拟的结果:
在这里插入图片描述

一、利用bullet进行自由落体仿真

Bullet是一个开源的物理模拟计算引擎,世界三大物理模拟引擎之一(另外两种是Havok和PhysX)。广泛应用于游戏开发和电影制作中。Bullet也是AMD开放物理计划成员之一。持Windows、Linux、MAC、Playstation3、XBOX360、Nintendo Wii。Bullet也整合到了Maya和Blender 3D中。

1.1 安装pybullet

现在Bullet已经可以很好的支持python了
安装方式简单粗暴直接
pip install pybullet
验证一下是否成功,打开命令行,输入

python -m pybullet_envs.examples.enjoy_TF_AntBulletEnv_v0_2017may

可以看见一只正在走路的四脚蜘蛛
在这里插入图片描述

1.2 导入模型

先看一个例子,如何初始化引擎并导入已有的模型。

import pybullet as p
import pybullet_data

# 连接引擎
_ = p.connect(p.GUI)
# 不展示GUI的套件
p.configureDebugVisualizer(p.COV_ENABLE_GUI, 0)

# 添加资源路径
p.setAdditionalSearchPath(pybullet_data.getDataPath())

planeUid = p.loadURDF("plane.urdf", useMaximalCoordinates=True)  # 加载一个地面
trayUid = p.loadURDF("tray/traybox.urdf", basePosition=[0, 0, 0])  # 加载一个箱子,设置初始位置为(0,0,0)

# 开始渲染
while True:
    p.stepSimulation()

代码结果
在这里插入图片描述
代码不详细解释了,有兴趣可以去看看pybullet官网,或者建议看官方的教程进行入门学习。
链接:https://pan.baidu.com/s/1mwyAjckm1IR6F-I-trU_og
提取码:w2uh

1.3 urdf文件

代码中比较重要的部分,这两句是用来导入urdf文件的,这个文件需要自己编写,里面包含了你要加载的模型的视觉属性和碰撞属性。

planeUid = p.loadURDF("plane.urdf", useMaximalCoordinates=True)  # 加载一个地面
trayUid = p.loadURDF("tray/traybox.urdf", basePosition=[0, 0, 0])  # 加载一个箱子,设置初始

我们进入到bullet的资源文件路径里面找一下这两个文件,看看是什么样子

print(pybullet_data.getDataPath())

‘D:\Anaconda\Anaconda\envs\bullet\lib\site-packages\pybullet_data’
找到traybox.urdf文件,打开后可以看到

<robot name="tabletop">
  <link name="base_link">
    <inertial>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <mass value="0"/>
      <inertia ixx="0" ixy="0" ixz="0" iyy="0" iyz="0" izz="0"/>
    </inertial>
    <visual>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <geometry>
        <mesh filename="tray_textured.obj" scale="5 5 5"/>
      </geometry>
      <material name="tray_material">
        <color rgba="1 1 1 1"/>
      </material>
    </visual>
     <collision>
      <origin rpy="0 0 0" xyz="0 0 0.005"/>
      <geometry>
	 			<box size=".6 .6 .02"/>
      </geometry>
    </collision>
     <collision>
      <origin rpy="0 0.575469961 0" xyz="0.25 0 0.059"/>
      <geometry>
	 			<box size=".02 .6 .15"/>
      </geometry>
    </collision>
		<collision>
      <origin rpy="0 -0.575469961 0" xyz="-0.25 0 0.059"/>
      <geometry>
	 			<box size=".02 .6 .15"/>
      </geometry>
    </collision>
    <collision>
      <origin rpy="0.575469961 0 0" xyz="0 -0.25 0.059"/>
      <geometry>
	 			<box size=".6 .02 .15"/>
      </geometry>
    </collision>
    <collision>
      <origin rpy="-0.575469961 0 0" xyz="0 0.25 0.059"/>
      <geometry>
	 			<box size=".6 .02 .15"/>
      </geometry>
    </collision>

  </link>
</robot>

因为我们要导入的是一个单独的零件,所以不存在有多个link joint(不理解请返回阅读官方的教程),重点看

    <visual>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <geometry>
        <mesh filename="tray_textured.obj" scale="5 5 5"/>
      </geometry>
      <material name="tray_material">
        <color rgba="1 1 1 1"/>
      </material>
    </visual>

这部分描述了物体的视觉属性,mesh filename就是我们自己的零件对应的.obj文件,如何画自己的.obj文件,可以参照我的另一篇从零实现无序抓取(二)制作属于自己的点云数据,此外可以这个里面还可以定义他的rgba颜色属性,还有material材料属性(非必须,这个文件就没有定义)。

然后另一个部分

    <collision>
      <origin rpy="-0.575469961 0 0" xyz="0 0.25 0.059"/>
      <geometry>
	 			<box size=".6 .02 .15"/>
      </geometry>
    </collision>

描述了模型的碰撞属性,官方这个文件是自己设置了一个box类型的碰撞属性。我们也可以设置成自己的obj,只需要把这一部分的内容和视觉属性部分保持一致就可以了。

1.4 制作自己的urdf文件

这里放上我自己写的一个urdf文件,part1.urdf

<robot name="blob000">
  <link name="random_obj_000">
    <contact>
      <lateral_friction value="1.0"/>
      <rolling_friction value="0.0"/>
      <inertia_scaling value="3.0"/>
      <contact_cfm value="0.0"/>
      <contact_erp value="1.0"/>
    </contact>
    <inertial>
      <origin rpy="0 0 0" xyz="0.6 0.05 0.4"/>
      <mass value="1"/>
      <inertia ixx="1" ixy="1" ixz="1" iyy="1" iyz="1" izz="1"/>
    </inertial>
    <visual>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <geometry>
        <mesh filename="part1.obj" scale="0.01 0.01 0.01"/>
      </geometry>
      <material name="blockmat">
        <color rgba="0.5 0.5 0.5 1"/>
      </material>
    </visual>
    <collision>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <geometry>
        <mesh filename="part1.obj" scale="0.01 0.01 0.01"/>
      </geometry>
    </collision>
  </link>
</robot>

这个文件就是将part1这个模型根据part1.obj进行了视觉属性和碰撞属性的定义,把这个模型加载后可以得到。
在这里插入图片描述

1.5 添加重力

现在只是把模型加载好,但是还没有设置重力,很简单

p.setGravity(0,0,-10)

把这句话加进去就行了。

最终的仿真效果,这里我同时加载了好几个模型,并让他们从不同的初始位置开始下落。
在这里插入图片描述

二、在bullet中采集RGB图和深度图

现在我们有了场景模型,那如何获得点云呢?bullet好像不能直接生成点云,他只能得到场景的点云图和深度图,后期的点云图需要用深度图合成出来。

width, height, rgbImg, depthImg, segImg 
= p.getCameraImage(width=1080, height=720,projectionMatrix=projectionMatrix)

获取深度图的方式也很简单,在渲染中加入这句话就行了。
返回的depthImg为numpy格式,自己把它保存下来就行了。

三、将深度图转换为点云图

有了深度图,我们还需要将深度图转换为点云数据,这里需要先获取相机的参数。

projectionMatrix = p.computeProjectionMatrixFOV(
    fov=50.0,               # 摄像头的视线夹角
    aspect=1.0,
    nearVal=0.001,            # 摄像头焦距下限
    farVal=20,               # 摄像头能看上限
)
print(projectionMatrix)

输出结果:
(2.1445069313049316, 0.0, 0.0, 0.0,
0.0, 2.1445069313049316, 0.0, 0.0,
0.0, 0.0, -1.0000998973846436, -1.0,
0.0, 0.0, -0.00200009997934103, 0.0)
这个矩阵就是openGL里面的投影矩阵了,不做详细解释了,可以自行去看看。
然后用深度图在这个矩阵上做变换,就可以得到点云数据了,这里我用matlab做了结果的可视化。
在这里插入图片描述

<think> 我们正在讨论如何利用姿文件实现点云拼接。根据引用[1]和[2]的内容,我们知道点云拼接需要以下步骤: 1. 读取姿文件(通常是一个包含多个姿的文件,每个姿对应一帧点云) 2. 遍历每一帧点云,同时读取对应的姿姿通常表示为3x4的变换矩阵,或者由旋转矩阵R和平移向量T组成) 3. 将姿矩阵应用于当前帧点云,将其从局部坐标系(如雷达坐标系)变换到全局坐标系(世界坐标系) 4. 将变换后的点云合并到一个总的点云中 5. 保存合并后的点云并可视化 另外,引用[2]提到了KITTI数据集中利用姿真值拼接点云,并提到了投影到相机图像上所需的参数,但用户当前的问题主要关注拼接。 因此,我们回答的重点是:如何利用姿文件实现点云拼接(即点云地图构建)。 步骤: 1. 准备数据:点云序列(每一帧一个文件)和对应的姿文件(每一行代表一个姿,通常是时间戳和变换矩阵或姿向量) 2. 读取姿文件,解析姿数据(通常为3x4矩阵或4x4齐次变换矩阵) 3. 遍历每一帧点云,对每个点云应用对应的姿变换 4. 合并所有变换后的点云 5. 后处理(可选):滤波去除离群点,降采样等 6. 保存和可视化 注意:姿变换通常使用齐次坐标变换。假设一帧点云中的点为$p_{local}$,那么在世界坐标系中的点为: $$ p_{world} = T \cdot p_{local} $$ 其中$T$是一个4x4的变换矩阵(包含旋转和平移)。 如果姿文件给出的不是4x4矩阵,而是旋转矩阵R(3x3)和平移向量t(3x1),则可以构建变换矩阵: $$ T = \begin{bmatrix} R & t \\ 0 & 1 \end{bmatrix} $$ 代码示例(使用Open3D): </think> ### 利用姿文件实现点云拼接的方法详解 利用姿文件实现点云拼接的核心思想是通过坐标系转换将多帧局部点云对齐到全局坐标系中,构建完整的场景地图。以下是详细步骤和技术要点: ```mermaid graph LR A[姿文件] --> B[坐标系转换] C[点云帧] --> B B --> D[全局坐标系点云] D --> E[点云合并] E --> F[滤波优化] ``` #### 1. **数据准备** - **姿文件**:包含每帧点云姿信息,通常是 $4 \times 4$ 齐次变换矩阵或 $[x, y, z, roll, pitch, yaw]$ 格式 - **点云序列**:按时间顺序采集的多帧点云(如 `.pcd` 或 `.bin` 格式) - **坐标系定义**: - **局部坐标系**:单帧点云的原始坐标系 - **全局坐标系**:拼接后的统一坐标系 #### 2. **核心算法:坐标变换 点云拼接的数学基础是坐标系变换,公式为: $$ p_{\text{global}} = T \cdot p_{\text{local}} $$ 其中: - $p_{\text{local}} = [x, y, z, 1]^T$ 是局部坐标系的齐次坐标 - $T$ 是从局部到全局的 $4 \times 4$ 变换矩阵: $$ T = \begin{bmatrix} R & t \\ 0 & 1 \end{bmatrix} $$ $R$ 是 $3 \times 3$ 旋转矩阵,$t$ 是 $3 \times 1$ 平移向量 #### 3. **完整实现步骤(基于Open3D)** ```python import open3d as o3d import numpy as np # 步骤1:读取姿文件 poses = [] # 存储变换矩阵 with open("poses.txt") as f: for line in f: # 将文本行转换为4x4矩阵 matrix = np.array([float(x) for x in line.split()]).reshape(4, 4) poses.append(matrix) # 步骤2:初始化全局点云 merged_cloud = o3d.geometry.PointCloud() # 步骤3:遍历处理每帧点云 for i, pose in enumerate(poses): # 读取点云帧 cloud = o3d.io.read_point_cloud(f"frame_{i}.pcd") # 应用姿变换 cloud.transform(pose) # T · p_local # 合并到全局点云 merged_cloud += cloud # 步骤4:后处理 # 降采样滤波 voxel_cloud = merged_cloud.voxel_down_sample(voxel_size=0.05) # 离群点去除 cln_cloud, _ = voxel_cloud.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0) # 保存与可视化 o3d.io.write_point_cloud("merged.pcd", cln_cloud) o3d.visualization.draw_geometries([cln_cloud]) ``` #### 4. **关键技术细节** 1. **姿文件解析**: - KITTI格式:时间戳 + $[x, y, z, roll, pitch, yaw]$[^2] - TUM格式:时间戳 + $[tx, ty, tz, qx, qy, qz, qw]$(元数) ```python # 元数转旋转矩阵 R = o3d.geometry.get_rotation_matrix_from_quaternion([qw, qx, qy, qz]) ``` 2. **优化技巧**: - **增量式拼接**:避免内存溢出(处理大规模点云) - **回环检测**:使用ICP精调姿,消除累积误差 - **多传感器融合**:联合相机姿提升精度[^2] 3. **典型应用场景**: - 自动驾驶建图(KITTI数据集)[^2] - SLAM实时场景重建 - 工业部件三维扫描 #### 5. **常见问题解决** - **问题:拼接后出现重影** - **原因**:姿误差累积 - **方案**:添加ICP精配准 ```python # 在transform后添加ICP优化 icp_result = o3d.pipelines.registration.registration_icp( cloud, merged_cloud, max_distance=0.1) cloud.transform(icp_result.transformation) ``` - **问题:大规模点云内存不足** - **方案**:使用八叉树分块存储 ```python octree = o3d.geometry.Octree(max_depth=10) octree.convert_from_point_cloud(merged_cloud) ``` ---
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值