【PCL】教程 sphere_removal.cpp 自定义的过滤条件来过滤点云中某个区域内的点

终端输出:

Cloud before filtering:
    -1.23392 1.81505 -0.968005
    -0.00934529 1.36497 0.158734
    0.488435 1.96851 -0.0534078
    1.27135 1.16404 -1.00462
    -0.249089 -0.0815883 1.13229
    0.448447 1.48914 1.78378
    1.14143 1.77363 1.68965
    1.08544 -1.01664 -1.13041
    1.1199 0.9951 -1.13308
    1.44268 -1.44434 -0.391739
Cloud after filtering:
    -1.23392 1.81505 -0.968005
    -0.00934529 1.36497 0.158734
    0.488435 1.96851 -0.0534078
    1.27135 1.16404 -1.00462
    1.14143 1.77363 1.68965
    1.08544 -1.01664 -1.13041
    1.1199 0.9951 -1.13308
    1.44268 -1.44434 -0.391739

这段代码使用C++和点云库(PCL)进行点云的处理。下面是代码详细解析:

  1. 首先,代码包含了PCL库的一些必要头文件,用于生成和处理点云数据:

  • <pcl/common/generate.h>:用于生成点云数据

  • <pcl/filters/experimental/functor_filter.h>提供一个函数式过滤器,用于根据自定义条件过滤点云

  • <pcl/point_types.h>:定义了PCL中的点类型。

  • <iostream>:用于输出信息到标准错误流中。

main函数中,首先定义了使用pcl::PointXYZ点类型的点云别名XYZCloud

创建两个shared_ptr类型的XYZCloud对象cloudfiltered_cloud,分别用于存储原始点云和过滤后的点云。

使用pcl::common::CloudGeneratorpcl::common::UniformGenerator生成随机点云数据。这里参数{-2.0, 2, 1234}分别代表随机生成点的坐标范围和种子,然后将生成的10个点填充到cloud点云中。

通过循环输出原始点云中的点坐标。

设置一个圆形区域的中心和半径,用于过滤点云中的点。此处中心设为(0, 0, 2),半径设为2

定义一个过滤条件filter,该条件为一个lambda函数,接受点云和索引号,计算点云中点与设定圆心的欧氏距离,若大于等于半径,则该点满足条件。

使用pcl::experimental::FunctionFilter根据上述过滤条件对原始点云进行过滤,得到过滤后的点云。

循环输出过滤后点云中的点坐标。

总之,这段代码的功能是生成一个随机的点云,并使用自定义的过滤条件(本例中是一个以(0, 0, 2)为中心,半径为2的圆形区域外的点)对该点云进行过滤。过滤后的点云中只包含满足条件的点,然后输出过滤前后的点云信息。这种过滤方式非常灵活,可以根据需要定制过滤条件,非常适合在点云预处理中使用。

#include <pcl/common/generate.h> // 包含生成点云的功能函数
#include <pcl/filters/experimental/functor_filter.h> // 包含过滤点云的函数
#include <pcl/point_types.h> // 包含PCL中点类型的定义


#include <iostream> // 包含输入输出流的库


int 
main()
{
  using XYZCloud = pcl::PointCloud<pcl::PointXYZ>; // 定义一个别名XYZCloud用于表示pcl::PointXYZ类型的点云
  const auto cloud = pcl::make_shared<XYZCloud>(); // 创建一个新的点云共享指针
  const auto filtered_cloud = pcl::make_shared<XYZCloud>(); // 创建一个新的用于存放过滤后点云的共享指针


  // 创建一个随机数生成器填充点云
  pcl::common::CloudGenerator<pcl::PointXYZ, pcl::common::UniformGenerator<float>>
      generator{{-2.0, 2, 1234}}; // 初始化随机数发生器,范围是-2到2,种子是1234
  generator.fill(10, 1, *cloud); // 使用随机数生成器生成10个点填充点云


  std::cerr << "Cloud before filtering: " << std::endl; // 输出过滤前的点云
  for (const auto& pt : *cloud)
    std::cerr << "    " << pt.x << " " << pt.y << " " << pt.z << std::endl; // 遍历并输出点云中的每个点


  // 设置一个条件来拒绝滤波器内的点
  const Eigen::Vector3f center{0, 0, 2}; // 定义一个中心点
  const float radius = 2; // 定义一个半径


  pcl::experimental::FilterFunction<pcl::PointXYZ> filter; // 定义一个过滤函数
  filter = [=](const XYZCloud& cloud, pcl::index_t idx) {
    return ((cloud[idx].getVector3fMap() - center).norm() >= radius); // 如果点到中心的欧式距离大于或等于半径则保留
  };


  // 构建过滤器
  pcl::experimental::FunctionFilter<pcl::PointXYZ> func_filter(filter); // 用上面定义的过滤条件创建过滤器
  func_filter.setInputCloud(cloud); // 设置输入点云


  // 应用过滤器
  func_filter.filter(*filtered_cloud); // 执行过滤操作并把结果点云存储在filtered_cloud中


  // 显示过滤后的点云
  std::cerr << "Cloud after filtering: " << std::endl; // 输出过滤后的点云
  for (const auto& pt : *filtered_cloud)
    std::cerr << "    " << pt.x << " " << pt.y << " " << pt.z << std::endl; // 遍历并输出过滤后点云中的每个点


  return (0); // 程序正常结束
}

这段代码的功能是创建一个含有随机生成的点云数据的点云对象,然后通过一个自定义的过滤条件来过滤点云中某个区域内的点。过滤条件是点到一个中心点的距离大于或等于一个指定的半径。过滤操作之后,输出过滤前和过滤后的点云数据。

const auto cloud = pcl::make_shared<XYZCloud>(); 
与   pcl::PointCloud<pcl::PointXYZ>::Ptr 区别

ee1db471ce0bec1a88bcd5351cd102c0.png

pcl::common::CloudGenerator<pcl::PointXYZ, pcl::common::UniformGenerator<float>>
generator{{-2.0, 2, 1234}};// 定义一个名为generator的CloudGenerator对象
  generator.fill(10, 1, *cloud);// 使用generator填充点云

3b3a5016520a20c6ea27fba28fbe501e.png

6fe8c32fe76b71163c1bd48518095447.png

filter = [=](const XYZCloud& cloud, pcl::index_t idx) {
    return ((cloud[idx].getVector3fMap() - center).norm() >= radius); // 如果点到中心的欧式距离大于或等于半径则保留
  };

这行代码定义了一个过滤器的行为,具体来说就是一个lambda表达式,用来决定点云中的哪些点应该被保留。

  • filter:这是一个pcl::experimental::FilterFunction<pcl::PointXYZ>类型的变量,它用作点云过滤的条件。

  • [=]:这是lambda表达式的捕获列表。[=]表示以值捕获的方式捕获所有外部作用域中的变量(在这个例子中,主要是centerradius)。

  • (const XYZCloud& cloud, pcl::index_t idx):这是lambda表达式的参数列表cloud是一个对点云的引用,idx是点云中点的索引。这意味着这个lambda表达式将对点云中的每一个点进行操作。

  • cloud[idx].getVector3fMap() - center:这部分代码通过点的索引idx访问点云中的点,并获取该点的坐标(使用getVector3fMap()方法获得一个Eigen::Vector3f类型的向量),然后计算该点与一个中心点center的向量差

  • .norm():这是Eigen库中的方法,用于计算向量的欧几里得范数,即从原点到向量指定中心点的直线距离

  • >= radius:这是条件判断,如果点到center的距离大于或等于radius,则表达式的结果为true,这表示这个点应被保留;如果小于radius,则为false,表示这个点应被过滤掉。

综上所述,这个lambda表达式定义的过滤器的作用是检查点云中的每一个点,如果点到一个给定中心center的距离大于或等于指定的半径radius,则这个点满足条件,应被保留;否则,这个点不满足条件,应被过滤掉。这种过滤方式常用于去除一个区域内的所有点,例如,去除距离传感器特定距离内的所有点,以清除噪声或其他不需要的数据

import tkinter as tk from tkinter import ttk, filedialog import open3d as o3d import numpy as np from scipy.optimize import least_squares class EnhancedShapeAnalyzer: def __init__(self): self.pcd = None self.raw_pcd = None self.transform = np.eye(4) self.results = {"type": "unknown", "confidence": 0.0} self.camera_params = { "focal_length": 500, # 像素单位 "sensor_width": 36, # 毫米 "max_offset": 0.2, # 最大中心偏移(米) "depth_base": 1000 # 基准深度(毫米) } def load_data(self, folder_path): """加载并预处理数据""" try: # 加载原始数据 self.raw_pcd = o3d.io.read_point_cloud(f"{folder_path}/scan.ply") transform_data = np.load(f"{folder_path}/transform.npz") self.transform = transform_data['matrix'] # 应用变换并中心化 self.pcd = self.raw_pcd.transform(np.linalg.inv(self.transform)) self._center_normalization() return True except Exception as e: print(f"数据加载失败: {str(e)}") return False def _center_normalization(self): """将点云中心对齐到坐标系原""" points = np.asarray(self.pcd.points) current_center = np.mean(points, axis=0) self.pcd.translate(-current_center) print(f"中心化偏移量: {current_center}") def _perspective_correction(self, raw_size, depth): """透视尺寸校正""" depth_mm = depth * 1000 # 转换为毫米 scale_factor = self.camera_params["focal_length"] / \ (self.camera_params["focal_length"] + (depth_mm - self.camera_params["depth_base"]) / self.camera_params["sensor_width"]) return raw_size * scale_factor def analyze_shape(self): """执行形状分析流程""" if not self._preprocess(): return "预处理失败" self.results = {"type": "unknown", "confidence": 0.0} # 并行检测所有形状 candidates = [] if sphere_conf := self._detect_sphere(): candidates.append(("sphere", self.results.copy())) if cylinder_conf := self._detect_cylinder(): candidates.append(("cylinder", self.results.copy())) if cuboid_conf := self._detect_cuboid(): candidates.append(("cuboid", self.results.copy())) # 选择最佳结果 valid_candidates = [c for c in candidates if c[1]["confidence"] >= 0.5] if valid_candidates: best = max(valid_candidates, key=lambda x: x[1]["confidence"]) self.results = best[1] return best[0] return "unknown" def _preprocess(self): """数据预处理流程""" try: # 多阶段降采样 self.pcd = self.pcd.voxel_down_sample(0.005) self.pcd = self.pcd.voxel_down_sample(0.003) # 统计滤波 self.pcd, _ = self.pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=1.5) return True except Exception as e: print(f"预处理失败: {str(e)}") return False def _detect_sphere(self): """精密球体检测""" try: # 分割平面点云 _, inliers = self.pcd.segment_plane(0.01, 3, 1000) sphere_pcd = self.pcd.select_by_index(inliers, invert=True) points = np.asarray(sphere_pcd.points) if len(points) < 100: return 0.0 # 最小二乘优化 def sphere_loss(params): cx, cy, cz, r = params return np.linalg.norm(points - [cx, cy, cz], axis=1) - r init_center = np.mean(points, axis=0) init_radius = np.mean(np.linalg.norm(points - init_center, axis=1)) opt = least_squares(sphere_loss, [*init_center, init_radius], loss='soft_l1') # 中心验证 if not self._validate_center(opt.x[:3]): print("球体中心超出允许范围") return 0.0 # 透视校正 corrected_radius = self._perspective_correction(opt.x[3], opt.x[2]) # 置信度计算 residuals = sphere_loss(opt.x) inlier_ratio = np.sum(np.abs(residuals) < 0.02) / len(residuals) center_score = 1 - np.linalg.norm(opt.x[:3]) / self.camera_params["max_offset"] confidence = 0.6 * inlier_ratio + 0.4 * center_score if confidence > self.results["confidence"]: self.results = { "type": "sphere", "radius": float(corrected_radius), "center": opt.x[:3].tolist(), "confidence": float(confidence), "_points": points } return confidence except Exception as e: print(f"球体检测异常: {str(e)}") return 0.0 def _detect_cylinder(self): """精密圆柱检测""" try: # 分割平面点云 _, inliers = self.pcd.segment_plane(0.01, 3, 1000) cyl_pcd = self.pcd.select_by_index(inliers, invert=True) points = np.asarray(cyl_pcd.points) if len(points) < 100: return 0.0 # PCA分析主方向 cov = np.cov(points.T) _, vecs = np.linalg.eigh(cov) axis = vecs[:, 2] # 非线性优化 def cylinder_loss(params): a, b, c, x0, y0, z0, r = params dir_vec = np.array([a, b, c]) dir_vec /= np.linalg.norm(dir_vec) + 1e-8 pts_vec = points - [x0, y0, z0] cross = np.cross(pts_vec, dir_vec) return np.linalg.norm(cross, axis=1) - r init_center = np.mean(points, axis=0) opt = least_squares(cylinder_loss, [*axis, *init_center, 0.1], loss='soft_l1') # 参数提取 axis = opt.x[:3] / (np.linalg.norm(opt.x[:3]) + 1e-8) proj = np.dot(points - opt.x[3:6], axis) height = np.ptp(proj) center = opt.x[3:6] + axis * (np.min(proj) + height / 2) # 中心验证 if not self._validate_center(center): print("圆柱中心超出允许范围") return 0.0 # 透视校正 corrected_radius = self._perspective_correction(opt.x[6], center[2]) corrected_height = self._perspective_correction(height, center[2]) # 置信度计算 residuals = cylinder_loss(opt.x) inlier_ratio = np.sum(np.abs(residuals) < 0.015) / len(residuals) center_score = 1 - np.linalg.norm(center) / self.camera_params["max_offset"] confidence = 0.5 * inlier_ratio + 0.3 * center_score + 0.2 * (corrected_height / (2 * corrected_radius)) if confidence > self.results["confidence"]: self.results = { "type": "cylinder", "radius": float(corrected_radius), "height": float(corrected_height), "axis": axis.tolist(), "center": center.tolist(), "confidence": float(confidence), "_points": points } return confidence except Exception as e: print(f"圆柱检测异常: {str(e)}") return 0.0 def _detect_cuboid(self): """精密长方体检测""" try: # 多平面检测 planes = [] residual_pcd = self.pcd for _ in range(3): plane, inliers = residual_pcd.segment_plane(0.02, 3, 2000) if len(inliers) < 500: break planes.append(plane[:3]) residual_pcd = residual_pcd.select_by_index(inliers, invert=True) if len(planes) < 3: return 0.0 # 正交化处理 u, v, w = self._gram_schmidt(planes[0], planes[1], planes[2]) points = np.asarray(self.pcd.points) # 计算几何参数 proj_u = np.dot(points, u) proj_v = np.dot(points, v) proj_w = np.dot(points, w) dims = [np.ptp(proj_u), np.ptp(proj_v), np.ptp(proj_w)] center_3d = np.dot([np.mean(proj_u), np.mean(proj_v), np.mean(proj_w)], [u, v, w]) # 中心验证 if not self._validate_center(center_3d): print("长方体中心超出允许范围") return 0.0 # 透视校正 corrected_dims = [self._perspective_correction(d, center_3d[2]) for d in dims] # 置信度计算 ortho_score = 1 - (abs(np.dot(u, v)) + abs(np.dot(u, w)) + abs(np.dot(v, w))) / 3 center_score = 1 - np.linalg.norm(center_3d) / self.camera_params["max_offset"] confidence = 0.6 * ortho_score + 0.4 * center_score if confidence > self.results["confidence"]: self.results = { "type": "cuboid", "dimensions": corrected_dims, "center": center_3d.tolist(), "confidence": float(confidence), "_planes": planes } return confidence except Exception as e: print(f"长方体检测异常: {str(e)}") return 0.0 def _validate_center(self, center): """验证中心位置有效性""" return np.linalg.norm(center) < self.camera_params["max_offset"] def _gram_schmidt(self, u, v, w): """Gram-Schmidt正交化""" u = u / (np.linalg.norm(u) + 1e-8) v = v - np.dot(v, u) * u v = v / (np.linalg.norm(v) + 1e-8) w = w - np.dot(w, u) * u - np.dot(w, v) * v w = w / (np.linalg.norm(w) + 1e-8) return u, v, w class AnalysisGUI(tk.Tk): def __init__(self): super().__init__() self.title("三维精密分析系统 v4.0") self.geometry("1280x960") self.analyzer = EnhancedShapeAnalyzer() self._init_ui() def _init_ui(self): main_frame = ttk.Frame(self) main_frame.pack(fill="both", expand=True, padx=20, pady=20) # 文件选择区 file_frame = ttk.LabelFrame(main_frame, text="数据加载") file_frame.pack(fill="x", pady=10) self.path_entry = ttk.Entry(file_frame, width=80) self.path_entry.pack(side="left", expand=True, padx=5) ttk.Button(file_frame, text="浏览...", command=self._browse).pack(side="left") # 控制区 ctrl_frame = ttk.Frame(main_frame) ctrl_frame.pack(fill="x", pady=10) ttk.Button(ctrl_frame, text="开始分析", command=self._analyze).pack(side="left", padx=5) ttk.Button(ctrl_frame, text="原始点云", command=lambda: self._show_pcd(self.analyzer.raw_pcd)).pack(side="left") ttk.Button(ctrl_frame, text="处理点云", command=lambda: self._show_pcd(self.analyzer.pcd)).pack(side="left") ttk.Button(ctrl_frame, text="显示结果", command=self._show_result).pack(side="left") # 结果显示区 result_frame = ttk.LabelFrame(main_frame, text="分析结果") result_frame.pack(fill="both", expand=True) self.result_text = tk.Text(result_frame, wrap="word", font=("Consolas", 12)) scrollbar = ttk.Scrollbar(result_frame, orient="vertical", command=self.result_text.yview) self.result_text.configure(yscrollcommand=scrollbar.set) scrollbar.pack(side="right", fill="y") self.result_text.pack(fill="both", expand=True, padx=10, pady=10) # 状态栏 self.status = ttk.Label(main_frame, relief="sunken") self.status.pack(fill="x", pady=5) def _browse(self): path = filedialog.askdirectory() if path: self.path_entry.delete(0, tk.END) self.path_entry.insert(0, path) def _analyze(self): self.status.config(text="分析中...") self.result_text.config(state="normal") self.result_text.delete(1.0, tk.END) if not self.analyzer.load_data(self.path_entry.get()): self._update_result("数据加载失败") return shape_type = self.analyzer.analyze_shape() res = self.analyzer.results output = [ "=== 三维精密分析结果 ===", f"识别类型: {shape_type.upper()}", f"综合置信度: {res.get('confidence', 0):.1%}", f"中心坐标: {np.round(res.get('center', [0, 0, 0]), 3).tolist()}" ] if shape_type == "sphere": output.extend([ f"\n球体半径: {res['radius']:.3f} 米", f"表面数: {len(res.get('_points', []))}" ]) elif shape_type == "cylinder": output.extend([ f"\n圆柱半径: {res['radius']:.3f} 米", f"圆柱高度: {res['height']:.3f} 米", f"轴向向量: {np.round(res.get('axis', [0, 0, 0]), 3).tolist()}" ]) elif shape_type == "cuboid": output.extend([ "\n长方体尺寸:", f"X: {res['dimensions'][0]:.3f} 米", f"Y: {res['dimensions'][1]:.3f} 米", f"Z: {res['dimensions'][2]:.3f} 米" ]) self.result_text.insert(tk.END, "\n".join(output)) self.result_text.config(state="disabled") self.status.config(text=f"分析完成 - 置信度: {res.get('confidence', 0):.1%}") def _show_pcd(self, pcd): """显示点云可视化""" try: if pcd.is_empty(): return vis = o3d.visualization.Visualizer() vis.create_window(window_name="点云视图", width=800, height=600) # 克隆点云以避免修改原始数据 cloned_pcd = o3d.geometry.PointCloud(pcd) cloned_pcd.paint_uniform_color([0.6, 0.6, 0.6]) vis.add_geometry(cloned_pcd) self._add_center_marker(vis) vis.run() vis.destroy_window() except Exception as e: print(f"可视化错误: {str(e)}") def _show_result(self): """显示分析结果的可视化""" try: res = self.analyzer.results if res["confidence"] < 0.5: self._update_result("无有效分析结果") return vis = o3d.visualization.Visualizer() vis.create_window(window_name="三维分析结果", width=1000, height=800) # 显示处理后的点云 base_pcd = self.analyzer.pcd.paint_uniform_color([0.8, 0.8, 0.8]) vis.add_geometry(base_pcd) # 显示检测结果 if res["type"] == "sphere": self._show_sphere_result(vis, res) elif res["type"] == "cylinder": self._show_cylinder_result(vis, res) elif res["type"] == "cuboid": self._show_cuboid_result(vis, res) # 添加中心标记和坐标系 self._add_center_marker(vis) coord_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=0.5) vis.add_geometry(coord_frame) vis.run() vis.destroy_window() except Exception as e: print(f"结果可视化失败: {str(e)}") def _show_sphere_result(self, vis, res): """显示球体检测结果""" sphere = o3d.geometry.TriangleMesh.create_sphere(res["radius"]) sphere.translate(res["center"]) sphere.paint_uniform_color([1, 0, 0]) vis.add_geometry(sphere) # 显示特征 inlier_pcd = o3d.geometry.PointCloud() inlier_pcd.points = o3d.utility.Vector3dVector(res["_points"]) inlier_pcd.paint_uniform_color([1, 0.5, 0]) vis.add_geometry(inlier_pcd) def _show_cylinder_result(self, vis, res): """显示圆柱检测结果""" cylinder = o3d.geometry.TriangleMesh.create_cylinder( radius=res["radius"], height=res["height"] ) # 计算旋转矩阵 axis = np.array(res["axis"]) z_axis = np.array([0, 0, 1]) rotation = self._get_rotation_matrix(axis, z_axis) cylinder.rotate(rotation) # 调整位置到底面中心 cylinder.translate(np.array(res["center"]) - axis * res["height"] / 2) cylinder.paint_uniform_color([0, 0.8, 0]) vis.add_geometry(cylinder) def _show_cuboid_result(self, vis, res): """显示长方体检测结果""" obb = o3d.geometry.OrientedBoundingBox.create_from_points( o3d.utility.Vector3dVector(np.asarray(self.analyzer.pcd.points)) ) obb.color = [0, 0, 1] vis.add_geometry(obb) def _add_center_marker(self, vis): """添加中心标记""" center_sphere = o3d.geometry.TriangleMesh.create_sphere(radius=0.03) center_sphere.paint_uniform_color([1, 1, 0]) # 黄色 vis.add_geometry(center_sphere) def _get_rotation_matrix(self, from_vec, to_vec): """计算两个向量间的旋转矩阵""" from_vec = from_vec / np.linalg.norm(from_vec) to_vec = to_vec / np.linalg.norm(to_vec) v = np.cross(from_vec, to_vec) c = np.dot(from_vec, to_vec) kmat = np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]]) return np.eye(3) + kmat + kmat.dot(kmat) * (1 / (1 + c)) def _update_result(self, msg): """更新结果显示""" self.result_text.config(state="normal") self.result_text.delete(1.0, tk.END) self.result_text.insert(tk.END, msg) self.result_text.config(state="disabled") self.status.config(text="操作完成") if __name__ == "__main__": app = AnalysisGUI() app.mainloop() 在运行时代码总是把一整个场景都识别进去判断了,如果把墙体和平面去掉,只留下物体的点云会不会有效果呢
05-12
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值