Waymo Open Dataset에 대해 알아봅시다.
이 데이터셋은 .tfrecord
파일들에 저장되어 있습니다. 우리는 먼저 모든 .tfrecord
파일 경로들의 리스트를 첫번째 파라미터로 갖는 TFRecordDataset
을 생성해야 합니다.
train_set = tf.data.TFRecordDataset(train_files, compression_type='')
그 다음 다음과 같이train_set
을 순회합니다. 해당 데이터셋의 각 샘플들은 각종 정보들이 포함된 프레임 입니다.
from waymo_open_dataset import dataset_pb2 as open_dataset
for i, data in enumerate(train_set):
frame = open_dataset.Frame()
frame.ParseFromString(bytearray(data.numpy()))
waymo_open_dataset
은 사전 구성된 패키지입니다. for
반복문 안에서 frame
은 data
로부터 모든 정보를 로드합니다. 이 frame
내부에 어떤 것들이 내부를 살펴봅시다.
list of XXX
를 가리키는 화살표가 표기되어 있다면 그 서브브랜치들은 해당 목록의 요소들입니다.frame.context.stats.location
frame.camera_labels[0].labels[0].box.length
open_dataset
|-- LaserName
| |-- UNKNOWN
| |-- TOP
| |-- FRONT
| |-- SIDE_LEFT
| |-- SIDE_RIGHT
| `-- REAR
|-- CameraName
| |-- UNKNOWN
| |-- FRONT
| |-- FRONT_LEFT
| |-- FRONT_RIGHT
| |-- SIDE_LEFT
| `-- SIDE_RIGHT
|-- RollingShutterReadOutDirection
| |-- UNKNOWN
| |-- TOP_TO_BOTTOM
| |-- LEFT_TO_RIGHT
| |-- BOTTOM_TO_TOP
| |-- RIGHT_TO_LEFT
| `-- GLOBAL_SHUTTER
|-- Frame
| |-- images ⇒ list of CameraImage
| | |-- name (CameraName)
| | |-- image
| | |-- pose
| | |-- velocity (v_x, v_y, v_z, w_x, w_y, w_z)
| | |-- pose_timestamp
| | |-- shutter
| | |-- camera_trigger_time
| | `-- camera_readout_done_time
| |-- Context
| | |-- name
| | |-- camera_calibrations ⇒ list of CameraCalibration
| | | |-- name
| | | |-- intrinsic
| | | |-- extrinsic
| | | |-- width
| | | |-- height
| | | `-- rolling_shutter_direction (RollingShutterReadOutDirection)
| | |-- laser_calibrations ⇒ list of LaserCalibration
| | | |-- name
| | | |-- beam_inclinations
| | | |-- beam_inclination_min
| | | |-- beam_inclination_max
| | | `-- extrinsic
| | `-- Stats
| | |-- laser_object_counts
| | |-- camera_object_counts
| | |-- time_of_day
| | |-- location
| | `-- weather
| |-- timestamp_micros
| |-- pose
| |-- lasers ⇒ list of Laser
| | |-- name (LaserName)
| | |-- ri_return1 (RangeImage class)
| | | |-- range_image_compressed
| | | |-- camera_projection_compressed
| | | |-- range_image_pose_compressed
| | | `-- range_image
| | `-- ri_return2 (same as ri_return1)
| |-- laser_labels ⇒ list of Label
| |-- projected_lidar_labels (same as camera_labels)
| |-- camera_labels ⇒ list of CameraLabels
| | |-- name (CameraName)
| | `-- labels ⇒ list of Label
| `-- no_label_zones (Refer to the doc)
`-- Label
|-- Box
| |-- center_x
| |-- center_y
| |-- center_z
| |-- length
| |-- width
| |-- height
| `-- heading
|-- Metadata
| |-- speed_x
| |-- speed_y
| |-- accel_x
| `-- accel_y
|-- type
|-- id
|-- detection_difficulty_level
`-- tracking_difficulty_level
frame
의 세부적인 내용들을 살펴봅시다.
Lidar 데이터는 frame.lasers
을 통해 얻을 수 있습니다. 해당 객체는 위, 앞, 옆, 뒤 등의 각기 다른 위치의 라이다들을 가리키는 raw laser data들을 순회할 수 있습니다. raw laser data의 각 요소들은 각 라이다의 이름을 나타내는 .name
속성을 가집니다. 그 라이다 이름들은 open_dataset
패키지에 아래와 같은 상수로 정의되어 있습니다.
open_dataset.LaserName.UNKNOWN = 0
open_dataset.LaserName.TOP = 1
open_dataset.LaserName.FRONT = 2
open_dataset.LaserName.SIDE_LEFT = 3
open_dataset.LaserName.SIDE_RIGHT = 4
open_dataset.LaserName.REAR = 5
포인트 클라우드 데이터에 접근해봅시다. 먼저 parse_range_image_and_camera_projection
함수를 사용하여 필요한 데이터들을 얻습니다.
(range_images, camera_projections, range_image_top_pose) = parse_range_image_and_camera_projection(frame)
range_images
는 2차원 데이터입니다.
LaserName
)위에서 획득한 세가지 데이터를 convert_range_image_to_point_cloud
함수를 통해 포인트 클라우드 데이터로 변환합니다.
points, cp_points = convert_range_image_to_point_cloud(frame,
range_images,
camera_projections,
range_image_top_pose)
이를 통해 range_images
는 포인트 클라이드 데이터로 변환되어 points
에 저장됩니다. 이는 길이 5인 리스트 데이터로, 3D 라이다 포인트 데이터 {[N, 3]}
가 저장되어 있습니다.
len(points)
return5
points[2].shape
returns(..., 3)
카메라 이미지는 frame.images
를 통해 접근할 수 있스니다. 이 역시 라이다와 같이 앞, 옆, 뒤 등의 각기 다른 위치의 카메라를 가리키는 순회가능한 데이터로 구성되어 있습니다. 각 요소들은 카메라 이름을 가리키는 .name
속성을 가집니다. 카메라 이름은 open_dataset
패키지에 아래와 같이 정의되어 있습니다.
open_dataset.CameraName.UNKNOWN = 0
open_dataset.CameraName.FRONT = 1
open_dataset.CameraName.FRONT_LEFT = 2
open_dataset.CameraName.FRONT_RIGHT = 3
open_dataset.CameraName.SIDE_LEFT = 4
open_dataset.CameraName.SIDE_RIGHT = 5
각 요소들은 .image
속성을 가지며 여기에 실제 이미지가 저장되어 있습니다. 카메라 이미지들은 JPEG
포맷입니다. 해당 이미지를 로컬 저장소에 저장하기 위한 코드는 다음과 같습니다.
from PIL import Image
import io
image = Image.open(io.BytesIO(frame.images[0].image))
image.save(filename, 'JPEG')
포인트 클라우드 프로젝션을 통해 이미지는 depth 정보와 결합될 수 있게 됩니다. 여기에는 프로젝션 라벨이 존재합니다.
front_label = None
for lbl in frame.camera_labels:
if lbl.name == open_dataset.CameraName.FRONT:
front_label = lbl
break
for label in front_label.labels:
if label.type == 1:
pass
frame.camera_labels
에는 각기 다른 5개의 카메라를 가리키는 5개의 요소들을 가질 수 있습니다. 각 lbl
은 CameraName
과 같은 .name
속성을 가지고 이는 각각 하나의 특정 카메라와 연관되며, 특정 카메라로 촬영된 이미지에 포함된 모든 바운딩 박스 정보들이 담겨있습니다.front_label
을 얻고 있습니다. front_label.labels
에는 하나의 이미지의 모든 바운딩박스 라벨이 저장되어 있습니다. .type
속성은 객체 타입(1=자동차)을 의미, .box
속성에는 바운딩박스 좌표가 저장되어 있습니다. 바운딩 박스에는 box = frame.camera_labels[0].labels[0].box
과 같이 접근할 수 있습니다.box
의 속성과 의미는 다음과 같습니다.box.width
: 상-하 길이box.length
: 좌-우 길이box.center_x
: length
방향의 중앙 x좌표box.center_y
: width
방향의 중앙 y좌표for label in frame.laser_labels:
if label.type == 1:
pass
이는 카메라 라벨과 달리 어떤 라이다가 사용되었는지 구분하지 않고 해당 frame에서 모든 방향의 바운딩박스를 저장합니다.
label
의 다른 속성들은 3D박스라는 것을 제외하고는 매우 단순합니다.
box.length
<-> box.center_x
(정면/앞 방향)
box.width
<-> box.center_y
(좌측 방향)
box.height
<-> box.center_z
(상/pointing-to-sky 방향)