『OPEN3D』1.8.1 ICP配准

news2024/11/15 17:22:22

a目录

1、点到点(point2point)的配准

2、 点到面(point2plane)的配准

3、基于颜色的配准(color-icp)

4、点云配准核函数(robust kernel)


前面已经介绍过点云配准的基础理论内容,可以查看之前的文章:

『OPEN3D』1.8 点云的配准理论-CSDN博客

1、点到点(point2point)的配准

点到点的配准,最小化如下误差函数

代码如下:

import open3d as o3d
import numpy as np
import copy

# 点到点的icp
def point_to_point_icp(source, target, threshold, trans_init):
    print("Apply point-to-point ICP")
    reg_p2p = o3d.pipelines.registration.registration_icp(
        source = source,#原始点云
        target = target,#目标点云
        # 原始点云与目标点云用于匹配的最大点对距离,小于该距离的才被认为是需要优化的点对
        max_correspondence_distance=threshold,
        init=trans_init,#初始变换矩阵
        #TransformationEstimationPointToPoint 类提供了计算残差和点到点ICP的Jacobian矩阵的函数
        estimation_method = o3d.pipelines.registration.TransformationEstimationPointToPoint()
    )
    print(reg_p2p)
    # 输出配准的变换矩阵
    print("Transformation is:")
    print(reg_p2p.transformation, "\n")
    # 绘制结果
    draw_registration_result(source, target, reg_p2p.transformation)


if __name__ == "__main__":
    pcd_data = o3d.data.DemoICPPointClouds()
    # 读取两份点云文件
    source = o3d.io.read_point_cloud(pcd_data.paths[0])
    target = o3d.io.read_point_cloud(pcd_data.paths[1])

    threshold = 0.04
    
    trans_init = np.asarray([[0.862, 0.011, -0.507, 0.5],
                             [-0.139, 0.967, -0.215, 0.7],
                             [0.487, 0.255, 0.835, -1.4], [0.0, 0.0, 0.0, 1.0]])
    draw_registration_result(source, target, trans_init)

    print("Initial alignment")
    # evaluate_registration函数评估了配准的两个参数:
    # 1、fitness评估了重叠面积(内点对/目标点云数量),越高越好
    # 2、inlier_rmse评估了所有内点对RMSE,数值越低越好
    evaluation = o3d.pipelines.registration.evaluate_registration(
        source, target, threshold, trans_init)
    print(evaluation, "\n")

    point_to_point_icp(source, target, threshold, trans_init)

注:通过evaluate_registration函数来判断初始的配准情况

evaluate_registration函数评估了配准的两个参数:

1、fitness评估了重叠面积(内点对/目标点云数量),越高越好

2、inlier_rmse评估了所有内点对RMSE,数值越低越好

输出结果:

可以看到fitness有提升且inlier_rmse有下降,并且匹配的点对数量增加;然后输出匹配的变换矩阵

Initial alignment
RegistrationResult with fitness=3.236251e-01, inlier_rmse=2.185569e-02, and correspondence_set size of 64348
Access transformation to get result. 

Apply point-to-point ICP
RegistrationResult with fitness=6.403400e-01, inlier_rmse=8.354068e-03, and correspondence_set size of 127322
Access transformation to get result.
Transformation is:
[[ 0.84051756  0.00711097 -0.54198802  0.64482424]
 [-0.1500491   0.96427647 -0.21978666  0.82019636]
 [ 0.52014373  0.26524577  0.81144428 -1.48547754]
 [ 0.          0.          0.          1.        ]] 

2、 点到面(point2plane)的配准

点到面的配准,最小化如下误差函数:

其中n{_p}为点p的法线;并且点到面的ICP比点到点的ICP拥有更快的收敛速度。

点到面的配准需要目标点云具有法线信息,若没有则需要先对目标点云进行法线估计

estimate_normals(
        search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))

代码如下:

# 点到面的icp
def point_to_plane_icp(source, target, threshold, trans_init):
    print("Apply point-to-plane ICP")
    reg_p2l = o3d.pipelines.registration.registration_icp(
        source=source,  # 原始点云
        target=target,  # 目标点云
        # 原始点云与目标点云用于匹配的最大点对距离,小于该距离的点对才被认为是需要优化的点对
        max_correspondence_distance=threshold,
        init=trans_init,  # 初始变换矩阵
        # TransformationEstimationPointToPlane 类提供了计算残差和点到面ICP的Jacobian矩阵的函数
        estimation_method = o3d.pipelines.registration.TransformationEstimationPointToPlane())
    
    print(reg_p2l)
    # 输出配准的变换矩阵
    print("Transformation is:")
    print(reg_p2l.transformation, "\n")
    # 绘制结果
    draw_registration_result(source, target, reg_p2l.transformation)

if __name__ == "__main__":
    pcd_data = o3d.data.DemoICPPointClouds()
    # 读取两份点云文件
    source = o3d.io.read_point_cloud(pcd_data.paths[0])
    target = o3d.io.read_point_cloud(pcd_data.paths[1])
    # 原始点云与目标点云用于匹配的最大点对距离,小于该距离的点对才被认为是需要优化的点对
    threshold = 0.04
    # 这里人为指定一个初始的变换矩阵,
    # 后续我们将使用点云特征匹配的方式来获取初始的变换矩阵
    trans_init = np.asarray([[0.862, 0.011, -0.507, 0.5],
                             [-0.139, 0.967, -0.215, 0.7],
                             [0.487, 0.255, 0.835, -1.4], [0.0, 0.0, 0.0, 1.0]])
    draw_registration_result(source, target, trans_init)

    print("Initial alignment")
    # evaluate_registration函数评估了配准的两个参数:
    # 1、fitness评估了重叠面积(内点对/目标点云数量),越高越好
    # 2、inlier_rmse评估了所有内点对RMSE,数值越低越好
    evaluation = o3d.pipelines.registration.evaluate_registration(
        source, target, threshold, trans_init)
    print(evaluation, "\n")

    point_to_plane_icp(source, target, threshold, trans_init)

点到面的匹配结果如下

Initial alignment
RegistrationResult with fitness=3.236251e-01, inlier_rmse=2.185569e-02, and correspondence_set size of 64348
Access transformation to get result. 

Apply point-to-plane ICP
RegistrationResult with fitness=6.400634e-01, inlier_rmse=8.221662e-03, and correspondence_set size of 127267
Access transformation to get result.
Transformation is:
[[ 0.84038344  0.00645131 -0.54220491  0.64577952]
 [-0.14771349  0.96522059 -0.21719886  0.81064328]
 [ 0.52102822  0.2618064   0.81199599 -1.48292341]
 [ 0.          0.          0.          1.        ]] 

3、基于颜色的配准(color-icp)

        前面两个算法只是对点云数据进行几何形状的配准,如果是两份平面数据有不同的颜色信息,则不发匹配颜色部分,如下所示:

两份带有不同颜色图案的平面点云

color icp的优化误差函数如下

其中E{_G}部分与点到面的ICP相同,E{_C}部分为photometric误差,(1- \delta)为平衡误差之间的权重;E{_C}的误差部分如下:

其中Cp(*)代表了在P切平面的预计算函数,f(*)为投影函数,将一个3d点投影到切面中;

import open3d as o3d
import numpy as np
import copy


# 画图函数
def draw_registration_result(source, target, transformation):
    source_temp = copy.deepcopy(source)
    source_temp.transform(transformation)
    o3d.visualization.draw_geometries([source_temp, target])


# 在此处加载两份点云数据
print("Load two point clouds and show initial pose ...")
ply_data = o3d.data.DemoColoredICPPointClouds()
source = o3d.io.read_point_cloud(ply_data.paths[0])
target = o3d.io.read_point_cloud(ply_data.paths[1])

if __name__ == "__main__":
    # 给定初始的变换矩阵
    current_transformation = np.identity(4)
    # 可视化初始两份点云数据
    draw_registration_result(source, target, current_transformation)
    print(current_transformation)

    # 对点云进行由粗到精的迭代配准
    # 对点云进行的下采样voxel大小,单位米
    voxel_radius = [0.04, 0.02, 0.01]
    # 每层最大迭代次数
    max_iter = [50, 30, 14]
    # 给定初始的变换矩阵
    current_transformation = np.identity(4)
    print("Colored point cloud registration ...\n")
    for scale in range(3):
        iter = max_iter[scale]
        radius = voxel_radius[scale]
        print([iter, radius, scale])

        print("1. 下采样点云,voxel大小为 %.2f" % radius)
        source_down = source.voxel_down_sample(radius)
        target_down = target.voxel_down_sample(radius)

        print("2. 估计点云的法线信息")
        source_down.estimate_normals(
            o3d.geometry.KDTreeSearchParamHybrid(radius=radius * 2, max_nn=30))
        target_down.estimate_normals(
            o3d.geometry.KDTreeSearchParamHybrid(radius=radius * 2, max_nn=30))

        # 使用coloricp进行配准
        print("3. 使用color icp进行配准")
        result_icp = o3d.pipelines.registration.registration_colored_icp(
            source=source_down,  # 原始点云
            target=target_down,  # 目标点云
            # 原始点云与目标点云用于匹配的最大点对距离,小于该距离的点对才被认为是需要优化的点对
            max_correspondence_distance=radius,
            init=current_transformation,  # 初始变换矩阵
            # TransformationEstimationForColoredICP 类提供了计算残差和点到面ICP的Jacobian矩阵的函数
            # 其中lambda_geometric是前面所说的颜色误差函数调整系数
            estimation_method=
            o3d.pipelines.registration.TransformationEstimationForColoredICP(lambda_geometric=0.9),

            # 迭代条件,达到其中一个则退出当前迭代
            criteria=o3d.pipelines.registration.ICPConvergenceCriteria(
                relative_fitness=1e-6, relative_rmse=1e-6, max_iteration=iter)
        )

        # # 使用point2plane进行配准
        # print("3. 使用point 2 plane icp进行配准")
        # result_icp = o3d.pipelines.registration.registration_icp(
        #     source=source,  # 原始点云
        #     target=target,  # 目标点云
        #     # 原始点云与目标点云用于匹配的最大点对距离,小于该距离的点对才被认为是需要优化的点对
        #     max_correspondence_distance=radius * 2,
        #     init=current_transformation,  # 初始变换矩阵
        #     # TransformationEstimationPointToPlane 类提供了计算残差和点到面ICP的Jacobian矩阵的函数
        #     estimation_method=o3d.pipelines.registration.TransformationEstimationPointToPlane())

        # 取出当前轮次的配准结果
        current_transformation = result_icp.transformation
        print(result_icp, "\n")

    # 可视化结果
    draw_registration_result(source, target, result_icp.transformation)
    print(current_transformation)

使用point2plane的配准结果,两份点云的颜色信息不能配准

使用color icp的结果, 两份点云的颜色信息可以良好的配准

4、点云配准核函数(robust kernel)

        若点云数据中具有大量的噪声,则前述的方法可能不能得到正确的结果,因此此处引入核函数来对噪声更加泛化。

        优化问题中,通常将最小化误差项的二范数平方和作为目标函数;但存在一个严重的问题:如果出于误匹配等原因,某个误差项给的数据是错误的,那么它的梯度也很大,意味着调整与它相关的变量会使目标函数下降更多。所以,算法将试图优先向这个误差大的outlier项进行调整,使其他正确的匹配向满足该项的无理要求,导致算法会抹平其他正确边的影响, 使优化算法专注于调整一个错误的值。这显然不是我们希望看到的。

        出现这种问题的原因是,当误差很大时,二范数增长得太快。于是就有了核函数的存在。核 函数保证每条边的误差不会大得没边而掩盖其他的边。具体的方式是,把原先误差的二范数度量 替换成一个增长没有那么快的函数,同时保证自己的光滑'性质(不然无法求导)。因为它们使得 整个优化结果更为稳健,所以又叫它们鲁棒核函数( Robust Kemel )。

         鲁棒核函数有许多种,例如最常用的 Huber 核:

        除了 Huber 核,还有 Cauchy 核、 Tukey 核,在Open3D中实现了Turky核函数,下面看看roubust icp的误差函数

当前open3d中只有PointToPlane的ICP方法已经实现了核函数,只需要通过在TransformationEstimationPointToPlane的estimation_method方法中加入核函数即可,

TransormationEstimationPointToPlane(loss)内部实现了带权重的残差计算节点到面的ICP的基于给定核函数的Jacobian矩阵实现

import open3d as o3d
import numpy as np
import copy

#画图函数
def draw_registration_result(source, target, transformation):
    source_temp = copy.deepcopy(source)
    target_temp = copy.deepcopy(target)
    source_temp.paint_uniform_color([1, 0.706, 0])
    target_temp.paint_uniform_color([0, 0.651, 0.929])
    source_temp.transform(transformation)
    o3d.visualization.draw_geometries([source_temp, target_temp])

#对点云添加高斯噪音
def apply_noise(pcd, mu, sigma):
    noisy_pcd = copy.deepcopy(pcd)
    points = np.asarray(noisy_pcd.points)
    # mu为高斯噪音的均值,sigma为方差
    points += np.random.normal(mu, sigma, size=points.shape)
    # 将生成噪音添加到每个点云上
    noisy_pcd.points = o3d.utility.Vector3dVector(points)
    return noisy_pcd


if __name__ == "__main__":
    pcd_data = o3d.data.DemoICPPointClouds()
    source = o3d.io.read_point_cloud(pcd_data.paths[0])
    target = o3d.io.read_point_cloud(pcd_data.paths[1])
    # 给定初始变换矩阵
    trans_init = np.asarray([[0.862, 0.011, -0.507, 0.5],
                             [-0.139, 0.967, -0.215, 0.7],
                             [0.487, 0.255, 0.835, -1.4], [0.0, 0.0, 0.0, 1.0]])

    # Mean and standard deviation.
    mu, sigma = 0, 0.05
    source_noisy = apply_noise(source, mu, sigma)

    print("可视化带有噪音的点云数据:")
    o3d.visualization.draw_geometries([source_noisy])

    print("可视化初始变换下的原始源点云和目标点云:")
    draw_registration_result(source, target, trans_init)

    threshold = 1.0
    print("Robust point-to-plane ICP, 点到面的阈值={}:".format(threshold))
    # 创建核函数,并将参数k设置的与高斯噪音的方差一致, 不同的核函数
    # loss = o3d.pipelines.registration.TukeyLoss(k=sigma)
    # loss = o3d.pipelines.registration.GMLoss(k=sigma)
    # loss = o3d.pipelines.registration.L1Loss()
    loss = o3d.pipelines.registration.L2Loss()
    print("使用的核函数为:", loss)
    # 可以去掉estimation_method中的核函数,看看配准的结果
    p2l = o3d.pipelines.registration.TransformationEstimationPointToPlane(loss)
    reg_p2l = o3d.pipelines.registration.registration_icp(
        source_noisy,
        target,
        threshold,
        trans_init,
        p2l)
    print(reg_p2l)
    print("NNNNNathan  变换矩阵为:")
    print(reg_p2l.transformation)
    # 可视化结果
    draw_registration_result(source, target, reg_p2l.transformation)

添加噪声后的原始点云数据

不使用核函数的配准结果

使用核函数的配准结果

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OLED双面显示广告机的应用场景

OLED双面显示广告机是一种创新的广告设备,它具有双面显示屏幕,可以同时向两个方向展示广告或信息。这种设备被广泛应用于各种场景,例如: 商业展示:在大型商业场所,如购物中心、百货商场等,OLED双…

18、串口通信

串口介绍 串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信。 单片机的串口可以使单片机与单片机,单片机与电脑、单片机与各式各样的模块互相通信,极大的扩展了单片机的应用范围&…

在MySQL中如何存储一个IPv4地址?

在MySQL如何存储IPv4地址?这个在秋招面试的过程中被问到过,没有答上来,今天猛地想起了这个问题,做一下复盘。 一个IPv4地址是由32位二进制来表示的,用点分十进制表示可以划分为4部分,每部分占8位&#xff…

一款适用于船载、化工园区、工厂的防水LoRa网关推荐

工业网关的实践应用场景非常广泛,比如:工业现场PLC、变频器、机器人等设备的远程维护;工程机械的远程维护和管理;车间设备与工艺系统的远程维护和管理;小区二次供水水泵的远程监测及控制;油气田和油井等现场…

sklearn 笔记:聚类

1 sklearn各方法比较 方法名称参数使用场景K-means簇的数量 非常大的样本数 中等簇数 簇大小需要均匀 Affinity Propagation 阻尼系数 样本偏好 样本数不能多 簇大小不均 MeanShift带宽 样本数不能多 簇大小均匀 谱聚类簇的数量 中等样本数 小簇数 簇大小均匀 层次聚类簇的数量…

RabbitMQ登录控制台显示--你与此网站的连接不是私密连接

一、RabbitMQ默认账号 Note: The default administrator username and password are guest and guest. 注:默认管理员用户名和密码为guest和guest 二、自己修改过或者注册的情况 由于本人之前用过,注册过账号密码,在登录时,用户名账号有异常出现以下问题 解决方案: 因为我的rab…

STM32CubeIDE(CUBE-MX hal库)----定时器

系列文章目录 STM32CubeIDE(CUBE-MX hal库)----初尝点亮小灯 STM32CubeIDE(CUBE-MX hal库)----按键控制 STM32CubeIDE(CUBE-MX hal库)----串口通信 文章目录 系列文章目录前言一、定时器二、使用步骤三、HAL库实验代码三、标准库代码 前言 STM32定时器是一种多功能外设&#…

计算虚拟化之内存

有了虚拟机,内存就变成了四类: 虚拟机里面的虚拟内存(Guest OS Virtual Memory,GVA),这是虚拟机里面的进程看到的内存空间;虚拟机里面的物理内存(Guest OS Physical Memory&#xf…

2023.11.29 关于 MyBatis resultMap 和 多表查询

目录 resultType 和 resultMap 多表查询 resultType 和 resultMap 在 MyBatis 中二者被用于设置查询后所返回的数据类型 resultType 大多数情况下均可使用 resultType 进行设置返回数据类型 实例理解 下图为数据库中的一个 user 表,该 user 表包含四个字段 为了能…

HTML-标签之文字排版、图片、链接、音视频

1、标签语法 HTML超文本标记语言——HyperText Markup Language 超文本是链接标记也叫标签,带尖括号的文本 2、HTML基本骨架 HTML基本骨架是网页模板 html:整个网页head:网页头部,存放给浏览器看的代码,例如CSSbody…

具备这四个特征的项目经理,牛逼!

大家好,我是老原。 成为一个业绩第一又能准时下班的工作强人,应该是每个职场人的梦想,但现实往往不那么尽如人意…… 虽然如此,但是不代表我们不能向能做到这样的大佬看齐啊。 工作十余年,见过各种各样的职场人士&a…

Linux系统iptables

目录 一. 防火墙简介 1. 防火墙定义 2. 防火墙分类 ①. 网络层防火墙 ②. 应用层防火墙 二. iptables 1. iptables定义 2. iptables组成 ①. 规则表 ②. 规则链 3. iptables格式 ①. 管理选项 ②. 匹配条件 ③. 控制类型 四. 案例说明 1. 查看规则表 2. 增加新…

PostGIS学习教程八:空间关系

PostGIS学习教程八:空间关系 到目前为止,我们只使用了测量(ST_Area、ST_Length)、序列化(ST_GeomFromText)或者反序列化(ST_AsGML)几何图形(geometry)的空间…

代码随想录刷题题Day2

刷题的第二天,希望自己能够不断坚持下去,迎来蜕变。😀😀😀 刷题语言:C / Python Day2 任务 977.有序数组的平方 209.长度最小的子数组 59.螺旋矩阵 II 1 有序数组的平方(重点:双指针…

口碑爆棚!10款项目时间轴软件带你实现高效管理!

当我们在组织、规划或管理一个项目时,将所有步骤清晰地展示在一个时间轴上,无疑可以帮助我们更好地理解整个项目的流程,确定关键任务,并在必要时进行调整,项目时间轴软件在此方面发挥了重要作用。 项目时间轴软件是什…

机器学习入门(第五天)——决策树(每次选一边)

Decision tree 知识树 Knowledge tree 一个小故事 A story 挑苹果: 根据这些特征,如颜色是否是红色、硬度是否是硬、香味是否是香,如果全部满足绝对是好苹果,或者红色硬但是无味也是好苹果,从上图可以看出来&#…

前端传参中带有特殊符号导致后端接收时乱码或转码失败的解决方案

文章目录 bug背景解决思路1:解决思路2解决思路3(最终解决方案)后记 bug背景 项目中采用富文本编辑器后传参引起的bug,起因如下: 数据库中存入的数据会变成这种未经转码的URL编码 解决思路1: 使用JSON方…

MyBatis的强大特性--动态SQL

目录 前言 if trim where set foreach 前言 动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表…

【linux防火墙】设置开启路由转发,SNAT和DNAT转换原理及应用实操,添加自定义链归类iptables规则

目录 一、关于iptables规则的保存 1.1持久保存规则 1.2加载规则 1.3开机自动加载规则 1.4使用iptables-service软件来进行规则的保存和加载(不建议使用) 二、SNAT和DNAT的原理和应用 SNAT的原理与应用: DNAT的原理和应用: …

MySQL之 InnoDB逻辑存储结构

InnoDB逻辑存储结构 InnoDB将所有数据都存放在表空间中,表空间又由段(segment)、区(extent)、页(page)组成。InnoDB存储引擎的逻辑存储结构大致如下图。下面我们就一个个来看看。 页&#xff08…