基于Open3D的点云处理16-特征点匹配

news2025/1/31 2:58:39

点云配准

将点云数据统一到一个世界坐标系的过程称之为点云配准或者点云拼接。(registration/align)

点云配准的过程其实就是找到同名点对;即找到在点云中处在真实世界同一位置的点。

常见的点云配准算法:
ICP、Color ICP、Trimed-ICP 算法流程:

  1. 选点:
    确定参与到配准过程中的点集。
  2. 匹配确定同名点对:
    ICP以两片点云中欧式空间距离最小的点对为同名点
  3. 非线性优化求解:
    采用SVD或者四元数求解变换
  4. 变换:
    将求解的变换参数应用到待配准点云上
  5. 迭代:
    计算此时的状态参数判断配准是否完成。以前后两次参数迭代变
    化的差或者RMSE值是否小于给定阈值为迭代终止条件。否则返回(1)

ICP 算法以两片点云中欧式空间距离最小的点对为同名点,如果初始点选择不合适,可能会陷入局部最优配准失败。

基于点特征的配准方法
两种方式:
一种通过特征描述,先分割出场景里的点线等特征,利用这些点进行同名点的匹配,如基于几何空间一致性筛选同名点对。
一种通过点特征描述符确定同名点,如基于FPFH双向最近邻确定同名点对,FPFH描述向量最大的特性是对于点云的同名点的特征向量表现出相似性,即该点云的同名点之间的FPFH特征描述子的二范数趋于零。

测试用例

基于icp的点云匹配(参考)

  • 点到点的配准
import open3d as o3d
import numpy as np

# 获取示例数据
source_cloud = o3d.io.read_point_cloud("./data/cloud_bin_0.pcd")
target_cloud = o3d.io.read_point_cloud("./data/cloud_bin_1.pcd")
source_cloud.paint_uniform_color([1, 0.706, 0])
target_cloud.paint_uniform_color([0, 0.651, 0.929])
threshold = 0.02# RMSE残差阈值,小于该残差阈值,迭代终止

#初始位姿
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]])
                         
# 显示未配准点云
o3d.visualization.draw_geometries([source_cloud, target_cloud],
                                  zoom=0.4459,
                                  front=[0.9288, -0.2951, -0.2242],
                                  lookat=[1.6784, 2.0612, 1.4451],
                                  up=[-0.3402, -0.9189, -0.1996])


# 点到点的ICP
result = o3d.pipelines.registration.registration_icp(
        source_cloud, target_cloud, threshold,trans_init,
        o3d.pipelines.registration.TransformationEstimationPointToPoint())
print(result)
print("Transformation is:")
print(result.transformation)

# 显示点到点的配准结果
source_cloud.transform(result.transformation)
o3d.visualization.draw_geometries([source_cloud, target_cloud],
                                  zoom=0.4459,
                                  front=[0.9288, -0.2951, -0.2242],
                                  lookat=[1.6784, 2.0612, 1.4451],
                                  up=[-0.3402, -0.9189, -0.1996])


配准前:
配准前
配准结果:
配准结果
在这里插入图片描述

  • 点到面的配准
import open3d as o3d
import numpy as np

# 获取示例数据
source_cloud = o3d.io.read_point_cloud("./data/cloud_bin_0.pcd")
target_cloud = o3d.io.read_point_cloud("./data/cloud_bin_1.pcd")
source_cloud.paint_uniform_color([1, 0.706, 0])
target_cloud.paint_uniform_color([0, 0.651, 0.929])
threshold = 0.02# RMSE残差阈值,小于该残差阈值,迭代终止

#初始位姿
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]])
                         

source_cloud = o3d.io.read_point_cloud("./data/cloud_bin_0.pcd")
target_cloud = o3d.io.read_point_cloud("./data/cloud_bin_1.pcd")
source_cloud.paint_uniform_color([1, 0.706, 0])
target_cloud.paint_uniform_color([0, 0.651, 0.929])

# 显示未配准点云
o3d.visualization.draw_geometries([source_cloud, target_cloud],
                                  zoom=0.4459,
                                  front=[0.9288, -0.2951, -0.2242],
                                  lookat=[1.6784, 2.0612, 1.4451],
                                  up=[-0.3402, -0.9189, -0.1996])

# 点到面的ICP
result = o3d.pipelines.registration.registration_icp(
    source_cloud, target_cloud, threshold,trans_init,
    o3d.pipelines.registration.TransformationEstimationPointToPlane())
print(result)
print("Transformation is:")
print(result.transformation, "\n")

# 显示点到面的配准结果
source_cloud.transform(result.transformation)
o3d.visualization.draw_geometries([source_cloud, target_cloud],
                                  zoom=0.4459,
                                  front=[0.9288, -0.2951, -0.2242],
                                  lookat=[1.6784, 2.0612, 1.4451],
                                  up=[-0.3402, -0.9189, -0.1996])


配准结果如图:
在这里插入图片描述
在这里插入图片描述

基于Color ICP的点云匹配 参考


import open3d as o3d
import numpy as np
import copy

def draw_registration_result_original_color(source, target, transformation):
    source_temp = copy.deepcopy(source)
    source_temp.transform(transformation)
    o3d.visualization.draw_geometries([source_temp, target],
                                      zoom=0.5,
                                      front=[-0.2458, -0.8088, 0.5342],
                                      lookat=[1.7745, 2.2305, 0.9787],
                                      up=[0.3109, -0.5878, -0.7468])
print("1. Load two point clouds and show initial pose")
demo_colored_icp_pcds = o3d.data.DemoColoredICPPointClouds()
source = o3d.io.read_point_cloud(demo_colored_icp_pcds.paths[0])
target = o3d.io.read_point_cloud(demo_colored_icp_pcds.paths[1])

# draw initial alignment
current_transformation = np.identity(4)
draw_registration_result_original_color(source, target, current_transformation)

# point to plane ICP
current_transformation = np.identity(4)
print("2. Point-to-plane ICP registration is applied on original point")
print("   clouds to refine the alignment. Distance threshold 0.02.")
result_icp = o3d.pipelines.registration.registration_icp(
    source, target, 0.02, current_transformation,
    o3d.pipelines.registration.TransformationEstimationPointToPlane())
print(result_icp)
draw_registration_result_original_color(source, target,
                                        result_icp.transformation)

# colored pointcloud registration
# This is implementation of following paper
# J. Park, Q.-Y. Zhou, V. Koltun,
# Colored Point Cloud Registration Revisited, ICCV 2017
voxel_radius = [0.04, 0.02, 0.01]
max_iter = [50, 30, 14]
current_transformation = np.identity(4)
print("3. Colored point cloud registration")
for scale in range(3):
    iter = max_iter[scale]
    radius = voxel_radius[scale]
    print([iter, radius, scale])

    print("3-1. Downsample with a voxel size %.2f" % radius)
    source_down = source.voxel_down_sample(radius)
    target_down = target.voxel_down_sample(radius)

    print("3-2. Estimate normal.")
    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))

    print("3-3. Applying colored point cloud registration")
    result_icp = o3d.pipelines.registration.registration_colored_icp(
        source_down, target_down, radius, current_transformation,
        o3d.pipelines.registration.TransformationEstimationForColoredICP(),
        o3d.pipelines.registration.ICPConvergenceCriteria(relative_fitness=1e-6,
                                                          relative_rmse=1e-6,
                                                          max_iteration=iter))
    current_transformation = result_icp.transformation
    print(result_icp)
draw_registration_result_original_color(source, target,
                                        result_icp.transformation)





配准前:
在这里插入图片描述

基于点到平面icp的配准:
在这里插入图片描述

基于color icp的配准结果:
在这里插入图片描述

基于fpfh特征的点云匹配

import numpy as np
import copy
import open3d as o3d

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],
                                      zoom=0.4559,
                                      front=[0.6452, -0.3036, -0.7011],
                                      lookat=[1.9892, 2.0208, 1.8945],
                                      up=[-0.2779, -0.9482, 0.1556])
    
def preprocess_point_cloud(pcd, voxel_size):
    print(":: Downsample with a voxel size %.3f." % voxel_size)
    pcd_down = pcd.voxel_down_sample(voxel_size)

    radius_normal = voxel_size * 2
    print(":: Estimate normal with search radius %.3f." % radius_normal)
    pcd_down.estimate_normals(
        o3d.geometry.KDTreeSearchParamHybrid(radius=radius_normal, max_nn=30))

    radius_feature = voxel_size * 5
    print(":: Compute FPFH feature with search radius %.3f." % radius_feature)
    pcd_fpfh = o3d.pipelines.registration.compute_fpfh_feature(
        pcd_down,
        o3d.geometry.KDTreeSearchParamHybrid(radius=radius_feature, max_nn=100))
    return pcd_down, pcd_fpfh

def prepare_dataset(voxel_size):
    print(":: Load two point clouds and disturb initial pose.")

    demo_icp_pcds = o3d.data.DemoICPPointClouds()
    source = o3d.io.read_point_cloud(demo_icp_pcds.paths[0])
    target = o3d.io.read_point_cloud(demo_icp_pcds.paths[1])
    trans_init = np.asarray([[0.0, 0.0, 1.0, 0.0], [1.0, 0.0, 0.0, 0.0],
                             [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]])
    source.transform(trans_init)
    draw_registration_result(source, target, np.identity(4))

    source_down, source_fpfh = preprocess_point_cloud(source, voxel_size)
    target_down, target_fpfh = preprocess_point_cloud(target, voxel_size)
    return source, target, source_down, target_down, source_fpfh, target_fpfh

def execute_global_registration(source_down, target_down, source_fpfh,
                                target_fpfh, voxel_size):
    distance_threshold = voxel_size * 1.5
    print(":: RANSAC registration on downsampled point clouds.")
    print("   Since the downsampling voxel size is %.3f," % voxel_size)
    print("   we use a liberal distance threshold %.3f." % distance_threshold)
    result = o3d.pipelines.registration.registration_ransac_based_on_feature_matching(
        source_down, target_down, source_fpfh, target_fpfh, True,
        distance_threshold,
        o3d.pipelines.registration.TransformationEstimationPointToPoint(False),
        3, [
            o3d.pipelines.registration.CorrespondenceCheckerBasedOnEdgeLength(
                0.9),
            o3d.pipelines.registration.CorrespondenceCheckerBasedOnDistance(
                distance_threshold)
        ], o3d.pipelines.registration.RANSACConvergenceCriteria(100000, 0.999))
    return result

voxel_size = 0.05  # means 5cm for this dataset
source, target, source_down, target_down, source_fpfh, target_fpfh = prepare_dataset(
    voxel_size)

result_ransac = execute_global_registration(source_down, target_down,
                                            source_fpfh, target_fpfh,
                                            voxel_size)
print(result_ransac)
draw_registration_result(source_down, target_down, result_ransac.transformation)

匹配前:
在这里插入图片描述
匹配结果:
在这里插入图片描述

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

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

相关文章

深入探讨梯度下降:优化机器学习的关键步骤(一)

文章目录 🍀引言🍀什么是梯度下降?🍀损失函数🍀梯度(gradient)🍀梯度下降的工作原理🍀梯度下降的变种🍀随机梯度下降(SGD)🍀批量梯度下降&#xf…

添加YDNS免费的ipv6动态域名解析

背景 又到了一年一度的dns域名到期,寻找替代了,前几年用了阿里、华为的免费域名,支持了几个搭建在NAS上的微服务;一旦涉及到域名续费,价格就比首年上去了不少,所以,打算找个长期的免费域名。 搜…

在Windows 10上部署ChatGLM2-6B:掌握信息时代的智能对话

在Windows 10上部署ChatGLM2-6B:掌握信息时代的智能对话 硬件环境ChatGLM2-6B的量化模型最低GPU配置说明准备工作ChatGLM2-6B安装部署ChatGLM2-6B运行模式解决问题总结 随着当代科技的快速发展,我们进入了一个数字化时代,其中信息以前所未有的…

python数据分析基础—pandas中set_index()、reset_index()的使用

文章目录 一、索引是什么?二、set_index()三、reset_index() 一、索引是什么? 在进行数据分析时,通常我们要根据业务情况进行数据筛选,要求筛选特定情况的行或列,这时就要根据数据类型(Series或者DataFrame)的索引情况…

小苹果他爹V5.8版本最强小苹果影视盒子增加46条内置优质单仓线路

这款软件直接使用了俊版的小苹果接口,并且许多资源似乎都是直接调用的小苹果官方资源。这样一来,小苹果的作者可能会面临版权方面的问题,而且也让更多的用户对小苹果的收费模式产生质疑。在这个信息传播如此快速的时代,开发者们应…

816. 模糊坐标

816. 模糊坐标 原题链接:完成情况:解题思路:参考代码:错误经验吸取 原题链接: 模糊坐标 完成情况: 解题思路: 参考代码: package 西湖算法题解___中等题;import java.util.Arra…

公司文件防泄密系统——「天锐绿盾透明加密系统」

「天锐绿盾透明加密系统」是一种公司文件防泄密系统,从源头上保障数据安全和使用安全。该系统采用文件过滤驱动实现透明加解密,对用户完全透明,不影响用户操作习惯。 PC访问地址: isite.baidu.com/site/wjz012xr/2eae091d-1b97-4…

贝叶斯神经网络 - 捕捉现实世界的不确定性

贝叶斯神经网络 - 捕捉现实世界的不确定性 Bayesian Neural Networks 生活本质上是不确定性和概率性的,贝叶斯神经网络 (BNN) 旨在捕获和量化这种不确定性 在许多现实世界的应用中,仅仅做出预测是不够的;您还想知道您对该预测的信心有多大。例…

ARM Cortex-M 的 SP

文章目录 1、栈2、栈操作3、Cortex-M中的栈4、MDK中的SP操作流程5、Micro-Lib的SP差别1. 使用 Micro-Lib2. 未使用 Micro-Lib 在嵌入式开发中,堆栈是一个很基础,同时也是非常重要的名词,堆栈可分为堆 (Heap) 和栈 (Stack) 。 栈(Stack): 一种…

2010-2021年上市公司和讯网社会责任评级CSR数据/和讯网上市公司社会责任数据

2010-2021年上市公司和讯网社会责任评级CSR数据 1、时间:2010-2021年 2、指标:股票名称、股票代码、年份、总得分、等级、股东责任、员工责任、供应商、客户和消费者权益责任、环境责任、社会责任、所属年份 3、样本量:4万 4、来源&#…

网工内推 | 上市公司,IT工程师、服务器工程师,IP以上优先

01 烟台睿创微纳技术股份有限公司 招聘岗位:IT工程师 职责描述: 1、负责网络及安全架构的规划、设计、性能优化; 2、负责网络设备的安装、配置、管理、排错、维护,提供网络设备维护方案; 3、负责防火墙、上网行为管理…

微机原理 || 第7章:中断系统8259 经典例题+手写解析

学习这件事应该和少菲学习,她会一个问题一个问题挨个解决,不会磨磨唧唧的, 这也不想干,那也不想做,一事无成! 新的学期,预祝姝垚和少菲可以学习进步,生活愉快,事业有成&a…

lv3 嵌入式开发-3 linux shell命令(权限、输入输出)

1 Shell概述 随着各式Linux系统的图形化程度的不断提高,用户在桌面环境下,通过点击、拖拽等操作就可以完成大部分的工作。 然而,许多Ubuntu Linux功能使用shell命令来实现,要比使用图形界面交互,完成的更快、更直接。…

系列五、Java操作RocketMQ简单消息之同步消息

一、概述 同步消息的特征是消息发出后会有一个返回值,即RocketMQ服务器收到消息后的一个确认,这种方式非常安全,但是性能上却没有那么高,而且在集群模式下,也是要等到所有的从机都复制了消息以后才会返回,适…

【vue2第九章】组件化开发和根组件以及style上的scoped作用

组件化开发和根组件 什么是组件化开发? 一个页面可以拆分为多个组件,每个组件有自己的样式,结构,行为,组件化开发的好处就是,便于维护,利于重复利用,提升开发的效率。 便于维护&…

输出归一化位置式PID(完整梯形图代码)

SMART PLC单自由度和双自由度位置式PID的完整源代码,请参看下面文章链接: 位置式PID(S7-200SMART 单自由度、双自由度梯形图源代码)_RXXW_Dor的博客-CSDN博客有关位置型PID和增量型PID的更多详细介绍请参看PID专栏的相关文章,链接如下:SMART PLC增量型PID算法和梯形图代码…

已解决‘jupyter‘ 不是内部或外部命令,也不是可运行的程序或批处理文件报错

本文摘要:本文已解决‘jupyter‘ 不是内部或外部命令,也不是可运行的程序或批处理文件的相关报错问题,并系统性地总结提出了几种可用解决方案。同时结合人工智能GPT排除可能得隐患及错误。 😎 作者介绍:我是程序员洲洲…

利用frps搭建本地自签名https服务的透传

nginx的搭建就不介绍了,教程很多,基本上油手就会。 在本例中,frp服务器的域名是 www.yourfrp.com,同时也是反向代理nginx服务器; 本地网站要用的域名: test.abcd.com 请事先将 test.abcd.com 解析到 frp所在服务器…

java.sql.SQLException: com.mysql.cj.jdbc.Driver

这篇文章分享一下Springboot整合Elasticsearch时遇到的一个问题,项目正常启动,但是查询数据库的时候发生了一个异常java.sql.SQLException: com.mysql.cj.jdbc.Driver java.sql.SQLException: com.mysql.cj.jdbc.Driverat com.alibaba.druid.util.JdbcU…

docker-compose 部署 Seata整合nacos,Postgresql 为DB存储

docker-compose 部署 Seata整合nacos,Postgresql 为DB存储 环境 详情环境可参考 https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E 我这里 <spring.cloud.alibaba-version>2021.1</spring.cloud.alibaba-version>所…