欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

Pointpillars三维点云实时检测

发布时间:2024/3/7 编程问答 40 豆豆
生活随笔 收集整理的这篇文章主要介绍了 Pointpillars三维点云实时检测 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

目录

一、项目方案

二、项目准备工作

1.安装并配置好Openpcdet的环境

2.安装好ROS melodic

三、项目工作空间创建及代码配置

四、具体代码修改与讲解

launch/pointpillars.launch的修改

launch/pointpillars.rviz的修改

五、实时检测效果展示

六、项目思考以及未解决的问题

七、Reference


一、项目方案

ROS的通讯机制使得它在机器人和无人驾驶领域应用十分广泛。所以本项目通讯都在ROS里进行。

1.激光雷达或者相机通过ROS发送点云信息

2.获得的点云msg消息通过转换送入pointpillars目标检测框架,检测完毕获得检测框通过ROS消息发送出去。

3.在ROS的rviz里面对这些消息进行显示。

二、项目准备工作

1.安装并配置好Openpcdet的环境

使用OpenPcdet框架里面的pointpillars代码进行,因为这个框架对于三维目标检测算法的封装集成度很高,方便我们进行使用。

2.安装好ROS melodic

本项目是基于ROS进行的,所以要提前安装好,其他的ROS版本也可

三、项目工作空间创建及代码配置

mkdir -p ~/pointpillars_ros/src cd pointpillars_ros/src

下载这个包,这个包是某个大佬写的,后面有参考链接。

git clone https://github.com/BIT-DYN/pointpillars_ros cd ..

 进入openpcdet的conda环境,然后安装几个库,jsk-recognition-msg这个库里面的消息主要是存放检测框的。具体可以看这个链接https://blog.csdn.net/leesanity/article/details/123137541

# 进入到搭建好的openpcdet环境 conda activate pcdet pip install --user rospkg catkin_pkg pip install pyquaternionsudo apt-get install ros-melodic-pcl-ros sudo apt-get install ros-melodic-jsk-recognition-msg sudo apt-get install ros-melodic-jsk-rviz-plugins catkin_make

然后再把Openpcdet里面的相关文件移进来,放到src/pointpillars/tools下面

 这里可以改一下demo.py里面配置文件和预训练模型,然后放入数据检查一下openpcdet移植过来还能跑通不。

四、具体代码修改与讲解

先修改ros.py,大家可以先用下面的ros.py代码替换掉原先的。代码如下:

#!/usr/bin/env python3import rospyfrom sensor_msgs.msg import PointCloud2,PointFieldimport sensor_msgs.point_cloud2 as pc2 from std_msgs.msg import Header from jsk_recognition_msgs.msg import BoundingBox, BoundingBoxArrayimport time import numpy as np from pyquaternion import Quaternionimport argparse import glob from pathlib import Pathimport numpy as np import torch import scipy.linalg as linalgimport sys sys.path.append("/home/wangchen/pointpillars_ros/src/pointpillars_ros")from pcdet.config import cfg, cfg_from_yaml_file from pcdet.datasets import DatasetTemplate from pcdet.models import build_network, load_data_to_gpu from pcdet.utils import common_utilsclass DemoDataset(DatasetTemplate):def __init__(self, dataset_cfg, class_names, training=True, root_path=None, logger=None, ext='.bin'):"""Args:root_path: 根目录dataset_cfg: 数据集配置class_names: 类别名称training: 训练模式logger: 日志ext: 扩展名"""super().__init__(dataset_cfg=dataset_cfg, class_names=class_names, training=training, root_path=root_path, logger=logger)class Pointpillars_ROS:def __init__(self):config_path, ckpt_path = self.init_ros()self.init_pointpillars(config_path, ckpt_path)def init_ros(self):""" Initialize ros parameters """config_path = rospy.get_param("/config_path", "/home/wangchen/pointpillars_ros/src/pointpillars_ros/tools/cfgs/kitti_models/pointpillar.yaml")ckpt_path = rospy.get_param("/ckpt_path", "/home/wangchen/pointpillars_ros/src/pointpillars_ros/tools/pointpillar_7728.pth")# # subscriber# self.sub_velo = rospy.Subscriber("/kitti/velo/pointcloud", PointCloud2, self.lidar_callback, queue_size=1,# buff_size=2 ** 12)## # publisher# self.sub_velo = rospy.Publisher("/modified", PointCloud2, queue_size=1)# self.pub_bbox = rospy.Publisher("/detections", BoundingBoxArray, queue_size=10)return config_path, ckpt_pathdef init_pointpillars(self, config_path, ckpt_path):""" Initialize second model """logger = common_utils.create_logger() # 创建日志logger.info('-----------------Quick Demo of Pointpillars-------------------------')cfg_from_yaml_file(config_path, cfg) # 加载配置文件self.demo_dataset = DemoDataset(dataset_cfg=cfg.DATA_CONFIG, class_names=cfg.CLASS_NAMES, training=False,ext='.bin', logger=logger)self.model = build_network(model_cfg=cfg.MODEL, num_class=len(cfg.CLASS_NAMES), dataset=self.demo_dataset)# 加载权重文件self.model.load_params_from_file(filename=ckpt_path, logger=logger, to_cpu=True)self.model.cuda() # 将网络放到GPU上self.model.eval() # 开启评估模式def rotate_mat(self, axis, radian):rot_matrix = linalg.expm(np.cross(np.eye(3), axis / linalg.norm(axis) * radian))return rot_matrixdef lidar_callback(self, msg):""" Captures pointcloud data and feed into second model for inference """pcl_msg = pc2.read_points(msg, field_names = ("x", "y", "z", "intensity"), skip_nans=True)#这里的field_names可以不要,不要就是把数据全部读取进来。也可以用field_names = ("x", "y", "z")这个只读xyz坐标#得到的pcl_msg是一个generator生成器,如果要一次获得全部的点,需要转成listnp_p = np.array(list(pcl_msg), dtype=np.float32)#print(np_p.shape)# 旋转轴#rand_axis = [0,1,0]#旋转角度#yaw = 0.1047#yaw = 0.0#返回旋转矩阵#rot_matrix = self.rotate_mat(rand_axis, yaw)#np_p_rot = np.dot(rot_matrix, np_p[:,:3].T).T# convert to xyzi point cloudx = np_p[:, 0].reshape(-1)#print(np.max(x),np.min(x))y = np_p[:, 1].reshape(-1)z = np_p[:, 2].reshape(-1)if np_p.shape[1] == 4: # if intensity field existsi = np_p[:, 3].reshape(-1)else:i = np.zeros((np_p.shape[0], 1)).reshape(-1)points = np.stack((x, y, z, i)).T# 组装数组字典input_dict = {'frame_id': msg.header.frame_id,'points': points}data_dict = self.demo_dataset.prepare_data(data_dict=input_dict) # 数据预处理data_dict = self.demo_dataset.collate_batch([data_dict])load_data_to_gpu(data_dict) # 将数据放到GPU上pred_dicts, _ = self.model.forward(data_dict) # 模型前向传播scores = pred_dicts[0]['pred_scores'].detach().cpu().numpy()mask = scores > 0.5scores = scores[mask]boxes_lidar = pred_dicts[0]['pred_boxes'][mask].detach().cpu().numpy()label = pred_dicts[0]['pred_labels'][mask].detach().cpu().numpy()num_detections = boxes_lidar.shape[0]arr_bbox = BoundingBoxArray()for i in range(num_detections):bbox = BoundingBox()bbox.header.frame_id = msg.header.frame_idbbox.header.stamp = rospy.Time.now()bbox.pose.position.x = float(boxes_lidar[i][0])bbox.pose.position.y = float(boxes_lidar[i][1])bbox.pose.position.z = float(boxes_lidar[i][2]) #+ float(boxes_lidar[i][5]) / 2bbox.dimensions.x = float(boxes_lidar[i][3]) # widthbbox.dimensions.y = float(boxes_lidar[i][4]) # lengthbbox.dimensions.z = float(boxes_lidar[i][5]) # heightq = Quaternion(axis=(0, 0, 1), radians=float(boxes_lidar[i][6]))bbox.pose.orientation.x = q.xbbox.pose.orientation.y = q.ybbox.pose.orientation.z = q.zbbox.pose.orientation.w = q.wbbox.value = scores[i]bbox.label = label[i]arr_bbox.boxes.append(bbox)arr_bbox.header.frame_id = msg.header.frame_id#arr_bbox.header.stamp = rospy.Time.now()if len(arr_bbox.boxes) is not 0:pub_bbox.publish(arr_bbox)self.publish_test(points, msg.header.frame_id)def publish_test(self, cloud, frame_id):header = Header()header.stamp = rospy.Time()header.frame_id = frame_idfields = [PointField('x', 0, PointField.FLOAT32, 1),PointField('y', 4, PointField.FLOAT32, 1),PointField('z', 8, PointField.FLOAT32, 1),PointField('intensity', 12, PointField.FLOAT32, 1)] # ,PointField('label', 16, PointField.FLOAT32, 1)#creat_cloud不像read,他必须要有fields,而且field定义有两种。一个是上面的,一个是下面的fields=_make_point_field(4)msg_segment = pc2.create_cloud(header = header,fields=fields,points = cloud)pub_velo.publish(msg_segment)#pub_image.publish(image_msg)def _make_point_field(num_field):msg_pf1 = pc2.PointField()msg_pf1.name = np.str_('x')msg_pf1.offset = np.uint32(0)msg_pf1.datatype = np.uint8(7)msg_pf1.count = np.uint32(1)msg_pf2 = pc2.PointField()msg_pf2.name = np.str_('y')msg_pf2.offset = np.uint32(4)msg_pf2.datatype = np.uint8(7)msg_pf2.count = np.uint32(1)msg_pf3 = pc2.PointField()msg_pf3.name = np.str_('z')msg_pf3.offset = np.uint32(8)msg_pf3.datatype = np.uint8(7)msg_pf3.count = np.uint32(1)msg_pf4 = pc2.PointField()msg_pf4.name = np.str_('intensity')msg_pf4.offset = np.uint32(16)msg_pf4.datatype = np.uint8(7)msg_pf4.count = np.uint32(1)if num_field == 4:return [msg_pf1, msg_pf2, msg_pf3, msg_pf4]msg_pf5 = pc2.PointField()msg_pf5.name = np.str_('label')msg_pf5.offset = np.uint32(20)msg_pf5.datatype = np.uint8(4)msg_pf5.count = np.uint32(1)return [msg_pf1, msg_pf2, msg_pf3, msg_pf4, msg_pf5] if __name__ == '__main__':global secsec = Pointpillars_ROS()rospy.init_node('pointpillars_ros_node', anonymous=True)# subscribersub_velo = rospy.Subscriber("/kitti/velo/pointcloud", PointCloud2, sec.lidar_callback, queue_size=1,buff_size=2 ** 12)# publisherpub_velo = rospy.Publisher("/modified", PointCloud2, queue_size=1)pub_bbox = rospy.Publisher("/detections", BoundingBoxArray, queue_size=10)try:rospy.spin()except KeyboardInterrupt:del secprint("Shutting down")

替换完之后,大家要改的是24行,56 ,57行的预训练权重和config文件,改成你自己的路径即可。如果是你自己的雷达或相机219行换成你自己的话题名

launch/pointpillars.launch的修改

<node pkg="rosbag" type="play" name="player" output="log" args="-l /media/ubuntu/ximing/dataset/ros_kitti/bag/2011_10_03/kitti_2011_10_03_drive_0027_synced.bag" />

把这个里面的ROSBAG路径改一下,改成你自己的就行。

这里附上一个ROSBAG包制作的链接https://blog.csdn.net/FSKEps/article/details/90345566

 这个链接里面需要下载的包在我百度网盘里,链接: https://pan.baidu.com/s/1EtG_Z8jujW77bU9o_ZoduQ提取码: sfiw

launch/pointpillars.rviz的修改

这个里面主要把Topic话题改一下,一个图像,一个点云,那个modified不改

Topic: /kitti/velo/pointcloud

Image Topic: /kitti/camera_color_left/image_raw

这两个话题是kitti的ROSbag包的话题,如果是自己的激光雷达要修改这两个话题。

还有一处如果是自己的雷达或相机也要修改。

Fixed Frame: velo_link,这里如果是自己的也要改。指的RVIZ里面的基准坐标系

五、实时检测效果展示

conda activate pcdet source ~/pointpillars_ros/devel/setup.bash roslaunch pointpillars_ros pointpillars.launch

可能会卡顿,打开RVIZ之后等一分钟。

具体动画效果看大佬的bilibili链接https://www.bilibili.com/video/BV1ce4y1D76o/

播放的时候点云换成modified,这时框与点云可以对上。如果不换对不上。

六、项目思考以及未解决的问题

首先是点云和检测框在RVIZ里面显示的问题,/kitti/velo/pointcloud这个是我们订阅的点云话题,送入pointpillars检测之后会耗费一段时间,这时检测出来的检测框和/kitti/velo/pointcloud的时间戳对不上,所以会导致RVIZ里面点云与框不对应。这时我们在检测完框之后,重新定义一个点云,把他的时间戳和检测框的对齐,再发布出来就是我们代码里的modified点云,所以modified点云与框可以对应,但此时图像是和/kitti/velo/pointcloud对应的,所以我的想法是同时也订阅一个图像信息,等检测完,创建一个新的modified图像再发布,让他们时间戳对齐,这样就可以解决了,但事实证明,这样尝试了一下还是对不齐,希望大家有想法的可以积极讨论一下,很期待和大家一起解决这个问题。

第二个问题就是大家用自己的相机或者雷达做实时检测显示,我这里也和一位博主讨论过,把点云图像换成自己相机的,还有RVIZ里面的launch文件的基准坐标系也要改成你自己的。但是在实际场景中检测出来的检测框乱飞。现在还在尝试解决,希望大家可以一起解决这个问题。目前就这么多,也希望大家可以把自己的想法和问题说出来,我们一起讨论,谢谢大家的参与。

关于自己数据实时检测已经实现了,自己数据检测时要注意两点:

1.坐标系一定要转换为openpcdet的统一坐标系。

2.自己雷达采集的intensity一定要置零之后再进行检测,不然检测框会乱飞。

七、Reference

https://blog.csdn.net/jiachang98/article/details/126106126?spm=1001.2014.3001.5501

https://blog.csdn.net/weixin_43807148/article/details/125867953

总结

以上是生活随笔为你收集整理的Pointpillars三维点云实时检测的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。