Udacity机器人软件工程师课程笔记(二十) - 感知 - 校准,过滤, 分段, RANSAC

本文深入探讨了点云数据的校准、过滤与细分技术,重点讲解了RANSAC算法在平面拟合与异常值去除中的应用。通过实践演示,读者将学会使用OpenCV和PCL库进行点云预处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

校准,过滤, 分段, RANSAC

首先,我们将讨论传感器校准,也就是说,从几何形状,失真和噪声方面校准相机如何看待周围的世界。了解相机的校准对于了解测量数据如何代表实际物理环境至关重要。

之后,我们将讨论过滤数据。

对于3D点云,大多数数据实际上对应于不需要的背景对象或其他必须过滤掉的对象,然后才能执行对象检测或识别。

最后,我们将介绍分段,这是将经过过滤的点云数据分解为有意义的片段的过程,每个片段都对应于数据中的一个对象或一组对象。

我们将使用“随机样本共识”或RANSAC算法,这是在存在噪声或异常高级数据的情况下执行细分的强大方法。

1.校准

要校准相机,我们需要测量世界上的3D点如何向下映射到相机的2D图像平面上。实际上,这是一个相当复杂的过程,但是在接下来的这些练习中,我们将使用一些的OpenCV工具使其变得简单。

(1) 校准图案

第一步是选择校准图案。我们使用以下棋盘样式:
在这里插入图片描述
棋盘并不是测试图案的唯一选择,它可以是由其他一些简单形状(例如圆形)组成的图案。无论使用哪种形状,测试图案的目的都是提供一种已知的几何形状,您可以使用相机拍摄该图像,并在将3D世界点映射到2D图像点时用作参考。

(2) 拍摄测试图案

下一步是打印出测试图案并为其拍照!您可以固定照相机的位置并在场景中四处移动测试图案,也可以固定照相机的位置并在场景中四处移动相机以从不同角度和距离拍摄照片。

在这里,测试图案每个正方形为10cm x 10cm,并将其粘贴到墙上,从各个方面拍摄了如下照片:
在这里插入图片描述

(3) 寻找内角

一旦获得测试图案的图像,首先需要在每个图像中找到测试图案。

使用OpenCV功能findChessboardCorners()drawChessboardCorners()在棋盘图案的图像上自动找到并绘制“内角”。

要了解有关这两个功能的更多信息,可以在此处查看OpenCV文档:cv2.findChessboardCorners()和cv2.drawChessboardCorners()

将这两个功能应用于样本图像,将获得如下结果:

在这里插入图片描述计算任何给定行中的内角数,然后在变量中输入该值nx。同样,计算给定列中的角数并将其存储在中ny。请记住,内角仅是两个黑色和两个白色正方形相交的点,换句话说,仅计算内角,而不计算外角。

(4) 程序示例

import numpy as np
import cv2
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import glob

# #准备对象点
nx = 8  # TODO: 在x中输入内边角的数量
ny = 6  # TODO: 在y中输入内边角的数量

img = cv2.imread('chessboardpattern.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 转换为灰度
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
cv2.imshow('iamge', img)
# 找到棋盘的角
ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)

# 如果找到,画角
if ret == True:
    # 绘制和显示角
    cv2.drawChessboardCorners(img, (nx, ny), corners, ret)
    # 使用cv2进行输出
    cv2.imshow('coners', img)
    
    # 使用plt进行输出
    plt.imshow(img)
    plt.show()

cv2.waitKey()

输出如下:
在这里插入图片描述
在示例程序中,我们使用了Opencv进行校准,所用到的函数为:cv2.findChessboardCorners,这个函数可以用来寻找棋盘图的内角点位置。通常作为我们使用Opencv校准的第一步。

然后我们可以使用cv2.calibrateCamera()计算校准矩阵和失真系数。然后使用cv2.undistort()函数使测试图像不失真。

关于cv2.calibrateCamera()的使用方法:

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, 
                                                  (img.shape[1], img.shape[0]), 
                                                   None, None)

我们需要关注的输出为mtxdist,分别包含摄像机本征矩阵和失真系数。

固有摄像机矩阵的形式为:

c a m a e r a   m a t r i x = [ f x 0 c x 0 f y c y 0 0 1 ] camaera \ matrix = \begin{bmatrix}f_x &0& c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1\end{bmatrix} camaera matrix=fx000fy0cxcy1

上面矩阵中 f x f_x fx f y f_y fy焦距(当像素为正方形时它们相等),而 c x c_x cx c y c_y cy
对应于相机中的(光学)中心 ( x , y ) (x ,y ) xy 平面。

共有五个失真系数,dist中按以下顺序给出:

D i s t o r t i o n   C o e f f i c i e n t s = ( k 1 , k 2 , p 1 , p 2 , k 3 ) Distortion \ Coefficients = (k_1, k_2, p_1, p_2, k_3) Distortion Coefficients=(k1,k2,p1,p2,k3)

其中,k是径向失真系数,p是切向失真系数。要校正图像的径向和切向畸变,将使用cv2.undistort()函数应用以下方程式:

在这里插入图片描述
使用cv2.undistort()函数来使图像不失真:

undist = cv2.undistort(img, mtx, dist, None, mtx)

import numpy as np
import cv2
import glob
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

# 规定内角数量
nx = 8
ny = 6
# 准备目标点, 如 (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((ny * nx, 3), np.float32)
# 关于np.mgrid的用法:https://www.cnblogs.com/wanghui-garcia/p/10763103.html
# .T是矩阵的转置
# reshape是转换成两列
objp[:, :2] = np.mgrid[0:nx, 0:ny].T.reshape(-1, 2)


# 用于存储来自所有图像的对象点和图像点的数组。
objpoints = []  # 在真实空间中的3D点
imgpoints = []  # 在图像平面的2D点

'''
# 制作校准图像列表
images = glob.glob('./ Cal * .jpg')
# 单步通过名单和搜索棋盘内角
for idx, fname in enumerate(images):
    img = mpimg.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

    # 找到棋盘的内角
    ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)

    # 如果找到,添加目标点,图像点
    if ret == True:
        objpoints.append(objp)
        imgpoints.append(corners)
'''
# 代替上面的校准图像列表
img = cv2.imread('test_image.png')
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

# 找到棋盘的内角
ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)

# 如果找到,添加目标点,图像点
if ret == True:
    objpoints.append(objp)
    imgpoints.append(corners)

img = cv2.imread('test_image.png')
img_size = (img.shape[1], img.shape[0])

# 摄像机标定给定的目标点和图像点
# calivrateCamera函数参考博客:https://blog.youkuaiyun.com/u011574296/article/details/73823569
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img_size, None, None)

# 执行undistortion函数使测试图像不失真
undist = cv2.undistort(img, mtx, dist, None, mtx)

# 展示 undistortion
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))
f.tight_layout()
ax1.imshow(img)
ax1.set_title('Original Image', fontsize=50)
ax2.imshow(undist)
ax2.set_title('Undistorted Image', fontsize=50)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
plt.show()

输出结果如下:
在这里插入图片描述

部分程序注释
images = glob.glob('./ Cal * .jpg')

glob.glob : 返回所有匹配的文件路径列表。它只有一个参数pathname,定义了文件路径匹配规则,这里可以是绝对路径,也可以是相对路径。注释掉的部分是因为我只有一张图片,所以我选择了按照上一个程序的的读入方法。

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img_size, None, None)

mtx :内参数矩阵。

dist :畸变矩阵。

rvecs :旋转向量。

tvecs :位移向量。

objectPoints :世界坐标系中的点。

imagePoints :其对应的图像点。

imageSize :图像的大小,在计算相机的内参数和畸变矩阵需要用到;

(5) 外在校准

在这里插入图片描述
2D像素坐标中的点的位置与3D世界坐标中的对应点的位置之间的关系由针孔相机模型定义:

z c [ u v 1 ] = K [ R T ] [ x w y w z w 1 ] z_c\begin{bmatrix}u\\v\\1\end{bmatrix} = K\begin{bmatrix}R& T\end{bmatrix}\begin{bmatrix}x_w\\y_w\\z_w\\1\end{bmatrix} zcuv1=K[RT]xwywzw1

其中 K K K代表在上一节中得出的本征矩阵。

R R R T T T是外部参数,表示从3D世界坐标到3D相机坐标的坐标系转换。等效地,外部参数定义摄像机中心的位置和摄像机在世界坐标中的航向。T是以相机为中心的坐标系的坐标表示的世界坐标系原点的位置。而摄像机的位置 C = − R ′ T C = -R'T C=RT

由于外部参数确定世界坐标中各个摄像机的3D姿态,因此它们也可以用于图像配准过程。

图像配准是指将一个摄像机的数据帧转换为另一个像素的数据帧匹配。

这对于创建精确的点云绝对必不可少。

(6) ROS中的RGBD校准

我们将使用ROS的image_pipeline来校准RGB-D相机,该相机包含一套套件,可提供多种图像处理工具。这些工具之一是camera_calibration的软件包,其可轻松校准单眼,立体相机和RGB-D相机。

camera_calibration软件包使用棋盘格作为校准的目标对象,其工作原理与之前所讲过的相同。

了解有关ROS提供的用于校准的工具的更多信息

校准过程将可以将RGB相机数据与随附的深度相机数据结合起来以生成3D点云。目前,我们将继续直接处理点云数据,但是在项目中,我们从原始RGB-D数据开始,并通过相机校准过程生成自己的点云。

2.过滤和细分

(1)点云过滤

以下是点云库中一些最常用的过滤器。

  • VoxelGrid下采样滤波器
  • ExtractIndices筛选器
  • 直通过滤器
  • RANSAC Plane Fitting
  • 离群值去除过滤器

尽管其中一些是使用底层算法来提高点云数据质量的实际过滤器,但有些是在感知管道中某些阶段所需的简单工具,以帮助提取输入点云的子集。

(2)桌面细分练习

我们将使用点云库来进行这个练习。

  • Step-1. 在提供的课程虚拟机中下载或复制此存储库,并遵循安装说明。
  • Step-2. 在接下来的部分中,尝试使用各种过滤器。
  • Step-3. 在场景中进行RANSAC平面拟合对表格进行分段。
  • Step-4. 分别保存两个只包含表和对象的新点云文件。

在存储库中,以单个文件的形式提供了模拟桌面场景的点云数据:tabletop.pcd。最后的目标是创建两个单独的点云文件:

  • Table.pcd - 只包含属于桌子的点
  • Objects.pcd - 包含所有桌上的对象

要完成这些任务,需要修改RANSAC.py脚本中的代码,该脚本在Exercise-1文件夹的存储库中提供。该脚本如下所示:

# Import PCL module
import pcl

# Load point cloud file
cloud = pcl.load_XYZRGB('tabletop.pcd')

# Voxel Grid filter
# Implement Voxel Grid Downsampling here

# Pass Through filter
# Implement a Pass Through Filter here

# RANSAC plane segmentation
# Implement plane segmentation here

# Extract inliers
# Implement inlier extraction here

# Save output point cloud data 
# example: filename = 'table.pcd' for table
# pcl.save(cloud, filename)

# Extract outliers

# Save pcd for tabletop objects

我们将在下文对脚本内容进行填充。由于虚拟机的python不支持中文,代码注释将使用英文注释。


pcl_helper.py 文件[链接]
pcl_helper.py包含用于使用RSO和PCL处理点云数据的功能。其相关的github链接见上。下面给出其相关功能的翻译。

random_color_gen()
生成rgb值的随机集合
Return: a 3-tuple with r,g,b values (range 0-255)

ros_to_pcl(sensor_msgs/PointCloud2)
Converts sensor_msgs/PointCloud2 to XYZRGB Point Cloud
Return: pcl.PointCloud_PointXYZRGB

pcl_to_ros(pcl.PointCloud_PointXYZRGB0
将sensor_msgs/PointCloud2转换为XYZRGB点云
Return: sensor_msgs/PointCloud2

XYZRGB_to_XYZ(XYZRGB_cloud)
将XYZRGB点云转换为XYZ点云
Return: pcl.PointCloud

XYZ_to_XYZRGB(XYZ_cloud, color)
将一个3元组作为颜色并将其添加到XYZ点云
Return: pcl.PointCloud_PointXYZRGB

rgb_to_float(color)
将3元组颜色转换为单个浮点数32
Return: rgb packed as a single float32

get_color_list(cluster_count)
创建一个3元组(rgb)列表,该列表的长度为cluster_count
Return: get_color_list.color_list

(3)体素网格降采样

RGB-D摄像机提供了丰富而特别密集的点云,这意味着单位体积内的点云比例如激光雷达点云要多。在全分辨率点云上运行计算可能很慢,而且可能不会对使用更稀疏采样点云获得的结果产生任何改进。

因此,在许多情况下,向下采样数据是有利的。我们将使用一个VoxelGrid下采样过滤器来派生一个点云,该点云具有较少的点,但是仍然可以很好地表示输入点云的整体。
在这里插入图片描述

“像素”是“图片元素”的缩写。同样,“体素”是“体积元素”的缩写。正如我们可以将2D图像分割为常规的区域元素网格(如上图左所示),我们也可以将3D点云分割为常规的三维体元素网格(如上图右所示)。网格中的每个单独的细胞现在都是一个体素,而3D网格被称为“体素网格”。

PCL提供了一个方便的函数来执行VoxelGrid下采样。

将代码更改到、到RANSAC.py的立体网格下采样部分。

import pcl

# 加载点云文件
cloud = pcl.load_XYZRGB('tabletop.pcd')

# 为我们的输入点云创建一个VoxelGrid过滤器对象
vox = cloud.make_voxel_grid_filter()

# 选择一个体素大小
LEAF_SIZE = 0.01

# 设置体素大小
vox.set_leaf_size(LEAF_SIZE, LEAF_SIZE, LEAF_SIZE)

# 调用过滤函数以获得最终的下采样点云
cloud_filtered = vox.filter()
filename = 'voxel_downsampled.pcd'
pcl.save(cloud_filtered, filename)

然后使用python运行文件,使用pcl_viewer

python RANSAC.py
pcl_viewer voxel_downsampled.pcd

注:
1)按下’r’键使整个点云居中
2)不要复制中文

在这里插入图片描述


体素大小的单位为米。将其设置为1意味着体素的体积为1立方米,这可能不会产生最佳效果,因为它可能会忽略重要的特征。因此,在实践中,我们应该以较小的体素尺寸开始,然后再增大为较大的尺寸,直到达到一个最合适的效果。可以通过具有一些有关场景的先验信息(如最小对象的大小,目标对象的大小或场景的总视野)来获得对体素大小的良好估计。

(4)通过过滤

如果我们知道一些有关目标在场景中位置的某些先验信息,则可以应用“通过过滤器”从点云中删除无用的数据。

直通滤镜的工作方式与裁剪工具非常相似,它可以通过指定沿该轴具有截止值的轴来裁剪任何给定的3D点云。允许通过的区域通常称为关注区域。

例如,在我们的桌面场景中,我们知道桌子大约在机器人视野的中心。因此,通过使用“通过过滤器”,我们可以选择一个感兴趣的区域来删除一些多余的数据。

要完成此任务,请RANSAC.py在“通过过滤器”部分的文件中添加以下代码,以从Voxel下采样过滤后的点云中选择感兴趣的区域。

为了方便编辑,可以使用以下命令:

sudo chmod 777 RANSAC.py

然后我们就可以使用编辑器来直接编写程序,而不用nano。

# Import PCL module
import pcl

# Load Point Cloud file
cloud = pcl.load_XYZRGB('tabletop.pcd')

# Voxel Grid filter
# Create a VoxelGrid filter object for our input point cloud
vox = cloud.make_voxel_grid_filter()

# Experiment and find the appropriate size!
LEAF_SIZE = 0.01   

# Set the voxel (or leaf) size  
vox.set_leaf_size(LEAF_SIZE, LEAF_SIZE, LEAF_SIZE)
cloud_filtered = vox.filter()

# PassThrough filter
# Create a PassThrough filter object.
passthrough = cloud_filtered.make_passthrough_filter()

# Assign axis and range to the passthrough filter object.
filter_axis = 'z'
passthrough.set_filter_field_name(filter_axis)
axis_min = 0.78
axis_max = 1.2
passthrough.set_filter_limits(axis_min, axis_max)

# Finally use the filter function to obtain the resultant point cloud. 
cloud_filtered = passthrough.filter()

# RANSAC plane segmentation


# Extract inliers

# Save pcd for table
# pcl.save(cloud, filename)


# Extract outliers


# Save pcd for tabletop objects
filename = 'voxel_downsampled.pcd'
pcl.save(cloud_filtered, filename)

其输出结果如下
在这里插入图片描述

3.RANSAC

(1)RANSAC概述

在我们的项目中,结合了有关场景的一些先验知识和一些点云过滤器,我们可以将点云缩小为仅包含表和表顶部的对象。

接下来,在感知中,需要将桌子本身从场景中删除。为此,我们使用一种称为随机样本共识或“ RANSAC” 的流行技术。RANSAC是一种算法,可用于识别数据集中属于特定模型的点。对于正在使用的3D场景,我们选择的模型可以是平面,圆柱体,盒子或任何其他常用形状。

RANSAC算法假定数据集中的所有数据均由离群值和离群值组成,其中离群值可以由具有特定参数集的特定模型定义,而离群值不适合该模型,因此可以丢弃。像下面的示例一样,我们可以提取出不太适合模型的轮廓绘制器。

在这里插入图片描述
如果对给定数据集中存在的某个形状有一定的先验知识,可以使用RANSAC通过假设一个特定的模型来估计点云集的哪些部分属于该形状。

将桌子建模为平面,将其从点云中移除,得到如下结果:
在这里插入图片描述

另一方面,RANSAC的缺点是没有计算模型参数所需时间的上限。通过选择固定数量的迭代,这在一定程度上得到了缓解,但是这也有它自己的缺点。如果选择较低的迭代次数,得到的解决方案可能不是最优的。通过这种方式,RANSAC在计算时间和模型检测精度之间进行了权衡。由于场景中桌面是最突出的平面,地面移除后,可以有效地利用RANSAC来识别属于桌面和discar的点。

(2)RANSAC平面拟合

RANSAC算法主要涉及对给定的数据集执行两个迭代重复的步骤:假设和验证。首先,通过随机选择n个点的最小子集并估计相应的形状模型参数来生成期望模型的假设形状。

最小子集包含唯一估计模型所需的最小点数。例如,确定一条直线需要2个点,而确定一个平面需要3个非共线点,如下所述。

使 p 1 p_1 p1 p 2 p_2 p2, 和 p 3 p_3 p3为随机选取的三个非共线点,使得:

p 1 = ( x 1 , y 1 , z 1 ) p_1=(x_1, y_1, z_1) p1=(x1,y1,z1)
p 2 = ( x 2 , y 2 , z 2 ) p_2=(x_2, y_2, z_2) p2=(x2,y2,z2)
p 3 = ( x 3 , y 3 , z 3 ) p_3=(x_3, y_3, z_3) p3=(x3,y3,z3)

平面可以用如下的方程来描述:

a x + b y + c z + d = 0 a x + b y + c z + d = 0 ax+by+cz+d=0

系数 a a a, b b b, c c c, d d d可以通过求解以下方程组得到:

a x 1 + b y 1 + c z 1 + d = 0 ax_1 + by_1 + cz_1 + d = 0 ax1+by1+cz1+d=0
a x 2 + b y 2 + c z 2 + d = 0 ax_2 + by_2 + cz_2 + d = 0 ax2+by2+cz2+d=0
a x 3 + b y 3 + c z 3 + d = 0 ax_3 + by_3 + cz_3 + d = 0 ax3+by3+cz3+d=0

这个系统可以用Cramer’s rule和一些基本的矩阵运算来解决,具体如下:

D = [ x 1 y 1 z 1 x 2 y 2 z 2 x 3 y 3 z 3 ] D = \begin{bmatrix}x_1&y_1&z_1\\x_2&y_2&z_2\\x_3&y_3&z_3\end{bmatrix} D=x1x2x3y1y2y3z1z2z3

如果D是非零的(对于没有经过原点的平面), a a a, b b b, c c c的值可以计算如下:

a = − d D [ 1 x 1 z 1 1 y 2 z 2 1 y 3 z 3 ] a = \frac{-d}{D} \begin{bmatrix}1&x_1&z_1\\1&y_2&z_2\\1&y_3&z_3\end{bmatrix} a=Dd111x1y2y3z1z2z3

b = − d D [ x 1 1 z 1 x 2 1 z 2 x 3 1 z 3 ] b = \frac{-d}{D} \begin{bmatrix}x_1&1&z_1\\x_2&1&z_2\\x_3&1&z_3\end{bmatrix} b=Ddx1x2x3111z1z2z3

c = − d D [ x 1 x 1 1 x 2 y 2 1 x 3 y 3 1 ] c = \frac{-d}{D} \begin{bmatrix}x_1&x_1&1\\x_2&y_2&1\\x_3&y_3&1\end{bmatrix} c=Ddx1x2x3x1y2y3111

一旦建立了模型,就针对所得的候选形状测试点云中的其余点,以确定该模型很好地近似了多少个点。

经过一定数量的迭代后,将提取出拥有最大百分比的线性像素的形状,并且算法将继续处理剩余数据。

将RANSAC与PCL结合使用:

RANSAC平面拟合算法已存在于PCL库中。要在本练习的代码中实现RANSAC平面拟合,请将以下代码添加到文件的RANSAC平面分段部分RANSAC.py:

# Create the segmentation object
seg = cloud_filtered.make_segmenter()

# Set the model you wish to fit 
seg.set_model_type(pcl.SACMODEL_PLANE)
seg.set_method_type(pcl.SAC_RANSAC)

# Max distance for a point to be considered fitting the model
# Experiment with different values for max_distance 
# for segmenting the table
max_distance = 1
seg.set_distance_threshold(max_distance)

# Call the segment function to obtain set of inlier indices and model coefficients
inliers, coefficients = seg.segment()

(3)提取指标

使用RANSAC,我们可以确定点云中的哪些索引与桌子相对应。如果正确应用了“通过过滤器”,那么与表不对应的索引就是代表桌子上的对象的索引。

顾名思义,ExtractIndices筛选器通过提供索引列表从点云中提取点。使用刚刚执行的RANSAC拟合,输出将inliers对应于max_distance最佳拟合模型中的点云索引。

尽管此过滤器不执行任何高级过滤操作,但它经常与其他技术一起用于从输入点云中获取点的子集。大多数对象识别算法会返回一组与形成已识别目标对象的点相关的索引。

因此,使用ExtractIndices过滤器提取与已识别对象关联的点云非常方便。为此,将以下代码添加到RANSAC.py脚本的“提取内部”部分:

# Extract inliers
extracted_inliers = cloud_filtered.extract(inliers, negative=False)
filename = 'extracted_inliers.pcd'
pcl.save(extracted_inliers, filename)
对象提取

实际上,我们只是想去掉桌子并专注于桌子上的对象。

只需将extract方法上的negativ=标志更改为True,就可以轻松使用已经完成的操作从点云中提取所有感兴趣的对象。将以下代码添加到RANSAC.py脚本的“提取异常值”部分,以检索所有不符合RANSAC模型的点的子集:

# Extract outliers
extracted_outliers = cloud_filtered.extract(inliers, negative=True)
filename = 'extracted_outliers.pcd'
pcl.save(extracted_outliers, filename)

总程序如下:

# Import PCL module
import pcl

# Load Point Cloud file
cloud = pcl.load_XYZRGB('tabletop.pcd')

# Voxel Grid filter
# Create a VoxelGrid filter object for our input point cloud
vox = cloud.make_voxel_grid_filter()

# Experiment and find the appropriate size!
LEAF_SIZE = 0.01   

# Set the voxel (or leaf) size  
vox.set_leaf_size(LEAF_SIZE, LEAF_SIZE, LEAF_SIZE)
cloud_filtered = vox.filter()

# PassThrough filter
# Create a PassThrough filter object.
passthrough = cloud_filtered.make_passthrough_filter()

# Assign axis and range to the passthrough filter object.
filter_axis = 'z'
passthrough.set_filter_field_name(filter_axis)
axis_min = 0.75
axis_max = 1.2
passthrough.set_filter_limits(axis_min, axis_max)

# Finally use the filter function to obtain the resultant point cloud. 
cloud_filtered = passthrough.filter()

# RANSAC plane segmentation

# Create the segmentation object
seg = cloud_filtered.make_segmenter()

# Set the model you wish to fit 
seg.set_model_type(pcl.SACMODEL_PLANE)
seg.set_method_type(pcl.SAC_RANSAC)

# Max distance for a point to be considered fitting the model
# Experiment with different values for max_distance 
# for segmenting the table
max_distance = 0.01
seg.set_distance_threshold(max_distance)

# Call the segment function to obtain set of inlier indices and model coefficients
inliers, coefficients = seg.segment()
# Extract inliers
extracted_inliers = cloud_filtered.extract(inliers, negative=False)
filename = 'extracted_inliers.pcd'
pcl.save(extracted_inliers, filename)
# Save pcd for table
# pcl.save(cloud, filename)
filename = 'voxel_downsampled.pcd'
pcl.save(cloud_filtered, filename)

# Extract outliers
try:
	extracted_outliers = cloud_filtered.extract(inliers, negative=True)
except RuntimeError:
	print("runtimeError!Plese check max_distance in your code.")
else:
	filename = 'extracted_outliers.pcd'
	pcl.save(extracted_outliers, filename)
# Save pcd for tabletop objects

其中extracted_outliers.pcd的输出如下:
在这里插入图片描述
extracted_inliers.pcd的输出如下:
在这里插入图片描述

(4)离群值去除过滤器

虽然校准可以解决失真问题,但由于外部因素(例如环境中的灰尘,空气中的湿度或各种光源的存在)导致的噪声会导致稀疏的异常值,从而进一步破坏结果。

这些离群值会导致点云特征(例如曲率,梯度等)的估计复杂化,从而导致错误的值,进而可能导致我们感知流程中各个阶段的失败。

用于删除此类离群值的一种过滤技术是在每个点附近执行统计分析,并删除不符合特定条件的那些点。PCL的Statistics Outlier Removal过滤器就是其中一种过滤技术的示例。对于点云中的每个点,它计算到其所有邻居的距离,然后计算平均距离。

通过假设为高斯分布,所有平均距离均在由全局距离均值+标准差定义的区间之外的点都被视为离群值,并从点云中删除。

下图显示了将Statistics Outlier Removal过滤器应用于噪声点云数据的结果:
在这里插入图片描述
可以这样实现:

# Much like the previous filters, we start by creating a filter object: 
outlier_filter = cloud_filtered.make_statistical_outlier_filter()

# Set the number of neighboring points to analyze for any given point
outlier_filter.set_mean_k(50)

# Set threshold scale factor
x = 1.0

# Any point with a mean distance larger than global (mean distance+x*std_dev) will be considered outlier
outlier_filter.set_std_dev_mul_thresh(x)

# Finally call the filter function for magic
cloud_filtered = outlier_filter.filter()

可以在此处找到统计异常值过滤器的可测试代码段,并在此处找到具有异常值的表点云的链接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Stan Fu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值