算法原理
这个算法是一种在网格表面上进行基于面积的随机采样方法。以下是该算法的步骤解析:
1. 计算三角形面积:
- 对于网格中的每个三角形,计算其面积。这有助于确定在每个三角形上采样的点数,面积越大的三角形将获得更多的采样点。
2. 确定每个三角形的采样点数:
- 对于每个三角形,根据其面积占整个网格总面积的比例来计算期望的采样点数。比如,如果一个三角形占据总面积的 10%,而目标是采样 100 个点,那么我们期望在这个三角形上采样大约 10 个点。
- 计算该期望值作为一个浮点数(例如 n=7.32 个点)。
3. 分离整数和小数部分:
- 使用 n 的整数部分(例如 7)在三角形上放置该数量的点。
- 对于小数部分(例如 0.32),将其视为一种概率。生成一个 0 到 1 的随机数,如果该数小于 0.32,则再增加一个点。这样,通过概率机制来加入小数点部分,从而平衡了采样精度和简单性。
4. 在三角形内进行随机采样:
- 对于每个要放置在三角形上的点,使用重心坐标(barycentric coordinates)在三角形内随机采样。
- 生成两个 0 到 1 之间的随机数 u 和 v。如果 u+v>1,将 u 和 v 分别变为 1−u 和 1−v 以确保采样点在三角形内。
- 使用这些坐标对三角形顶点进行线性插值,从而得到三角形内的一个采样点。
5. 对所有三角形重复以上步骤:
- 将以上步骤应用于网格中的每个三角形,并根据各自的面积调整采样点数。
算法实现
(1)采样总点数作为采样参数
可以通过 PCL 实现该算法,将 pcl::PolygonMesh
中的三角形面片进行采样。我们可以读取 pcl::PolygonMesh
中的顶点和面片,然后基于每个三角形的面积分布采样指定数量的点。以下是一个基于 PCL 的示例代码:
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/PolygonMesh.h>
#include <pcl/io/vtk_lib_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/common/geometry.h>
#include <random>
#include <iostream>
// 计算三角形面积
float calculateTriangleArea(const pcl::PointXYZ& p1, const pcl::PointXYZ& p2, const pcl::PointXYZ& p3) {
Eigen::Vector3f v1 = p2.getVector3fMap() - p1.getVector3fMap();
Eigen::Vector3f v2 = p3.getVector3fMap() - p1.getVector3fMap();
return 0.5f * v1.cross(v2).norm();
}
// 生成三角形内的随机点
pcl::PointXYZ samplePointInTriangle(const pcl::PointXYZ& p1, const pcl::PointXYZ& p2, const pcl::PointXYZ& p3) {
float u = static_cast<float>(rand()) / RAND_MAX;
float v = static_cast<float>(rand()) / RAND_MAX;
if (u + v > 1.0f) {
u = 1.0f - u;
v = 1.0f - v;
}
float w = 1.0f - u - v;
return pcl::PointXYZ(
u * p1.x + v * p2.x + w * p3.x,
u * p1.y + v * p2.y + w * p3.y,
u * p1.z + v * p2.z + w * p3.z
);
}
// 根据每个三角形的面积进行随机采样
pcl::PointCloud<pcl::PointXYZ>::Ptr samplePointsFromMesh(const pcl::PolygonMesh& mesh, int total_points) {
pcl::PointCloud<pcl::PointXYZ> cloud;
pcl::fromPCLPointCloud2(mesh.cloud, cloud);
// 遍历所有三角形并计算总面积
float total_area = 0.0f;
std::vector<float> triangle_areas;
for (const auto& polygon : mesh.polygons) {
if (polygon.vertices.size() != 3) continue; // 确保是三角形
const auto& p1 = cloud.points[polygon.vertices[0]];
const auto& p2 = cloud.points[polygon.vertices[1]];
const auto& p3 = cloud.points[polygon.vertices[2]];
float area = calculateTriangleArea(p1, p2, p3);
triangle_areas.push_back(area);
total_area += area;
}
// 初始化结果点云
pcl::PointCloud<pcl::PointXYZ>::Ptr sampled_points(new pcl::PointCloud<pcl::PointXYZ>);
// 在每个三角形上按比例采样
for (size_t i = 0; i < mesh.polygons.size(); ++i) {
if (mesh.polygons[i].vertices.size() != 3) continue; // 确保是三角形
const auto& p1 = cloud.points[mesh.polygons[i].vertices[0]];
const auto& p2 = cloud.points[mesh.polygons[i].vertices[1]];
const auto& p3 = cloud.points[mesh.polygons[i].vertices[2]];
// 计算应采样的点数
float expected_points = total_points * (triangle_areas[i] / total_area);
int n_points = static_cast<int>(std::floor(expected_points));
float fractional_part = expected_points - n_points;
// 根据小数部分的概率决定是否多采样一个点
if (static_cast<float>(rand()) / RAND_MAX < fractional_part) {
++n_points;
}
// 在三角形内采样点
for (int j = 0; j < n_points; ++j) {
sampled_points->points.push_back(samplePointInTriangle(p1, p2, p3));
}
}
sampled_points->width = sampled_points->points.size();
sampled_points->height = 1;
sampled_points->is_dense = true;
return sampled_points;
}
int main() {
// 加载 STL 文件并转换为 pcl::PolygonMesh
pcl::PolygonMesh mesh;
pcl::io::loadPolygonFileSTL("path/to/your/file.stl", mesh);
// 设定采样点的总数量
int total_points = 10000; // 可以根据需要调整
// 从网格中采样点
pcl::PointCloud<pcl::PointXYZ>::Ptr sampled_points = samplePointsFromMesh(mesh, total_points);
// 可视化采样后的点云
pcl::visualization::PCLVisualizer viewer("Sampled Points Viewer");
viewer.addPointCloud<pcl::PointXYZ>(sampled_points, "sampled points");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "sampled points");
viewer.spin();
return 0;
}
代码说明
-
计算三角形面积:
- 使用向量叉积计算每个三角形的面积,以确定在每个三角形上应采样的点数。
-
生成三角形内随机点:
- 使用重心坐标在三角形内随机采样一个点,确保采样点均匀分布在三角形表面上。
-
按面积比例采样:
- 根据每个三角形的面积相对于网格总面积的比例,计算该三角形应采样的点数
expected_points
。 - 将
expected_points
的整数部分作为基础采样点数,并根据小数部分作为概率,决定是否多采样一个点。
- 根据每个三角形的面积相对于网格总面积的比例,计算该三角形应采样的点数
-
生成结果点云:
- 将采样的点添加到
sampled_points
点云中。
- 将采样的点添加到
-
可视化:
- 使用
PCLVisualizer
显示采样后的点云。
- 使用
注意事项
- 随机数生成器:为增加随机数生成的灵活性,可以考虑使用
std::mt19937
生成器代替rand()
。 - 采样数量调整:
total_points
可以调整,控制采样的总点数。
效果
(2)采样密度作为采样参数
如果要使用采样密度(点数/单位面积)来控制采样点的数量,可以按照以下思路修改代码:
- 设定采样密度:定义每单位面积的采样点数。
- 根据面积计算采样点数:对于每个三角形,根据其面积和设定的采样密度来计算采样点数。
- 按面积采样点数采样:在每个三角形内按计算的点数进行随机采样。
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/PolygonMesh.h>
#include <pcl/io/vtk_lib_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/common/geometry.h>
#include <random>
#include <iostream>
// 计算三角形面积
float calculateTriangleArea(const pcl::PointXYZ& p1, const pcl::PointXYZ& p2, const pcl::PointXYZ& p3) {
Eigen::Vector3f v1 = p2.getVector3fMap() - p1.getVector3fMap();
Eigen::Vector3f v2 = p3.getVector3fMap() - p1.getVector3fMap();
return 0.5f * v1.cross(v2).norm();
}
// 在三角形内生成随机点
pcl::PointXYZ samplePointInTriangle(const pcl::PointXYZ& p1, const pcl::PointXYZ& p2, const pcl::PointXYZ& p3) {
float u = static_cast<float>(rand()) / RAND_MAX;
float v = static_cast<float>(rand()) / RAND_MAX;
if (u + v > 1.0f) {
u = 1.0f - u;
v = 1.0f - v;
}
float w = 1.0f - u - v;
return pcl::PointXYZ(
u * p1.x + v * p2.x + w * p3.x,
u * p1.y + v * p2.y + w * p3.y,
u * p1.z + v * p2.z + w * p3.z
);
}
// 根据采样密度对网格进行采样
pcl::PointCloud<pcl::PointXYZ>::Ptr samplePointsFromMesh(const pcl::PolygonMesh& mesh, float density) {
pcl::PointCloud<pcl::PointXYZ> cloud;
pcl::fromPCLPointCloud2(mesh.cloud, cloud);
// 初始化采样后的点云
pcl::PointCloud<pcl::PointXYZ>::Ptr sampled_points(new pcl::PointCloud<pcl::PointXYZ>);
// 遍历每个三角形,按面积计算采样点数
for (const auto& polygon : mesh.polygons) {
if (polygon.vertices.size() != 3) continue; // 确保是三角形
// 获取三角形顶点
const auto& p1 = cloud.points[polygon.vertices[0]];
const auto& p2 = cloud.points[polygon.vertices[1]];
const auto& p3 = cloud.points[polygon.vertices[2]];
// 计算三角形面积
float area = calculateTriangleArea(p1, p2, p3);
// 根据面积和采样密度计算采样点数
float expected_points = density * area;
int n_points = static_cast<int>(std::floor(expected_points));
float fractional_part = expected_points - n_points;
// 根据小数部分的概率决定是否多采样一个点
if (static_cast<float>(rand()) / RAND_MAX < fractional_part) {
++n_points;
}
// 在三角形内采样点
for (int j = 0; j < n_points; ++j) {
sampled_points->points.push_back(samplePointInTriangle(p1, p2, p3));
}
}
sampled_points->width = sampled_points->points.size();
sampled_points->height = 1;
sampled_points->is_dense = true;
return sampled_points;
}
int main() {
// 加载 STL 文件并转换为 pcl::PolygonMesh
pcl::PolygonMesh mesh;
pcl::io::loadPolygonFileSTL("path/to/your/file.stl", mesh);
// 设定采样密度(点数/单位面积)
float density = 10.0f; // 可以根据需要调整密度
// 从网格中采样点
pcl::PointCloud<pcl::PointXYZ>::Ptr sampled_points = samplePointsFromMesh(mesh, density);
// 可视化采样后的点云
pcl::visualization::PCLVisualizer viewer("Sampled Points Viewer");
viewer.addPointCloud<pcl::PointXYZ>(sampled_points, "sampled points");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "sampled points");
viewer.spin();
return 0;
}
代码说明
- 定义采样密度:
density
表示每单位面积的采样点数,可以根据需要调整。 - 计算采样点数:
- 根据
density
和三角形面积area
计算每个三角形的预期采样点数expected_points
。 - 将
expected_points
的整数部分作为基础采样点数,小数部分作为概率决定是否再多采样一个点。
- 根据
- 生成采样点:在三角形内生成采样点,确保每个三角形按设定密度采样。
- 可视化:使用
PCLVisualizer
显示采样后的点云。
注意事项
- 采样密度单位:
density
表示每单位面积的点数,适合网格大小各异的情况。如果网格非常大或非常小,可以调整density
以确保合适的点云密度。 - 随机数生成器:为保证结果稳定性和灵活性,可以替换
rand()
为std::mt19937
随机生成器。
两种方法的区别
这两种方法在原理上有相似之处,但在控制采样的方式和适用场景上有一些区别。
相同点
- 基于面积的采样:两种方法都基于三角形的面积决定采样点数,即面积越大的三角形获得的采样点越多,从而确保采样点分布更能反映模型的真实几何形状。
- 小数点概率机制:两种方法在计算采样点数时,都使用了整数部分加上小数部分作为概率的方法。例如,如果期望值为
7.32
点,那么会先放置 7 个点,然后根据 0.32 的概率决定是否再放置一个点。
不同点
-
控制参数不同:
- 总点数控制:第一种方法通过指定采样的总点数来控制采样密度。算法根据三角形的面积占比分配总点数,因此适用于需要在整个模型上均匀分配固定数量的点的情况。
- 密度控制:第二种方法通过每单位面积的点数来控制采样,即通过定义采样密度(点数/单位面积),直接决定每个三角形上生成的点数。适用于需要精确控制局部区域采样密度的情况。
-
采样点数的分配策略:
- 总点数控制:第一种方法根据每个三角形相对于总面积的比例,动态计算每个三角形的采样点数。换句话说,它是基于全局面积进行的采样点数分配。
- 密度控制:第二种方法为每个三角形分配的采样点数完全依赖于三角形的局部面积,而与其他三角形无关,因此在计算上更加简单明了,适合在大型模型或任意区域的密集采样。
-
适用场景的差异:
- 总点数控制:适用于需要均匀分布总点数的场景。例如,在点云数据需要特定数量的点时,使用总点数控制更合适,这样可以精确生成点数并确保分布的均匀性。
- 密度控制:适合在大模型或区域密度采样的情况下使用。例如,如果模型非常大而某些细节区域需要更多的采样点,使用密度控制可以轻松实现局部的高密度采样。
总结
- 第一种方法(总点数控制):通过设定整个模型的总点数,按比例将点分配到每个三角形上,适合于固定总点数的采样。
- 第二种方法(密度控制):通过设定单位面积的采样密度,直接控制每个三角形的采样点数,适合于需要局部控制点云密度的采样。
根据需求选择合适的方法可以更好地实现采样目标。