几何尺寸与公差论坛

 找回密码
 注册
查看: 148|回复: 4

点云数据配准

  [复制链接]
发表于 2025-5-9 16:54:12 | 显示全部楼层 |阅读模式
ICP (Iterative Closest Point) 算法是点云数据配准中的经典算法,它通过迭代地寻找最优的刚性变换(平移和旋转),使得两个点云之间的距离最小化。其核心思想是在每次迭代中:

    对源点云中的每个点,找到其在目标点云中最接近的点(最近邻点)。

    计算刚性变换(平移和旋转),使得源点云尽可能与目标点云对齐。

    使用新的变换进行源点云的更新,然后继续下一轮迭代,直到收敛。

ICP 算法的步骤

    初始化:给定源点云和目标点云。初始化变换矩阵,通常为单位矩阵。

    匹配最近邻点:对于源点云中的每个点,找到目标点云中最接近的点。

    计算刚性变换:根据匹配的最近邻点,计算一个平移和旋转变换。

    更新源点云:使用计算出的变换更新源点云。

    判断收敛:检查变换是否足够小,若满足收敛条件则终止,否则返回第2步继续迭代。

下面是一个简化版的 ICP 算法的 C++ 实现,可以用于点云数据匹配。
C++ ICP 算法实现

#include <iostream>
#include <vector>
#include <cmath>
#include <Eigen/Dense>
#include <Eigen/Geometry>  // For transformations and rotations

// 计算点到点的欧几里得距离
double euclidean_distance(const Eigen::Vector3d& p1, const Eigen::Vector3d& p2) {
    return (p1 - p2).norm();
}

// 寻找源点云中每个点在目标点云中的最近邻点
void find_correspondences(const std::vector<Eigen::Vector3d>& source,
                          const std::vector<Eigen::Vector3d>& target,
                          std::vector<int>& correspondences) {
    for (size_t i = 0; i < source.size(); ++i) {
        double min_distance = std::numeric_limits<double>::max();
        int nearest_index = -1;
        for (size_t j = 0; j < target.size(); ++j) {
            double dist = euclidean_distance(source[i], target[j]);
            if (dist < min_distance) {
                min_distance = dist;
                nearest_index = j;
            }
        }
        correspondences[i] = nearest_index;
    }
}

// 计算刚性变换:平移和旋转矩阵
void compute_transformation(const std::vector<Eigen::Vector3d>& source,
                            const std::vector<Eigen::Vector3d>& target,
                            const std::vector<int>& correspondences,
                            Eigen::Matrix3d& rotation, Eigen::Vector3d& translation) {
    Eigen::MatrixXd A(3, 3); // 用于计算旋转矩阵
    Eigen::Vector3d B(0, 0, 0); // 用于计算平移向量
    Eigen::Vector3d center_source(0, 0, 0);
    Eigen::Vector3d center_target(0, 0, 0);

    // 计算源点云和目标点云的质心
    for (size_t i = 0; i < source.size(); ++i) {
        center_source += source[i];
        center_target += target[correspondences[i]];
    }
    center_source /= source.size();
    center_target /= source.size();

    // 计算协方差矩阵
    Eigen::MatrixXd H = Eigen::MatrixXd::Zero(3, 3);
    for (size_t i = 0; i < source.size(); ++i) {
        Eigen::Vector3d source_vec = source[i] - center_source;
        Eigen::Vector3d target_vec = target[correspondences[i]] - center_target;
        H += source_vec * target_vec.transpose();
    }

    // 计算旋转矩阵
    Eigen::JacobiSVD<Eigen::MatrixXd> svd(H, Eigen::ComputeThinU | Eigen::ComputeThinV);
    rotation = svd.matrixV() * svd.matrixU().transpose();

    // 计算平移向量
    translation = center_target - rotation * center_source;
}

// ICP 算法
void icp(const std::vector<Eigen::Vector3d>& source,
         const std::vector<Eigen::Vector3d>& target,
         int max_iterations = 50, double tolerance = 1e-6) {
    std::vector<Eigen::Vector3d> transformed_source = source;
    Eigen::Matrix3d rotation = Eigen::Matrix3d::Identity();
    Eigen::Vector3d translation = Eigen::Vector3d::Zero();

    for (int iteration = 0; iteration < max_iterations; ++iteration) {
        std::vector<int> correspondences(source.size(), -1);

        // Step 1: Find closest points (correspondences)
        find_correspondences(transformed_source, target, correspondences);

        // Step 2: Compute transformation (rotation and translation)
        Eigen::Matrix3d new_rotation;
        Eigen::Vector3d new_translation;
        compute_transformation(transformed_source, target, correspondences, new_rotation, new_translation);

        // Step 3: Apply the transformation to the source points
        for (size_t i = 0; i < transformed_source.size(); ++i) {
            transformed_source[i] = new_rotation * transformed_source[i] + new_translation;
        }

        // Check for convergence (translation and rotation change below tolerance)
        double delta = (new_translation - translation).norm() + (new_rotation - rotation).norm();
        if (delta < tolerance) {
            std::cout << "Converged in " << iteration + 1 << " iterations." << std::endl;
            break;
        }

        // Update rotation and translation
        rotation = new_rotation;
        translation = new_translation;
    }

    std::cout << "Final rotation: \n" << rotation << std::endl;
    std::cout << "Final translation: \n" << translation.transpose() << std::endl;
}

int main() {
    // Example source and target point clouds (2D for simplicity, can be extended to 3D)
    std::vector<Eigen::Vector3d> source = {
        Eigen::Vector3d(1.0, 1.0, 0.0),
        Eigen::Vector3d(2.0, 2.0, 0.0),
        Eigen::Vector3d(3.0, 3.0, 0.0)
    };
    std::vector<Eigen::Vector3d> target = {
        Eigen::Vector3d(2.0, 2.0, 0.0),
        Eigen::Vector3d(3.0, 3.0, 0.0),
        Eigen::Vector3d(4.0, 4.0, 0.0)
    };

    // Perform ICP
    icp(source, target);

    return 0;
}

解释

    计算欧几里得距离 (euclidean_distance):
    计算源点云中的每个点和目标点云中的最近点之间的距离。

    找到最近邻点 (find_correspondences):
    对于源点云中的每个点,找到目标点云中最接近的点,并记录其索引。

    计算刚性变换 (compute_transformation):
    通过源点云和目标点云之间的最近邻对应点,计算旋转矩阵和平移向量。这一步通常通过奇异值分解(SVD)来得到最优的旋转矩阵。

    ICP算法 (icp):
    在每次迭代中,更新源点云的位置,直到满足停止条件(例如,平移和旋转的变化小于某个阈值)。

    主函数:
    给出了一个简单的例子,源点云和目标点云都是二维的,可以轻松扩展到三维点云。

注意事项

    最近邻搜索:在实际应用中,寻找最近邻点通常会使用更高效的算法,如 k-d树 或 Octree,特别是当点云非常大时。

    初始化:ICP 算法对初始值敏感,良好的初始猜测能显著提高收敛速度。如果源点云和目标点云相差较大,可以考虑使用其他方法(如粗配准)来初始化变换。

    收敛性:ICP 算法可能会收敛到局部最优解,尤其是在初始配准较差时,因此可能需要合适的初始化或者使用多次尝试来避免陷入局部最小值。

总结

上述代码是一个基本的 ICP 算法 实现,能够处理点云数据的刚性配准。实际应用中,你可以扩展这个框架,增加更多的优化措施(如使用加权最近邻、数据预处理等)来提高性能和准确度。
 楼主| 发表于 2025-5-9 16:55:45 | 显示全部楼层
继续,pcl库有现成的算法吗?
 楼主| 发表于 2025-5-9 16:56:00 | 显示全部楼层
是的,PCL (Point Cloud Library) 提供了非常强大的工具和算法用于点云数据的处理,其中包括了 ICP 算法 的现成实现。PCL的ICP算法实现非常高效,并且提供了许多额外的功能,例如加速算法、不同类型的距离度量、加权点云配准等。
使用PCL库中的ICP算法

PCL库中提供了 pcl::IterativeClosestPoint 类来执行ICP算法。它可以用于刚性配准(旋转和翻译)两个点云,使得源点云与目标点云尽可能对齐。
步骤

    准备点云数据:首先需要加载或创建点云数据。

    应用ICP算法:通过 pcl::IterativeClosestPoint 类来进行点云配准。

    获取结果:获取配准后的点云。

下面是一个简化的示例,演示了如何使用 PCL 库来实现 ICP 算法的点云配准。
PCL ICP 算法示例

#include <iostream>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/registration/icp.h>
#include <pcl/filters/voxel_grid.h>

int main(int argc, char** argv)
{
    // 创建点云对象
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_source(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_target(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_final(new pcl::PointCloud<pcl::PointXYZ>);

    // 加载源点云和目标点云
    if (pcl::io::loadPCDFile<pcl::PointXYZ>("source.pcd", *cloud_source) == -1)
    {
        PCL_ERROR("Couldn't read source point cloud\n");
        return -1;
    }
    if (pcl::io::loadPCDFile<pcl::PointXYZ>("target.pcd", *cloud_target) == -1)
    {
        PCL_ERROR("Couldn't read target point cloud\n");
        return -1;
    }

    std::cout << "Source cloud size: " << cloud_source->points.size() << std::endl;
    std::cout << "Target cloud size: " << cloud_target->points.size() << std::endl;

    // 使用体素网格滤波器来减少点云的数量(可选)
    pcl::VoxelGrid<pcl::PointXYZ> sor;
    sor.setInputCloud(cloud_source);
    sor.setLeafSize(0.01f, 0.01f, 0.01f);  // 设置滤波器的网格大小
    sor.filter(*cloud_source);

    sor.setInputCloud(cloud_target);
    sor.filter(*cloud_target);

    std::cout << "Downsampled source cloud size: " << cloud_source->points.size() << std::endl;
    std::cout << "Downsampled target cloud size: " << cloud_target->points.size() << std::endl;

    // 创建ICP对象
    pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;
    icp.setMaximumIterations(50);  // 设置最大迭代次数
    icp.setMaxCorrespondenceDistance(0.05);  // 设置最大对应点距离
    icp.setTransformationEpsilon(1e-9);  // 设置收敛阈值
    icp.setEuclideanFitnessEpsilon(1);  // 设置欧几里得误差阈值

    // 设置输入点云
    icp.setInputSource(cloud_source);
    icp.setInputTarget(cloud_target);

    // 执行ICP算法
    icp.align(*cloud_final);

    std::cout << "Has converged: " << icp.hasConverged() << " with score: " << icp.getFitnessScore() << std::endl;
    std::cout << "Transformation matrix: \n" << icp.getFinalTransformation() << std::endl;

    // 保存配准后的点云
    pcl::io::savePCDFileASCII("aligned_cloud.pcd", *cloud_final);

    return 0;
}

代码说明

    加载点云数据:

if (pcl::io::loadPCDFile<pcl::PointXYZ>("source.pcd", *cloud_source) == -1)
if (pcl::io::loadPCDFile<pcl::PointXYZ>("target.pcd", *cloud_target) == -1)

这两行代码从PCD文件中加载源点云和目标点云。如果你没有点云数据,可以使用自己的数据文件,或者使用PCL提供的示例点云数据。

体素网格滤波:

pcl::VoxelGrid<pcl::PointXYZ> sor;
sor.setInputCloud(cloud_source);
sor.setLeafSize(0.01f, 0.01f, 0.01f);
sor.filter(*cloud_source);

在点云较大时,使用体素网格滤波器可以加速ICP算法的执行。通过减小点云的分辨率(通过 setLeafSize 方法)来减少计算量。

设置ICP算法参数:

icp.setMaximumIterations(50);
icp.setMaxCorrespondenceDistance(0.05);
icp.setTransformationEpsilon(1e-9);
icp.setEuclideanFitnessEpsilon(1);

这里设置了ICP的几个关键参数:

    setMaximumIterations(50):设置最大迭代次数为50次。

    setMaxCorrespondenceDistance(0.05):设置最大对应点的距离阈值,超过该距离的点将不被认为是匹配点。

    setTransformationEpsilon(1e-9):设置变换收敛的阈值。

    setEuclideanFitnessEpsilon(1):设置允许的误差范围。

执行ICP算法:

icp.align(*cloud_final);

执行ICP算法进行点云配准,将配准后的结果保存在 cloud_final 中。

获取结果:

std::cout << "Has converged: " << icp.hasConverged() << " with score: " << icp.getFitnessScore() << std::endl;

icp.hasConverged() 返回一个布尔值,表示算法是否已收敛。icp.getFitnessScore() 返回最终的配准误差,表示配准质量。

保存配准后的点云:

    pcl::io::savePCDFileASCII("aligned_cloud.pcd", *cloud_final);

    将配准后的点云保存为一个新的PCD文件。

ICP参数调优

    最大迭代次数 (setMaximumIterations):在实际应用中,可能需要增加最大迭代次数,以便获得更精确的结果。

    最大对应点距离 (setMaxCorrespondenceDistance):这是影响算法速度和质量的重要参数。如果源点云和目标点云之间的变换较大,则需要增大这个距离。

    欧几里得误差阈值 (setEuclideanFitnessEpsilon):当源点云和目标点云的距离变化小于该阈值时,算法会停止迭代。

结论

PCL 提供了非常高效和灵活的ICP算法实现,适用于大多数点云配准任务。通过调整参数,你可以根据具体应用场景(例如,点云密度、精度要求等)来优化ICP算法的表现。对于复杂的点云配准任务,PCL库也支持更多高级功能,如加权ICP、全局最优配准等。
 楼主| 发表于 2025-5-9 17:02:46 | 显示全部楼层
配准后的点云是什么东西?不会输出刚性变换矩阵吗?
 楼主| 发表于 2025-5-9 17:03:00 | 显示全部楼层
在ICP算法中,配准后的点云是指通过算法处理后,源点云与目标点云对齐(或尽可能对齐)的结果点云。具体来说,它是将源点云应用了刚性变换(旋转和平移)后的新点云。

而刚性变换矩阵(包括旋转矩阵和平移向量)是ICP算法优化过程中计算得到的,用于描述如何将源点云通过旋转和平移对齐到目标点云。
1. 配准后的点云

配准后的点云指的是经过ICP算法应用刚性变换后的源点云,它在空间中的位置已经调整,使其尽可能地与目标点云对齐。

    源点云(Source cloud):你最初输入的点云,通常是未经处理的点云。

    目标点云(Target cloud):你想要对齐的参考点云。

    配准后的源点云(Transformed cloud):经过ICP配准后,源点云的每个点都经过了旋转和平移,使它们尽量与目标点云的对应点对齐。

在PCL中,ICP算法会计算一个最优的刚性变换,并应用到源点云上,得到对齐后的点云。这个对齐后的点云会保存在cloud_final中。
2. 刚性变换矩阵

刚性变换矩阵(刚性变换 = 旋转 + 平移)是描述如何将源点云对齐到目标点云的变换信息。在PCL中,ICP计算完成后,你可以通过以下方式获取最终的变换矩阵:

Eigen::Matrix4f transformation = icp.getFinalTransformation();

这个 transformation 矩阵是一个 4x4 的刚性变换矩阵,其中包括旋转和平移:

    旋转部分:前 3x3 的部分表示旋转矩阵。

    平移部分:最后一列的前三个元素表示平移向量。

变换矩阵的构成

假设最终的变换矩阵是 T,它的结构如下:
T=[Rt01]
T=[R0&#8203;t1&#8203;]

其中:

    RR 是 3x3 的旋转矩阵,表示源点云需要旋转的角度。

    tt 是 3x1 的平移向量,表示源点云需要平移的位置。

    最后一行 [0, 0, 0, 1] 用于确保矩阵是4x4形式,适用于齐次坐标的运算。

获取旋转矩阵和平移向量

在 PCL 中,ICP 的变换矩阵是 Eigen::Matrix4f 类型。如果你需要单独获取旋转矩阵和/或平移向量,可以通过以下方式提取:

    旋转矩阵:

Eigen::Matrix3f rotation_matrix = transformation.block<3, 3>(0, 0);

平移向量:

    Eigen::Vector3f translation_vector = transformation.block<3, 1>(0, 3);

3. 如何使用刚性变换矩阵

有了变换矩阵后,你可以应用它来更新源点云的坐标,得到配准后的点云。例如:

pcl::transformPointCloud(*cloud_source, *cloud_final, transformation);

这行代码将刚性变换应用到源点云,并将结果保存到 cloud_final 中。
总结

    配准后的点云:是指源点云在应用了旋转和平移后,与目标点云对齐的点云。

    刚性变换矩阵:是描述如何将源点云旋转和平移到目标点云的位置。它包括一个旋转矩阵和一个平移向量。

    在PCL的ICP中,你可以通过 icp.getFinalTransformation() 获取最终的刚性变换矩阵,并且可以利用该矩阵来更新源点云的位置信息,从而得到配准后的点云。

如果你需要输出刚性变换矩阵,你可以直接通过 getFinalTransformation() 来获得它。如果你需要配准后的点云,则可以在ICP运行结束后访问 cloud_final 变量。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|Archiver|小黑屋|几何尺寸与公差论坛

GMT+8, 2025-5-17 11:59 , Processed in 0.039067 second(s), 20 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表