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

ops/2024/11/14 15:28:45/

        该代码用于分析和处理点云数据,通过对点云数据进行裁剪、平面拟合和生成包围框来提取特定区域的特征并发布结果。主要使用了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/ops/132703.html

相关文章

微信小程序——用户隐私保护指引填写(详细版)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

苍穹外卖 新订单提醒

通过WebSocket技术实现新订单提醒功能&#xff0c;每当有新的订单&#xff0c;就在商家管理端发送消息提示。 WebSocketServer package com.sky.Websocket;import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.PathVariable;im…

vue/react做多语言国际化的时候,在语言配置中不同的语言配置不同的字体,动态引入scss里面

如果想直接在vue文件的css里面使用&#xff0c;就可以使用i18n的t函数&#xff0c;注意t外层也有引号&#xff1a; font-size: v-bind("t(style.teamCurModelFontSize)"); 前提是要引入t函数&#xff1a;

性能调优专题(7)之Innodb底层原理与Mysql日志机制深入剖析

一、MYSQL的内部组件结构 大体来说&#xff0c;Mysql可以分为Server层和存储引擎层两部分。 1.1 Server层 Server层主要包括连接器、查询缓存、词法分析器、优化器等。涵盖MYSQL的大多数核心服务功能&#xff0c;以及所有的内置函数(如日期、时间、数学和加密函数等)&#x…

Webserver(4.4)多进程/多线程实现并发服务器

目录 多进程实现并发服务器多线程实现并发服务器TCP状态转换 多进程实现并发服务器 要实现TCP服务器处理并发的任务&#xff0c;使用多线程或者多进程来解决 一个父进程&#xff0c;多个子进程 父进程负责等待并接受客户端的连接 子进程&#xff1a;完成通信&#xff0c;接收一…

qt配合映美精取图开发

最近开发一个项目&#xff0c;用映美精相机配合halcon做取图开发&#xff0c;由于网上资料小特意写个记录。到映美精官网下载驱动&#xff0c;映美精官网&#xff0c;下载映美精的工具开发包SDK 映美精的SDK下载SDK后找到classlib文件夹 里面就是SDK新建一个qt程序&#xff0c…

安全编码实践:反射API的“间谍游戏”

在编程的世界里&#xff0c;反射API就像是一把双刃剑&#xff0c;它既强大又危险。它能让你的代码像007一样灵活多变&#xff0c;但稍不留神&#xff0c;就可能引发安全危机。今天&#xff0c;我们就来聊聊如何在这场“间谍游戏”中&#xff0c;安全地使用反射API进行数据操作。…

JAVA-顺序表ArrayList(实现ArrayList)

1.线性表 线性表 &#xff08; linear list &#xff09; 是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列... 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条直线。…