04-cloudcompare算法处理

1. 滤波算法

1.1 SOR滤波(Statistical Outlier Removal)

文件位置 : CloudCompare/qCC\mainwindow.cpp (行5783-5900)

数学原理:

  1. 对每个点计算到其K个最近邻的平均距离 d_mean
  2. 计算所有点的距离均值 μ 和标准差 σ
  3. 移除满足条件的点: d_mean > μ + n_sigma × σ

核心实现:

cpp 复制代码
// 调用CCCoreLib的SOR滤波
CCCoreLib::ReferenceCloud* selection =
    CCCoreLib::CloudSamplingTools::sorFilter(
        cloud,
        s_sorFilterKnn,      // K近邻数量(默认6)
        s_sorFilterNSigma,   // 标准差倍数(默认1.0)
        cloud->getOctree().data(),
        &pDlg
    );

参数说明:

  • KNN : 近邻点数量,默认6
    • 太小:容易误删
    • 太大:计算慢,可能保留噪声
  • nSigma : 标准差倍数,默认1.0
    • 越小:过滤越严格
    • 越大:保留更多点

使用示例:

cpp 复制代码
// 1. 显示对话框获取参数
ccSORFilterDlg dlg(parent);
if (dlg.exec() == QDialog::Rejected)
    return;

int knn = dlg.knnSpinBox->value();
double nSigma = dlg.nSigmaDoubleSpinBox->value();

// 2. 应用SOR滤波
for (ccPointCloud* cloud : selectedClouds) {
    CCCoreLib::ReferenceCloud* selection =
        CCCoreLib::CloudSamplingTools::sorFilter(
            cloud, knn, nSigma,
            cloud->getOctree().data(),
            progressDialog
        );

    if (selection) {
        // 3. 创建过滤后的点云
        ccPointCloud* cleanCloud = cloud->partialClone(selection);
        cleanCloud->setName(cloud->getName() + ".clean");

        // 4. 添加到场景
        mainWindow->addToDB(cleanCloud);
    }
}

算法复杂度 : O(N × K × log(N)) (N为点数,K为近邻数)


1.2 噪声滤波(Noise Filter)

文件位置 : ccEntityAction.cpp

支持两种模式:

模式1: 基于半径
cpp 复制代码
// 在固定半径内搜索邻域
parameters.useKnn = false;
parameters.radius = 0.1;  // 半径值
模式2: 基于KNN
cpp 复制代码
// 搜索K个最近邻
parameters.useKnn = true;
parameters.knn = 6;  // 近邻数量

可选参数:

cpp 复制代码
struct NoiseFilterParameters {
    bool useKnn;              // 使用KNN还是半径
    union {
        double radius;        // 半径
        unsigned knn;         // K近邻数
    };

    // 过滤条件
    double absError;          // 绝对误差阈值
    double nSigma;            // 相对误差(标准差倍数)
    bool removeIsolatedPoints; // 移除孤立点
};

实现原理:

  1. 对每个点,搜索邻域
  2. 计算点到邻域中心的距离 d
  3. 计算邻域距离的标准差 σ
  4. 如果 d > absError + nSigma × σ,标记为噪声

2. 配准算法

2.1 ICP (Iterative Closest Point)

文件位置:

  • CloudCompare/qCC\ccRegistrationTools.h/cpp
  • CloudCompare/qCC\ccLibAlgorithms.cpp (行596-835)

核心实现:

cpp 复制代码
bool ccRegistrationTools::ICP(
    ccHObject* data,           // 数据云(要对齐的)
    ccHObject* model,          // 模型云(参考)
    ccGLMatrix& transMat,      // 输出:变换矩阵
    double& finalScale,        // 输出:最终尺度
    double& finalRMS,          // 输出:最终RMS误差
    unsigned& finalPointCount, // 输出:最终点数
    const CCCoreLib::ICPRegistrationTools::Parameters& inputParameters,
    bool useDataSFAsWeights,   // 数据标量场作为权重
    bool useModelSFAsWeights,  // 模型标量场作为权重
    QWidget* parent
)

ICP参数结构:

cpp 复制代码
struct Parameters {
    // 收敛类型
    enum ConvergenceMethod {
        MAX_ERROR_CONVERGENCE,   // 最大误差收敛
        MAX_ITER_CONVERGENCE     // 最大迭代次数收敛
    };
    ConvergenceMethod convType;

    // 收敛参数
    double minRMSDecrease;       // 最小RMS减少量
    unsigned nbMaxIterations;    // 最大迭代次数

    // 变换参数
    bool adjustScale;            // 是否调整尺度
    unsigned samplingLimit;      // 采样点数限制(默认50000)
    double finalOverlapRatio;    // 最终重叠率(0-1)

    // 变换过滤器
    enum TransformationFilters {
        SKIP_NONE = 0,
        SKIP_ROTATION = 1,
        SKIP_TRANSLATION = 2,
        SKIP_ROTATION_TRANS = 3
    };
    int transformationFilters;

    // 法向量匹配
    enum NormalsMatching {
        NO_NORMAL = 0,
        OPPOSITE_NORMALS = 1,
        DOUBLE_SIDED_NORMALS = 2
    };
    int normalsMatching;
};

算法流程:

复制代码
┌─────────────────────────────────┐
│ 1. 预处理阶段                   │
│  ├─ 如果模型是网格,采样点      │
│  ├─ 创建临时标量场              │
│  └─ 计算八叉树                  │
└─────────┬───────────────────────┘
          ↓
┌─────────────────────────────────┐
│ 2. 重叠区域估计                 │
│  ├─ 计算近似距离                │
│  ├─ 对距离排序                  │
│  └─ 确定最大搜索距离            │
└─────────┬───────────────────────┘
          ↓
┌─────────────────────────────────┐
│ 3. 迭代配准                     │
│  ├─ 查找最近邻                  │
│  ├─ 计算变换矩阵                │
│  ├─ 应用变换                    │
│  ├─ 计算RMS误差                 │
│  └─ 检查收敛                    │
└─────────┬───────────────────────┘
          ↓
┌─────────────────────────────────┐
│ 4. 输出结果                     │
│  ├─ 变换矩阵                    │
│  ├─ RMS误差                     │
│  ├─ 最终点数                    │
│  └─ 最终尺度                    │
└─────────────────────────────────┘

重叠区域估计 (关键优化):

cpp 复制代码
// 计算近似距离以确定最大重叠距离
int gridLevel = log10(max(dataSize, modelSize)) + 2;

CCCoreLib::DistanceComputationTools::computeApproxCloud2CloudDistance(
    dataCloud, modelCloud, gridLevel);

// 对距离排序,选择对应重叠率的点
ParallelSort(distances.begin(), distances.end());
double margin = 0.1;  // 10%边际
unsigned count = distances.size();
maxSearchDist = distances[count * (finalOverlapRatio + margin) - 1];

迭代配准核心:

cpp 复制代码
CCCoreLib::ICPRegistrationTools::Register(
    modelCloud,          // 参考云
    modelMesh,           // 参考网格(可选)
    dataCloud,           // 数据云
    params,              // 参数
    transform,           // 输出:变换矩阵
    finalRMS,            // 输出:RMS误差
    finalPointCount      // 输出:点数
);

输出变换矩阵:

cpp 复制代码
// CCCoreLib::PointProjectionTools::Transformation
struct Transformation {
    SquareMatrixd R;  // 旋转矩阵(3×3)
    CCVector3d T;     // 平移向量
    double s;         // 尺度因子
};

// 转换为ccGLMatrix
transMat = FromCCLibMatrix<double, float>(
    transform.R, transform.T, transform.s);
finalScale = transform.s;

典型用法:

cpp 复制代码
// 1. 设置参数
CCCoreLib::ICPRegistrationTools::Parameters params;
params.convType = MAX_ERROR_CONVERGENCE;
params.minRMSDecrease = 1e-5;
params.nbMaxIterations = 20;
params.adjustScale = false;
params.samplingLimit = 50000;
params.finalOverlapRatio = 1.0;
params.transformationFilters = SKIP_NONE;
params.normalsMatching = NO_NORMAL;

// 2. 执行ICP
ccGLMatrix transMat;
double finalScale, finalRMS;
unsigned finalPointCount;

bool success = ccRegistrationTools::ICP(
    dataCloud, modelCloud,
    transMat, finalScale, finalRMS, finalPointCount,
    params, false, false, parent
);

// 3. 应用变换
if (success) {
    dataCloud->applyGLTransformation_recursive(&transMat);
    ccLog::Print(QString("ICP converged: RMS=%1, Points=%2")
                .arg(finalRMS).arg(finalPointCount));
}

2.2 尺度匹配算法

文件位置 : ccLibAlgorithms.cpp (行596-835)

支持的方法:

cpp 复制代码
enum ScaleMatchingAlgorithm {
    BB_MAX_DIM,      // 包围盒最大维度
    BB_VOLUME,       // 包围盒体积
    PCA_MAX_DIM,     // PCA主成分最大维度
    ICP_SCALE        // 基于ICP的尺度估计
};

实现示例 - BB_MAX_DIM:

cpp 复制代码
double ComputeScale_BB_MAX_DIM(
    ccHObject* entities1,
    ccHObject* entities2)
{
    ccBBox box1 = entities1->getOwnBB();
    ccBBox box2 = entities2->getOwnBB();

    CCVector3 diag1 = box1.getDiagVec();
    CCVector3 diag2 = box2.getDiagVec();

    double maxDim1 = std::max(diag1.x, std::max(diag1.y, diag1.z));
    double maxDim2 = std::max(diag2.x, std::max(diag2.y, diag2.z));

    return maxDim2 / maxDim1;
}

3. 分割算法

3.1 RANSAC形状检测

插件位置 : CloudCompare/plugins\core\Standard\qRANSAC_SD/

支持的几何图元:

  1. 平面 (Plane)
  2. 球体 (Sphere)
  3. 圆柱 (Cylinder)
  4. 圆锥 (Cone)
  5. 圆环 (Torus)

核心参数:

cpp 复制代码
struct RansacParams {
    double epsilon;              // 点到形状的最大距离
    double bitmapEpsilon;        // 采样分辨率
    double maxNormalDev_deg;     // 最大法向量偏差(度)
    double probability;          // 概率阈值(默认0.01)
    unsigned supportPoints;      // 最小支撑点数(默认500)

    // 形状约束
    bool enablePlane;
    bool enableSphere;
    double minSphereRadius, maxSphereRadius;
    bool enableCylinder;
    double minCylinderRadius, maxCylinderRadius;
    double minCylinderLength, maxCylinderLength;
    bool enableCone;
    double maxConeAngle_deg;
    double minConeRadius, maxConeRadius;
    double minConeLength, maxConeLength;
    bool enableTorus;
    double minTorusMinorRadius, maxTorusMinorRadius;
    double minTorusMajorRadius, maxTorusMajorRadius;
};

检测流程:

cpp 复制代码
// 1. 初始化RANSAC检测器
RansacShapeDetector detector(params);
detector.Add(new PlanePrimitiveShapeConstructor());
detector.Add(new SpherePrimitiveShapeConstructor());
detector.Add(new CylinderPrimitiveShapeConstructor());
detector.Add(new ConePrimitiveShapeConstructor());
detector.Add(new TorusPrimitiveShapeConstructor());

// 2. 执行检测
MiscLib::Vector<std::pair<MiscLib::RefCountPtr<PrimitiveShape>, size_t>> shapes;
size_t remainingPoints = detector.Detect(
    *cloud,
    0,                  // 起始索引
    cloud->size(),      // 结束索引
    shapes              // 输出:检测到的形状
);

// 3. 处理检测结果
for (const auto& shapePair : shapes) {
    PrimitiveShape* shape = shapePair.first;
    size_t pointCount = shapePair.second;

    // 根据形状类型创建CloudCompare对象
    if (dynamic_cast<PlanePrimitiveShape*>(shape)) {
        ccPlane* plane = CreatePlane(shape, pointCount);
        container->addChild(plane);
    }
    else if (dynamic_cast<SpherePrimitiveShape*>(shape)) {
        ccSphere* sphere = CreateSphere(shape, pointCount);
        container->addChild(sphere);
    }
    // ... 其他形状类型
}

RANSAC原理:

  1. 随机选择最小点集(例如3个点定义平面)
  2. 拟合形状参数
  3. 计算内点数量(距离<epsilon的点)
  4. 重复N次,选择内点最多的形状
  5. 提取内点,从点云中移除
  6. 重复直到剩余点数<minPoints

迭代次数估算:

复制代码
N = log(1 - probability) / log(1 - w^s)

其中:
- probability: 至少找到一个好模型的概率
- w: 内点比例(估计值)
- s: 最小点集大小(平面3,球4,圆柱7等)

3.2 区域生长分割

基于连通性的分割,常用于分离独立对象。

核心算法:

cpp 复制代码
// 使用CCCoreLib的连通组件算法
CCCoreLib::ReferenceCloudContainer components;
int componentCount = CCCoreLib::AutoSegmentationTools::labelConnectedComponents(
    cloud,
    octreeLevel,     // 八叉树层级
    false,           // 使用6连通还是26连通
    &progressDialog,
    octree
);

// 为每个连通组件创建子云
for (int i = 0; i < componentCount; ++i) {
    CCCoreLib::ReferenceCloud* component = components[i];
    ccPointCloud* subCloud = cloud->partialClone(component);
    subCloud->setName(QString("Component_%1").arg(i + 1));
    container->addChild(subCloud);
}

4. 法向量计算

4.1 法向量估计方法

文件位置 : CloudCompare/qCC\ccEntityAction.cpp (行2075-2370)

支持的局部模型:

cpp 复制代码
enum LOCAL_MODEL_TYPES {
    LS = 0,      // 最小二乘平面拟合(最常用)
    QUADRIC = 1, // 二次曲面拟合(更准确,但慢)
    TRI = 2      // 三角化方法
};

计算方法:

方法1: 基于八叉树
cpp 复制代码
bool success = cloud->computeNormalsWithOctree(
    model,               // LS/QUADRIC/TRI
    preferredOrientation,
    defaultRadius,       // 邻域半径
    &progressDialog
);

数学原理(LS平面拟合):

  1. 对每个点P,搜索半径R内的邻域点 {P₁, P₂, ..., Pₙ}

  2. 计算邻域中心:

    复制代码
    C = (1/n) Σ Pᵢ
  3. 构建协方差矩阵:

    复制代码
    M = (1/n) Σ (Pᵢ - C)(Pᵢ - C)ᵀ
  4. 计算M的特征值和特征向量:

    复制代码
    M·v = λ·v
  5. 法向量 = 最小特征值对应的特征向量

代码实现:

cpp 复制代码
// CCCoreLib/GeometricalAnalysisTools.cpp
bool ComputeCharactersitic(
    GeomCharacteristic c,
    int subOption,
    ccGenericPointCloud* cloud,
    PointCoordinateType radius,
    const CCVector3* roughnessUpDir,
    GenericProgressCallback* progressCb,
    DgmOctree* inputOctree)
{
    // 为每个点计算
    for (unsigned i = 0; i < pointCount; ++i) {
        const CCVector3* P = cloud->getPoint(i);

        // 搜索邻域
        DgmOctree::NeighboursSet neighbours;
        octree->getPointsInSphericalNeighbourhood(*P, radius, neighbours);

        // 计算中心
        CCVector3 C(0, 0, 0);
        for (auto& n : neighbours) {
            C += *cloud->getPoint(n.pointIndex);
        }
        C /= neighbours.size();

        // 构建协方差矩阵
        SquareMatrixd covMat(3);
        for (auto& n : neighbours) {
            CCVector3d d = CCVector3d(*cloud->getPoint(n.pointIndex)) - C;
            for (int row = 0; row < 3; ++row) {
                for (int col = 0; col < 3; ++col) {
                    covMat.m_values[row][col] += d.u[row] * d.u[col];
                }
            }
        }
        covMat /= neighbours.size();

        // 计算特征值和特征向量
        SquareMatrixd eigVectors(3);
        std::vector<double> eigValues;
        if (Jacobi<double>::ComputeEigenValuesAndVectors(
                covMat, eigVectors, eigValues, true)) {

            // 法向量 = 最小特征值的特征向量
            int minEigIndex = 0;
            if (eigValues[1] < eigValues[minEigIndex]) minEigIndex = 1;
            if (eigValues[2] < eigValues[minEigIndex]) minEigIndex = 2;

            CCVector3 N(
                eigVectors.m_values[0][minEigIndex],
                eigVectors.m_values[1][minEigIndex],
                eigVectors.m_values[2][minEigIndex]
            );

            // 存储法向量
            cloud->addNorm(N);
        }
    }
}
方法2: 基于扫描网格
cpp 复制代码
bool success = cloud->computeNormalsWithGrids(
    minGridAngle_deg,    // 网格三角形最小角度(默认1度)
    &progressDialog,
    preferredOrientation
);

适用场景: TLS(地基激光扫描)数据,已有结构化网格


4.2 法向量半径自动估计

文件位置 : ccOctree.cpp

cpp 复制代码
struct BestRadiusParams {
    int aimedPopulationPerCell = 16;    // 目标单元格点数
    int aimedPopulationRange = 4;       // 点数范围
    int minCellPopulation = 6;          // 最小单元格点数
    double minAboveMinRatio = 0.97;     // 高于最小值的比例
};

PointCoordinateType ccOctree::GuessBestRadius(
    ccGenericPointCloud* cloud,
    const BestRadiusParams& params,
    ccOctree::Shared octree,
    GenericProgressCallback* progressCb)
{
    // 1. 随机采样最多200个点
    unsigned sampleCount = std::min(cloud->size(), 200u);
    std::vector<unsigned> sampleIndexes;
    for (unsigned i = 0; i < sampleCount; ++i) {
        sampleIndexes.push_back(rand() % cloud->size());
    }

    // 2. 对每个采样点测试不同半径
    std::vector<double> testedRadii;
    double minRadius = cloud->getOwnBB().getDiagNorm() * 0.001;
    double maxRadius = cloud->getOwnBB().getDiagNorm() * 0.1;
    for (double r = minRadius; r <= maxRadius; r *= 1.5) {
        testedRadii.push_back(r);
    }

    // 3. 统计每个半径下的邻域点数分布
    std::map<double, std::vector<int>> radiusToNeighbourCounts;
    for (double radius : testedRadii) {
        std::vector<int> counts;
        for (unsigned idx : sampleIndexes) {
            DgmOctree::NeighboursSet neighbours;
            octree->getPointsInSphericalNeighbourhood(
                *cloud->getPoint(idx), radius, neighbours);
            counts.push_back(neighbours.size());
        }
        radiusToNeighbourCounts[radius] = counts;
    }

    // 4. 选择满足统计要求的最小半径
    for (double radius : testedRadii) {
        const auto& counts = radiusToNeighbourCounts[radius];

        // 计算有多少点的邻域点数在目标范围内
        int validCount = 0;
        for (int count : counts) {
            if (count >= params.aimedPopulationPerCell - params.aimedPopulationRange &&
                count <= params.aimedPopulationPerCell + params.aimedPopulationRange) {
                validCount++;
            }
        }

        // 检查是否满足条件
        double validRatio = static_cast<double>(validCount) / counts.size();
        if (validRatio >= params.minAboveMinRatio) {
            return radius;  // 返回第一个满足条件的半径
        }
    }

    // 5. 如果没有找到合适的半径,返回中间值
    return testedRadii[testedRadii.size() / 2];
}

4.3 法向量方向调整

支持的方向调整方法:

方法1: 基于扫描网格
cpp 复制代码
cloud->orientNormalsWithGrids();

使用结构化扫描数据确定法向量方向(朝向传感器)

方法2: 基于传感器
cpp 复制代码
cloud->orientNormalsTowardViewPoint(sensorPosition);

所有法向量朝向给定视点

方法3: 首选方向
cpp 复制代码
enum Orientation {
    PLUS_X, MINUS_X,
    PLUS_Y, MINUS_Y,
    PLUS_Z, MINUS_Z,
    PLUS_SENSOR_ORIGIN,
    MINUS_SENSOR_ORIGIN,
    UNDEFINED
};

cloud->orientNormalsWithFM(preferredOrientation);
方法4: 最小生成树(MST)
cpp 复制代码
cloud->orientNormalsWithMST(
    mstNeighbors,  // 邻居数量(默认6)
    &progressDialog
);

MST原理:

  1. 构建KNN图(每个点连接到K个最近邻)
  2. 边权重 = 法向量夹角
  3. 计算最小生成树
  4. 从根节点开始传播方向(确保相邻法向量方向一致)

5. 几何特征计算

5.1 支持的特征类型

文件位置 : CloudCompare/qCC\ccLibAlgorithms.cpp (行168-421)

特征枚举:

cpp 复制代码
namespace CCCoreLib {
namespace GeometricalAnalysisTools {

enum GeomCharacteristic {
    // ===== 特征值分析 =====
    Feature_EigenValuesSum,      // λ₁ + λ₂ + λ₃
    Feature_Omnivariance,        // ³√(λ₁·λ₂·λ₃)
    Feature_EigenEntropy,        // -Σ λᵢ·ln(λᵢ)
    Feature_Anisotropy,          // (λ₁ - λ₃) / λ₁
    Feature_Planarity,           // (λ₂ - λ₃) / λ₁
    Feature_Linearity,           // (λ₁ - λ₂) / λ₁
    Feature_PCA1,                // λ₁ / (λ₁ + λ₂ + λ₃)
    Feature_PCA2,                // λ₂ / (λ₁ + λ₂ + λ₃)
    Feature_SurfaceVariation,    // λ₃ / (λ₁ + λ₂ + λ₃)
    Feature_Sphericity,          // λ₃ / λ₁
    Feature_Verticality,         // 1 - |N·Z|
    Feature_EigenValue1,         // λ₁
    Feature_EigenValue2,         // λ₂
    Feature_EigenValue3,         // λ₃

    // ===== 曲率 =====
    Curvature_GAUSSIAN,          // 高斯曲率 κ₁·κ₂
    Curvature_MEAN,              // 平均曲率 (κ₁+κ₂)/2
    Curvature_NORMAL_CHANGE_RATE, // 法向量变化率

    // ===== 密度 =====
    Density_KNN,                 // KNN密度
    Density_2D,                  // 2D表面密度
    Density_3D,                  // 3D体密度

    // ===== 其他 =====
    Roughness,                   // 粗糙度
    MomentOrder1                 // 一阶矩
};

}}

5.2 计算流程

cpp 复制代码
bool ComputeGeomCharacteristic(
    CCCoreLib::GeometricalAnalysisTools::GeomCharacteristic c,
    int subOption,
    PointCoordinateType radius,
    ccHObject::Container& entities,
    const CCVector3* roughnessUpDir,
    QWidget* parent,
    ccProgressDialog* progressDialog)
{
    for (ccHObject* entity : entities) {
        ccPointCloud* pc = ccHObjectCaster::ToPointCloud(entity);
        if (!pc) continue;

        // 1. 创建标量场
        QString sfName = GetScalarFieldName(c);
        int sfIdx = pc->addScalarField(sfName.toStdString());
        pc->setCurrentScalarField(sfIdx);

        // 2. 计算八叉树
        ccOctree::Shared octree = pc->getOctree();
        if (!octree) {
            octree = pc->computeOctree(progressDialog);
        }

        // 3. 调用核心算法
        bool success =
            CCCoreLib::GeometricalAnalysisTools::ComputeCharactersitic(
                c,
                subOption,
                pc,
                radius,
                roughnessUpDir,
                progressDialog,
                octree.data()
            );

        if (success) {
            // 4. 更新显示
            pc->setCurrentDisplayedScalarField(sfIdx);
            pc->showSF(true);
            pc->getCurrentInScalarField()->computeMinAndMax();

            // 5. 粗糙度特殊处理(对称色标)
            if (c == Roughness && roughnessUpDir) {
                pc->getCurrentInScalarField()->setSymmetricalScale(true);
            }
        }
    }

    return true;
}

5.3 特征值分析示例

计算Planarity(平面性):

cpp 复制代码
// 对每个点
for (unsigned i = 0; i < pointCount; ++i) {
    // 1. 搜索邻域
    DgmOctree::NeighboursSet neighbours;
    octree->getPointsInSphericalNeighbourhood(*P, radius, neighbours);

    // 2. 计算协方差矩阵
    SquareMatrixd covMat = ComputeCovarianceMatrix(neighbours);

    // 3. 计算特征值
    std::vector<double> eigValues;
    ComputeEigenValues(covMat, eigValues);

    // 按降序排序: λ₁ ≥ λ₂ ≥ λ₃
    std::sort(eigValues.begin(), eigValues.end(), std::greater<double>());

    // 4. 计算平面性
    double planarity = (eigValues[1] - eigValues[2]) / eigValues[0];

    // 5. 存储到标量场
    sf->setValue(i, planarity);
}

物理意义:

  • Planarity ≈ 1: 点在平面上(屋顶、墙壁)
  • Planarity ≈ 0: 点在线或体积中(边缘、散乱点)

6. 距离计算

6.1 Cloud-to-Cloud距离

对话框 : CloudCompare/qCC\ccComparisonDlg.h

比较类型:

cpp 复制代码
enum CC_COMPARISON_TYPE {
    CLOUDCLOUD_DIST = 0,  // 点云到点云
    CLOUDMESH_DIST = 1    // 点云到网格
};

核心功能:

cpp 复制代码
// 近似距离计算(快速)
int octreeLevel = ccComparisonDlg::getBestOctreeLevel(
    comparedCloud, referenceCloud);

CCCoreLib::DistanceComputationTools::computeApproxCloud2CloudDistance(
    comparedCloud,
    referenceCloud,
    octreeLevel,
    maxSearchDist,
    &progressDialog,
    comparedOctree.data()
);

精确距离计算(慢但准确):

cpp 复制代码
int result = CCCoreLib::DistanceComputationTools::computeCloud2CloudDistances(
    comparedCloud,
    referenceCloud,
    params,
    &progressDialog,
    comparedOctree.data(),
    referenceOctree.data()
);

6.2 M3C2算法(多尺度模型到模型云比较)

插件位置 : CloudCompare/plugins\core\Standard\qM3C2/

文献 : Lague, D., Brodu, N. and Leroux, J., 2013

"Accurate 3D comparison of complex topography with terrestrial laser scanner"

核心参数:

cpp 复制代码
struct M3C2Params {
    // 投影参数
    PointCoordinateType projectionRadius;   // 投影半径
    PointCoordinateType projectionDepth;    // 投影深度

    // 法向量参数
    bool updateNormal;                      // 更新法向量
    bool exportNormal;                      // 导出法向量

    // 统计参数
    bool useMedian;                         // 使用中位数(更鲁棒)
    bool computeConfidence;                 // 计算置信区间
    unsigned minPoints4Stats;               // 统计最小点数(默认3)

    // 搜索参数
    bool progressiveSearch;                 // 渐进式搜索(自适应深度)
    bool onlyPositiveSearch;                // 仅正向搜索

    // 配准参数
    double registrationRms;                 // 配准RMS误差
};

输出标量场:

cpp 复制代码
static const char M3C2_DIST_SF_NAME[] = "M3C2 distance";
static const char DIST_UNCERTAINTY_SF_NAME[] = "distance uncertainty";
static const char SIG_CHANGE_SF_NAME[] = "significant change";
static const char STD_DEV_CLOUD1_SF_NAME[] = "StdDev_cloud1";
static const char STD_DEV_CLOUD2_SF_NAME[] = "StdDev_cloud2";
static const char DENSITY_CLOUD1_SF_NAME[] = "Npoints_cloud1";
static const char DENSITY_CLOUD2_SF_NAME[] = "Npoints_cloud2";

M3C2距离计算:

cpp 复制代码
bool ComputeM3C2DistForPoint(
    const CCVector3& P,          // 核心点
    const CCVector3& N,          // 法向量
    ccPointCloud* cloud1,        // 云1
    ccPointCloud* cloud2,        // 云2
    const M3C2Params& params,
    double& dist,                // 输出:M3C2距离
    double& LOD)                 // 输出:检测限(Level of Detection)
{
    // 1. 提取圆柱形邻域(云1)
    CCCoreLib::DgmOctree::ProgressiveCylindricalNeighbourhood cn1;
    cn1.center = P;
    cn1.dir = N;
    cn1.level = level1;
    cn1.maxHalfLength = params.projectionDepth;
    cn1.radius = params.projectionRadius;

    unsigned n1 = octree1->getPointsInCylindricalNeighbourhood(cn1);
    if (n1 < params.minPoints4Stats) {
        return false;  // 点数不足
    }

    // 2. 计算统计量(云1)
    double mean1, stdDev1;
    qM3C2Tools::ComputeStatistics(
        cn1.neighbours,
        params.useMedian,
        mean1,      // 输出:沿法向量的平均位置
        stdDev1     // 输出:标准差
    );

    // 3. 提取圆柱形邻域(云2)
    CCCoreLib::DgmOctree::ProgressiveCylindricalNeighbourhood cn2;
    cn2.center = P + N * mean1;  // 从云1的平均位置开始
    cn2.dir = N;
    cn2.level = level2;
    cn2.maxHalfLength = params.projectionDepth;
    cn2.radius = params.projectionRadius;

    unsigned n2 = octree2->getPointsInCylindricalNeighbourhood(cn2);
    if (n2 < params.minPoints4Stats) {
        return false;
    }

    // 4. 计算统计量(云2)
    double mean2, stdDev2;
    qM3C2Tools::ComputeStatistics(
        cn2.neighbours,
        params.useMedian,
        mean2,
        stdDev2
    );

    // 5. M3C2距离
    dist = mean2 - mean1;

    // 6. 距离不确定性(LOD - Level of Detection)
    double LODStdDev;
    if (params.usePrecisionMaps) {
        // 使用精度图
        LODStdDev = stdDev1 * stdDev1 + stdDev2 * stdDev2;
    } else {
        // 标准M3C2
        LODStdDev = (stdDev1 * stdDev1) / n1 + (stdDev2 * stdDev2) / n2;
    }

    // 95%置信区间
    LOD = 1.96 * (sqrt(LODStdDev) + params.registrationRms);

    return true;
}

显著性变化检测:

cpp 复制代码
// 如果距离大于LOD,认为是显著变化
bool significant = (dist < -LOD || dist > LOD);

// 存储到标量场
sigChangeSF->setValue(pointIndex, significant ? 1.0f : 0.0f);

渐进式搜索优化:

cpp 复制代码
// 自适应调整圆柱深度,直到有足够的点或达到最大深度
while (cn1.currentHalfLength < cn1.maxHalfLength) {
    unsigned n = octree->getPointsInCylindricalNeighbourhoodProgressive(cn1);

    if (n >= params.minPoints4Stats) {
        ComputeStatistics(cn1.neighbours, params.useMedian, mean1, stdDev1);

        // 检查是否足够精确(均值±2σ < 当前深度)
        if (abs(mean1) + 2 * stdDev1 < cn1.currentHalfLength) {
            break;  // 足够精确,停止搜索
        }
    }

    // 扩大搜索深度
    cn1.currentHalfLength *= 1.5;
}

7. 重采样和子采样

7.1 子采样方法

文件位置 : CloudCompare/qCC\ccSubsamplingDlg.cpp

支持的方法:

cpp 复制代码
enum CC_SUBSAMPLING_METHOD {
    RANDOM = 0,         // 随机采样
    RANDOM_PERCENT = 1, // 百分比随机采样
    SPATIAL = 2,        // 空间采样(泊松盘)
    OCTREE = 3          // 八叉树采样
};
方法1: 随机采样
cpp 复制代码
CCCoreLib::ReferenceCloud* subsampledCloud =
    CCCoreLib::CloudSamplingTools::subsampleCloudRandomly(
        cloud,
        count,        // 要保留的点数
        progressCb
    );

实现: 随机洗牌,取前N个点

方法2: 百分比随机采样
cpp 复制代码
unsigned count = cloud->size() * (percentage / 100.0);
CCCoreLib::ReferenceCloud* subsampledCloud =
    CCCoreLib::CloudSamplingTools::subsampleCloudRandomly(
        cloud, count, progressCb);
方法3: 空间采样(泊松盘)
cpp 复制代码
// 基于最小点间距的泊松盘采样
PointCoordinateType minDist = 0.01;  // 最小点间距

// 可选:标量场调制
CCCoreLib::CloudSamplingTools::SFModulationParams modParams;
modParams.enabled = true;
modParams.a = (sfMaxSpacing - sfMinSpacing) / deltaSF;
modParams.b = sfMinSpacing - modParams.a * sfMin;

CCCoreLib::ReferenceCloud* subsampledCloud =
    CCCoreLib::CloudSamplingTools::resampleCloudSpatially(
        cloud,
        minDist,
        modParams,
        octree,
        progressCb
    );

数学原理:

  • 基于泊松盘采样
  • 可选标量场调制: spacing(p) = a × SF(p) + b
  • 保证任意两点间距 ≥ minDist
  • 优先保留特征区域的点(通过标量场)
方法4: 八叉树采样
cpp 复制代码
unsigned char level = 7;  // 八叉树层级(1-21)

CCCoreLib::ReferenceCloud* subsampledCloud =
    CCCoreLib::CloudSamplingTools::subsampleCloudWithOctreeAtLevel(
        cloud,
        level,
        NEAREST_POINT_TO_CELL_CENTER,  // 采样策略
        progressCb,
        octree
    );

采样策略:

  • NEAREST_POINT_TO_CELL_CENTER: 选择最接近单元中心的点
  • RANDOM_POINT: 随机选择一个点

层级vs分辨率:

复制代码
层级1: 1个单元
层级2: 8个单元
层级3: 64个单元
...
层级N: 8^(N-1)个单元

8. 八叉树和空间索引

8.1 八叉树结构

类定义 : CloudCompare/libs\qCC_db\include\ccOctree.h

cpp 复制代码
class ccOctree : public QObject, public CCCoreLib::DgmOctree {
public:
    // ===== 构建与更新 =====
    static Shared ComputeOctree(
        ccGenericPointCloud* cloud,
        GenericProgressCallback* progressCb = nullptr
    );

    // ===== 包围盒操作 =====
    void multiplyBoundingBox(PointCoordinateType multFactor);
    void translateBoundingBox(const CCVector3& T);
    ccBBox getSquareBB() const;
    ccBBox getPointsBB() const;

    // ===== 显示模式 =====
    enum DisplayMode {
        WIRE = 0,        // 线框
        MEAN_POINTS = 1, // 平均点
        MEAN_CUBES = 2   // 实体立方体
    };

    // ===== 绘制 =====
    void draw(CC_DRAW_CONTEXT& context);
};

8.2 邻域搜索

球形邻域搜索
cpp 复制代码
DgmOctree::NeighboursSet neighbours;
unsigned count = octree->getPointsInSphericalNeighbourhood(
    *queryPoint,
    radius,
    neighbours
);

// 处理结果
for (const auto& n : neighbours) {
    unsigned pointIndex = n.pointIndex;
    double squaredDist = n.squareDistd;
    // ...
}
圆柱形邻域搜索(M3C2专用)
cpp 复制代码
CCCoreLib::DgmOctree::CylindricalNeighbourhood cn;
cn.center = P;
cn.dir = N;
cn.radius = 0.1;
cn.maxHalfLength = 1.0;
cn.level = 7;

unsigned count = octree->getPointsInCylindricalNeighbourhood(cn);
KNN搜索
cpp 复制代码
unsigned K = 10;
DgmOctree::NeighboursSet neighbours;
unsigned count = octree->findPointNeighbourhood(
    *queryPoint,
    &neighbours,
    K,
    octreeLevel
);

8.3 点拾取

cpp 复制代码
bool ccOctree::pointPicking(
    const CCVector2d& clickPos,      // 鼠标点击位置(屏幕坐标)
    const ccGLCameraParameters& camera, // 相机参数
    PointDescriptor& output,         // 输出:拾取的点信息
    double pickWidth_pix = 3.0       // 拾取宽度(像素)
) const
{
    // 1. 构建拾取射线
    CCVector3d rayOrigin, rayDir;
    camera.unproject(clickPos, rayOrigin, rayDir);

    // 2. 遍历八叉树单元
    // 3. 对射线附近的点进行投影距离检测
    // 4. 返回最近的点

    return found;
}

免责声明

本文档仅供技术学习和交流使用,内容基于作者对CloudCompare开源项目的个人理解和分析整理。

重要提示

  • 本文档不代表CloudCompare官方文档或权威说明
  • 文档内容为个人学习整理,仅供参考
  • CloudCompare是开源项目,遵循GPL v2+许可证,版权归原作者所有
  • 实际使用CloudCompare时应参考官方文档:https://github.com/CloudCompare/CloudCompare
  • 文档内容可能存在理解偏差或过时信息,欢迎指正
  • 因使用本文档产生的任何后果由使用者自行承担
相关推荐
点云SLAM7 个月前
PCL点云库点云数据处理入门系列教材目录(2025年5月更新....)
计算机视觉·slam·点云配准·点云数据处理·pcl点云库·点云分割·点云识别
LiDAR点云1 年前
open3d:随机采样一致性分割平面
ransac·open3d·点云分割