|
|
데이터 증강이 어디에서 이루어지는지?
https://cafe.daum.net/SmartRobot/RoVa/2275
위의 데이터 증강들을 모아둔 함수들과 그를 사용하기 위해 Class로 정의해둠
OpenPCDet/pcdet/datasets/augmentor/data_augmentor.py
이를 생성자로 호출하는 코드 확인
OpenPCDet/pcdet/datasets/dataset.py
코드 일부분 가져옴
---------------------------------------------------------------------------------------------------------------------------------------
from .augmentor.data_augmentor import DataAugmentor
from .processor.data_processor import DataProcessor
from .processor.point_feature_encoder import PointFeatureEncoder
class DatasetTemplate(torch_data.Dataset):
def __init__(self, dataset_cfg=None, class_names=None, training=True, root_path=None, logger=None):
super().__init__()
self.dataset_cfg = dataset_cfg
self.training = training
self.class_names = class_names
self.logger = logger
self.root_path = root_path if root_path is not None else Path(self.dataset_cfg.DATA_PATH)
self.logger = logger
if self.dataset_cfg is None or class_names is None:
return
self.point_cloud_range = np.array(self.dataset_cfg.POINT_CLOUD_RANGE, dtype=np.float32)
self.point_feature_encoder = PointFeatureEncoder(
self.dataset_cfg.POINT_FEATURE_ENCODING,
point_cloud_range=self.point_cloud_range
)
self.data_augmentor = DataAugmentor(
self.root_path, self.dataset_cfg.DATA_AUGMENTOR, self.class_names, logger=self.logger
) if self.training else None
self.data_processor = DataProcessor(
self.dataset_cfg.DATA_PROCESSOR, point_cloud_range=self.point_cloud_range,
training=self.training, num_point_features=self.point_feature_encoder.num_point_features
)
self.grid_size = self.data_processor.grid_size
self.voxel_size = self.data_processor.voxel_size
self.total_epochs = 0
self._merge_all_iters_to_one_epoch = False
if hasattr(self.data_processor, "depth_downsample_factor"):
self.depth_downsample_factor = self.data_processor.depth_downsample_factor
else:
self.depth_downsample_factor = None
---------------------------------------------------------------------------------------------------------------------------------------
이 클래스 내 있는 prepare_data 함수 내에 forward를 호출해서 증강을 순차적으로 적용함
---------------------------------------------------------------------------------------------------------------------------------------
def prepare_data(self, data_dict):
"""
Args:
data_dict:
points: optional, (N, 3 + C_in)
gt_boxes: optional, (N, 7 + C) [x, y, z, dx, dy, dz, heading, ...]
gt_names: optional, (N), string
...
Returns:
data_dict:
frame_id: string
points: (N, 3 + C_in)
gt_boxes: optional, (N, 7 + C) [x, y, z, dx, dy, dz, heading, ...]
gt_names: optional, (N), string
use_lead_xyz: bool
voxels: optional (num_voxels, max_points_per_voxel, 3 + C)
voxel_coords: optional (num_voxels, 3)
voxel_num_points: optional (num_voxels)
...
"""
if self.training:
assert 'gt_boxes' in data_dict, 'gt_boxes should be provided for training'
gt_boxes_mask = np.array([n in self.class_names for n in data_dict['gt_names']], dtype=np.bool_)
if 'calib' in data_dict:
calib = data_dict['calib']
data_dict = self.data_augmentor.forward(
data_dict={
**data_dict,
'gt_boxes_mask': gt_boxes_mask
}
)
if 'calib' in data_dict:
data_dict['calib'] = calib
data_dict = self.set_lidar_aug_matrix(data_dict)
if data_dict.get('gt_boxes', None) is not None:
selected = common_utils.keep_arrays_by_name(data_dict['gt_names'], self.class_names)
data_dict['gt_boxes'] = data_dict['gt_boxes'][selected]
data_dict['gt_names'] = data_dict['gt_names'][selected]
gt_classes = np.array([self.class_names.index(n) + 1 for n in data_dict['gt_names']], dtype=np.int32)
gt_boxes = np.concatenate((data_dict['gt_boxes'], gt_classes.reshape(-1, 1).astype(np.float32)), axis=1)
data_dict['gt_boxes'] = gt_boxes
if data_dict.get('gt_boxes2d', None) is not None:
data_dict['gt_boxes2d'] = data_dict['gt_boxes2d'][selected]
if data_dict.get('points', None) is not None:
data_dict = self.point_feature_encoder.forward(data_dict)
data_dict = self.data_processor.forward(
data_dict=data_dict
)
if self.training and len(data_dict['gt_boxes']) == 0:
new_index = np.random.randint(self.__len__())
return self.__getitem__(new_index)
data_dict.pop('gt_names', None)
return data_dict
---------------------------------------------------------------------------------------------------------------------------------------
OpenPCDet/pcdet.datasets/augmentor/data_augmentor.py
---------------------------------------------------------------------------------------------------------------------------------------
def forward(self, data_dict):
"""
Args:
data_dict:
points: (N, 3 + C_in)
gt_boxes: optional, (N, 7) [x, y, z, dx, dy, dz, heading]
gt_names: optional, (N), string
...
Returns:
"""
for cur_augmentor in self.data_augmentor_queue:
data_dict = cur_augmentor(data_dict=data_dict)
data_dict['gt_boxes'][:, 6] = common_utils.limit_period(
data_dict['gt_boxes'][:, 6], offset=0.5, period=2 * np.pi
)
# if 'calib' in data_dict:
# data_dict.pop('calib')
if 'road_plane' in data_dict:
data_dict.pop('road_plane')
if 'gt_boxes_mask' in data_dict:
gt_boxes_mask = data_dict['gt_boxes_mask']
data_dict['gt_boxes'] = data_dict['gt_boxes'][gt_boxes_mask]
data_dict['gt_names'] = data_dict['gt_names'][gt_boxes_mask]
if 'gt_boxes2d' in data_dict:
data_dict['gt_boxes2d'] = data_dict['gt_boxes2d'][gt_boxes_mask]
data_dict.pop('gt_boxes_mask')
return data_dict
---------------------------------------------------------------------------------------------------------------------------------------
증강 추가하는 방법
OpenPCDet/tools/cfg/kitti_models/pv_rcnn.yaml
DATA_CONFIG: 안에 DATA_AUGMENTOR: 가 있고 여기에 있는 AUG_CONFIG_LIST: 에 원하는 증강을 추가해주면 됨
사용할 수 있는 증강은 OpenPCDet/pcdet/datasets/augmentor/data_augmentor.py의 DataAugmentor에 있음
형식을 맞춰서 yaml에 작성해주면 됨
예시: random_world_frustum_dropout을 적용하는 법
---------------------------------------------------------------------------------------------------------------------------------------
def random_world_frustum_dropout(self, data_dict=None, config=None):
"""
Please check the correctness of it before using.
"""
if data_dict is None:
return partial(self.random_world_frustum_dropout, config=config)
intensity_range = config['INTENSITY_RANGE']
gt_boxes, points = data_dict['gt_boxes'], data_dict['points']
for direction in config['DIRECTION']:
assert direction in ['top', 'bottom', 'left', 'right']
gt_boxes, points = getattr(augmentor_utils, 'global_frustum_dropout_%s' % direction)(
gt_boxes, points, intensity_range,
)
data_dict['gt_boxes'] = gt_boxes
data_dict['points'] = points
return data_dict
---------------------------------------------------------------------------------------------------------------------------------------
['INTENSITY_RANGE'] 와 ['DIRECTION'] 를 yaml에서 그대로 이름을 가져와 값을 형식대로 주면 됨
---------------------------------------------------------------------------------------------------------------------------------------
- NAME: random_world_frustum_dropout # 일부분 방향 잘라버림 (증식 이름)
DIRECTION: ['left', 'right'] # 적용할 방향
INTENSITY_RANGE: [0.0, 0.5] # 0 ~ 0.5 사이 drop
---------------------------------------------------------------------------------------------------------------------------------------
이런식으로 증강을 추가하여 사용하면 된다
증강이 잘 추가되었다면 훈련 시작 시
출력되는 결과를 보고 확인하면 됨
증강 적용 전후 시각화
기존 pv_rcnn.yaml에 증강을 임의로 더 추가한 상태임
GT 샘플링에 관한 증식 개수를 늘려서 시각화해서 확인해보기로 함
visualization_augmentor.py
---------------------------------------------------------------------------------------------------------------------------------------
import open3d as o3d
import numpy as np
from pcdet.datasets.kitti.kitti_dataset import KittiDataset
from pcdet.config import cfg, cfg_from_yaml_file
import os
cfg_from_yaml_file('cfgs/kitti_models/pv_rcnn.yaml', cfg) # config 파일 불러오기
dataset_noaug = KittiDataset(dataset_cfg=cfg.DATA_CONFIG, class_names=cfg.CLASS_NAMES, training=False) # 증강 X
dataset_aug = KittiDataset(dataset_cfg=cfg.DATA_CONFIG, class_names=cfg.CLASS_NAMES, training=True) # 증강 O
sample_idx = 1
# 샘플 인덱스
data_orig = dataset_noaug[sample_idx] # 증강 전 샘플
data_aug = dataset_aug[sample_idx] # 증강 후 샘플
pts_orig = data_orig['points'] # 증강 전 포인트
pts_aug = data_aug['points'] # 증강 후 포인트
gt_boxes_orig = data_orig.get('gt_boxes', None) # 증강 전 박스
gt_boxes_aug = data_aug.get('gt_boxes', None) # 증강 후 박스
pt_colors = np.tile([1.0, 1.0, 1.0], (pts_aug.shape[0], 1))
def box_lineset(box, color):
center = box[:3]
extents = box[3:6]
yaw = box[6]
rot_mat = o3d.geometry.get_rotation_matrix_from_axis_angle([0, 0, yaw]) # yaw로 회전행렬 생성
obb = o3d.geometry.OrientedBoundingBox(center, rot_mat, extents)
lineset = o3d.geometry.LineSet.create_from_oriented_bounding_box(obb)
lineset.paint_uniform_color(color)
return lineset # 결과 반환
# ---- 원본 시각화 ----
pc_orig = o3d.geometry.PointCloud()
pc_orig.points = o3d.utility.Vector3dVector(pts_orig[:, :3])
pc_orig.colors = o3d.utility.Vector3dVector(np.tile([1.0, 1.0, 1.0], (pts_orig.shape[0], 1)))
geoms_orig = [pc_orig]
if gt_boxes_orig is not None:
for box in gt_boxes_orig:
geoms_orig.append(box_lineset(box, [1, 0, 0])) # 빨강
vis_orig = o3d.visualization.Visualizer()
vis_orig.create_window(window_name="before", width=1280, height=800)
opt = vis_orig.get_render_option()
opt.background_color = np.array([0, 0, 0])
opt.point_size = 1.0
for geom in geoms_orig:
vis_orig.add_geometry(geom)
vis_orig.run() # 실행
vis_orig.destroy_window() # 종료
# ---- 증강 후 시각화 ----
pc_aug = o3d.geometry.PointCloud() # 포인트클라우드 geometry
pc_aug.points = o3d.utility.Vector3dVector(pts_aug[:, :3]) # xyz 지정
pc_aug.colors = o3d.utility.Vector3dVector(pt_colors)
geoms_aug = [pc_aug]
if gt_boxes_aug is not None:
for box in gt_boxes_aug:
geoms_aug.append(box_lineset(box, [0, 1, 0])) # 초록
vis_aug = o3d.visualization.Visualizer()
vis_aug.create_window(window_name="after", width=1280, height=800) # 창 생성
opt = vis_aug.get_render_option()
opt.background_color = np.array([0, 0, 0])
opt.point_size = 1.0
for geom in geoms_aug:
vis_aug.add_geometry(geom)
vis_aug.run()
vis_aug.destroy_window()
---------------------------------------------------------------------------------------------------------------------------------------
이전 게시글에서 설명했던 gt sampling을 적용해서 배경 point cloud에 일부분 복사해서 붙여넣었기 때문에
없던 포인트들과 GT BOX가 생기는 걸 확인할 수 있음
증강 적용 전후 평가지표 비교
pv-rcnn model을 가져와서 증강을 더 추가 후 학습을 진행한 모델과 기존에 있던 모델의 성능 비교를 해보려고 함
3D box에 관한 정확도 중에서 kitti 난이도 hard를 기준으로 확인했음 (AP_R40 기준)
기존 pv-rcnn
증강 적용 후
Car class에 관한 건 기존에 있던 pv-rcnn이 성능이 더 좋았음
다른 Pedestrain, Cyclist에 관해서는 증강을 더 추가했을 때가 성능이 좋게 나온 것을 확인
ckpt는 10으로 loss가 가장 낮을 때를 잡아서 확인함
아무래도 GT-Sampling에서 기존 Car에 관한 샘플 개수와 다른 클래스들의 샘플 개수를 늘려서 데이터셋에 관련한 다양성이 증가한 듯
Car를 놔두고 다른 클래스들만 샘플 개수 늘려도 될 것 같음
