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三维点云实时检测的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何学习3dsmax?3dsmax教程
- 下一篇: 5个常用的上传图片进行搜索的网站