描述

  1. 背景:曲面重建技术在数据可视化、机器视觉、虚拟现实等领域中有着广泛应用。例如在汽车、航空等工业领域,一些复杂外形产品的设计仍需要根据手工模型,利用逆向工程的手段建立产品的数字模型;在医学和定制生产领域,可以根据测量数据去建立人体等计算机模型;除此之外RGBD相机在娱乐领域,也让人们开始用点云去处理对象。

  2. 定义:曲面重建技术是将点云数据转换为三角网格模型的过程。它是计算机视觉领域中的一个重要问题,应用广泛。曲面重建技术可以用于三维建模、医学图像处理、机器人导航等领域。

  3. 分类:在点云数据中,每个点都有其位置和属性信息。曲面重建技术的目标是从这些点中提取出一个连续的曲面,以便于后续的处理和分析。曲面重建技术可以分为基于点的算法和基于图的算法两种。其中,基于点的算法主要是通过对点云数据进行插值或拟合来得到曲面,是完全通过原始数据点的一种表现形式;基于图的算法则是通过对点云数据进行分割和连接来得到曲面,是利用重建曲面对原始点数据的一种逼近。

PCL凹包计算

PCL库中有一个surface模块,可以对三维点云进行曲面重建。其中ConcaveHull类实现了创建点云的凹多边形的算法。

#include <iostream>
#include <pcl/common/common.h>
#include <pcl/point_types.h>
#include <pcl/io/ply_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/surface/concave_hull.h>

int main(int argc, char** argv) {
    std::string file_name = "../data/bunny.ply";
    pcl::PointCloud<pcl::PointXYZ> ::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>());
    pcl::io::loadPLYFile<pcl::PointXYZ>(file_name, *cloud);

    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_boundary(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::ConcaveHull<pcl::PointXYZ> concave;
    concave.setInputCloud(cloud);
    concave.setAlpha(0.01);
    std::vector<pcl::Vertices> vertices;
    concave.reconstruct(*cloud_boundary, vertices);

    drawPointCloud(cloud_boundary, "alpha boundary points");

    return 0;
}

reconstruct函数提取了凹多边形的顶点,cloud_boundary记录了所有顶点,构成凹多边形的是一系列三角形,vertices记录了每个三角形的三个顶点在原始点云的点云索引。使用时这样来取值vertices[index].vertices[0/1/2],index代表第几个三角形,0、1、2表示三个顶点

设置alpha可以改变凹多边形的细分程度

当设置为0.5时,点云的凹多边形边界点较少

当设置为0.05时,点云的凹多边形边界点增多,能明显看出点云的外形

当设置为0.01时,点云的凹多边形边界点个数基本与点云原始数据一致

基于形状的点云排序

当我们描述了点云的形状后,有时需要基于形状对点云进行重排序。举个例子,当空间中存在一个任意位姿的香蕉时,我希望能得到一个按照香蕉长度方向,从香蕉头到香蕉尾的排序结果。仅仅利用x、y或者z的值大小,是不能实现正确排序的。

设计了一种方法,计算点云的特征值和特征向量。随后我们将点云进行变换,变换到其特征方向上。变换后的点云,其三维值代表着其在整个形状的位置。这时我们就可以对变换后的点云执行排序或其他操作了。对于排序后的结果,我们还可以利用逆变换,变换回点云的原始位置

  • 基于PCL的操作
#include <pcl/point_types.h>
#include <pcl/common/centroid.h>
#include <pcl/common/eigen.h>
#include <pcl/common/pca.h>
#include <pcl/common/transforms.h>
#include <pcl/common/distances.h>
#include <pcl/segmentation/extract_clusters.h>
#include <pcl/search/kdtree.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <pcl/segmentation/region_growing.h> 
#include <pcl/filters/voxel_grid.h>
#include <pcl/features/normal_3d.h> 

// Load the point cloud
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
for (int i = 0; i < points.size(); i++) {
    pcl::PointXYZ point;
    point.x = points[i].x;
    point.y = points[i].y;
    point.z = 0;
    cloud->points.push_back(point);
}

// Eigen::Vector4f pcaCentroid; // 点云的质心
pcl::compute3DCentroid(*cloud, pcaCentroid); // 计算质心
Eigen::Matrix3f covariance; // 协方差矩阵
pcl::computeCovarianceMatrixNormalized(*cloud, pcaCentroid, covariance);
Eigen::SelfAdjointEigenSolver<Eigen::Matrix3f> eigen_solver(covariance, Eigen::ComputeEigenvectors);
Eigen::Matrix3f eigenVectorsPCA = eigen_solver.eigenvectors(); // 特征向量
Eigen::Vector3f eigenValuesPCA = eigen_solver.eigenvalues(); // 特征值

Eigen::Matrix4f tm = Eigen::Matrix4f::Identity();
Eigen::Matrix4f tm_inv = Eigen::Matrix4f::Identity();
tm.block<3, 3>(0, 0) = eigenVectorsPCA.transpose(); // R
tm.block<3, 1>(0, 3) = -1.0f * (eigenVectorsPCA.transpose()) *(pcaCentroid.head<3>());//  -R*t
tm_inv = tm.inverse();

pcl::PointCloud<pcl::PointXYZ>::Ptr transformedCloud(new pcl::PointCloud<pcl::PointXYZ>);
transformedCloud = cloud;
pcl::transformPointCloud(*cloud, *transformedCloud, tm); // 原始点云变换到特征点云

for (int i = 0; i < transformedCloud->points.size(); i++) {
    points[i].x = transformedCloud->points[i].z;
    points[i].y = transformedCloud->points[i].y;
}

std::sort(points.begin(), points.end(), [&](const cv::Point2f& p1, const cv::Point2f& p2) {
    if (p1.x != p2.x) {
    return p1.x < p2.x;
    } else {
    return p1.y < p2.y;
    }
});

// 省略,对排序后的点云做一些操作,操作后的点云下面叫做 process_points

 // Load the point cloud
 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_sorted (new pcl::PointCloud<pcl::PointXYZ>);
 for (int i = 0; i < process_points.size(); i++) {
     pcl::PointXYZ point;
     point.x = 0;
     point.y = process_points[i].y;
     point.z = process_points[i].x;
     cloud_sorted->points.push_back(point);
 }
 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_sorted_trans(new pcl::PointCloud<pcl::PointXYZ>);
 cloud_sorted_trans = cloud_sorted;
 pcl::transformPointCloud(*cloud_sorted, *cloud_sorted_trans, tm_inv); // 原始点云变换到特征点云

for (int i = 0; i < cloud_sorted->points.size(); i++) {
    process_points[i].x = cloud_sorted_trans->points[i].x;
    process_points[i].y = cloud_sorted_trans->points[i].y;
}

// process_points就是根据特征方向排序后,同时执行完所需操作的点
  • 基于Opencv的操作
    同样的操作也可以使用Opencv库来操作,下面是一个小例子
#include <iostream>
#include <vector>
#include <opencv2/core.hpp>

int main() {
    std::vector<cv::Point2f> points = {{1, 2}, {3, 4}, {5, 6}};

    // Convert the points to a matrix
    cv::Mat pointsMat(points.size(), 2, CV_64F);
    for (int i = 0; i < points.size(); i++) {
        pointsMat.at<double>(i, 0) = points[i].x;
        pointsMat.at<double>(i, 1) = points[i].y;
    }

    // Calculate the covariance matrix
    cv::Mat covMat, mean;
    cv::calcCovarMatrix(pointsMat, covMat, mean, cv::COVAR_NORMAL | cv::COVAR_ROWS);

    // Calculate the eigenvalues and eigenvectors
    cv::Mat eigenvalues, eigenvectors;
    cv::eigen(covMat, eigenvalues, eigenvectors);
    std::cout << "Covariance matrix:\n" << covMat << std::endl;
    std::cout << "Eigenvalues:\n" << eigenvalues << std::endl;
    std::cout << "Eigenvectors:\n" << eigenvectors << std::endl;

    // Create the transformation matrix
    cv::Mat transform = eigenvectors.t();

    // Transform the points
    std::vector<cv::Point2f> transformed_points;
    cv::transform(points, transformed_points, transform);

    // Print the original and transformed points
    for (int i = 0; i < points.size(); i++) {
        std::cout << "Original point: (" << points[i].x << ", " << points[i].y << ")" << std::endl;
        std::cout << "Transformed point: (" << transformed_points[i].x << ", " << transformed_points[i].y << ")" << std::endl;
    }

    std::sort(transformed_points.begin(), transformed_points.end(), [&](const cv::Point2f& p1, const cv::Point2f& p2) {
        if (p1.x != p2.x) {
            return p1.x < p2.x;
        } else {
            return p1.y < p2.y;
        }
    });

    // Create the inverse transformation matrix
    cv::Mat inverse_transform = transform.t();

    // Transform the points back to their original coordinate system
    std::vector<cv::Point2f> transformed_back_points;
    cv::transform(transformed_points, transformed_back_points, inverse_transform);

    // Print the original and transformed-back points
    for (int i = 0; i < points.size(); i++) {
        std::cout << "Original point: (" << points[i].x << ", " << points[i].y << ")" << std::endl;
        std::cout << "Transformed-back point: (" << transformed_back_points[i].x << ", " << transformed_back_points[i].y << ")" << std::endl;
    }

    return 0;
}