目录
- 说明
- 代码展示
- 结果展示
- 问题说明
说明
这篇博客以代码为主,使用CGAL中的region growing方法检测圆柱体。将不同的圆柱按不同颜色保存,并输出圆柱体的中心坐标、轴方向以及半径。
region growing的具体思想网上的文章已经有很多,可以参考这个。
代码展示
#include <CGAL/Point_set_3.h>
#include <CGAL/Point_set_3/IO.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing.h>
#include <CGAL/IO/read_points.h>
#include <CGAL/property_map.h>
#include <CGAL/Shape_detection/Region_growing/Point_set.h>
#include <boost/iterator/function_output_iterator.hpp>
#include <CGAL/utils.h>
#include <fstream>
// 源文件
#define src_file_path "cylinder1_dense.ply"
// 检测结果分颜色保存
#define result_path "cylinder1_dense_result.ply"
// 参数保存
#define param_path "cylinder1_dense_param.txt"
// Typedefs.
using Kernel = CGAL::Simple_cartesian<double>;
using FT = Kernel::FT;
using Point_3 = Kernel::Point_3;
using Vector_3 = Kernel::Vector_3;
using Point_set = CGAL::Point_set_3<Point_3>;
using Point_map = typename Point_set::Point_map;
//using Normal_map = typename Point_set::Vector_map;
typedef std::pair<Point_3, Vector_3> Point_with_normal;
typedef std::vector<Point_with_normal> Pwn_vector;
using Neighbor_query = CGAL::Shape_detection::Point_set::K_neighbor_query_for_point_set<Point_set>;
using Region_type = CGAL::Shape_detection::Point_set::Least_squares_cylinder_fit_region_for_point_set<Point_set>;
using Region_growing = CGAL::Shape_detection::Region_growing<Neighbor_query, Region_type>;
void detect_and_save(Point_set& point_set, Neighbor_query& neighbor_query, Region_type& region_type)
{
// Create an instance of the region growing class.
Region_growing region_growing(
point_set, neighbor_query, region_type);
// Add maps to get a colored output.
Point_set::Property_map<unsigned char>
red = point_set.add_property_map<unsigned char>("red", 0).first,
green = point_set.add_property_map<unsigned char>("green", 0).first,
blue = point_set.add_property_map<unsigned char>("blue", 0).first;
// Run the algorithm.
//CGAL::Random random;
std::size_t num_cylinders = 0;
region_growing.detect(
boost::make_function_output_iterator(
[&](const std::pair< Region_type::Primitive, std::vector<typename Point_set::Index> >& region) {
// Assign a random color to each region.
const unsigned char r = static_cast<unsigned char>(rand() % 255);
const unsigned char g = static_cast<unsigned char>(rand() % 255);
const unsigned char b = static_cast<unsigned char>(rand() % 255);
for (auto id : region.second) {
put(red, id, r);
put(green, id, g);
put(blue, id, b);
}
++num_cylinders;
}
)
);
std::cout << "* number of found cylinders: " << num_cylinders << std::endl;
// Save regions to a file.
std::ofstream out(src_file_path);
CGAL::IO::set_ascii_mode(out);
out << point_set;
}
void detect_and_print_param(Point_set& point_set, Neighbor_query& neighbor_query, Region_type& region_type)
{
// Create an instance of the region growing class.
Region_growing region_growing(
point_set, neighbor_query, region_type);
std::vector<typename Region_growing::Primitive_and_region> regions;
region_growing.detect(std::back_inserter(regions));
// 打开输出文件
std::ofstream outFile(param_path);
for (size_t i = 0; i < regions.size(); i++)
{
const auto& primitive_and_region = regions[i];
//const auto& region = primitive_and_region.second;
const auto& cylinder_param = primitive_and_region.first;
// 获取轴的方向
const auto& dx = cylinder_param.axis.direction().dx();
const auto& dy = cylinder_param.axis.direction().dy();
const auto& dz = cylinder_param.axis.direction().dz();
// 获取圆柱中心位置
const auto& cx = cylinder_param.axis.point(0).x();
const auto& cy = cylinder_param.axis.point(0).y();
const auto& cz = cylinder_param.axis.point(0).z();
// 获取圆柱半径
const auto& r = cylinder_param.radius;
outFile << r << " " << dx << " " << dy << " " << dz << " " << cx << " " << cy << " " << cz << "\n";
std::cout << "圆柱半径:" << r << std::endl;
std::cout << "圆柱轴方向:" << dx << ", " << dy << ", " << dz << std::endl;
std::cout << "圆柱中心:" << cx << ", " << cy << ", " << cz << std::endl;
}
outFile.close();
}
int main(int argc, char** argv) {
// Load ply data either from a local folder or a user-provided file.
const std::string input_file = src_file_path;
std::ifstream in(CGAL::data_file_path(input_file));
CGAL::IO::set_ascii_mode(in);
//CGAL::IO::set_binary_mode(in);
if (!in) {
std::cerr << "ERROR: cannot read the input file!" << std::endl;
return EXIT_FAILURE;
}
Point_set point_set;
in >> point_set;
in.close();
std::cout << "* number of input points: " << point_set.size() << std::endl;
//assert(!is_default_input || point_set.size() == 1813);
assert(point_set.has_normal_map()); // input should have normals
// Default parameter values for the data file cuble.pwn.
const std::size_t k = 10;
const FT max_distance = FT(1) / FT(500);
const FT max_angle = FT(60);
const std::size_t min_region_size = 200;
// Create instances of the classes Neighbor_query and Region_type.
Neighbor_query neighbor_query = CGAL::Shape_detection::Point_set::make_k_neighbor_query(point_set, CGAL::parameters::k_neighbors(k));
Region_type region_type = CGAL::Shape_detection::Point_set::make_least_squares_cylinder_fit_region(
point_set,
CGAL::parameters::
maximum_distance(max_distance).
maximum_angle(max_angle).
minimum_region_size(min_region_size));
// 检测圆柱,对属于不同圆柱的点赋予不同颜色
//detect_and_save(point_set, neighbor_query, region_type);
// 检测圆柱,输出每个圆柱的参数
detect_and_print_param(point_set, neighbor_query, region_type);
return EXIT_SUCCESS;
}
关于代码
写这个博客的一部分原因是,记录CGAL中保存region growing结果与检测到物体的参数的方式。可以从代码中看到,进行region_growing.detect(*)时,保存结果与输出参数所用的region类型并不一致。
另外还需注意,代码读取的ply文件必须是ASCII格式保存的,否则会出现读取的点不完整问题。
结果展示
原文件
检测结果
问题说明
当原点云中的圆柱体拥有上下底面时,检测结果很差。
源文件
检测结果
结论
进行圆柱体检测时,干扰点越少结果越精准。其中干扰点除了一些杂点之外,还包括圆柱体的上下两个底面点。