利用RANSAC算法拟合平面并生成包围框的点云处理方法,点云聚类、质心坐标、倾斜角度、点云最小外接矩形

news/2024/11/14 7:28:14/

        该代码用于分析和处理点云数据,通过对点云数据进行裁剪、平面拟合和生成包围框来提取特定区域的特征并发布结果。主要使用了RANSAC算法来识别并拟合平面,从而提取平面的法向量,接着根据该平面计算出该区域的最小矩形包围框(Bounding Box),并得到包围框的中心点以及相对于Y轴的倾角。最终,程序将处理后的点云和包围框通过ROS节点发布出来,用于后续的处理或可视化。

1.导入必要的库

import rospy
import numpy as np
from sensor_msgs.msg import PointCloud2
from sensor_msgs import point_cloud2
from std_msgs.msg import Header
from sklearn.linear_model import RANSACRegressor
from visualization_msgs.msg import Marker
from geometry_msgs.msg import Point
import math

2. 设置XYZ范围限制和RANSAC误差阈值

X_MIN, X_MAX = 0.5, 3
Y_MIN, Y_MAX = 0, 2
Z_MIN, Z_MAX = -0.5, 0.2
DISTANCE_THRESHOLD = 0.05  # RANSAC 拟合的误差阈值

3. 裁剪点云,限制XYZ坐标范围

def filter_xyz_range(points):"""裁剪点云,将点限制在指定 XYZ 范围内"""return points[(points[:, 0] >= X_MIN) & (points[:, 0] <= X_MAX) &(points[:, 1] >= Y_MIN) & (points[:, 1] <= Y_MAX) &(points[:, 2] >= Z_MIN) & (points[:, 2] <= Z_MAX)]

4. 使用RANSAC算法拟合平面

def fit_plane_ransac(points):"""使用 RANSAC 算法拟合平面,返回平面的法向量和拟合的平面点"""if len(points) < 3:return None, NoneX = points[:, :2]  # 使用 x, y 特征y = points[:, 2]   # Z 坐标作为目标值ransac = RANSACRegressor(residual_threshold=DISTANCE_THRESHOLD)ransac.fit(X, y)inlier_mask = ransac.inlier_mask_plane_points = points[inlier_mask]  # 选取平面内的点normal_vector = np.array([ransac.estimator_.coef_[0], ransac.estimator_.coef_[1], -1])  # 计算法向量return normal_vector, plane_points

5. 算包围框的8个顶点坐标及中心点坐标

def compute_bounding_box(points):"""根据点云计算最小矩形包围框的8个顶点坐标"""min_vals = np.min(points, axis=0)max_vals = np.max(points, axis=0)# 计算矩形框8个顶点坐标p1 = [min_vals[0], min_vals[1], min_vals[2]]p2 = [min_vals[0], min_vals[1], max_vals[2]]p3 = [min_vals[0], max_vals[1], min_vals[2]]p4 = [min_vals[0], max_vals[1], max_vals[2]]p5 = [max_vals[0], min_vals[1], min_vals[2]]p6 = [max_vals[0], min_vals[1], max_vals[2]]p7 = [max_vals[0], max_vals[1], min_vals[2]]p8 = [max_vals[0], max_vals[1], max_vals[2]]return np.array([p1, p2, p3, p4, p5, p6, p7, p8])def compute_bounding_box_center(bbox_points):"""计算包围框的中心坐标"""center = np.mean(bbox_points, axis=0)return center

6. 计算平面法向量与Y轴的倾斜角

def compute_pitch_angle(normal_vector):"""计算法向量和Y轴的倾斜角(以度为单位)"""y_axis = np.array([0, 1, 0])cos_angle = np.dot(normal_vector, y_axis) / (np.linalg.norm(normal_vector) * np.linalg.norm(y_axis))cos_angle = np.clip(cos_angle, -1.0, 1.0)angle = math.acos(cos_angle)  # 计算夹角(弧度)return math.degrees(angle)  # 转换为角度

7. 创建包围框的Marker消息(可视化)

def create_bounding_box_marker(bbox_points, frame_id):"""创建一个长方体包围框的 Marker 消息,用于可视化"""marker = Marker()marker.header.frame_id = frame_idmarker.header.stamp = rospy.Time.now()marker.type = Marker.LINE_LIST  # 使用线框模型marker.action = Marker.ADDmarker.scale.x = 0.01  # 线的粗细marker.color.r = 1.0marker.color.g = 0.0marker.color.b = 0.0marker.color.a = 1.0# 定义包围框的边界线edges = [[0, 1], [1, 3], [3, 2], [2, 0],  # 底面四条边[4, 5], [5, 7], [7, 6], [6, 4],  # 顶面四条边[0, 4], [1, 5], [2, 6], [3, 7]   # 连接底面和顶面的四条边]# 构建包围框的线条for edge in edges:p1 = Point(*bbox_points[edge[0]])p2 = Point(*bbox_points[edge[1]])marker.points.append(p1)marker.points.append(p2)return marker

8.发布点云话题:

def publish_point_cloud(points, frame_id, topic):"""发布点云消息"""if len(points) == 0:rospy.logwarn("点云为空,未发布消息。")returnheader = Header()header.stamp = rospy.Time.now()header.frame_id = frame_idcloud_msg = point_cloud2.create_cloud_xyz32(header, points)rospy.Publisher(topic, PointCloud2, queue_size=1).publish(cloud_msg)

 9.回调函数及主函数

def point_cloud_callback(msg):points = np.array([p[:3] for p in point_cloud2.read_points(msg, field_names=("x", "y", "z"), skip_nans=True)])points_in_range = filter_xyz_range(points)if points_in_range.size == 0:rospy.logwarn("裁剪后没有点云数据。")returnnormal1, plane_points1 = fit_plane_ransac(points_in_range)normal2, plane_points2 = fit_plane_ransac(points_in_range)if normal1 is None or normal2 is None:rospy.logwarn("平面拟合失败,无法生成包围框。")returnbounding_box_points = compute_bounding_box(points_in_range)bbox_center = compute_bounding_box_center(bounding_box_points)angle = compute_pitch_angle(normal1)# 输出三维矩形框的中心坐标rospy.loginfo(f"Bounding Box Center: {bbox_center}")rospy.loginfo(f"Angle with Y axis: {angle:.2f} degrees")bbox_marker = create_bounding_box_marker(bounding_box_points, msg.header.frame_id)publish_point_cloud(points_in_range, msg.header.frame_id, "/filtered_points")bbox_pub.publish(bbox_marker)def main():rospy.init_node("bounding_box_fitter")# 初始化发布器global bbox_pubbbox_pub = rospy.Publisher("/bounding_box", Marker, queue_size=1)# 订阅点云话题rospy.Subscriber("/merged_point_cloud", PointCloud2, point_cloud_callback)rospy.spin()

10.完整的代码如下:

import rospy
import numpy as np
from sensor_msgs.msg import PointCloud2
from sensor_msgs import point_cloud2
from std_msgs.msg import Header
from sklearn.linear_model import RANSACRegressor
from visualization_msgs.msg import Marker
from geometry_msgs.msg import Point
import math# 设置 XYZ 范围限制
X_MIN, X_MAX = 0.5, 3
Y_MIN, Y_MAX = 0, 2
Z_MIN, Z_MAX = -0.5, 0.2
DISTANCE_THRESHOLD = 0.05  # RANSAC 拟合的误差阈值def filter_xyz_range(points):"""裁剪点云,将点限制在指定 XYZ 范围内"""return points[(points[:, 0] >= X_MIN) & (points[:, 0] <= X_MAX) &(points[:, 1] >= Y_MIN) & (points[:, 1] <= Y_MAX) &(points[:, 2] >= Z_MIN) & (points[:, 2] <= Z_MAX)]def fit_plane_ransac(points):"""使用 RANSAC 算法拟合平面,返回平面的法向量和拟合的平面点"""if len(points) < 3:return None, NoneX = points[:, :2]  # 使用 x, y 特征y = points[:, 2]   # Z 坐标作为目标值ransac = RANSACRegressor(residual_threshold=DISTANCE_THRESHOLD)ransac.fit(X, y)inlier_mask = ransac.inlier_mask_plane_points = points[inlier_mask]  # 选取平面内的点normal_vector = np.array([ransac.estimator_.coef_[0], ransac.estimator_.coef_[1], -1])  # 计算法向量return normal_vector, plane_pointsdef compute_bounding_box(points):"""根据点云计算最小矩形包围框的8个顶点坐标"""min_vals = np.min(points, axis=0)max_vals = np.max(points, axis=0)# 计算矩形框8个顶点坐标p1 = [min_vals[0], min_vals[1], min_vals[2]]p2 = [min_vals[0], min_vals[1], max_vals[2]]p3 = [min_vals[0], max_vals[1], min_vals[2]]p4 = [min_vals[0], max_vals[1], max_vals[2]]p5 = [max_vals[0], min_vals[1], min_vals[2]]p6 = [max_vals[0], min_vals[1], max_vals[2]]p7 = [max_vals[0], max_vals[1], min_vals[2]]p8 = [max_vals[0], max_vals[1], max_vals[2]]return np.array([p1, p2, p3, p4, p5, p6, p7, p8])def compute_bounding_box_center(bbox_points):"""计算包围框的中心坐标"""center = np.mean(bbox_points, axis=0)return centerdef compute_pitch_angle(normal_vector):"""计算法向量和Y轴的倾斜角(以度为单位)"""y_axis = np.array([0, 1, 0])cos_angle = np.dot(normal_vector, y_axis) / (np.linalg.norm(normal_vector) * np.linalg.norm(y_axis))cos_angle = np.clip(cos_angle, -1.0, 1.0)angle = math.acos(cos_angle)  # 计算夹角(弧度)return math.degrees(angle)  # 转换为角度def create_bounding_box_marker(bbox_points, frame_id):"""创建一个长方体包围框的 Marker 消息,用于可视化"""marker = Marker()marker.header.frame_id = frame_idmarker.header.stamp = rospy.Time.now()marker.type = Marker.LINE_LIST  # 使用线框模型marker.action = Marker.ADDmarker.scale.x = 0.01  # 线的粗细marker.color.r = 1.0marker.color.g = 0.0marker.color.b = 0.0marker.color.a = 1.0# 定义包围框的边界线edges = [[0, 1], [1, 3], [3, 2], [2, 0],  # 底面四条边[4, 5], [5, 7], [7, 6], [6, 4],  # 顶面四条边[0, 4], [1, 5], [2, 6], [3, 7]   # 连接底面和顶面的四条边]for edge in edges:p1 = Point(*bbox_points[edge[0]])p2 = Point(*bbox_points[edge[1]])marker.points.append(p1)marker.points.append(p2)return markerdef publish_point_cloud(points, frame_id, topic):"""发布裁剪后的点云消息"""if len(points) == 0:rospy.logwarn("点云为空,未发布消息。")returnheader = Header()header.stamp = rospy.Time.now()header.frame_id = frame_idcloud_msg = point_cloud2.create_cloud_xyz32(header, points)rospy.Publisher(topic, PointCloud2, queue_size=1).publish(cloud_msg)def point_cloud_callback(msg):"""接收并处理点云数据,进行裁剪、平面拟合、包围框计算并发布"""points = np.array([p[:3] for p in point_cloud2.read_points(msg, field_names=("x", "y", "z"), skip_nans=True)])points_in_range = filter_xyz_range(points)if points_in_range.size == 0:rospy.logwarn("裁剪后没有点云数据。")returnnormal1, plane_points1 = fit_plane_ransac(points_in_range)normal2, plane_points2 = fit_plane_ransac(points_in_range)if normal1 is None or normal2 is None:rospy.logwarn("平面拟合失败,无法生成包围框。")returnbounding_box_points = compute_bounding_box(points_in_range)bbox_center = compute_bounding_box_center(bounding_box_points)angle = compute_pitch_angle(normal1)# 输出三维矩形框的中心坐标rospy.loginfo(f"Bounding Box Center: {bbox_center}")rospy.loginfo(f"Angle with Y axis: {angle:.2f} degrees")bbox_marker = create_bounding_box_marker(bounding_box_points, msg.header.frame_id)publish_point_cloud(points_in_range, msg.header.frame_id, "/filtered_points")bbox_pub.publish(bbox_marker)def main():"""初始化 ROS 节点、发布器和订阅器"""rospy.init_node("bounding_box_fitter")# 初始化发布器global bbox_pubbbox_pub = rospy.Publisher("/bounding_box", Marker, queue_size=1)# 订阅点云话题rospy.Subscriber("/merged_point_cloud", PointCloud2, point_cloud_callback)rospy.spin()if __name__ == "__main__": main()

11.运行程序以及自己的bag包,别忘了更改相应的话题 ,结果如下:

 


http://www.ppmy.cn/news/1546278.html

相关文章

[论文粗读][REALM: Retrieval-Augmented Language Model Pre-Training

引言 今天带来一篇检索增强语言模型预训练论文笔记——REALM: Retrieval-Augmented Language Model Pre-Training。这篇论文是在RAG论文出现之前发表的。 为了简单&#xff0c;下文中以翻译的口吻记录&#xff0c;比如替换"作者"为"我们"。 语言模型预训练…

Openlayers10.2.1最新版在安卓Compose中使用的一个例子

题目 这是一个中小公司的面试题&#xff1a; Openlayers 是一个功能完善的地图引擎&#xff0c;能在WEB页面上显示瓦片地图或者矢量地图&#xff0c;官方网址是https://openlayers.org/。 1、尝试做一个安卓App&#xff0c;使用Openlayers显示高德或者百度在线地图&#xff0c…

一台手机如何录制播客——程序员也可玩转播客

用提词宝完成高质量播客录制攻略 播客制作对于内容创作者来说是一种兼具创意与表达的媒介&#xff0c;但录制过程中可能面临如台词不流畅、音质不佳等问题。借助提词宝App的悬浮窗提词与手机系统录音功能&#xff0c;您可以更高效地完成播客录制。以下是详细攻略和实用技巧。 …

电子电气架构 --- 基于以太网的车载网络协议的描述

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所有人的看法和评价都是暂时的,只有自己的经历是伴随一生的,几乎所有的担忧和畏惧,都是来源于自己的想象,只有你真的去做了,才会发现有多快乐。…

Flutter鸿蒙next 使用 BLoC 模式进行状态管理详解

1. 引言 在 Flutter 中&#xff0c;随着应用规模的扩大&#xff0c;管理应用中的状态变得越来越复杂。为了处理这种复杂性&#xff0c;许多开发者选择使用不同的状态管理方案。其中&#xff0c;BLoC&#xff08;Business Logic Component&#xff09;模式作为一种流行的状态管…

明日周刊-第27期

本周的天气暖洋洋的&#xff0c;很舒服。丝毫没有冬日的迹象&#xff0c;希望这样的暖冬能够再久一些。 文章目录 1. 科技短讯AI深度伪造技术进入视频配音领域OpenAI应对AI提升放缓谷歌发布新一代AI芯片OpenAI华人副总裁翁荔离职台积电将暂停向中国出口AI芯片&#x1f4f1;科技…

PyQt5入门级超详细教程中篇

PyQt5入门级超详细教程 中篇&#xff1a;信号槽机制与表格数据展示 接上篇&#xff1a; 第4部分&#xff1a;事件处理与信号槽机制 4.1 什么是信号与槽&#xff1f; 在 PyQt5 中&#xff0c;信号&#xff08;Signal&#xff09; 和 槽&#xff08;Slot&#xff09; 是处理事…

淘宝/天猫按图搜索商品:taobao.item_search_img API的奇幻之旅

在这个看脸的时代&#xff0c;我们不仅对人要看颜值&#xff0c;连买东西都要“看脸”了。没错&#xff0c;我说的就是淘宝/天猫的按图搜索商品功能——taobao.item_search_img API。这个功能就像是电商平台的“人脸识别”&#xff0c;只不过它认的是商品的颜值。下面&#xff…