『OPEN3D』1.8.1 ICP配准

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.mfbz.cn/a/205563.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity Canvas、Canvas Scaler、Graphic Raycaster、EventSystem 组件详解

文章目录 0. 参考文章1. Canvas1.1 Screen Space-Overlay —— 屏幕空间覆盖模式1.2 Screen Space-Camera —— 相机模式1.3 World Space —— 世界模式 2. Canvas Scaler:控制UI画布的放大缩放的比例2.1 Constant Pixer Size —— 恒定像素2.2 Scale With Screen S…

98.套接字-Socket网络编程1(基础概念)

目录 1.局域网和广域网 2.IP 互联网协议(Internet Protocol) IP的作用 3.查看IP地址 Windows上查看IP ​编辑 Linux上查看IP 4.端口 主要类型: 用途: 示例: 端口的表示: 5.OSI/ISO 网络分层模型 1.局域网和广域网 …

2021年6月3日 Go生态洞察:Fuzzing技术的Beta测试

🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…

哪吒汽车拔头筹,造车新势力首家泰国工厂投产

中国造车新势力首家泰国工厂投产!11月30日,哪吒汽车位于泰国的首家海外工厂——泰国生态智慧工厂正式投产下线新车,哪吒汽车联合创始人兼CEO张勇、哪吒汽车泰国合作伙伴BGAC公司首席执行官万查曾颂翁蓬素等出席仪式。首辆“泰国制造”的哪吒汽…

【Java Web学习笔记】0 - 技术体系的说明

B/S软件开发架构简述 B/S架构 1.B/S框架,意思是前端(Browser浏览器)和服务器端( Server )组成的系统的框架结构。 2. B/S架构也可理解为web架构,包含前端、后端、数据库三大组成部分。 3.示意图 ●前端 前端开发技术工具包括三要素: HTML、CSS和Jav…

基于SpringBoot母婴商城

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本母婴商城系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息&am…

零基础学编程系列,看一下具体中文编程代码是什么样子的

零基础学编程系列,看一下具体中文编程代码是什么样子的 上图 编写一个单选的程序 上图 是单选 按钮的中文编程代码 附:中文编程工具构件工具箱总共22组305个构件,构件明细如下: 文本件16个: (普通标签&am…

使用 kubeadm 部署 Kubernetes 集群(一)linux环境准备

一、 初始化集群环境 准备三台 rocky8.8 操作系统的 linux 机器。每台机器配置:4VCPU/4G 内存/60G 硬盘 环境说明: IP 主机名 角色 内存 cpu 192.168.1.63 xuegod63 master 4G 4vCPU 192.168.1.64 xuegod64 worker 4G 4vCPU 192.168.1.62 xuegod62 work…

iptables防火墙之SNAT与DNET

NAT 1.SNAT:让内网可以访问外网 2.DNAT:让外网可以访问到内网的机器 网关服务器,要开启路由功能 内核功能: sysctl -a 列出所有参数 内核参数,然后grep可以查看到默认的内核参数 内核参数配置文件 /etc/sysctl.…

CANDENCE: PCB 中 元器件对齐

PCB 中 元器件对齐 以下面的几个电阻为例: step1:选择以下工具 step2:选中要对齐的器件,右键 Align Components 选择你想要的对齐方式即可

沿着马可·波罗的足迹,看数字云南

刚入行的时候,有位前辈跟我说过一句话:去现场“要像外国人一样去看”,重新审视那些自己可能早已“熟视无睹”的事物。 前不久,我跟随“看见数字云南——云南数字经济媒体探营活动”,奔赴昆明、大理、西双版纳等地&…

键入网址到网页显示,期间发生了什么?(计算机网络)

浏览器首先会对URL进行解析 下面以http://www.server.com/dir1/file1.html为例 当没有路径名时,就代表访问根目录下事先设置的默认文件,也就是 /index.html 或者 /default.html 对URL进行解析之后,浏览器确定了 Web 服务器和文件名&#x…

MySQL之binlog日志

聊聊BINLOG binlog记录什么? MySQL server中所有的搜索引擎发生了更新(DDL和DML)都会产生binlog日志,记录的是语句的原始逻辑 为什么需要binlog? binlog主要有两个应用场景,一是数据复制,在…

【ECCV 2022】《Transformers as Meta-learners for Implicit Neural Representations》

文章目录 一、动机二、相关工作三、方法四、实验部分五、Does the INR Exploit Data Structures?六、结论 一、动机 \quad 与像素、体素和网格等离散数据表示相比,INRs不需要依赖于分辨率的二次或三次存储。它们的表示能力并不依赖于网格分辨率,而是依赖…

开源运维监控系统-Nightingale(夜莺)应用实践(未完)

一、前言 某业务系统因OS改造,原先的Zabbix监控系统推倒后未重建,本来计划用外部企业内其他监控系统接入,后又通知需要自建才能对接,考虑之前zabbix的一些不便,本次计划采用一个类Prometheus的监控系统,镜调研后发现Nightingale兼容Prometheus,又有一些其他功能增强,又…

TZOJ 1389 人见人爱A^B

答案&#xff1a; #include <stdio.h> int pow(int a, int b) //定义一个a的b次方函数 {int m 1;int i 0;for (i 0; i < b; i) //b次方{m (m * a) % 1000; // %1000用来控制最后输出为后三位&#xff0c;同时每次乘法结果取模&#xff0c;避免溢出 }retu…

Mybatis批处理数据插入(rewriteBatchedStatements参数)

一、rewriteBatchedStatements参数 1、MySQL JDBC驱动在默认情况下会无视executeBatch()【也就是说JDBC默认情况下&#xff0c;会将你的语句分拆成单个&#xff0c;一条一条发给数据库执行&#xff0c;数据量小时感知不大&#xff0c;1w或10w以上差距越来越大】 2、MySQL的JDBC…

基于Linux下搭建NextCloud构建自己的私有网盘

NextCloud是什么 Nextcloud是一款开源免费的私有云存储网盘项目&#xff0c;可以让你快速便捷地搭建一套属于自己或团队的云同步网盘&#xff0c;从而实现跨平台跨设备文件同步、共享、版本控制、团队协作等功能。它的客户端覆盖了Windows、Mac、Android、iOS、Linux 等各种平…

tex2D使用学习

1. 背景&#xff1a; 项目中使用到了纹理进行插值的加速&#xff0c;因此记录一些自己在学习tex2D的一些过程 2. 代码&#xff1a; #include "cuda_runtime.h" #include "device_launch_parameters.h" #include <assert.h> #include <stdio.h>…

【23真题】复录比高达2.24,但题目很棒!

今天分享的是23年广东工业837的信号与系统试题及解析。注意官方不公示真题&#xff0c;所以这套试卷为回忆版本。 本套试卷难度分析&#xff1a;22年广东工业837考研真题&#xff0c;我也发布过&#xff0c;若有需要&#xff0c;戳这里自取&#xff01;平均分107.93&#xff…