├── assets ├── multiagent_21.gif └── multitraversal_61.gif ├── readme.md └── visualize ├── lidar_cam_viz_agent.py ├── lidar_cam_viz_traversal.py └── lidar_viz.py /assets/multiagent_21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ai4ce/MARS/dd1baed493cef7aa99544417bf7632bcbfa59a26/assets/multiagent_21.gif -------------------------------------------------------------------------------- /assets/multitraversal_61.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ai4ce/MARS/dd1baed493cef7aa99544417bf7632bcbfa59a26/assets/multitraversal_61.gif -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Multiagent Multitraversal Multimodal Self-Driving: Open MARS Dataset 2 | [Yiming Li](https://roboticsyimingli.github.io/), 3 | [Zhiheng Li](https://zl3466.github.io/), 4 | [Nuo Chen](), 5 | [Moonjun Gong](https://moonjungong.github.io/), 6 | [Zonglin Lyu](), 7 | [Zehong Wang](), 8 | [Peili Jiang](), 9 | [Chen Feng](https://engineering.nyu.edu/faculty/chen-feng) 10 | 11 | [Paper](https://arxiv.org/abs/2406.09383) 12 | 13 | [Tutorial](#tutorial) 14 | 15 | Checkout our [project website](https://ai4ce.github.io/MARS/) for more demo videos. 16 | Codes to reproduce the videos are available in `/visualize` folder of `main` branch. 17 | 18 | ![teaser](https://github.com/ai4ce/MARS/assets/105882130/963f7ea2-0590-42dc-9ddd-22a9b57f947c) 19 | 20 | # Multiagent 21 | 22 | 23 | # Multitraversal 24 | 25 | 26 | 27 | 28 |
29 | 30 | # News 31 | 32 | - [2024/06] Both Multiagent and Multitraversal subsets are now available for download on [huggingface](https://huggingface.co/datasets/ai4ce/MARS). 33 | 34 | - [2024/06]The preprint version is available on [arXiv]([https://huggingface.co/datasets/ai4ce/MARS](https://arxiv.org/abs/2406.09383)). 35 | 36 | - [2024/02] Our paper has been accepted on CVPR 2024 🎉🎉🎉 37 | 38 | 39 | 40 |
41 | 42 | # Abstract 43 | In collaboration with the self-driving company [May Mobility](https://maymobility.com/), we present the MARS dataset which unifies scenarios that enable multiagent, multitraversal, and multimodal autonomous vehicle research. 44 | 45 | MARS is collected with a fleet of autonomous vehicles driving within a certain geographical area. Each vehicle has its own route and different vehicles may appear at nearby locations. Each vehicle is equipped with a LiDAR and surround-view RGB cameras. 46 | 47 | We curate two subsets in MARS: one facilitates collaborative driving with multiple vehicles simultaneously present at the same location, and the other enables memory retrospection through asynchronous traversals of the same location by multiple vehicles. We conduct experiments in place recognition and neural reconstruction. More importantly, MARS introduces new research opportunities and challenges such as multitraversal 3D reconstruction, multiagent perception, and unsupervised object discovery. 48 | 49 | 50 | ## Our dataset uses the same structure as the [NuScenes](https://www.nuscenes.org/nuscenes) Dataset: 51 | 52 | - Multitraversal: each location is saved as one NuScenes object, and each traversal is one scene. 53 | - Multiagent: the whole set is a NuScenes object, and each multiagent encounter is one scene. 54 | 55 |
56 | 57 | # License 58 | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) 59 | 60 |
61 | 62 | # Bibtex 63 | 64 | ``` 65 | @InProceedings{Li_2024_CVPR, 66 | author = {Li, Yiming and Li, Zhiheng and Chen, Nuo and Gong, Moonjun and Lyu, Zonglin and Wang, Zehong and Jiang, Peili and Feng, Chen}, 67 | title = {Multiagent Multitraversal Multimodal Self-Driving: Open MARS Dataset}, 68 | booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, 69 | month = {June}, 70 | year = {2024}, 71 | pages = {22041-22051} 72 | } 73 | ``` 74 | 75 |
76 | 77 | # Tutorial 78 | This tutorial explains how the NuScenes structure works in our dataset, including how you may access a scene and query its samples of sensor data. 79 | 80 | - [Devkit Initialization](#initialization) 81 | - [Multitraversal](#load-multitraversal) 82 | - [Multiagent](#load-multiagent) 83 | - [Scene](#scene) 84 | - [Sample](#sample) 85 | - [Sample Data](#sample-data) 86 | - [Sensor Names](#sensor-names) 87 | - [Camera](#camera-data) 88 | - [LiDAR](#lidar-data) 89 | - [IMU](#imu-data) 90 | - [Ego & Sensor Pose](#vehicle-and-sensor-pose) 91 | - [LiDAR-Image projection](#lidar-image-projection) 92 | 93 |
94 | 95 | ## Initialization 96 | First, install `nuscenes-devkit` following NuScenes's repo tutorial, [Devkit setup section](https://github.com/nutonomy/nuscenes-devkit?tab=readme-ov-file#devkit-setup). The easiest way is install via pip: 97 | ``` 98 | pip install nuscenes-devkit 99 | ``` 100 | 101 | Import NuScenes devkit: 102 | ``` 103 | from nuscenes.nuscenes import NuScenes 104 | ``` 105 | 106 | #### Load Multitraversal 107 | loading data of location 10: 108 | ``` 109 | # The "version" variable is the name of the folder holding all .json metadata tables. 110 | location = 10 111 | nusc = NuScenes(version='v1.0', dataroot=f'/MARS_multitraversal/{location}', verbose=True) 112 | ``` 113 | 114 | #### Load Multiagent 115 | loading data for the full set: 116 | ``` 117 | nusc = NuScenes(version='v1.0', dataroot=f'/MARS_multiagent', verbose=True) 118 | ``` 119 | 120 |
121 | 122 | ## Scene 123 | To see all scenes in one set (one location of the Multitraversal set, or the whole Multiagent set): 124 | ``` 125 | print(nusc.scene) 126 | ``` 127 | Output: 128 | ``` 129 | [{'token': '97hitl8ya1335v8zkixvsj3q69tgx801', 'nbr_samples': 611, 'first_sample_token': 'udrq868482482o88p9r2n8b86li7cfxx', 'last_sample_token': '7s5ogk8m9id7apixkqoh3rep0s9113xu', 'name': '2023_10_04_scene_3_maisy', 'intersection': 10, 'err_max': 20068.00981996727}, 130 | {'token': 'o858jv3a464383gk9mm8at71ai994d3n', 'nbr_samples': 542, 'first_sample_token': '933ho5988jo3hu848b54749x10gd7u14', 'last_sample_token': 'os54se39x1px2ve12x3r1b87e0d7l1gn', 'name': '2023_10_04_scene_4_maisy', 'intersection': 10, 'err_max': 23959.357933579337}, 131 | {'token': 'xv2jkx6m0o3t044bazyz9nwbe5d5i7yy', 'nbr_samples': 702, 'first_sample_token': '8rqb40c919d6n5cd553c3j01v178k28m', 'last_sample_token': 'skr79z433oyi6jljr4nx7ft8c42549nn', 'name': '2023_10_04_scene_6_mike', 'intersection': 10, 'err_max': 27593.048433048432}, 132 | {'token': '48e90c7dx401j97391g6549zmljbg0hk', 'nbr_samples': 702, 'first_sample_token': 'ui8631xb2in5la133319c5301wvx1fib', 'last_sample_token': 'xrns1rpma4p00hf39305ckol3p91x59w', 'name': '2023_10_04_scene_9_mike', 'intersection': 10, 'err_max': 24777.237891737892}, 133 | ... 134 | ] 135 | 136 | ``` 137 | 138 | The scenes can then be retrieved by indexing: 139 | ``` 140 | num_of_scenes = len(nusc.scene) 141 | my_scene = nusc.scene[0] # scene at index 0, which is the first scene of this location 142 | print(first_scene) 143 | ``` 144 | Output: 145 | ``` 146 | {'token': '97hitl8ya1335v8zkixvsj3q69tgx801', 147 | 'nbr_samples': 611, 148 | 'first_sample_token': 'udrq868482482o88p9r2n8b86li7cfxx', 149 | 'last_sample_token': '7s5ogk8m9id7apixkqoh3rep0s9113xu', 150 | 'name': '2023_10_04_scene_3_maisy', 151 | 'intersection': 10, 152 | 'err_max': 20068.00981996727} 153 | ``` 154 | - `nbr_samples`: number of samples (frames) of this scene. 155 | - `name`: name of the scene, including its date and name of the vehicle it is from (in this example, the data is from Oct. 4th 2023, vehicle maisy). 156 | - `intersection`: location index. 157 | - `err_max`: maximum time difference (in millisecond) between camera images of a same frame in this scene. 158 | 159 |
160 | 161 | ## Sample 162 | Get the first sample (frame) of one scene: 163 | ``` 164 | first_sample_token = my_scene['first_sample_token'] # get sample token 165 | my_sample = nusc.get('sample', first_sample_token) # get sample metadata 166 | print(my_sample) 167 | ``` 168 | 169 | Output: 170 | ``` 171 | {'token': 'udrq868482482o88p9r2n8b86li7cfxx', 172 | 'timestamp': 1696454482883182, 173 | 'prev': '', 174 | 'next': 'v15b2l4iaq1x0abxr45jn6bi08j72i01', 175 | 'scene_token': '97hitl8ya1335v8zkixvsj3q69tgx801', 176 | 'data': { 177 | 'CAM_FRONT_CENTER': 'q9e0pgk3wiot983g4ha8178zrnr37m50', 178 | 'CAM_FRONT_LEFT': 'c13nf903o913k30rrz33b0jq4f0z7y2d', 179 | 'CAM_FRONT_RIGHT': '67ydh75sam2dtk67r8m3bk07ba0lz3ib', 180 | 'CAM_BACK_CENTER': '1n09qfm9vw65xpohjqgji2g58459gfuq', 181 | 'CAM_SIDE_LEFT': '14up588181925s8bqe3pe44d60316ey0', 182 | 'CAM_SIDE_RIGHT': 'x95k7rvhmxkndcj8mc2821c1cs8d46y5', 183 | 'LIDAR_FRONT_CENTER': '13y90okaf208cqqy1v54z87cpv88k2qy', 184 | 'IMU_TOP': 'to711a9v6yltyvxn5653cth9w2o493z4' 185 | }, 186 | 'anns': []} 187 | ``` 188 | - `prev`: token of the previous sample. 189 | - `next`': token of the next sample. 190 | - `data`: dict of data tokens of this sample's sensor data. 191 | - `anns`: empty as we do not have annotation data at this moment. 192 | 193 |
194 | 195 | ## Sample Data 196 | ### Sensor Names 197 | Our sensor names are different from NuScenes' sensor names. It is important that you use the correct name when querying sensor data. Our sensor names are: 198 | ``` 199 | ['CAM_FRONT_CENTER', 200 | 'CAM_FRONT_LEFT', 201 | 'CAM_FRONT_RIGHT', 202 | 'CAM_BACK_CENTER', 203 | 'CAM_SIDE_LEFT', 204 | 'CAM_SIDE_RIGHT', 205 | 'LIDAR_FRONT_CENTER', 206 | 'IMU_TOP'] 207 | ``` 208 | 209 | --- 210 | ### Camera Data 211 | All image data are already undistorted. 212 | 213 | To load a piece data, we start with querying its `sample_data` dictionary object from the metadata: 214 | ``` 215 | sensor = 'CAM_FRONT_CENTER' 216 | sample_data_token = my_sample['data'][sensor] 217 | FC_data = nusc.get('sample_data', sample_data_token) 218 | print(FC_data) 219 | ``` 220 | Output: 221 | ``` 222 | {'token': 'q9e0pgk3wiot983g4ha8178zrnr37m50', 223 | 'sample_token': 'udrq868482482o88p9r2n8b86li7cfxx', 224 | 'ego_pose_token': 'q9e0pgk3wiot983g4ha8178zrnr37m50', 225 | 'calibrated_sensor_token': 'r5491t78vlex3qii8gyh3vjp0avkrj47', 226 | 'timestamp': 1696454482897062, 227 | 'fileformat': 'jpg', 228 | 'is_key_frame': True, 229 | 'height': 464, 230 | 'width': 720, 231 | 'filename': 'sweeps/CAM_FRONT_CENTER/1696454482897062.jpg', 232 | 'prev': '', 233 | 'next': '33r4265w297khyvqe033sl2r6m5iylcr', 234 | 'sensor_modality': 'camera', 235 | 'channel': 'CAM_FRONT_CENTER'} 236 | ``` 237 | - `ego_pose_token`: token of vehicle ego pose at the time of this sample. 238 | - `calibrated_sensor_token`: token of sensor calibration information (e.g. distortion coefficient, camera intrinsics, sensor pose & location relative to vehicle, etc.). 239 | - `is_key_frame`: disregard; all images have been marked as key frame in our dataset. 240 | - `height`: image height in pixel 241 | - `width`: image width in pixel 242 | - `filename`: image directory relative to the dataset's root folder 243 | - `prev`: previous data token for this sensor 244 | - `next`: next data token for this sensor 245 | 246 |
247 | 248 | After getting the `sample_data` dictionary, Use NuScenes devkit's `get_sample_data()` function to retrieve the data's absolute path. 249 | 250 | Then you may now load the image in any ways you'd like. Here's an example using `cv2`: 251 | ``` 252 | import cv2 253 | 254 | data_path, boxes, camera_intrinsic = nusc.get_sample_data(sample_data_token) 255 | img = cv2.imread(data_path) 256 | cv2.imshow('fc_img', img) 257 | cv2.waitKey() 258 | ``` 259 | 260 | Output: 261 | ``` 262 | ('{$dataset_root}/MARS_multitraversal/10/sweeps/CAM_FRONT_CENTER/1696454482897062.jpg', 263 | [], 264 | array([[661.094568 , 0. , 370.6625195], 265 | [ 0. , 657.7004865, 209.509716 ], 266 | [ 0. , 0. , 1. ]])) 267 | ``` 268 | ![image](https://github.com/ai4ce/MARS/assets/105882130/e47b3fe4-fb98-4651-b3e8-0de360ea7d19) 269 | 270 | --- 271 | ### LiDAR Data 272 | Same as loading camera data, we start with querying the `sample_data` dictionary for LiDAR sensor. 273 | 274 | Impoirt data calss "LidarPointCloud" from NuScenes devkit for convenient lidar pcd loading and manipulation. 275 | 276 | The `.bcd.bin` LiDAR data in our dataset has 5 dimensions: [ x || y || z || intensity || ring ]. 277 | 278 | The 5-dimensional data array is in `pcd.points`. Below is an example of visualizing the pcd with Open3d interactive visualizer. 279 | 280 | 281 | ``` 282 | import open3d as o3d 283 | from nuscenes.utils.data_classes import LidarPointCloud 284 | 285 | sensor = 'LIDAR_FRONT_CENTER' 286 | sample_data_token = my_sample['data'][sensor] 287 | lidar_data = nusc.get('sample_data', sample_data_token) 288 | 289 | data_path, boxes, _ = nusc.get_sample_data(my_sample['data'][sensor]) 290 | 291 | pcd = LidarPointCloud.from_file(data_path) 292 | print(pcd.points) 293 | pts = pcd.points[:3].T 294 | 295 | # open3d visualizer 296 | vis1 = o3d.visualization.Visualizer() 297 | vis1.create_window( 298 | window_name='pcd viewer', 299 | width=256 * 4, 300 | height=256 * 4, 301 | left=480, 302 | top=270) 303 | vis1.get_render_option().background_color = [0, 0, 0] 304 | vis1.get_render_option().point_size = 1 305 | vis1.get_render_option().show_coordinate_frame = True 306 | 307 | o3d_pcd = o3d.geometry.PointCloud() 308 | o3d_pcd.points = o3d.utility.Vector3dVector(pts) 309 | 310 | vis1.add_geometry(o3d_pcd) 311 | while True: 312 | vis1.update_geometry(o3d_pcd) 313 | vis1.poll_events() 314 | vis1.update_renderer() 315 | time.sleep(0.005) 316 | ``` 317 | 318 | Output: 319 | ``` 320 | 5-d lidar data: 321 | [[ 3.7755847e+00 5.0539265e+00 5.4277039e+00 ... 3.1050100e+00 322 | 3.4012783e+00 3.7089713e+00] 323 | [-6.3800979e+00 -7.9569578e+00 -7.9752398e+00 ... -7.9960880e+00 324 | -7.9981585e+00 -8.0107889e+00] 325 | [-1.5409404e+00 -3.2752687e-01 5.7313687e-01 ... 5.5921113e-01 326 | -7.5427920e-01 6.6252775e-02] 327 | [ 9.0000000e+00 1.6000000e+01 1.4000000e+01 ... 1.1000000e+01 328 | 1.8000000e+01 1.6000000e+01] 329 | [ 4.0000000e+00 5.3000000e+01 1.0200000e+02 ... 1.0500000e+02 330 | 2.6000000e+01 7.5000000e+01]] 331 | ``` 332 | 333 | ![image](https://github.com/ai4ce/MARS/assets/105882130/e28c8620-93e0-4a7a-890a-7a0f0635eeb4) 334 | 335 | 336 | --- 337 | ### IMU Data 338 | IMU data in our dataset is saved as json files. 339 | ``` 340 | sensor = 'IMU_TOP' 341 | sample_data_token = my_sample['data'][sensor] 342 | lidar_data = nusc.get('sample_data', sample_data_token) 343 | 344 | data_path, boxes, _ = nusc.get_sample_data(my_sample['data'][sensor]) 345 | 346 | imu_data = json.load(open(data_path)) 347 | print(imu_data) 348 | ``` 349 | 350 | Output: 351 | ``` 352 | {'utime': 1696454482879084, 353 | 'lat': 42.28098291158676, 354 | 'lon': -83.74725341796875, 355 | 'elev': 259.40500593185425, 356 | 'vel': [0.19750464521348476, -4.99952995654127e-27, -0.00017731071625348704], 357 | 'avel': [-0.0007668623868539726, -0.0006575787383553688, 0.0007131154834496556], 358 | 'acc': [-0.28270150907337666, -0.03748669268679805, 9.785771369934082]} 359 | ``` 360 | - `lat`: GPS latitude. 361 | - `lon`: GPS longitude. 362 | - `elev`: GPS elevation. 363 | - `vel`: vehicle instant velocity [x, y, z] in m/s. 364 | - `avel`: vehicle instant angular velocity [x, y, z] in rad/s. 365 | - `acc`: vehicle instant acceleration [x, y, z] in m/s^2. 366 | 367 | --- 368 | ### Vehicle and Sensor Pose 369 | Poses are represented as one rotation matrix and one translation matrix. 370 | - rotation: quaternion [w, x, y, z] 371 | - translation: [x, y, z] in meters 372 | 373 | Sensor-to-vehicle poses may differ for different vehicles. But for each vehicle, its sensor poses should remain unchanged across all scenes & samples. 374 | 375 | Vehicle ego pose can be quaried from sensor data. It should be the same for all sensors in the same sample. 376 | 377 | ``` 378 | # get the vehicle ego pose at the time of this FC_data 379 | vehicle_pose_fc = nusc.get('ego_pose', FC_data['ego_pose_token']) 380 | print("vehicle pose: \n", vehicle_pose_fc, "\n") 381 | 382 | # get the vehicle ego pose at the time of this lidar_data, should be the same as that queried from FC_data as they are from the same sample. 383 | vehicle_pose = nusc.get('ego_pose', lidar_data['ego_pose_token']) 384 | print("vehicle pose: \n", vehicle_pose, "\n") 385 | 386 | # get camera pose relative to vehicle at the time of this sample 387 | fc_pose = nusc.get('calibrated_sensor', FC_data['calibrated_sensor_token']) 388 | print("CAM_FRONT_CENTER pose: \n", fc_pose, "\n") 389 | 390 | # get lidar pose relative to vehicle at the time of this sample 391 | lidar_pose = nusc.get('calibrated_sensor', lidar_data['calibrated_sensor_token']) 392 | print("CAM_FRONT_CENTER pose: \n", lidar_pose) 393 | ``` 394 | 395 | Output: 396 | ``` 397 | vehicle pose: 398 | {'token': 'q9e0pgk3wiot983g4ha8178zrnr37m50', 399 | 'timestamp': 1696454482883182, 400 | 'rotation': [-0.7174290249840286, 0.0, -0.0, -0.6966316057361065], 401 | 'translation': [-146.83352790433003, -21.327001411798392, 0.0]} 402 | 403 | vehicle pose: 404 | {'token': '13y90okaf208cqqy1v54z87cpv88k2qy', 405 | 'timestamp': 1696454482883182, 406 | 'rotation': [-0.7174290249840286, 0.0, -0.0, -0.6966316057361065], 407 | 'translation': [-146.83352790433003, -21.327001411798392, 0.0]} 408 | 409 | CAM_FRONT_CENTER pose: 410 | {'token': 'r5491t78vlex3qii8gyh3vjp0avkrj47', 411 | 'sensor_token': '1gk062vf442xsn86xo152qw92596k8b9', 412 | 'translation': [2.24715, 0.0, 1.4725], 413 | 'rotation': [0.49834929780875276, -0.4844970241435727, 0.5050790448056688, -0.5116695901338464], 414 | 'camera_intrinsic': [[661.094568, 0.0, 370.6625195], [0.0, 657.7004865, 209.509716], [0.0, 0.0, 1.0]], 415 | 'distortion_coefficient': [0.122235, -1.055498, 2.795589, -2.639154]} 416 | 417 | CAM_FRONT_CENTER pose: 418 | {'token': '6f367iy1b5c97e8gu614n63jg1f5os19', 419 | 'sensor_token': 'myfmnd47g91ijn0a7481eymfk253iwy9', 420 | 'translation': [2.12778, 0.0, 1.57], 421 | 'rotation': [0.9997984797097376, 0.009068089160690487, 0.006271772522201215, -0.016776012592418482]} 422 | 423 | ``` 424 | 425 |
426 | 427 | ## LiDAR-Image projection 428 | - Use NuScenes devkit's `render_pointcloud_in_image()` method. 429 | - The first variable is a sample token. 430 | - Use `camera_channel` to specify the camera name you'd like to project the poiint cloud onto. 431 | ``` 432 | nusc.render_pointcloud_in_image(my_sample['token'], 433 | pointsensor_channel='LIDAR_FRONT_CENTER', 434 | camera_channel='CAM_FRONT_CENTER', 435 | render_intensity=False, 436 | show_lidarseg=False) 437 | ``` 438 | 439 | Output: 440 | 441 | ![image](https://github.com/ai4ce/MARS/assets/105882130/f50623e1-fa79-4e59-9daf-b76a760d20f5) 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | -------------------------------------------------------------------------------- /visualize/lidar_cam_viz_agent.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import time 3 | 4 | import matplotlib 5 | from nuscenes import NuScenes 6 | from nuscenes.utils.geometry_utils import BoxVisibility, transform_matrix, view_points 7 | from nuscenes.utils.data_classes import LidarPointCloud, RadarPointCloud, Box 8 | import numpy as np 9 | import open3d as o3d 10 | from pyquaternion import Quaternion 11 | import cv2 12 | from tqdm import tqdm 13 | import math 14 | import datetime 15 | 16 | img_width = 720 17 | img_height = 464 18 | img_divider = 1 19 | frame_width = 4 20 | font = cv2.FONT_HERSHEY_DUPLEX 21 | color_dict = {"maisy": (200, 50, 0), "metheven": (0, 125, 250), "mike": (50, 150, 0), "mithrandir": (150, 0, 150)} 22 | # color_dict = {"maisy": (200, 0, 0), "marinara": (0, 200, 0), "metheven": (0, 0, 200), "mike": (200, 200, 0), "mithrandir": (200, 0, 200)} 23 | # color_dict = {"maisy": (100, 100, 0), "metheven": (200, 200, 20), "mike": (50, 200, 200), "mithrandir": (120, 0, 120)} 24 | # color_dict = {"maisy": (0, 80, 250), "metheven": (0, 190, 80), "mike": (200, 200, 0), "mithrandir": (200, 0, 200)} 25 | color_idx_dict = {"maisy": 0, "marinara": 1, "metheven": 2, "mike": 3, "mithrandir": 4} 26 | v_id_dict = {"maisy": "Agent 0", "marinara": "Agent 1", "metheven": "Agent 2", "mike": "Agent 3", "mithrandir": "Agent 4"} 27 | cmap = matplotlib.colormaps['Set1'] 28 | 29 | def transform_pcd(pcd, tran_matrix): 30 | ''' 31 | :param pcd: 3*n np array (1st row x, 2nd row y, 3rd row z) 32 | :param tran_matrix: 4*4 transformation matrix 33 | :return: n*3 pcd (each row is a point [x, y, z]) 34 | ''' 35 | # append the pt by 1 to apply the transformation matrix 36 | rot = tran_matrix[:3, :3] 37 | trl = tran_matrix[:3, 3] 38 | pcd = (rot @ pcd).T + trl 39 | 40 | return pcd 41 | 42 | 43 | def get_scene_tokens(nusc, first_sample_token): 44 | token_list = [] 45 | sample = nusc.get('sample', first_sample_token) 46 | scene = nusc.get('scene', sample["scene_token"]) 47 | 48 | nbr_samples = scene['nbr_samples'] 49 | last_sample_token = scene['last_sample_token'] 50 | token_list.append(first_sample_token) 51 | print(scene["name"]) 52 | # vehicle = scene["name"].split("_")[-1] 53 | for i in range(nbr_samples - 1): 54 | if sample == last_sample_token: 55 | break 56 | token_list.append(sample['next']) 57 | sample = nusc.get('sample', sample['next']) 58 | # return token_list, vehicle 59 | return token_list 60 | 61 | 62 | def get_scene_list(nusc): 63 | scene_dict = {} 64 | for i in range(len(nusc.scene)): 65 | scene = nusc.scene[i] 66 | first_sample_token = scene['first_sample_token'] 67 | vehicle = scene["vehicle_list"] 68 | sample = nusc.get('sample', first_sample_token) 69 | tp = int(sample["timestamp"]) 70 | scene_dict[first_sample_token] = [tp, vehicle] 71 | scene_dict = dict(sorted(scene_dict.items(), key=lambda item: item[1][0])) 72 | # print(scene_dict) 73 | 74 | return scene_dict 75 | 76 | 77 | def img_frame(img, color): 78 | 79 | # print(color.astype(np.uint8)) 80 | framed_img = cv2.copyMakeBorder( 81 | img, 82 | top=frame_width, 83 | bottom=frame_width, 84 | left=frame_width, 85 | right=frame_width, 86 | borderType=cv2.BORDER_CONSTANT, 87 | value=color[0].tolist() 88 | ) 89 | 90 | # cv2.imshow("framed cam", framed_img) 91 | # cv2.waitKey() 92 | return framed_img 93 | 94 | 95 | def add_tag(img, tp, vehicle, color=(0, 0, 0), fontScale=1.2, thickness=2): 96 | v_color = color_dict[vehicle] 97 | 98 | date_time = datetime.datetime.fromtimestamp(int(tp)/1000000).strftime('%Y-%m-%d %H:%M:%S.%f') 99 | date = date_time.split(" ")[0] 100 | time_s = date_time.split(" ")[1][:-3] 101 | h = img.shape[0] 102 | w = img.shape[1] 103 | # blank = np.ones((h, h, 3)) * 255 104 | blank = cv2.imread("calender_img_edge.png") 105 | blank = cv2.resize(blank, dsize=(h, h), interpolation=cv2.INTER_CUBIC) 106 | org1 = (h//8+50, (h//2)) 107 | time_tag = cv2.putText(blank, date, org1, font, fontScale, color, thickness, cv2.LINE_AA) 108 | org2 = (h//8+50, (h//2)+80) 109 | time_tag = cv2.putText(time_tag, time_s, org2, font, fontScale, color, thickness, cv2.LINE_AA) 110 | org3 = (h//8+98, (h//2)+160) 111 | time_tag = cv2.putText(time_tag, v_id_dict[vehicle], org3, font, fontScale, v_color, thickness, cv2.LINE_AA) 112 | result = np.concatenate((time_tag, img), axis=1) 113 | return result 114 | 115 | 116 | if __name__ == '__main__': 117 | root_dir = f"I:/MARS_agent_10Hz_40ms" 118 | nusc = NuScenes(version='v1.0', dataroot=root_dir, verbose=True) 119 | scene_dict = get_scene_list(nusc) 120 | nbr_scene = len(nusc.scene) 121 | # nbr_scene = 10 122 | # step = len(nusc.scene)//nbr_scene 123 | # print(f"Multi Agent has {len(nusc.scene)} scenes, showing first {nbr_scene}") 124 | 125 | ''' idx of scene you'd like to visualize ''' 126 | # scene 21 has three cars, scene 30 has encounter from opposite direction 127 | scene_idx = 21 128 | 129 | scene = nusc.scene[scene_idx] 130 | first_token_list = list(scene_dict.keys()) 131 | first_token = first_token_list[scene_idx] 132 | vehicles = scene_dict[first_token][1] 133 | all_token_list = get_scene_tokens(nusc, first_token) 134 | nbr_sample = len(all_token_list) 135 | nbr_vehicle = len(vehicles) 136 | 137 | cap = cv2.VideoCapture(0) 138 | fourcc = cv2.VideoWriter_fourcc(*'MJPG') 139 | ''' camera + lidar width + frame width + time tag width ''' 140 | out0 = cv2.VideoWriter(f"{root_dir}/agent_80m_{scene_idx}.avi", fourcc, 30, 141 | ((img_width * 3 + img_height * nbr_vehicle//3*2)//img_divider + frame_width*2 + img_height//img_divider -4, 142 | (img_height//img_divider + frame_width*2) * nbr_vehicle)) 143 | 144 | vis1 = o3d.visualization.Visualizer() 145 | vis1.create_window( 146 | window_name='viz', 147 | width=img_height // img_divider //3*2 * nbr_vehicle, 148 | height=(img_height // img_divider + frame_width*2) * nbr_vehicle, 149 | left=480, 150 | top=270) 151 | vis1.get_render_option().background_color = [1, 1, 1] 152 | vis1.get_render_option().point_size = 1 153 | vis1.get_render_option().show_coordinate_frame = True 154 | 155 | # empty canvas for img display 156 | img_canvas = np.ones(((img_height // img_divider + frame_width*2)*nbr_vehicle, img_width // img_divider * 3 + frame_width*2 + img_height // img_divider, 3)) * 255 157 | # empty point cloud 158 | pcd_obj = o3d.geometry.PointCloud() 159 | 160 | start_flag = True 161 | for i in tqdm(range(1, nbr_sample), desc=f"scene {scene_idx} of {nbr_scene}"): 162 | 163 | if start_flag: 164 | sample = nusc.get('sample', first_token) 165 | else: 166 | if sample['next'] == '': 167 | break 168 | else: 169 | sample = nusc.get('sample', sample['next']) 170 | 171 | sample_pcd_list = [] 172 | sample_color_list = [] 173 | # print(vehicles) 174 | for j in range(len(vehicles)): 175 | vehicle = vehicles[j] 176 | ''' ======================================== lidar ======================================== ''' 177 | sample_lidar_token = sample['data'][f'LIDAR_FRONT_CENTER_{vehicle}'] 178 | lidar_data = nusc.get('sample_data', sample_lidar_token) 179 | 180 | pcd_path = f"{nusc.dataroot}/{lidar_data['filename']}" 181 | pcd = LidarPointCloud.from_file(pcd_path).points 182 | 183 | ''' preserve only pts within 40 meter radius ''' 184 | mask = np.sqrt((pcd[0, :] ** 2 + pcd[1, :] ** 2)) < 80 185 | pcd = pcd[:, mask][:3, :] 186 | # pcd = pcd[:3, :] 187 | 188 | ''' transform pcd from local frame to global frame ''' 189 | ego_pose = nusc.get('ego_pose', lidar_data['ego_pose_token']) 190 | 191 | lidar_pose = nusc.get('calibrated_sensor', lidar_data['calibrated_sensor_token']) 192 | # Get global pose 193 | ego_pose = transform_matrix(ego_pose['translation'], Quaternion(ego_pose['rotation']), inverse=False) 194 | lidar_pose = transform_matrix(lidar_pose['translation'], Quaternion(lidar_pose['rotation']), inverse=False) 195 | global_pose = np.dot(ego_pose, lidar_pose) 196 | 197 | pcd = transform_pcd(pcd, global_pose) 198 | 199 | # color = np.array(color_dict[vehicle]) 200 | color = np.array([color_dict[vehicle]])/255 201 | # print(vehicle, color) 202 | # color = np.array([cmap(1 / 5 * color_idx_dict[vehicle]*2)[:3]]) 203 | colors = np.repeat(color, len(pcd), axis=0) 204 | sample_pcd_list.append(pcd) 205 | sample_color_list.append(colors) 206 | 207 | ''' ======================================== camera ======================================== ''' 208 | sample_fc_token = sample['data'][f'CAM_FRONT_CENTER_{vehicle}'] 209 | sample_fl_token = sample['data'][f'CAM_FRONT_LEFT_{vehicle}'] 210 | sample_fr_token = sample['data'][f'CAM_FRONT_RIGHT_{vehicle}'] 211 | 212 | fc_data = nusc.get('sample_data', sample_fc_token) 213 | fc_path = f"{nusc.dataroot}/{fc_data['filename']}" 214 | fc_img = cv2.imread(fc_path) 215 | 216 | fl_data = nusc.get('sample_data', sample_fl_token) 217 | fl_path = f"{nusc.dataroot}/{fl_data['filename']}" 218 | fl_img = cv2.imread(fl_path) 219 | 220 | fr_data = nusc.get('sample_data', sample_fr_token) 221 | fr_path = f"{nusc.dataroot}/{fr_data['filename']}" 222 | fr_img = cv2.imread(fr_path) 223 | 224 | h = max([fc_img.shape[0], fl_img.shape[0], fr_img.shape[0]]) 225 | w = max([fc_img.shape[1], fl_img.shape[1], fr_img.shape[1]]) 226 | 227 | fc_img = cv2.resize(fc_img, dsize=(w // img_divider, h // img_divider), interpolation=cv2.INTER_CUBIC) 228 | fl_img = cv2.resize(fl_img, dsize=(w // img_divider, h // img_divider), interpolation=cv2.INTER_CUBIC) 229 | fr_img = cv2.resize(fr_img, dsize=(w // img_divider, h // img_divider), interpolation=cv2.INTER_CUBIC) 230 | 231 | img_arr1 = [fl_img, fc_img, fr_img] 232 | 233 | vehicle_img = np.concatenate(img_arr1, axis=1) 234 | # vehicle_img2 = np.concatenate(img_arr2, axis=1) 235 | # vehicle_img = np.concatenate((vehicle_img1, vehicle_img2), axis=0) 236 | tp = lidar_data['filename'].split("/")[-1].split(".")[0] 237 | 238 | vehicle_img = add_tag(vehicle_img, tp, vehicle) 239 | # vehicle_img = img_frame(vehicle_img, color) 240 | 241 | num_row = vehicle_img.shape[0] 242 | num_col = vehicle_img.shape[1] 243 | img_canvas[num_row * j:num_row * (j + 1), :num_col, :] = vehicle_img 244 | img_canvas = img_canvas.astype(np.uint8) 245 | 246 | ''' new lidar scan ''' 247 | pcd_obj.points = o3d.utility.Vector3dVector(np.concatenate(sample_pcd_list, axis=0)) 248 | pcd_obj.colors = o3d.utility.Vector3dVector(np.concatenate(sample_color_list, axis=0)) 249 | 250 | 251 | if start_flag: 252 | vis1.add_geometry(pcd_obj) 253 | start_flag = False 254 | view = vis1.get_view_control() 255 | view.set_zoom(0.5) 256 | if scene_idx == 1: 257 | view.translate(-50, -100) 258 | elif scene_idx == 2: 259 | view.translate(-50, -100) 260 | elif scene_idx == 5: 261 | view.translate(0, 60) 262 | elif scene_idx == 18: 263 | view.translate(50, -300) 264 | elif scene_idx == 21: 265 | view.translate(-150, 0) 266 | elif scene_idx == 29: 267 | view.translate(150, -0) 268 | elif scene_idx == 30: 269 | view.translate(0, -50) 270 | 271 | 272 | else: 273 | vis1.update_geometry(pcd_obj) 274 | 275 | vis1.poll_events() 276 | vis1.update_renderer() 277 | # cv2.imshow("cam", img_canvas) 278 | # cv2.waitKey(100) 279 | time.sleep(0.02) 280 | 281 | ''' write into video ''' 282 | o3d_screenshot_1 = vis1.capture_screen_float_buffer() 283 | o3d_screenshot_1 = (255.0 * np.asarray(o3d_screenshot_1)).astype(np.uint8) 284 | out0.write(np.concatenate((img_canvas, o3d_screenshot_1), axis=1)) 285 | 286 | ''' save some screenshots ''' 287 | # if i % 5 == 0: 288 | # img_out_dir = f"{root_dir}/img/{scene_idx}" 289 | # if not os.path.exists(img_out_dir): 290 | # os.makedirs(img_out_dir) 291 | # cv2.imwrite(f"{img_out_dir}/{i}.jpg", np.concatenate((img_canvas, o3d_screenshot_1), axis=1)) 292 | # cv2.imwrite(f"{img_out_dir}/{i}_lidar.jpg", o3d_screenshot_1) 293 | 294 | -------------------------------------------------------------------------------- /visualize/lidar_cam_viz_traversal.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import time 3 | 4 | import matplotlib 5 | from nuscenes import NuScenes 6 | from nuscenes.utils.geometry_utils import BoxVisibility, transform_matrix, view_points 7 | from nuscenes.utils.data_classes import LidarPointCloud, RadarPointCloud, Box 8 | import numpy as np 9 | import open3d as o3d 10 | from pyquaternion import Quaternion 11 | import cv2 12 | from tqdm import tqdm 13 | import math 14 | import datetime 15 | 16 | img_width = 720 17 | img_height = 464 18 | img_divider = 4 19 | frame_width = 4 20 | font = cv2.FONT_HERSHEY_DUPLEX 21 | 22 | color_dict = {"maisy": (200, 0, 0), "marinara": (0, 200, 0), "metheven": (0, 0, 200), "mike": (200, 200, 0), "mithrandir": (200, 0, 200)} 23 | cmap = matplotlib.colormaps['tab20'] 24 | 25 | def transform_pcd(pcd, tran_matrix): 26 | ''' 27 | :param pcd: 3*n np array (1st row x, 2nd row y, 3rd row z) 28 | :param tran_matrix: 4*4 transformation matrix 29 | :return: n*3 pcd (each row is a point [x, y, z]) 30 | ''' 31 | # append the pt by 1 to apply the transformation matrix 32 | rot = tran_matrix[:3, :3] 33 | trl = tran_matrix[:3, 3] 34 | pcd = (rot @ pcd).T + trl 35 | 36 | return pcd 37 | 38 | 39 | def get_scene_tokens(nusc, first_sample_token): 40 | token_list = [] 41 | sample = nusc.get('sample', first_sample_token) 42 | scene = nusc.get('scene', sample["scene_token"]) 43 | 44 | nbr_samples = scene['nbr_samples'] 45 | last_sample_token = scene['last_sample_token'] 46 | token_list.append(first_sample_token) 47 | print(scene["name"]) 48 | # vehicle = scene["name"].split("_")[-1] 49 | for i in range(nbr_samples - 1): 50 | if sample == last_sample_token: 51 | break 52 | token_list.append(sample['next']) 53 | sample = nusc.get('sample', sample['next']) 54 | # return token_list, vehicle 55 | return token_list 56 | 57 | 58 | def get_scene_list(nusc): 59 | scene_dict = {} 60 | for i in range(len(nusc.scene)): 61 | scene = nusc.scene[i] 62 | first_sample_token = scene['first_sample_token'] 63 | vehicle = scene["name"].split("_")[-1] 64 | sample = nusc.get('sample', first_sample_token) 65 | tp = int(sample["timestamp"]) 66 | scene_dict[first_sample_token] = [tp, vehicle] 67 | scene_dict = dict(sorted(scene_dict.items(), key=lambda item: item[1][0])) 68 | print(scene_dict) 69 | 70 | return scene_dict 71 | 72 | 73 | def img_frame(img, color): 74 | framed_img = cv2.copyMakeBorder( 75 | img, 76 | top=frame_width, 77 | bottom=frame_width, 78 | left=frame_width, 79 | right=frame_width, 80 | borderType=cv2.BORDER_CONSTANT, 81 | value=color[0]*255 82 | ) 83 | # print(color[0]*255) 84 | 85 | # cv2.imshow("framed cam", framed_img) 86 | # cv2.waitKey() 87 | return framed_img 88 | 89 | 90 | def add_tag(img, tp, vehicle, color=(0, 0, 0), fontScale=0.4, thickness=1): 91 | date_time = datetime.datetime.fromtimestamp(int(tp)/1000000).strftime('%Y-%m-%d %H:%M:%S.%f') 92 | date = date_time.split(" ")[0] 93 | time_s = date_time.split(" ")[1][:-3] 94 | h = img.shape[0] 95 | w = img.shape[1] 96 | # blank = np.ones((h, h, 3)) * 255 97 | blank = cv2.imread("calender_img_edge.png") 98 | blank = cv2.resize(blank, dsize=(h, h), interpolation=cv2.INTER_CUBIC) 99 | org1 = (h//8, (h//2)) 100 | time_tag = cv2.putText(blank, date, org1, font, fontScale, color, thickness, cv2.LINE_AA) 101 | org2 = (h//8, (h//2)+20) 102 | time_tag = cv2.putText(time_tag, time_s, org2, font, fontScale, color, thickness, cv2.LINE_AA) 103 | org3 = (h//8, (h//2)+38) 104 | 105 | time_tag = cv2.putText(time_tag, vehicle, org3, font, fontScale, color_dict[vehicle], thickness, cv2.LINE_AA) 106 | result = np.concatenate((time_tag, img), axis=1) 107 | return result 108 | 109 | 110 | if __name__ == '__main__': 111 | root_dir = "K:/MARS_10Hz_location" 112 | location_list = [0, 1, 2, 3, 4, 5] 113 | 114 | for location in location_list: 115 | if str(location) not in os.listdir(root_dir): 116 | continue 117 | 118 | root_dir = f"K:/MARS_10Hz_location/{location}" 119 | nusc = NuScenes(version='v1.0', dataroot=root_dir, verbose=True) 120 | scene_dict = get_scene_list(nusc) 121 | 122 | ''' number of scenes to be visualized ''' 123 | # nbr_scene = len(nusc.scene) 124 | nbr_scene = 10 125 | step = len(nusc.scene)//nbr_scene 126 | print(f"location {location} has {len(nusc.scene)} scenes, showing first {nbr_scene}") 127 | 128 | cap = cv2.VideoCapture(0) 129 | fourcc = cv2.VideoWriter_fourcc(*'MJPG') 130 | out0 = cv2.VideoWriter(f"{root_dir}/../{location}_80m.avi", fourcc, 120, 131 | ((img_width * 3 + img_height * 10)//img_divider + frame_width*2 + img_height//img_divider, 132 | (img_height//img_divider + frame_width*2) * 10)) 133 | 134 | vis1 = o3d.visualization.Visualizer() 135 | vis1.create_window( 136 | window_name='viz', 137 | width=img_height // img_divider * 10, 138 | height=(img_height // img_divider + frame_width * 2) * 10, 139 | left=480, 140 | top=270) 141 | vis1.get_render_option().background_color = [1, 1, 1] 142 | vis1.get_render_option().point_size = 1 143 | vis1.get_render_option().show_coordinate_frame = True 144 | 145 | 146 | ''' empty canvas for img display ''' 147 | img_canvas = np.ones(((img_height // img_divider + frame_width*2)*10, img_width // img_divider * 3 + frame_width*2 + img_height // img_divider, 3)) * 255 148 | 149 | ''' open3d point cloud object ''' 150 | pcd_obj = o3d.geometry.PointCloud() 151 | 152 | sample_token_dict = {} 153 | frame_offset_dict = {} 154 | 155 | start_flag = True 156 | first_token_list = list(scene_dict.keys()) 157 | for i in range(nbr_scene): 158 | first_token = first_token_list[i*step] 159 | vehicle = scene_dict[first_token][1] 160 | sample_token_dict[str(i)] = [vehicle, get_scene_tokens(nusc, first_token)] 161 | frame_offset_dict[str(i)] = 0 162 | nbr_sample = len(sample_token_dict[str(i)]) 163 | 164 | frame = 0 165 | finish_count = 0 166 | skip_idx = [] 167 | batch = [0] 168 | 169 | last_scene_idx = max(batch) 170 | 171 | while True: 172 | location_pcd_list = [] 173 | location_color_list = [] 174 | img_list = [] 175 | 176 | for idx in range(len(batch)): 177 | i = batch[idx] 178 | ''' if scene_i is finished playing, skip it ''' 179 | if i in skip_idx: 180 | continue 181 | 182 | scene_idx = str(i) 183 | offset = frame_offset_dict[scene_idx] 184 | ''' if last frame (frame - offset >= nbr samples) ''' 185 | if frame - offset >= len(sample_token_dict[scene_idx][1]): 186 | finish_count += 1 187 | skip_idx.append(i) 188 | 189 | last_scene_idx += 1 190 | # if last_scene_idx < nbr_scene: 191 | if last_scene_idx < nbr_scene: 192 | ''' substitute the finished scene_idx with a new one ''' 193 | # if len(batch) == 1: 194 | target_idx = batch.index(i) 195 | batch[target_idx] = last_scene_idx 196 | frame_offset_dict[str(last_scene_idx)] = frame 197 | print(f"{frame} scene {i} finished, showing {batch}") 198 | else: 199 | print(f"{frame} scene {i} finished, this is the last scene") 200 | continue 201 | 202 | sample_token = sample_token_dict[scene_idx][1][frame - offset] 203 | sample = nusc.get('sample', sample_token) 204 | # print(sample['data']) 205 | 206 | ''' ======================================== lidar ======================================== ''' 207 | sample_lidar_token = sample['data']['LIDAR_FRONT_CENTER'] 208 | lidar_data = nusc.get('sample_data', sample_lidar_token) 209 | pcd_path = f"{nusc.dataroot}/{lidar_data['filename']}" 210 | pcd = LidarPointCloud.from_file(pcd_path).points 211 | 212 | ''' preserve only pts within 40 meter radius ''' 213 | mask = np.sqrt((pcd[0, :] ** 2 + pcd[1, :] ** 2)) < 80 214 | pcd = pcd[:, mask][:3, :] 215 | # pcd = pcd[:3, :] 216 | 217 | ''' transform pcd from local frame to global frame ''' 218 | ego_pose = nusc.get('ego_pose', lidar_data['ego_pose_token']) 219 | lidar_pose = nusc.get('calibrated_sensor', lidar_data['calibrated_sensor_token']) 220 | # Get global pose 221 | ego_pose = transform_matrix(ego_pose['translation'], Quaternion(ego_pose['rotation']), inverse=False) 222 | lidar_pose = transform_matrix(lidar_pose['translation'], Quaternion(lidar_pose['rotation']), inverse=False) 223 | global_pose = np.dot(ego_pose, lidar_pose) 224 | 225 | pcd = transform_pcd(pcd, global_pose) 226 | # color = np.array([color_list[idx]]) / 255 227 | color = np.array([cmap(1/10 * i)[:3]]) 228 | colors = np.repeat(color, len(pcd), axis=0) 229 | 230 | location_pcd_list.append(pcd) 231 | location_color_list.append(colors) 232 | 233 | ''' ======================================== camera ======================================== ''' 234 | ''' visualize only the front three cameras ''' 235 | sample_fc_token = sample['data']['CAM_FRONT_CENTER'] 236 | sample_fl_token = sample['data']['CAM_FRONT_LEFT'] 237 | sample_fr_token = sample['data']['CAM_FRONT_RIGHT'] 238 | # sample_bc_token = sample['data']['CAM_BACK_CENTER'] 239 | # sample_lc_token = sample['data']['CAM_SIDE_LEFT'] 240 | # sample_rc_token = sample['data']['CAM_SIDE_RIGHT'] 241 | 242 | fc_data = nusc.get('sample_data', sample_fc_token) 243 | fc_path = f"{nusc.dataroot}/{fc_data['filename']}" 244 | fc_img = cv2.imread(fc_path) 245 | 246 | fl_data = nusc.get('sample_data', sample_fl_token) 247 | fl_path = f"{nusc.dataroot}/{fl_data['filename']}" 248 | fl_img = cv2.imread(fl_path) 249 | 250 | fr_data = nusc.get('sample_data', sample_fr_token) 251 | fr_path = f"{nusc.dataroot}/{fr_data['filename']}" 252 | fr_img = cv2.imread(fr_path) 253 | 254 | # bc_data = nusc.get('sample_data', sample_bc_token) 255 | # bc_path = f"{nusc.dataroot}/{bc_data['filename']}" 256 | # bc_img = cv2.imread(bc_path) 257 | # 258 | # lc_data = nusc.get('sample_data', sample_lc_token) 259 | # lc_path = f"{nusc.dataroot}/{lc_data['filename']}" 260 | # lc_img = cv2.imread(lc_path) 261 | # 262 | # rc_data = nusc.get('sample_data', sample_rc_token) 263 | # rc_path = f"{nusc.dataroot}/{rc_data['filename']}" 264 | # rc_img = cv2.imread(rc_path) 265 | 266 | h = max([fc_img.shape[0], fl_img.shape[0], fr_img.shape[0]]) 267 | w = max([fc_img.shape[1], fl_img.shape[1], fr_img.shape[1]]) 268 | 269 | fc_img = cv2.resize(fc_img, dsize=(w//img_divider, h//img_divider), interpolation=cv2.INTER_CUBIC) 270 | fl_img = cv2.resize(fl_img, dsize=(w//img_divider, h//img_divider), interpolation=cv2.INTER_CUBIC) 271 | fr_img = cv2.resize(fr_img, dsize=(w//img_divider, h//img_divider), interpolation=cv2.INTER_CUBIC) 272 | # bc_img = cv2.resize(bc_img, dsize=(w, h), interpolation=cv2.INTER_CUBIC) 273 | # lc_img = cv2.resize(lc_img, dsize=(w, h), interpolation=cv2.INTER_CUBIC) 274 | # rc_img = cv2.resize(rc_img, dsize=(w, h), interpolation=cv2.INTER_CUBIC) 275 | 276 | img_arr1 = [fl_img, fc_img, fr_img] 277 | # img_arr2 = [lc_img, bc_img, rc_img] 278 | 279 | vehicle_img = np.concatenate(img_arr1, axis=1) 280 | # vehicle_img2 = np.concatenate(img_arr2, axis=1) 281 | # vehicle_img = np.concatenate((vehicle_img1, vehicle_img2), axis=0) 282 | tp = lidar_data['filename'].split("/")[-1].split(".")[0] 283 | vehicle = sample_token_dict[scene_idx][0] 284 | vehicle_img = add_tag(vehicle_img, tp, vehicle) 285 | 286 | vehicle_img = img_frame(vehicle_img, color) 287 | 288 | num_row = vehicle_img.shape[0] 289 | num_col = vehicle_img.shape[1] 290 | img_canvas[num_row*i:num_row*(i+1), :num_col, :] = vehicle_img 291 | img_canvas = img_canvas.astype(np.uint8) 292 | 293 | if finish_count == nbr_scene: 294 | img_out_dir = f"K:/MARS_10Hz_location/img/{location}/" 295 | if not os.path.exists(img_out_dir): 296 | os.makedirs(img_out_dir) 297 | cv2.imwrite(f"{img_out_dir}/{frame}.jpg", np.concatenate((img_canvas, o3d_screenshot_1), axis=1)) 298 | vis1.close() 299 | break 300 | 301 | if len(location_pcd_list) == 0: 302 | continue 303 | 304 | ''' new lidar scan ''' 305 | pcd_obj.points = o3d.utility.Vector3dVector(np.concatenate(location_pcd_list, axis=0)) 306 | pcd_obj.colors = o3d.utility.Vector3dVector(np.concatenate(location_color_list, axis=0)) 307 | 308 | ''' new background (down-sampled) ''' 309 | if start_flag: 310 | background = pcd_obj.voxel_down_sample(voxel_size=4) 311 | background.points = o3d.utility.Vector3dVector(background.points - np.array([0, 0, 2])) 312 | else: 313 | down_sampled = pcd_obj.voxel_down_sample(voxel_size=4) 314 | background.points = o3d.utility.Vector3dVector(np.concatenate((background.points, down_sampled.points - np.array([0, 0, 10])), axis=0)) 315 | background.colors = o3d.utility.Vector3dVector(np.concatenate((background.colors, down_sampled.colors), axis=0)) 316 | background = background.voxel_down_sample(voxel_size=1) 317 | 318 | ''' new full frame (background + new scan) ''' 319 | pcd_obj.points = o3d.utility.Vector3dVector(np.concatenate((background.points, pcd_obj.points), axis=0)) 320 | pcd_obj.colors = o3d.utility.Vector3dVector(np.concatenate((background.colors, pcd_obj.colors), axis=0)) 321 | 322 | if start_flag: 323 | vis1.add_geometry(pcd_obj) 324 | start_flag = False 325 | view = vis1.get_view_control() 326 | 327 | ''' adjust visualize initial viewpoint as needed ''' 328 | if location == 24: 329 | # # 24 330 | view.set_zoom(1) 331 | view.translate(-150, -100) 332 | # # 15 333 | elif location == 15: 334 | view.set_zoom(1) 335 | view.translate(0, -250) 336 | # # 59 337 | elif location == 59: 338 | view.set_zoom(1) 339 | view.translate(-150, -100) 340 | # # 57 341 | elif location == 57: 342 | view.set_zoom(1) 343 | view.translate(-100, 50) 344 | # # 3 345 | elif location == 3: 346 | view.set_zoom(1) 347 | view.translate(-0, -150) 348 | # 61 349 | elif location == 61: 350 | view.set_zoom(1) 351 | view.translate(200, 50) 352 | else: 353 | vis1.update_geometry(pcd_obj) 354 | 355 | vis1.poll_events() 356 | vis1.update_renderer() 357 | time.sleep(0.005) 358 | 359 | ''' write into video ''' 360 | o3d_screenshot_1 = vis1.capture_screen_float_buffer() 361 | o3d_screenshot_1 = (255.0 * np.asarray(o3d_screenshot_1)).astype(np.uint8) 362 | out0.write(np.concatenate((img_canvas, o3d_screenshot_1), axis=1)) 363 | 364 | ''' save some snapshots ''' 365 | # if frame % 30 == 0: 366 | # img_out_dir = f"K:/MARS_10Hz_location/img/{location}/" 367 | # if not os.path.exists(img_out_dir): 368 | # os.makedirs(img_out_dir) 369 | # cv2.imwrite(f"{img_out_dir}/{frame}.jpg", np.concatenate((img_canvas, o3d_screenshot_1), axis=1)) 370 | 371 | frame += 1 372 | -------------------------------------------------------------------------------- /visualize/lidar_viz.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import matplotlib 4 | from nuscenes import NuScenes 5 | from nuscenes.utils.geometry_utils import BoxVisibility, transform_matrix, view_points 6 | from nuscenes.utils.data_classes import LidarPointCloud, RadarPointCloud, Box 7 | import numpy as np 8 | import open3d as o3d 9 | from pyquaternion import Quaternion 10 | import cv2 11 | from tqdm import tqdm 12 | import math 13 | 14 | img_width = 256 15 | img_height = 256 16 | font = cv2.FONT_HERSHEY_SIMPLEX 17 | 18 | color_list = [(255, 102, 102), (255, 255, 102), (102, 255, 102), (102, 255, 255), (178, 102, 255)] 19 | cmap = matplotlib.colormaps['jet'] 20 | 21 | def transform_pcd(pcd, tran_matrix): 22 | ''' 23 | :param pcd: 3*n np array (1st row x, 2nd row y, 3rd row z) 24 | :param tran_matrix: 4*4 transformation matrix 25 | :return: n*3 pcd (each row is a point [x, y, z]) 26 | ''' 27 | # append the pt by 1 to apply the transformation matrix 28 | rot = tran_matrix[:3, :3] 29 | trl = tran_matrix[:3, 3] 30 | pcd = (rot @ pcd).T + trl 31 | 32 | return pcd 33 | 34 | 35 | def get_scene_tokens(nusc, scene_index): 36 | token_list = [] 37 | scene = nusc.scene[scene_index] 38 | nbr_samples = scene['nbr_samples'] 39 | first_sample_token = scene['first_sample_token'] 40 | last_sample_token = scene['last_sample_token'] 41 | sample = nusc.get('sample', first_sample_token) 42 | token_list.append(first_sample_token) 43 | print(scene["name"]) 44 | # vehicle = scene["name"].split("_")[-1] 45 | for i in range(nbr_samples - 1): 46 | if sample == last_sample_token: 47 | break 48 | token_list.append(sample['next']) 49 | sample = nusc.get('sample', sample['next']) 50 | # return token_list, vehicle 51 | return token_list 52 | 53 | 54 | if __name__ == '__main__': 55 | location = 2 56 | root_dir = f"K:/MARS_10Hz_location/{location}" 57 | nusc = NuScenes(version='v1.0', dataroot=root_dir, verbose=True) 58 | nbr_scene = len(nusc.scene) 59 | print(f"location {location} has {nbr_scene} scenes") 60 | 61 | cap = cv2.VideoCapture(0) 62 | fourcc = cv2.VideoWriter_fourcc(*'MJPG') 63 | out0 = cv2.VideoWriter(f"{root_dir}/lidar_view_traversal.avi", fourcc, 10, (img_width * 4, img_height * 4)) 64 | 65 | vis1 = o3d.visualization.Visualizer() 66 | vis1.create_window( 67 | window_name='Ego Vehicle Segmented Scene', 68 | width=img_width * 4, 69 | height=img_height * 4, 70 | left=480, 71 | top=270) 72 | vis1.get_render_option().background_color = [0, 0, 0] 73 | vis1.get_render_option().point_size = 2 74 | vis1.get_render_option().show_coordinate_frame = True 75 | 76 | pcd_obj = o3d.geometry.PointCloud() 77 | '''=======================================================================================''' 78 | # start_flag = True 79 | # location_pcd_list = [] 80 | # for i in tqdm(range(nbr_scene)): 81 | # scene = nusc.scene[i] 82 | # 83 | # nbr_samples = scene['nbr_samples'] 84 | # first_sample_token = scene['first_sample_token'] 85 | # last_sample_token = scene['last_sample_token'] 86 | # sample = nusc.get('sample', first_sample_token) 87 | # 88 | # while True: 89 | # sample_data_token = sample['data']['LIDAR_FRONT_CENTER'] 90 | # 91 | # lidar_data = nusc.get('sample_data', sample_data_token) 92 | # pcd_path = f"{nusc.dataroot}/{lidar_data['filename']}" 93 | # pcd = LidarPointCloud.from_file(pcd_path).points 94 | # 95 | # # ''' preserve only the inner 64 rings of points (total 128 rings) to reduce pcd size ''' 96 | # # mask = pcd[4, :] < 64 97 | # # pcd = pcd[:, mask][:3, :] 98 | # ''' preserve only pts within 40 meter radius ''' 99 | # mask = np.sqrt((pcd[0, :]**2 + pcd[1, :]**2)) < 40 100 | # pcd = pcd[:, mask][:3, :] 101 | # 102 | # ego_pose = nusc.get('ego_pose', lidar_data['ego_pose_token']) 103 | # lidar_pose = nusc.get('calibrated_sensor', lidar_data['calibrated_sensor_token']) 104 | # # Get global pose 105 | # ego_pose = transform_matrix(ego_pose['translation'], Quaternion(ego_pose['rotation']), inverse=False) 106 | # lidar_pose = transform_matrix(lidar_pose['translation'], Quaternion(lidar_pose['rotation']), inverse=False) 107 | # global_pose = np.dot(ego_pose, lidar_pose) 108 | # 109 | # pcd = transform_pcd(pcd, global_pose) 110 | # 111 | # location_pcd_list.append(pcd) 112 | # 113 | # pcd_obj.points = o3d.utility.Vector3dVector(pcd) 114 | # 115 | # if start_flag: 116 | # vis1.add_geometry(pcd_obj) 117 | # start_flag = False 118 | # else: 119 | # vis1.update_geometry(pcd_obj) 120 | # 121 | # vis1.poll_events() 122 | # vis1.update_renderer() 123 | # time.sleep(0.005) 124 | # 125 | # if sample["next"] != "": 126 | # sample = nusc.get('sample', sample["next"]) 127 | # else: 128 | # break 129 | 130 | '''=======================================================================================''' 131 | # sample_token_dict = {} 132 | # frame_offset_dict = {} 133 | # 134 | # start_flag = True 135 | # max_len = 0 136 | # for i in range(nbr_scene): 137 | # sample_token_dict[str(i)] = get_scene_tokens(nusc, i) 138 | # frame_offset_dict[str(i)] = 0 139 | # nbr_sample = len(sample_token_dict[str(i)]) 140 | # if nbr_sample > max_len: 141 | # max_len = nbr_sample 142 | # 143 | # frame = 0 144 | # finish_count = 0 145 | # skip_idx = [] 146 | # batch = [0] 147 | # 148 | # last_scene_idx = max(batch) 149 | # 150 | # while True: 151 | # # for _ in tqdm(range(max_len)): 152 | # location_pcd_list = [] 153 | # location_color_list = [] 154 | # 155 | # if frame % 50 == 0 and frame != 0 and len(batch) < 5: 156 | # last_scene_idx += 1 157 | # if last_scene_idx < nbr_scene: 158 | # batch.append(last_scene_idx) 159 | # frame_offset_dict[str(last_scene_idx)] = frame 160 | # 161 | # for idx in range(len(batch)): 162 | # i = batch[idx] 163 | # ''' if scene_i is finished playing, skip it ''' 164 | # if i in skip_idx: 165 | # continue 166 | # 167 | # scene_idx = str(i) 168 | # offset = frame_offset_dict[scene_idx] 169 | # if frame - offset >= len(sample_token_dict[scene_idx]): 170 | # finish_count += 1 171 | # skip_idx.append(i) 172 | # 173 | # last_scene_idx += 1 174 | # if last_scene_idx < nbr_scene: 175 | # ''' substitute the finished scene_idx with a new one ''' 176 | # target_idx = batch.index(i) 177 | # batch[target_idx] = last_scene_idx 178 | # frame_offset_dict[str(last_scene_idx)] = frame 179 | # print(f"{frame} scene {i} finished, added scene {last_scene_idx}, offset {frame_offset_dict[str(last_scene_idx)]}, showing {batch}") 180 | # else: 181 | # print(f"{frame} scene {i} finished, this is the last scene") 182 | # continue 183 | # 184 | # sample_token = sample_token_dict[scene_idx][frame - offset] 185 | # sample = nusc.get('sample', sample_token) 186 | # 187 | # sample_data_token = sample['data']['LIDAR_FRONT_CENTER'] 188 | # 189 | # lidar_data = nusc.get('sample_data', sample_data_token) 190 | # pcd_path = f"{nusc.dataroot}/{lidar_data['filename']}" 191 | # pcd = LidarPointCloud.from_file(pcd_path).points 192 | # 193 | # ''' preserve only pts within 40 meter radius ''' 194 | # mask = np.sqrt((pcd[0, :]**2 + pcd[1, :]**2)) < 40 195 | # pcd = pcd[:, mask][:3, :] 196 | # 197 | # ''' transform pcd from local frame to global frame ''' 198 | # ego_pose = nusc.get('ego_pose', lidar_data['ego_pose_token']) 199 | # lidar_pose = nusc.get('calibrated_sensor', lidar_data['calibrated_sensor_token']) 200 | # # Get global pose 201 | # ego_pose = transform_matrix(ego_pose['translation'], Quaternion(ego_pose['rotation']), inverse=False) 202 | # lidar_pose = transform_matrix(lidar_pose['translation'], Quaternion(lidar_pose['rotation']), inverse=False) 203 | # global_pose = np.dot(ego_pose, lidar_pose) 204 | # 205 | # pcd = transform_pcd(pcd, global_pose) 206 | # color = np.array([color_list[idx]])/255 207 | # colors = np.repeat(color, len(pcd), axis=0) 208 | # 209 | # location_pcd_list.append(pcd) 210 | # location_color_list.append(colors) 211 | # 212 | # if finish_count == nbr_scene: 213 | # break 214 | # 215 | # pcd_obj.points = o3d.utility.Vector3dVector(np.concatenate(location_pcd_list, axis=0)) 216 | # 217 | # pcd_obj.colors = o3d.utility.Vector3dVector(np.concatenate(location_color_list, axis=0)) 218 | # if start_flag: 219 | # vis1.add_geometry(pcd_obj) 220 | # start_flag = False 221 | # else: 222 | # vis1.update_geometry(pcd_obj) 223 | # 224 | # vis1.poll_events() 225 | # vis1.update_renderer() 226 | # time.sleep(0.005) 227 | # 228 | # ''' write into video ''' 229 | # o3d_screenshot_1 = vis1.capture_screen_float_buffer() 230 | # o3d_screenshot_1 = (255.0 * np.asarray(o3d_screenshot_1)).astype(np.uint8) 231 | # out0.write(o3d_screenshot_1) 232 | # 233 | # frame += 1 234 | 235 | sample_token_dict = {} 236 | frame_offset_dict = {} 237 | 238 | start_flag = True 239 | max_len = 0 240 | for i in range(nbr_scene): 241 | sample_token_dict[str(i)] = get_scene_tokens(nusc, i) 242 | frame_offset_dict[str(i)] = 0 243 | nbr_sample = len(sample_token_dict[str(i)]) 244 | if nbr_sample > max_len: 245 | max_len = nbr_sample 246 | 247 | frame = 0 248 | finish_count = 0 249 | skip_idx = [] 250 | batch = [0] 251 | 252 | last_scene_idx = max(batch) 253 | 254 | while True: 255 | # for _ in tqdm(range(max_len)): 256 | location_pcd_list = [] 257 | location_color_list = [] 258 | 259 | # if frame % 250 == 0 and frame != 0 and len(batch) < 5: 260 | # last_scene_idx += 1 261 | # if last_scene_idx < nbr_scene: 262 | # batch.append(last_scene_idx) 263 | # frame_offset_dict[str(last_scene_idx)] = frame 264 | 265 | for idx in range(len(batch)): 266 | i = batch[idx] 267 | ''' if scene_i is finished playing, skip it ''' 268 | if i in skip_idx: 269 | continue 270 | 271 | scene_idx = str(i) 272 | offset = frame_offset_dict[scene_idx] 273 | if frame - offset >= len(sample_token_dict[scene_idx]): 274 | finish_count += 1 275 | skip_idx.append(i) 276 | 277 | last_scene_idx += 1 278 | # if last_scene_idx < nbr_scene: 279 | if last_scene_idx < 10: 280 | ''' substitute the finished scene_idx with a new one ''' 281 | # if len(batch) == 1: 282 | target_idx = batch.index(i) 283 | batch[target_idx] = last_scene_idx 284 | frame_offset_dict[str(last_scene_idx)] = frame 285 | print(f"{frame} scene {i} finished, showing {batch}") 286 | else: 287 | print(f"{frame} scene {i} finished, this is the last scene") 288 | continue 289 | 290 | sample_token = sample_token_dict[scene_idx][frame - offset] 291 | sample = nusc.get('sample', sample_token) 292 | 293 | sample_data_token = sample['data']['LIDAR_FRONT_CENTER'] 294 | 295 | lidar_data = nusc.get('sample_data', sample_data_token) 296 | pcd_path = f"{nusc.dataroot}/{lidar_data['filename']}" 297 | pcd = LidarPointCloud.from_file(pcd_path).points 298 | 299 | ''' preserve only pts within 40 meter radius ''' 300 | mask = np.sqrt((pcd[0, :] ** 2 + pcd[1, :] ** 2)) < 40 301 | pcd = pcd[:, mask][:3, :] 302 | 303 | ''' transform pcd from local frame to global frame ''' 304 | ego_pose = nusc.get('ego_pose', lidar_data['ego_pose_token']) 305 | lidar_pose = nusc.get('calibrated_sensor', lidar_data['calibrated_sensor_token']) 306 | # Get global pose 307 | ego_pose = transform_matrix(ego_pose['translation'], Quaternion(ego_pose['rotation']), inverse=False) 308 | lidar_pose = transform_matrix(lidar_pose['translation'], Quaternion(lidar_pose['rotation']), inverse=False) 309 | global_pose = np.dot(ego_pose, lidar_pose) 310 | 311 | pcd = transform_pcd(pcd, global_pose) 312 | # color = np.array([color_list[idx]]) / 255 313 | color = np.array([cmap(1/10 * i)[:3]]) 314 | # print(f"i {i}, color {1/nbr_scene * i}/1, {color}") 315 | colors = np.repeat(color, len(pcd), axis=0) 316 | 317 | location_pcd_list.append(pcd) 318 | location_color_list.append(colors) 319 | 320 | # if finish_count == nbr_scene: 321 | # break 322 | if finish_count == 10: 323 | break 324 | 325 | if len(location_pcd_list) == 0: 326 | continue 327 | 328 | ''' new lidar scan ''' 329 | pcd_obj.points = o3d.utility.Vector3dVector(np.concatenate(location_pcd_list, axis=0)) 330 | pcd_obj.colors = o3d.utility.Vector3dVector(np.concatenate(location_color_list, axis=0)) 331 | 332 | ''' new background (down-sampled) ''' 333 | if start_flag: 334 | background = pcd_obj.voxel_down_sample(voxel_size=4) 335 | background.points = o3d.utility.Vector3dVector(background.points - np.array([0, 0, 2])) 336 | else: 337 | down_sampled = pcd_obj.voxel_down_sample(voxel_size=4) 338 | background.points = o3d.utility.Vector3dVector(np.concatenate((background.points, down_sampled.points - np.array([0, 0, 2])), axis=0)) 339 | background.colors = o3d.utility.Vector3dVector(np.concatenate((background.colors, down_sampled.colors), axis=0)) 340 | background = background.voxel_down_sample(voxel_size=1) 341 | 342 | ''' new full frame (background + new scan) ''' 343 | pcd_obj.points = o3d.utility.Vector3dVector(np.concatenate((background.points, pcd_obj.points), axis=0)) 344 | pcd_obj.colors = o3d.utility.Vector3dVector(np.concatenate((background.colors, pcd_obj.colors), axis=0)) 345 | 346 | if start_flag: 347 | vis1.add_geometry(pcd_obj) 348 | start_flag = False 349 | else: 350 | vis1.update_geometry(pcd_obj) 351 | 352 | vis1.poll_events() 353 | vis1.update_renderer() 354 | time.sleep(0.005) 355 | 356 | ''' write into video ''' 357 | o3d_screenshot_1 = vis1.capture_screen_float_buffer() 358 | o3d_screenshot_1 = (255.0 * np.asarray(o3d_screenshot_1)).astype(np.uint8) 359 | out0.write(o3d_screenshot_1) 360 | 361 | frame += 1 362 | --------------------------------------------------------------------------------