点云投影到拟合平面是将三维点云数据降维到二维平面的过程,核心思想是正交投影:
平面方程: a x + b y + c z + d = 0 ax + by + cz + d = 0 ax+by+cz+d=0
平面法向量: n = [ a b c ] T n = \begin{bmatrix} a & b & c \end{bmatrix}^T n=[abc]T
点投影公式:
proj p = p − n ⋅ ( p − p 0 ) ∥ n ∥ 2 n \text{proj}_p = p - \frac{n \cdot (p - p_0)}{\|n\|^2} n projp=p−∥n∥2n⋅(p−p0)n
其中 p p p 是原始点, p 0 p_0 p0 是平面上的参考点
for each point p in pointcloud:
v = p - p0 # 向量差
distance = dot(n, v) / norm(n) # 沿法向的距离
projected_point = p - distance * normalized(n)
类名 | 功能 | 常用方法 |
---|---|---|
pcl::ProjectInliers |
点云投影主类 | setInputCloud() , setModelType() , filter() |
pcl::SACSegmentation |
RANSAC平面拟合 | setModelType(SACMODEL_PLANE) , segment() |
pcl::ModelCoefficients |
模型参数存储 | values 向量属性 |
pcl::PointIndices |
内点索引 | indices 索引容器 |
#include
#include
#include
#include
#include // 投影滤波
#include
#include
using namespace std;
int main(int argc, char** argv)
{
// --------------------------------------读取点云-------------------------------------------
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>("E://data//Armadillo.pcd", *cloud) == -1)
{
PCL_ERROR("Could not read file\n");
}
cout << "读取点云个数: " << cloud->points.size() << endl;
//本例使用ax+by+cz+d=0的平面模型 创建一个系数为a=b==d=0,c=1的平面,也就是X-Y平面。Z轴相关的点全部投影在X-Y面上
pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients());
coefficients->values.resize(4);
coefficients->values[0] = coefficients->values[1] = 0;
coefficients->values[2] = 1.0;
coefficients->values[3] = 0;
// -----------------------------------创建滤波器对象------------------------------------
pcl::ProjectInliers<pcl::PointXYZ> proj;
proj.setModelType(pcl::SACMODEL_PLANE);
proj.setInputCloud(cloud);
proj.setModelCoefficients(coefficients);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_projected(new pcl::PointCloud<pcl::PointXYZ>);
proj.filter(*cloud_projected);
// -------------------------------------结果可视化--------------------------------------
boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("cloud show"));
int v1 = 0;
int v2 = 1;
viewer->createViewPort(0, 0, 0.5, 1, v1);
viewer->createViewPort(0.5, 0, 1, 1, v2);
viewer->setBackgroundColor(0, 0, 0, v1);
viewer->setBackgroundColor(0.05, 0, 0, v2);
//原始点云绿色
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> src_h(cloud, 0, 255, 0);
//投影后的点云
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> pro_cloud(cloud_projected, 0, 0, 255);
//viewer->setBackgroundColor(255, 255, 255);
viewer->addPointCloud(cloud, src_h, "cloud", v1);
viewer->addPointCloud(cloud_projected, pro_cloud, "pro_cloud", v2);
while (!viewer->wasStopped())
{
viewer->spinOnce(100);
boost::this_thread::sleep(boost::posix_time::microseconds(10000));
}
return 0;
}
参数名称 | 默认值 | 作用 | 推荐范围 |
---|---|---|---|
setDistanceThreshold() |
0.01 | 内点距离阈值 | 0.005-0.05 (米) |
setOptimizeCoefficients() |
false | 系数优化开关 | 建议true |
setMaxIterations() |
(自动) | RANSAC最大迭代次数 | 500-5000 |
setProbability() |
0.99 | 模型置信概率 | 0.95-0.99 |
void multiPlaneProjection(
pcl::PointCloud<pcl::PointXYZ>::Ptr input,
pcl::PointCloud<pcl::PointXYZ>::Ptr output)
{
std::vector<pcl::PointIndices> cluster_indices = estimateClusters(input);
output->reserve(input->size());
for (const auto& indices : cluster_indices) {
// 提取当前簇
pcl::PointCloud<pcl::PointXYZ>::Ptr cluster(new pcl::PointCloud<pcl::PointXYZ>);
pcl::copyPointCloud(*input, indices, *cluster);
// 拟合簇的最佳模型
auto cluster_type = detectGeometryType(cluster);
if (cluster_type == PLANAR) {
pcl::PointCloud<pcl::PointXYZ>::Ptr projected(new pcl::PointCloud<pcl::PointXYZ>);
projectToPlane(cluster, projected);
*output += *projected;
} else {
*output += *cluster; // 非平面保留原样
}
}
}
pcl::PointCloud<pcl::PointXYZ>::Ptr iterativeProjection(
pcl::PointCloud<pcl::PointXYZ>::Ptr input,
int iterations = 3,
float refinement_threshold = 0.005f)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr result(new pcl::PointCloud<pcl::PointXYZ>);
pcl::copyPointCloud(*input, *result);
for (int i = 0; i < iterations; i++) {
// 步骤1: 拟合并投影
pcl::PointCloud<pcl::PointXYZ>::Ptr projected(new pcl::PointCloud<pcl::PointXYZ>);
projectToPlane(result, projected);
// 步骤2: 误差分析
float max_err = calculateMaxDeviation(result, projected);
if (max_err <= refinement_threshold) break;
// 步骤3: 加权重建 (原始点与投影点的加权平均)
for (size_t j = 0; j < result->size(); j++) {
float weight = min(0.5f, (max_err - 0.002f) * 100.0f);
const auto& rp = projected->points[j];
auto& p = result->points[j];
p.x = (1-weight)*p.x + weight*rp.x;
p.y = (1-weight)*p.y + weight*rp.y;
p.z = (1-weight)*p.z + weight*rp.z;
}
}
return result;
}
void constrainedProjection(
pcl::PointCloud<pcl::PointXYZ>::Ptr input,
const Eigen::Vector3f& normal_constraint,
pcl::PointCloud<pcl::PointXYZ>::Ptr output)
{
// 创建带约束的平面模型
pcl::SampleConsensusModelPerpendicularPlane<pcl::PointXYZ>::Ptr model(
new pcl::SampleConsensusModelPerpendicularPlane<pcl::PointXYZ>(input));
model->setAxis(normal_constraint); // 设置法线约束方向
model->setEpsAngle(0.1f); // 角度容差 (弧度)
// RANSAC拟合
pcl::RandomSampleConsensus<pcl::PointXYZ> ransac(model);
ransac.setDistanceThreshold(0.02); // 内点阈值
ransac.computeModel();
// 获取模型参数
Eigen::VectorXf coeffs;
ransac.getModelCoefficients(coeffs);
// 创建投影对象
pcl::ProjectInliers<pcl::PointXYZ> proj;
proj.setModelType(pcl::SACMODEL_PLANE);
proj.setInputCloud(input);
pcl::ModelCoefficients::Ptr model_coeffs(new pcl::ModelCoefficients);
model_coeffs->values.resize(4);
for (int i = 0; i < 4; i++) model_coeffs->values[i] = coeffs[i];
proj.setModelCoefficients(model_coeffs);
proj.filter(*output);
}
指标名称 | 计算方法 | 评估标准 |
---|---|---|
RMS误差 | 1 N ∑ ( p i − proj p ) 2 \sqrt{\frac{1}{N}\sum{(p_i - \text{proj}_p)^2}} N1∑(pi−projp)2 | <0.01m 优 |
最大偏差 | max ∣ p i − proj p ∣ \max|p_i - \text{proj}_p| max∣pi−projp∣ | <0.03m 优 |
平面平整度 | max proj z − min proj z \max\text{proj}_z - \min\text{proj}_z maxprojz−minprojz | <0.005m优 |
内点比例 | 拟合时的内点比例 | >90%优 |
void evaluateProjectionQuality(
pcl::PointCloud<pcl::PointXYZ>::Ptr original,
pcl::PointCloud<pcl::PointXYZ>::Ptr projected,
const pcl::ModelCoefficients::Ptr& coeffs)
{
if (original->size() != projected->size()) {
cerr << "点云大小不匹配!" << endl;
return;
}
float max_dist = 0.0f;
float mean_dist = 0.0f;
float rms_dist = 0.0f;
// 提取平面系数
float a = coeffs->values[0];
float b = coeffs->values[1];
float c = coeffs->values[2];
float d = coeffs->values[3];
// 遍历点对计算误差
for (size_t i = 0; i < original->size(); i++) {
const auto& o = original->points[i];
const auto& p = projected->points[i];
// 向量距离
float dx = o.x - p.x;
float dy = o.y - p.y;
float dz = o.z - p.z;
float dist3d = sqrt(dx*dx + dy*dy + dz*dz);
// 点到平面距离
float dist2plane = abs(a*o.x + b*o.y + c*o.z + d) / sqrt(a*a + b*b + c*c);
// 更新统计量
if (dist3d > max_dist) max_dist = dist3d;
mean_dist += dist3d;
rms_dist += dist2plane * dist2plane;
}
mean_dist /= original->size();
rms_dist = sqrt(rms_dist / original->size());
// 平整度评估 (使用投影后点云)
float min_z = std::numeric_limits<float>::max();
float max_z = std::numeric_limits<float>::min();
for (const auto& p : projected->points) {
if (p.z > max_z) max_z = p.z;
if (p.z < min_z) min_z = p.z;
}
// 输出报告
cout << "======== 投影质量评估报告 ========" << endl;
cout << "最大偏差: " << max_dist << " 米" << endl;
cout << "平均偏差: " << mean_dist << " 米" << endl;
cout << "RMS偏差: " << rms_dist << " 米" << endl;
cout << "点云平整度: " << max_z - min_z << " 米" << endl;
cout << "参与评估点数: " << original->size() << endl;
}
void checkPartFlatness(pcl::PointCloud<pcl::PointXYZ>::Ptr part_cloud) {
// 1. 平面拟合
auto [coefficients, inliers] = fitPlaneModel(part_cloud);
// 2. 点云投影
pcl::PointCloud<pcl::PointXYZ>::Ptr projected(new pcl::PointCloud<pcl::PointXYZ>);
projectToPlane(part_cloud, projected, coefficients);
// 3. 平整度分析
float min_z = std::numeric_limits<float>::max();
float max_z = std::numeric_limits<float>::min();
for (const auto& p : projected->points) {
if (p.z < min_z) min_z = p.z;
if (p.z > max_z) max_z = p.z;
}
// 4. 平整度检测标准判断
float flatness = max_z - min_z;
cout << "零件平整度: " << flatness * 1000 << " mm" << endl;
// 5. 根据标准值判断是否合格
if (flatness < 0.001) {
cout << "平整度满足要求" << endl;
} else {
cout << "平整度不合格!超过阈值" << endl;
// 标记不平区域用于质量改进
markUnevenRegions(projected);
}
}
DigitalElevationModel createDEM(
pcl::PointCloud<pcl::PointXYZ>::Ptr terrain_cloud)
{
// 1. 地面点分离
auto ground_cloud = extractGroundPoints(terrain_cloud);
// 2. 最佳拟合平面投影
pcl::PointCloud<pcl::PointXYZ>::Ptr projected(new pcl::PointCloud<pcl::PointXYZ>);
projectToPlane(ground_cloud, projected);
// 3. 创建高程模型
float resolution = 1.0; // 1米分辨率
DigitalElevationModel dem(resolution);
// 4. 填充DEM网格
for (const auto& p : projected->points) {
dem.addElevation(p.x, p.y, p.z);
}
// 5. 插值和平滑处理
dem.fillGaps();
dem.applySmoothFilter(3);
return dem;
}
void reconstructBuildingFacade(
pcl::PointCloud<pcl::PointXYZ>::Ptr facade_cloud)
{
// 1. 约束平面投影 (垂直平面)
Eigen::Vector3f vertical_vector(0, 0, 1); // Z轴方向
pcl::PointCloud<pcl::PointXYZ>::Ptr projected(new pcl::PointCloud<pcl::PointXYZ>);
constrainedProjection(facade_cloud, vertical_vector, projected);
// 2. 坐标变换到XY平面
Eigen::Affine3f transform = calculateAlignmentTransform(projected);
pcl::transformPointCloud(*projected, *projected, transform);
// 3. 创建二维网格模型
auto mesh = create2DMesh(projected);
// 4. 添加纹理 (如果有RGB信息)
if (has_color_data) {
applyTextureMapping(mesh, facade_cloud);
}
// 5. 输出重建模型
saveAsOBJ(mesh, "building_facade.obj");
}
问题现象 | 可能原因 | 解决方案 |
---|---|---|
投影后点云分散 | 平面拟合质量差 | 降低RANSAC距离阈值,增加迭代次数 |
投影效果不连续 | 多点云平面混合 | 先做平面分割再分别投影 |
投影方向错误 | 法线方向确定错误 | 添加法线方向约束 |
边缘区域扭曲 | 非平面区域 | 设置投影距离阈值setDistanceThreshold() |
平面拟合失败 | 点云无平面特征 | 检查输入点云,添加人工引导点 |
大点云处理慢 | 全局拟合策略 | 使用分块处理或近似算法 |
void handleNoisyData(pcl::PointCloud<pcl::PointXYZ>::Ptr noisy_cloud) {
// 方案1: 预处理滤波
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(noisy_cloud);
sor.setMeanK(50);
sor.setStddevMulThresh(1.0);
sor.filter(*noisy_cloud); // 去除离群点
// 方案2: 加权平面拟合
pcl::SampleConsensusModelWeightedPlane<pcl::PointXYZ>::Ptr model(
new pcl::SampleConsensusModelWeightedPlane<pcl::PointXYZ>(noisy_cloud));
// 为不同区域赋予不同权重
assignRegionWeights(model, noisy_cloud);
pcl::RandomSampleConsensus<pcl::PointXYZ> ransac(model);
ransac.computeModel();
// 使用加权模型进行投影...
}
void handleLargePointCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr large_cloud) {
// 方案1: 体素栅格下采样
pcl::PointCloud<pcl::PointXYZ>::Ptr downsampled(new pcl::PointCloud<pcl::PointXYZ>);
pcl::VoxelGrid<pcl::PointXYZ> vox;
vox.setInputCloud(large_cloud);
vox.setLeafSize(0.01f, 0.01f, 0.01f);
vox.filter(*downsampled);
// 方案2: 分块处理
std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> blocks = divideIntoBlocks(large_cloud);
pcl::PointCloud<pcl::PointXYZ>::Ptr result(new pcl::PointCloud<pcl::PointXYZ>);
for (auto& block : blocks) {
pcl::PointCloud<pcl::PointXYZ>::Ptr projected(new pcl::PointCloud<pcl::PointXYZ>);
projectToPlane(block, projected);
*result += *projected;
}
}
void visualizeProjection(
pcl::PointCloud<pcl::PointXYZ>::Ptr original,
pcl::PointCloud<pcl::PointXYZ>::Ptr projected)
{
// 创建可视化对象
pcl::visualization::PCLVisualizer viewer("点云投影可视化");
// 原始点云 (红色)
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>
orig_color(original, 255, 0, 0);
viewer.addPointCloud(original, orig_color, "original_cloud");
// 投影点云 (绿色)
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>
proj_color(projected, 0, 255, 0);
viewer.addPointCloud(projected, proj_color, "projected_cloud");
// 添加投影线
for (size_t i = 0; i < original->size(); i++) {
// 随机采样显示避免线太多
if (rand() % 100 > 5) continue;
std::string line_id = "line_" + std::to_string(i);
viewer.addLine(original->points[i], projected->points[i],
0.5, 0.5, 0.5, line_id);
}
// 添加平面模型
pcl::ModelCoefficients::Ptr coeffs = getPlaneFromCloud(projected);
viewer.addPlane(*coeffs, "fitted_plane");
// 启动交互式可视化
viewer.setBackgroundColor(0.05, 0.05, 0.05); // 深灰背景
viewer.addCoordinateSystem(0.5); // 0.5米坐标系
viewer.initCameraParameters();
viewer.spin();
}
void visualizeErrorHeatmap(
pcl::PointCloud<pcl::PointXYZ>::Ptr original,
pcl::PointCloud<pcl::PointXYZ>::Ptr projected)
{
// 创建带强度的点云
pcl::PointCloud<pcl::PointXYZI>::Ptr error_cloud(new pcl::PointCloud<pcl::PointXYZI>);
error_cloud->resize(original->size());
// 计算最大误差值
float max_err = 0.0f;
for (size_t i = 0; i < original->size(); i++) {
float dx = original->points[i].x - projected->points[i].x;
float dy = original->points[i].y - projected->points[i].y;
float dz = original->points[i].z - projected->points[i].z;
float dist = sqrt(dx*dx + dy*dy + dz*dz);
if (dist > max_err) max_err = dist;
}
// 填充点云并计算强度
for (size_t i = 0; i < original->size(); i++) {
pcl::PointXYZI p;
p.x = projected->points[i].x;
p.y = projected->points[i].y;
p.z = projected->points[i].z;
float dx = p.x - projected->points[i].x;
float dy = p.y - projected->points[i].y;
float dz = p.z - projected->points[i].z;
float dist = sqrt(dx*dx + dy*dy + dz*dz);
p.intensity = (dist / max_err) * 100; // 归一化到0-100
error_cloud->push_back(p);
}
// 可视化
pcl::visualization::PCLVisualizer viewer("投影误差热力图");
pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZI>
intensity_handler(error_cloud, "intensity");
viewer.addPointCloud(error_cloud, intensity_handler, "error_cloud");
// 添加颜色刻度条
viewer.addText("蓝色:低误差 红色:高误差", 10, 15, "error_scale");
viewer.setPointCloudRenderingProperties(
pcl::visualization::PCL_VISUALIZER_COLOR, 1.0, 0.0, 0.0, "error_scale");
viewer.spin();
}
本指南全面覆盖了点云平面投影的各个方面,从基础实现到高级应用,帮助您在三维数据处理中有效利用平面投影技术。