目录

目录

Waymo Open Dataset v2 数据集尝试与解读

Difference of asset annotation between waymo open dataset v1 & v2


preliminaries: waymo v1 标注检查

lidar_camera_projection 标注

range_images, camera_projections, seg_labels, range_image_top_pose = frame_utils.parse_range_image_and_camera_projection(frame)

points, cp_points = frame_utils.convert_range_image_to_point_cloud(
    frame,
    range_images,
    camera_projections,
    range_image_top_pose, 
    ri_index=0) # first return
  • points 是一个列表,每个 item 来自不同的 laser;每个 item 是从 range_images 计算出的 [N, 3] 尺寸点云,N 是当前 lidar 的 range>0 合法点

  • cp_points 也是一个列表,每个 item 来自不同的 laser;每个 item 是 [N, 6] 尺寸点云

    • 另外考虑:这里的投影过程应该是已经考虑了 lidar 的 rolling shutter effect、camera 的 rolling shutter effect
    • 6的维度实际上存储了在至多两个相机中的投影后的相机索引和像素位置,如下图所示

https://longtimenohack.com/posts/datasets/waymo_asset_v1v2/image-20231110143227012.png

为什么至多两个相机?

📌 至多两个是合理的, 一个三维点最多同时被两个 camera 看到, 因为 waymo 的相机 overlap 最多只有 2 个相机 overlap 在一起

  • 如果在所有 camera 中都不可见, 那么都是 0
  • 如果只在一个相机中可见,那么前3个维度依次就是相机的索引、投影后W值、H值
  • 如果同时在两个相机中都可见,那么后3个维度就是另一个相机的索引、投影后W值、H值

camera projection 数据在使用时,就是检查相机索引然后投到对应的相机中进行可视化

images = sorted(frame.images, key=lambda i:i.name)
cp_points_all_concat = np.concatenate([cp_points_all, points_all], axis=-1)
cp_points_all_concat_tensor = tf.constant(cp_points_all_concat)

# The distance between lidar points and vehicle frame origin.
points_all_tensor = tf.norm(points_all, axis=-1, keepdims=True)
cp_points_all_tensor = tf.constant(cp_points_all, dtype=tf.int32)

# 这里mask 的就是 camera projection 数据中那些落在了 images[0] 对应的相机的点云
mask = tf.equal(cp_points_all_tensor[..., 0], images[0].name)

cp_points_all_tensor = tf.cast(tf.gather_nd(
    cp_points_all_tensor, tf.where(mask)), dtype=tf.float32)
points_all_tensor = tf.gather_nd(points_all_tensor, tf.where(mask))

projected_points_all_from_raw_data = tf.concat(
    [cp_points_all_tensor[..., 1:3], points_all_tensor], axis=-1).numpy()

lidar_camera_synced_box 标注

  • 和 lidar_box 一起,是 frame.laser_labels[i] 的一部分(所以 laser_object_id 和 lidar_box 是完全一一对应的)

  • 从统一的 frame 时间戳,调整到某个最可见相机(只会挑一个相机)的时间戳的自车pose 下的新的 box

Label 0Wc77bUoGOYWn3rPibjmqQ has an offset of 0.068m between box and camera_synced_box, and is most visible in camera FRONT_LEFT.
Label 6APguACNgkS7CIVhp-OQwQ has an offset of 0.154m between box and camera_synced_box, and is most visible in camera SIDE_LEFT.
Label JImzpboUhxF18N6Ku8UULQ has an offset of 0.025m between box and camera_synced_box, and is most visible in camera FRONT.
Label MoBcLGa2BvFbGK4efeOPbA has an offset of 0.005m between box and camera_synced_box, and is most visible in camera FRONT.
Label StYQUhqtSqD2y3TnbV1ayA has an offset of 0.06m between box and camera_synced_box, and is most visible in camera FRONT_LEFT.
Label Wrqs7uofPDuTDgnEOJOcKA has an offset of 0.036m between box and camera_synced_box, and is most visible in camera FRONT.
Label _cDBO3MzR4neXuQjeTKZnQ has an offset of 0.015m between box and camera_synced_box, and is most visible in camera FRONT_RIGHT.
Label fHGJMU6j43dn9Y_YjVTiYw has an offset of 0.059m between box and camera_synced_box, and is most visible in camera FRONT.
Label fsjZKk-BzLWNyuPoyh75qQ has an offset of 0.127m between box and camera_synced_box, and is most visible in camera SIDE_LEFT.
Label gQvR2IhjRfh1ovWQqyGxDA has an offset of 0.006m between box and camera_synced_box, and is most visible in camera FRONT.
Label nCg9Xm6Kd8bCUf6MkAHA7Q has an offset of 0.045m between box and camera_synced_box, and is most visible in camera FRONT_LEFT.
Label prtV3IR59gZ1cHzN2a0Mdg has an offset of 0.042m between box and camera_synced_box, and is most visible in camera FRONT_LEFT.
Label sNVLswUHR7MSUN2vAVCDLw has an offset of 0.033m between box and camera_synced_box, and is most visible in camera FRONT_RIGHT.
Label tK9w92yB8jL61tzJH3AaZA has an offset of 0.05m between box and camera_synced_box, and is most visible in camera FRONT_RIGHT.
Label tbzPJcEAVb29SQMguGDmvw has an offset of 0.044m between box and camera_synced_box, and is most visible in camera FRONT.
Label uYIg4rzNu8iUj_RG-ckg_w has an offset of 0.003m between box and camera_synced_box, and is most visible in camera FRONT_LEFT.
Label xCw6JowWM9tbVw9mrpNlBQ has an offset of 0.019m between box and camera_synced_box, and is most visible in camera FRONT_LEFT.

projected_lidar_box 标注

官方解释:

Ref: src/waymo_open_dataset/dataset.proto

The native 3D LiDAR labels (laser_labels) projected to camera images.
A projected label is the smallest image axis aligned rectangle that can cover all projected points from the 3d LiDAR label. The projected label is ignored if the projection is fully outside a camera image. The projected label is clamped to the camera image if it is partially outside.

也就是:最小能覆盖全部3dbox投射点的、图片坐标系对齐(axis-aligned)的矩形

可视化检查

  • 非紧密贴合,会多框一些,毕竟是 3dbox 投射后点的 2dbox

https://longtimenohack.com/posts/datasets/waymo_asset_v1v2/image-20231110150814147.png

📌 frame.projected_lidar_labels[i].labels[j].id 和 lidar_box 的 laser_object_id 是一致的,但不是一一对应,因为会有没有投到任何相机中的 lidar_box 物体;另外这个 id 会额外加上当前投影到的相机的 id,形如 {laser_object_id}_{camera_name}

camera_box 标注

官方解释:

Ref: src/waymo_open_dataset/dataset.proto

Native 2D camera labels. Note that if a camera identified by CameraLabels.name has an entry in this field, then it has been labeled, even though it is possible that there are no labeled objects in the corresponding image, which is identified by a zero sized CameraLabels.labels.

❓ 可能会存在在实际图片中 zero-sized 的 camera_label?

📌 camera_object_id 是完全独立的一套 id 相对于 laser_object_id,连命名格式都不一样

可视化检查

特点:

  • 同相机的同一个物体,多帧之间保持相同的 camera object id (2d tracking)
  • 框与物体边缘紧密贴合,类似 2d detection 任务的贴合程度
  • 不同相机的同一个物体, camera object id 不同
  • 只有 pedestrian 类别和 vehicle 类别有;与 3d track id 完全独立的一套标注和命名规则;不过针对 pedestrian 类别专门做了和 3d track id 的相关性标注

https://longtimenohack.com/posts/datasets/waymo_asset_v1v2/0000.jpg

camera_to_lidar_box_association

frame.camera_labels[i].labels[j].association.laser_object_id

  • 只有一部分 camera object id 是有在 lidar 中的 association 的

  • ⚠️ 截止 v2 版本,只有 pedestrian 有 association lidar id,其他类别没有

    •   // Information to cross reference between labels for different modalities.
        message Association {
          // Currently only CameraLabels with class `TYPE_PEDESTRIAN` store
          // information about associated lidar objects.
          optional string laser_object_id = 1;
        }
        optional Association association = 10;
      

某帧的全部 camera_object_id 及其对应的 laser_object_id 检查:

camera_name: 1
1b25ab17-7755-4b82-9db7-e17a632b30e0 N/A
34705e37-54b3-4e27-93e3-cea8b53d2460 N/A
352a8334-a9f8-4b81-bfc9-2128704a3b09 N/A
3b6f9266-7c75-4ebe-9cac-c44c8c0b3063 N/A
44624189-e26e-4089-8c9a-fdbbb8ac70f7 N/A
50f9e0b3-0671-46e3-b1f1-3ecc691d935a laser_object_id: "uNDQ7xVc_R2jX3WpxCE8Ow"
52a1bffb-c095-48d6-8348-84de62ed38c5 N/A
77582c43-0d16-4c1d-bb87-441b66b01097 N/A
81501373-9d9c-4175-a45c-53f8f116b300 N/A
8fc98f75-8d23-44b6-9833-2b5dbcc68212 N/A
94c95279-67aa-46db-b258-4d3088091ddc N/A
a22d90d7-929b-422d-b378-03c52b8b7387 N/A
a6158372-9b47-4ef6-a5c0-81ca08bd0835 N/A
a7ac63c7-a3e4-4b4c-9595-72117294a424 N/A
aab66ab7-845f-48b4-8997-b1e5bb3dfc85 N/A
adfa6727-c8a2-4ade-98ff-b26ae582d3a0 laser_object_id: "_wnDKltsjPg1hxa01n4SyA"
cc84c53e-3e43-415c-984a-a39fdc780af6 N/A
d790a8c9-29d5-4bf6-8caa-58da59b6b149 N/A
e5a25bdd-de45-489e-b559-33dbe6cbb3b3 N/A
ec02fd23-5f8a-4cab-b8eb-ad49723d7eee N/A
fd3e5f39-0a7a-4f03-b068-1eb22c60eb84 N/A

camera_name: 2
1629c4ea-9b7f-4a8b-ba30-b315e7f1cba0 N/A
2f547184-8c71-4d9d-b650-1b2b40c4f199 N/A
40948f43-ddf6-4eca-91f4-23fb09235d8a N/A
4e2ca531-72e3-46f9-99dd-26804eda849f N/A
520be614-ed49-497f-a736-2ed1c5013024 N/A
57889f3e-49ba-4c94-bcd0-3cdd82d85f62 N/A
8a0dedc1-3154-4b9c-ac36-4364b8cb1b93 N/A
8f6b487b-34cf-43f2-9cad-db36f621b4e2 N/A
92f96255-d216-413b-a755-d272551f5d31 N/A
a6d0c7e9-23d4-46f5-918a-051a7b0c1b45 laser_object_id: "_wnDKltsjPg1hxa01n4SyA"
a91b0f72-515b-4772-8b9a-ca7832e9253e N/A
ae15f357-53aa-4140-aced-17e2f75cdc93 N/A
b007248f-06eb-4d45-b0b9-eeffafde3e02 N/A
c2873481-51ec-4ad0-a9e5-75af2d0c8a62 N/A
cb542abf-eb4c-4423-b893-be7c8a8f81d1 N/A
ce5e2829-74ff-4530-9090-c2226969c335 N/A
dd4d3de0-95be-42a5-8109-78e629db9ee0 N/A
e0b56401-ead7-4f8a-98f0-532d4721f1bc N/A
e4a644de-7998-45dc-8e63-0fcae58775bb N/A

camera_name: 4
07cf7f0a-cc86-44cd-80f2-ed72ba2aa241 N/A
433d0795-b05c-450d-b4e8-6f8b9aa36690 N/A
6f798a10-29cf-4e2d-9e7c-3efccb999736 laser_object_id: "RYdUFrGl8fCvFc_onu53jg"
a7ace8b6-7ac5-421f-8462-e6936940edab N/A

camera_name: 3
3f22cb6a-bd34-4e3c-b2ea-8f7fdee31c46 N/A
60054127-4dac-419c-b05a-e7ba47231181 N/A
6a714bf8-289c-4b95-bacb-0e4beb5b307e N/A
e7da9cb7-f023-4a19-bdb2-7ffc986cc73a laser_object_id: "3s1ihjXhOSQxN-UIaWA-_A"
f56391ea-a0dc-4910-9337-fea6ad3f64e7 N/A

camera_name: 5
# camera box 物体

waymo v2 标注检查

overview

  • 逐帧格式的数据 & 标注全部都来自 v1 格式的 copy paste,只是换了一种存储顺序和格式

    • including 传感器数据、标定、逐帧相机box、逐帧lidar box标注 etc.
  • waymo v2 额外提供了一组逐物体格式的标注,提供比较 dense 的 object (tracking) mask 和 大号 camera patch

    • 这里的 mask/box 的 track id 就是原先标注中的 laser_object_id,是 unique & 多帧多相机一致的
    • 只有 vehicle 和 pedestrian 两个类别,但是比之前 v1 版本中的 panoptic mask 要 dense 很多,缺失帧比例很小,大致 <20%

components / tags

通过 api 自带的工具可以查看所有 tag 对应哪些 component

print('Available components:')
for component, tag in v2.TAG_BY_COMPONENT.items():
  print(f'{tag}: {component.__name__}')
  schema = component.schema()
  for column, arrow_type in zip(schema.names, schema.types):
    print(f'\t{str(arrow_type):40s} {column}')
Component Python ClassDirectory / component tagDescriptionv1 存在性
CameraBoxComponentcamera_box逐个 camera 的、逐个时间戳的、逐个 camera_object_id 的 2dboxframe.camera_labels[i].labels[j]
i 是 camera_name 的索引,j 是 相应 camera 下具体 camera_object_id 的索引
CameraCalibrationComponentcamera_calibration逐个 camera 的frame.context.camera_calibrations[i]
i 是 camera_name 的索引
CameraHumanKeypointsComponentcamera_hkpCamera human keypoints
CameraImageComponentcamera_image逐个 camera 、逐个时间戳的frame.images[i].image
frame.images[i].velocity
frame.images[i].pose.transform
frame.images[i].pose_timestamp
rolling shutter 相关参数
i 是 camera_name 的索引
CameraSegmentationLabelComponentcamera_segmentationpanoptic 标注,但只有约 10% 的帧存在、完全没有和 3d track id 的对应关系、有的 2d id 不一定有 trackframe.images[i].camera_segmentation_label
.instance_id_to_global_id_mapping,
.panoptic_label_divisor
.panoptic_label
i 是 camera_name 的索引
CameraToLiDARBoxAssociationComponentcamera_to_lidar_box_association逐个 frame.camera_labels
每个 camera label 对象都会标一个 association.laser_object_id,对应其在 lidar 标注中的 object_id
camera_object_id=frame.camera_labels[i].labels[j].id
laser_object_id=frame.camera_labels[i].labels[j].association.laser_object_id
📌 这个 association 信息原来在 v1 格式中就是存在的
i 是 camera_name 的索引,j 是 相应 camera 下具体 camera_object_id 的索引
LiDARComponentlidar逐个 lidar 的原始 return_1 和 return_2 range imageframe.lasers[i].ri_return1.range_image_compressed
frame.lasers[i].ri_return2.range_image_compressed
i 是 lidar_name 的索引
LiDARBoxComponentlidar_box逐个 frame.laser_labels (laser_object_id)的原始 3dbox (tracklet),还额外包含加速度、速度、难度等信息,是 3D detection 主要使用的frame.laser_labels[i].xxx
speed, accel, num_points, difficulty
i 是 laser_object_id 的索引
LiDARCalibrationComponentlidar_calibration逐个 lidar 的标定数据context.laser_calibrations[i].lidar_calibration
i 是 lidar_name 的索引
LiDARCameraProjectionComponentlidar_camera_projection逐个 lidar 的三维点,在至多2个相机中的投影的相机索引和像素位置 (i, w, h),尺寸[N, 6]frame.lasers[i].ri_return1.camera_projection_compressed
frame.lasers[i].ri_return2.camera_projection_compressed
i 是 lidar_name 的索引
LiDARCameraSyncedBoxComponentlidar_camera_synced_box逐个 frame.laser_labels(laser_object_id),逐个时间戳,在某个单个最可见的相机下的时间同步的 3dboxframe.laser_labels[i].most_visible_camera_name
frame.laser_labels[i].camera_synced_box
i 是 laser_object_id 的索引
LiDARHumanKeypointsComponentlidar_hkpLiDAR human keypoints
LiDARPoseComponentlidar_pose逐个 lidar、 逐个时间戳的 ,每个lidar光束对应自车poseframe.lasers[i].ri_return1.range_image_pose_compressed
i 是 lidar_name 的索引
LiDARSegmentationLabelComponentlidar_segmentationrange image 上的 segmentation 标注,可以转为 3d 点的 segmentation 标注
只有 top lidar 有、不是所有帧都有、即使是有的帧也可能有点没有分割标注
frame.lasers[TOP].ri_return1.segmentation_label_compressed
frame.lasers[TOP].ri_return1.segmentation_label_compressed
只有 top lidar 有 segmentation label
ProjectedLiDARBoxComponentprojected_lidar_box逐个 frame.projected_lidar_labels (laser_object_id)、逐个 camera 的投影后的 2dbox 的 center 和 sizeframe.projected_lidar_labels[i].labels[j].box
i 是 camera_name 的索引
j 是 laser_object_id 的索引
StatsComponentstats当前序列的一些 meta 信息,包括大概的位置、天气天时(实际测试下来并不准)frame.context.stats
VehiclePoseComponentvehicle_pose自车 poseframe.pose.transform

Asset level tags: v2 引入的全新标注

Component Python ClassDirectory / component tagDescriptionv1 存在性
ObjectAssetAutoLabelComponentxxx_asset_auto_labelDense auto-labels on the camera image patches
📌 这里的 Dense 是指帧的密度比较高,经测试,只要是有标注的物体,帧缺失率在 20% 以内
No
ObjectAssetCameraSensorComponentxxx_asset_camera_sensorCamera image patches, cropping config, and projected LiDAR pointsNo
ObjectAssetLiDARSensorComponentxxx_asset_lidar_sensorLiDAR points within each bounding boxNo
ObjectAssetRayComponentxxx_asset_rayCamera rays transformed to the bounding box coordinate frameNo
ObjectAssetRayCompressedComponentxxx_asset_ray_compressedA compressed copy of the corresponding ObjectAssetRayComponent.
使用PCA压缩,显著减少文件大小;大概在 rays_o 和 rays_d 有 1e-5 m 量级的误差;从直接存储的 1000KiB / patch_frame 压缩到了 30KiB / patch_frame,大致 1/40 的压缩率
No
ObjectAssetRefinedPoseComponentxxx_asset_refined_poseOptional refined bounding box;
针对 Vehicle 类别,额外使用点云配准 refine 了 4x4 刚体 pose
No

xxx_asset_auto_label 标注 + xxx_asset_camera_sensor 标注

  • 是按照 laser_object_id 组织的
  • 只有 vehicle 和 pedestrian 两个类别有
  • 相对来说比较 dense

包含的信息

key.segment_context_name <class 'str'>
key.frame_timestamp_micros <class 'numpy.int64'>
key.laser_object_id <class 'str'>
key.camera_name <class 'numpy.int8'>
[ObjectAssetCameraSensorComponent].camera_region.center.x <class 'numpy.float64'>
[ObjectAssetCameraSensorComponent].camera_region.center.y <class 'numpy.float64'>
[ObjectAssetCameraSensorComponent].camera_region.size.x <class 'numpy.float64'>
[ObjectAssetCameraSensorComponent].camera_region.size.y <class 'numpy.float64'>
[ObjectAssetCameraSensorComponent].rgb_image <class 'bytes'>
[ObjectAssetCameraSensorComponent].proj_points_dist.values <class 'numpy.ndarray'>
[ObjectAssetCameraSensorComponent].proj_points_dist.shape <class 'numpy.ndarray'>
[ObjectAssetCameraSensorComponent].proj_points_mask <class 'bytes'>
[ObjectAssetAutoLabelComponent].semantic_mask <class 'bytes'>
[ObjectAssetAutoLabelComponent].instance_mask <class 'bytes'>
[ObjectAssetAutoLabelComponent].object_mask <class 'bytes'>

camera region

会比物体实际的 2dbox 大一圈,类似于 imagenet 等数据集的以物体为中心的正方形大 patch 的形式

https://longtimenohack.com/posts/datasets/waymo_asset_v1v2/0030.jpg

几种不同的 mask

  • semantic mask 就是自动产生的 semantic mask

    • 另外,基本可以确定这里的 dense 的 auto label 的 object_mask 是基于 semantic_mask 搭配 3d track box 等信息来做筛选提取的,比如在下面的图例中 object_mask 把行人右上角的一处背景也划归给了当前行人,这一问题在 semantic mask 中就已经错误地把右上角的一处背景归为行人类别了
  • instance mask 这里只是名字是 instance mask,实际检查发现事实上是 semantic mask 在每个 laser_object_id 的 camera_region 中框出节选的部分

  • object mask 类似 instance mask,是逐物体的、比较高质量的、逐帧tracking(有缺失帧但缺得不多) 的 mask

  • proj_point_mask 就是把落入该物体的 3d tracking box 的 lidar 点云投射到图像上的点,所以一般会包含地面的 lidar 点云,使用时一般需要搭配 object mask 一起来使用

https://longtimenohack.com/posts/datasets/waymo_asset_v1v2/image-20231115135404834.png

waymo v1 兼容的部分

编者语:和 waymo v1 兼容的部分全部都是 v1 格式中数据的 copy paste,只是换个存储顺序和格式,没有改变或增删

官方原话:

Note that column values in v2-supported components is the same as corresponding proto fields in the v1.4.2

官方提供的 correspondence 检查 ref:

Refer to the section “List columns in all components” for the full list of supported components and the code under waymo_open_dataset/v2/perception/compat_v1/ to learn more how this correspondence is defined.

LiDARBoxComponent

class LiDARBoxComponentExtractor(interfaces.LiDARLabelComponentExtractor):
  """Extracts LiDAR box label component."""

  def __call__(
      self, src: interfaces.LiDARLabelComponentSrc
  ) -> Iterable[_v2_box.LiDARBoxComponent]:
    if not src.lidar_label.HasField('box'):
      return []
    key = _v2_base.LaserLabelKey(
        segment_context_name=src.frame.context.name,
        frame_timestamp_micros=src.frame.timestamp_micros,
        laser_object_id=src.lidar_label.id,
    )
    yield _v2_box.LiDARBoxComponent(
        key=key,
        box=_get_box3d(src.lidar_label.box),
        type=src.lidar_label.type,
        speed=_get_metadata_vector(src.lidar_label, 'speed'),
        acceleration=_get_metadata_vector(src.lidar_label, 'accel'),
        num_lidar_points_in_box=src.lidar_label.num_lidar_points_in_box,
        num_top_lidar_points_in_box=src.lidar_label.num_top_lidar_points_in_box,
        difficulty_level=_get_difficulty(src.lidar_label),
    )

VehiclePoseComponent

class VehiclePoseFrameExtractor(interfaces.FrameComponentExtractor):
  """Extracts human keypoints from frame protos."""

  def __call__(
      self, src: interfaces.FrameComponentSrc
  ) -> Iterable[_v2_pose.VehiclePoseComponent]:
    yield _v2_pose.VehiclePoseComponent(
        key=_v2_base.FrameKey(
            segment_context_name=src.frame.context.name,
            frame_timestamp_micros=src.frame.timestamp_micros,
        ),
        world_from_vehicle=column_types.Transform(
            transform=list(src.frame.pose.transform)
        ),
    )

各种 2d/3d box 的区别和联系

有这些潜在的 box:

  • lidar_box (3D tracking box)
  • lidar_camera_synced_box (3D tracking box)
  • projected_lidar_box (2D projected tracking box)
  • camera_box (2D tracking box,不同相机不同)
  • camera_to_lidar_box_association (camera_box 数据和 lidar_box 数据的联系)
  • v2: xxx_asset_auto_label 的 keys
  • v2: xxx_asset_camera_sensor 的 keys
  • v2: xxx_asset_lidar_sensor 的 keys

区别对比:

  • lidar_box vs. lidar_camera_synced_box

    • 都是在 frame.laser_labels 中的,两者的 keys 完全一致完全一一对应
    • lidar_box 是在 统一的 “frame” 时间戳下标注的,而 lidar_camera_synced_box 则是在最可见的相机(只会挑一个相机)的时间戳下重新调整估计了 自车 pose 后调整的 box
  • lidar_box vs. projected_lidar_box

    • frame.projected_lidar_labels[i].labels[j].id 和 lidar_box 的 laser_object_id 是一致的,但不是一一对应,因为会有没有投到任何相机中的 lidar_box 物体
    • projected_lidar_box 的 id 会额外加上当前投影到的 camera_name,形如 {laser_object_id}_{camera_name}
  • lidar_box vs. camera_box

    • camera_object_id 是完全独立的一套区分和命名机制,如果不同相机看到同一物体,camera object id 是完全不同的
    • 只有pedestrian 类别的 camera object id 是有在 lidar 3d track id 中的 association 的
  • camera_box vs. projected_lidar_box

    • camera box 是完全独立于 3d 的 纯 2d tracking 标注,只有 pedestrian 类别额外建立了与 3d track id 的 association,而 projected lidar box 就是 3d tracking box 直接投影到 相机图像上的结果
    • camera box 虽然是 tracking box,但是不同相机是不同的 id;projected lidar box 因为是直接来自于 3d tracking box 的投影,不同帧不同相机都是一致的
    • camera box 类似于 检测任务中的 box 标注是紧密贴合的;projected lidar box 是 3d box 投到 2d 上的,因此会大一圈
  • camera_box vs. v2.xxx_asset_camera_sensor.camera_region

    • v1 中的 camera box 类似检测任务中的紧密贴合的 tracking box;v2 中的 asset 标注中的 camera region 更类似于 imagenet 的形式,是以物体为中心的正方形大一圈的 patch
    • v1 中的 camera box 比较全,很多只看到一小部分的帧都有;v2 中的 asset 标注有一定的筛选规则,一般投影比较完整的帧才有标注
    • v1 中的 camera box 是独立于 3d 标注的,只有 pedestrian 类别额外建立了与 3d track id 的 association;v2 的 asset 标注是完全按照 3d track id 来组织的
    • v1 中的 camera box 虽然帧比较全,但是只有 box 没有 mask;v2 的 asset 标注只要有 camera region 的帧就一定有 object mask

waymo v2 API 学习杂项

parquet 数据格式

Ref: https://parquet.apache.org/docs/file-format/

  • 📌 多个形式的数据的 key-val pair 的 merge 机制,以及 merge 过程中的 null 判断
    • 比如把不同物体 id 的 camera 信息 和 lidar 信息合并的时候,可以选择 left_nullable=True,允许有空;或者也可以不设置(默认 left_nullable=False),这样空的 key 是忽略的;对于在 cam 中标注和在 lidar 中标注不对齐的物体的数据整理有帮助
    • 列中的 null 的行是直接在定义层就 run-length encoded (RLE),不会编码到数据中
  • Apache arrow vs. Apache parquet
    • arrow 是一种列式内存格式,代表数据在内存中的存储方式,是一种 columnar memory format,支持 zero-copy reads for lightning-fast data access without serialization overhead
    • parquet 是一种列式存储文件格式,代表数据在文件系统中的存储方式;
    • 可以用 arrow 的接口来读写 parquet 格式的文件,不过 arrow 也支持其他格式的文件,比如 csv, json, feather, orc, avro, protobuf 等等常见格式
  • parquet 的 column vs. row groups / colume chunks
    • column 就是数据库中的 “列” 的概念,代表某种具体类型的数据存储,比如对于 人员信息等级的 “age” ,“gender” 等就是一个个的列
    • 每一列的数据量可能非常非常大,所以会额外先拆分成 row groups 的层级,然后每个 row groups 又是由一个个 column chunks 构成的,column chunks 就是物理存储连续的多行数据

working with python: dask.dataframe

parquet 格式只是文件存储中的格式;在实际使用时,waymo team 使用了 dask.dataframe 工具来读取并操作;

  • dask 是一个并行计算库,提供了一个分布式的 dataFrame 的实现,可以处理大于内存的数据集;dask 的 dataframe 是一个大型的并行的 DataFrame,由许多小型的 Pandas Dataframe 构成,分布在多个核心或机器上,这些小的 DataFrame 统称为 partitions

  • dask 的 dataframe

    • 尽可能做了很多和 pandas 的 dataframe 一样的 API,唯一不同的是 dask 的 dataframe 支持很多个 partitions(根据行来切分),支持更 scalable 的大数据,包括支持大于内存的数据集
    • 延迟计算特性,对 dataframe 进行的所有操作都不是立即执行的,只会生成一个执行计划,只有显式地请求结果,如调用 compute() 时,才会真正执行。 -> 这主要是为了能够对执行计划进行优化 & 尽可能并行
  • dataframe 的各种 method,在 .compute() 都是 delayed 对象,只有 compute() 后才会计算并返回需要的类型

  • persist() 方法:简单来说,就是让一个 lazy 的 dask collection 变得不再 lazy

  • groupby() 方法:大部分和 pandas 的 groupby() 方法的逻辑是一致的

  • merge() 方法:把两个 dataframe 中,指定的 on 的列的值一致的行匹配合并在一起

    • 对应 SQL 的 join 操作
    • how=left,right,inner,outer 主要指的是在那些指定的 on 列中,如果一个 dataframe 有某种列值组合、而另一个 dataframe 没有、应该怎样操作
    • 不在指定的 on 值中的列,都是直接合并在一起;merge 主要就是为了把具有相同 id 但有不同意义的列组合在一起
    • 如果一个 dataframe 的 某行的 on 列的值 和 另一个 dataframe 很多行的 on列的值都是对应的,那么前一个 dataframe 其他列的值都会被复制很多行,放在组合后的新行里
      • 比如如果把 cam_img_df 和 camera_box_df 直接 merge 在一起,由于他们的 common keys (“on” columns) 是 segment, timestamp 和 camera_name,因此对于某帧某相机的多个 camera_object_id 行 在 merge 后会把 cam_img_df 中的 image 复制很多遍
      • 所以 waymo team 在 merge 函数外额外包了一层,提供了 left_group 和 right_group 配置,允许把 left 或 right dataframe 先按照 common keys 进行一次 groupby(common_keys).agg(list) 操作,把某个 dataframe 中能按照 common keys group 在一起的全部数据拼成单个 list
dataframe.groupby('xx').agg(list) 笔记

其实就是按某个 key 的 unique 来进行 group,如果有多个行对应同一个 unique key,那么就把多个行的值直接拼成一个 list ,这样就能塞成一行了

https://longtimenohack.com/posts/datasets/waymo_asset_v1v2/image-20231115164721153.png

dataframe.merge() 笔记

📌 merge 的主要作用还是把同 on/key 值,但是两方互有不同意义的列的两个表合在一起,所以一方有某些列、一方没有某些列是很正常的现象

  1. 按照 “on” 给出的 列们是否一致 来确定两个 dataframe 中的一对"行"是否 merge 在一起
    • 如果 两个 dataframe 在 “on” 的列上的值是一致的,那么这些列就应该 merge 在一起
  2. 只要 “on” 存在且一致,两个行就会合并
  3. how=inner, outer, left, right 说的是对于 “on” 指定的那些列,左侧、右侧两个df的列的存在性;其他列是不管的,统一当做数据列
  4. 不在 on 指定范围中的列
    • 如果 left, right 中有一方没有而另一方有,就拿on列(们)匹配的有值的那一方的值来填充
    • 如果 left, right 中两方都有,不管是否一样,都会强行重命名成两个列合在一起

case1:

  • 在 df1 中,[A=1,B=3] 只有1个,在 df2 中,[A=1,B=3] 有3个

    • -> 不管 how 是哪个模式,df1 中的这1行和 df2 中的这3行都是匹配的,因此合并后会有3个 [A=1,B=3] 的行
  • 在 df1 中,有 [A=2,B=4],在 df2 中没有:在 df2 中,有 [A=4,B=6],在 df1 中没有

    • inner 模式下,取交叉,因此合并后的行没有 [A=2,B=4],也没有 [A=4,B=6]

    • left 模式下,以 left(df1) 优先,因此合并后的行有 [A=2,B=4],没有 [A=4,B=6]

    • right 模式下,以 right(df2) 优先,因此合并后的行没有 [A=2,B=4],有 [A=4,B=6]

    • outer 模式下,取并集,因此合并后的行 [A=2,B=4] 和 [A=4,B=6] 都有

  • 其他列(不在 on 中的列)遵循 一方有则用那方,两方有则复制重命名 的原则

    • ‘C’, ’left1’, ‘right1’, ‘right2’ 在合并后的行中都是有的

    • 比如 df1 中完全没有 C 列,而且 df1 的 [A=1,B=3] 这一行和 df2 中 3个 C列值不同的行是共同满足 [A=1,B=3] 的

      • 因此,在合并后的3个行中,就有了 3个不同的 C 值的行
common_keys = ['A', 'B']
df1 = pd.DataFrame({"A": [1, 2, 3], "B": [3, 4, 5], 
                    # "common1": [11, None, 13], 
                    "left1": [11.0, 22.0, 33.0]
                    })
ddf1 = dd.from_pandas(df1, npartitions=2)

df2 = pd.DataFrame({"A": [1, 1, 1, 3, 4], "B": [3, 3, 3, 5, 6], "C": [4, 5, 6, 7, 8], 
                    # "common1": [21, 23], 
                    "right1": [-1., -11., -111., -3., -4.], 
                    "right2": ['A', 'AA', 'AAA', 'C', 'D']
                    })
ddf2 = dd.from_pandas(df2, npartitions=2)

# 下面这段后面都一样,省略不写了
ddf_lf_inner = ddf1.copy().merge(ddf2, on=common_keys, how='inner')
ddf_lf_left = ddf1.copy().merge(ddf2, on=common_keys, how='left')
ddf_lf_right = ddf1.copy().merge(ddf2, on=common_keys, how='right')
ddf_lf_outer = ddf1.copy().merge(ddf2, on=common_keys, how='outer')

其结果是:

left.right, inner
   A  B  left1  C  right1 right2
0  1  3   11.0  4    -1.0      A
1  1  3   11.0  5   -11.0     AA
2  1  3   11.0  6  -111.0    AAA
0  3  5   33.0  7    -3.0      C
left.right, left
   A  B  left1    C  right1 right2
0  1  3   11.0  4.0    -1.0      A
1  1  3   11.0  5.0   -11.0     AA
2  1  3   11.0  6.0  -111.0    AAA
0  2  4   22.0  NaN     NaN    NaN
1  3  5   33.0  7.0    -3.0      C
left.right, right
   A  B  left1  C  right1 right2
0  1  3   11.0  4    -1.0      A
1  1  3   11.0  5   -11.0     AA
2  1  3   11.0  6  -111.0    AAA
0  3  5   33.0  7    -3.0      C
1  4  6    NaN  8    -4.0      D
left.right, outer
   A  B  left1    C  right1 right2
0  1  3   11.0  4.0    -1.0      A
1  1  3   11.0  5.0   -11.0     AA
2  1  3   11.0  6.0  -111.0    AAA
0  2  4   22.0  NaN     NaN    NaN
1  3  5   33.0  7.0    -3.0      C
2  4  6    NaN  8.0    -4.0      D

case2: 对于不在 on 中、但两边都有的列名,不管值是否相同都强制复制重命名

common_keys = ['A', 'B']
df1 = pd.DataFrame({"A": [1, 2, 3], "B": [3, 4, 5], 
                    # "common1": [11, None, 13], 
                    "left1": [11.0, 22.0, 33.0]
                    })
ddf1 = dd.from_pandas(df1, npartitions=2)

df2 = pd.DataFrame({"A": [1, 2, 3], "B": [3, 4, 5], "C": [5, 6, 7], 
                    "left1": [11.0, 22.0, 33.0], 
                    "right1": [-1., -2., -3.], 
                    "right2": ['A', 'B', 'C']
                    })
ddf2 = dd.from_pandas(df2, npartitions=2)

其结果是:(可以看到始终强制复制出了 left1_x,left1_y 两个列,即使他们的值是一样的)

left.right, inner
   A  B  left1_x  C  left1_y  right1 right2
0  1  3     11.0  5     11.0    -1.0      A
0  3  5     33.0  7     33.0    -3.0      C
1  2  4     22.0  6     22.0    -2.0      B
left.right, left
   A  B  left1_x  C  left1_y  right1 right2
0  1  3     11.0  5     11.0    -1.0      A
0  2  4     22.0  6     22.0    -2.0      B
1  3  5     33.0  7     33.0    -3.0      C
left.right, right
   A  B  left1_x  C  left1_y  right1 right2
0  1  3     11.0  5     11.0    -1.0      A
0  2  4     22.0  6     22.0    -2.0      B
1  3  5     33.0  7     33.0    -3.0      C
left.outer, outer
   A  B  left1_x  C  left1_y  right1 right2
0  1  3     11.0  5     11.0    -1.0      A
0  3  5     33.0  7     33.0    -3.0      C
1  2  4     22.0  6     22.0    -2.0      B

waymo v2 tutorials: visu3d 环境问题

  • waymo v2 版本 tutorial 依赖 visu3d 报错

    • 发现 waymo v2 的 tutorial 中,关键的可视化库 visu3d 在默认安装配置下会各种在其依赖包 import 时报错;经查,主要是因为由于 etils 和 dataclass_array 这两个 visu3d 的依赖包的默认安装最新版本已经不再兼容 tensorflow==2.11.0

    • 解决方案是重新安装兼容的依赖库版本:

      pip uninstall visu3d dataclass_array etils
      pip install visu3d==1.5.1 etils[edc,enp,epath,epy,etree]==1.3.0 dataclass_array==1.4.2
      
    • 具体 issue 及解决方案见 issue#741

  • visu3d 在非 notebook 环境下 不显示

    • visu3d 的显示逻辑是基于 plotly 的,而 plotly 的 fig 对象在 jupyternotebook 中只要打印就会显示,但是在非 jupyternotebook 环境下,还是需要配置下 ① 显示在哪里 ② 使用 Figure() 对象的 .show() 来手动显示的。

      import plotly.io as pio
      pio.renderers.default = "browser"
      # ...
      visu3d.make_fig(...).show()
      
    • 具体 plotly 如何调整显示、如何搭配 dash 等,学习相关具体文档就行了。