정말 오랜만에 글을 올린다...
지난 글에 이어 OpenPCDet model config를 분석해볼 것이다.
이전 demo.py에서 pointpillar 모델을 사용했었고, pointpillar config 파일을 기준으로 코드를 뜯어볼 예정이다.
추가적으로 직접 custom config 파일을 만들어본다.
PointPillars 네트워크에 대해 간단히 설명하자면 voxel-based 3D object detection 모델이며, 기존의 Voxelnet, SECOND에서는 3D conv연산을 진행하면서 연산량이 많아지게 되고 그로 인해 추론시간이 느려 실시간으로 사용하기 어려웠던 문제점이 있었다.
PointPillars 모델에서는 voxel을 z축으로 제한이 없는 pillar(기둥) 형태로 encoding 하는 방법을 제시했다. 쉽게 생각해서 편의점에 파는 나무젓가락 같은 수직기둥형태의 voxel 모양으로 feature를 만들어내는 것이다.
이렇게 해줌으로써 텐서를 가상의 이미지 (Pseudo image) 처럼 만들고 오로지 2D conv만 사용함으로써 연산 시간을 압도적으로 줄어들게 했다. 이 모델이 나왔을 당시에 정확도, 시간 측면에서 이전 모델들보다 훨씬 좋은 성능 보여줬다.
코드 설명은 OpenPCDet ./tools/cfgs/kitti_models/pointpillar.yaml 파일을 기준으로 진행할 예정이다.
CLASS_NAMES: ['Car', 'Pedestrian', 'Cyclist']
DATA_CONFIG:
_BASE_CONFIG_: cfgs/dataset_configs/kitti_dataset.yaml
POINT_CLOUD_RANGE: [0, -39.68, -3, 69.12, 39.68, 1]
DATA_PROCESSOR:
- NAME: mask_points_and_boxes_outside_range
REMOVE_OUTSIDE_BOXES: True
- NAME: shuffle_points
SHUFFLE_ENABLED: {
'train': True,
'test': False
}
- NAME: transform_points_to_voxels
VOXEL_SIZE: [0.16, 0.16, 4]
MAX_POINTS_PER_VOXEL: 32
MAX_NUMBER_OF_VOXELS: {
'train': 16000,
'test': 40000
}
DATA_AUGMENTOR:
DISABLE_AUG_LIST: ['placeholder']
AUG_CONFIG_LIST:
- NAME: gt_sampling
USE_ROAD_PLANE: True
DB_INFO_PATH:
- kitti_dbinfos_train.pkl
PREPARE: {
filter_by_min_points: ['Car:5', 'Pedestrian:5', 'Cyclist:5'],
filter_by_difficulty: [-1],
}
SAMPLE_GROUPS: ['Car:15','Pedestrian:15', 'Cyclist:15']
NUM_POINT_FEATURES: 4
DATABASE_WITH_FAKELIDAR: False
REMOVE_EXTRA_WIDTH: [0.0, 0.0, 0.0]
LIMIT_WHOLE_SCENE: False
- NAME: random_world_flip
ALONG_AXIS_LIST: ['x']
- NAME: random_world_rotation
WORLD_ROT_ANGLE: [-0.78539816, 0.78539816]
- NAME: random_world_scaling
WORLD_SCALE_RANGE: [0.95, 1.05]
주요 내용들은 다음과 같다.
CLASS_NAMES: 인지할 객체 이름을 뜻한다. 여기선 차량, 보행자, 자전거를 인지한다.
POINT_CLOUD_RANGES: 사용하는 포인트의 ROI를 설정한다. 라이다 좌표계 기준 x,y,z,x,y,z 순
VOXEL_SIZE: 말 그대로 voxel의 가로, 세로, 높이는 어떻게 해줄것인가. (z축 ROI 범위가 4라서 높이가 4)
MAX_POINTS_PER_VOXEL: 하나의 voxel 내부에 point cloud 최대 개수 설정
MAX_NUMBER_OF_VOXELS: 최대 몇개의 voxel을 생성할 것인지 (voxel 개수는 연산량에 영향을 줌)
REMOVE_EXTRA_WIDTH: 3d bounding box의 여백 제거
flip, rotation, scaling: point cloud augmentation 기법 설정 (논문에서 해당 기법들 사용한다고 나옴)
.
.
MODEL:
NAME: PointPillar
VFE:
NAME: PillarVFE
WITH_DISTANCE: False
USE_ABSLOTE_XYZ: True
USE_NORM: True
NUM_FILTERS: [64]
MAP_TO_BEV:
NAME: PointPillarScatter
NUM_BEV_FEATURES: 64
BACKBONE_2D:
NAME: BaseBEVBackbone
LAYER_NUMS: [3, 5, 5]
LAYER_STRIDES: [2, 2, 2]
NUM_FILTERS: [64, 128, 256]
UPSAMPLE_STRIDES: [1, 2, 4]
NUM_UPSAMPLE_FILTERS: [128, 128, 128]
DENSE_HEAD:
NAME: AnchorHeadSingle
CLASS_AGNOSTIC: False
USE_DIRECTION_CLASSIFIER: True
DIR_OFFSET: 0.78539
DIR_LIMIT_OFFSET: 0.0
NUM_DIR_BINS: 2
ANCHOR_GENERATOR_CONFIG: [
{
'class_name': 'Car',
'anchor_sizes': [[3.9, 1.6, 1.56]],
'anchor_rotations': [0, 1.57],
'anchor_bottom_heights': [-1.78],
'align_center': False,
'feature_map_stride': 2,
'matched_threshold': 0.6,
'unmatched_threshold': 0.45
},
{
'class_name': 'Pedestrian',
'anchor_sizes': [[0.8, 0.6, 1.73]],
'anchor_rotations': [0, 1.57],
'anchor_bottom_heights': [-0.6],
'align_center': False,
'feature_map_stride': 2,
'matched_threshold': 0.5,
'unmatched_threshold': 0.35
},
{
'class_name': 'Cyclist',
'anchor_sizes': [[1.76, 0.6, 1.73]],
'anchor_rotations': [0, 1.57],
'anchor_bottom_heights': [-0.6],
'align_center': False,
'feature_map_stride': 2,
'matched_threshold': 0.5,
'unmatched_threshold': 0.35
}
]
TARGET_ASSIGNER_CONFIG:
NAME: AxisAlignedTargetAssigner
POS_FRACTION: -1.0
SAMPLE_SIZE: 512
NORM_BY_NUM_EXAMPLES: False
MATCH_HEIGHT: False
BOX_CODER: ResidualCoder
LOSS_CONFIG:
LOSS_WEIGHTS: {
'cls_weight': 1.0,
'loc_weight': 2.0,
'dir_weight': 0.2,
'code_weights': [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
}
POST_PROCESSING:
RECALL_THRESH_LIST: [0.3, 0.5, 0.7]
SCORE_THRESH: 0.1
OUTPUT_RAW_SCORE: False
EVAL_METRIC: kitti
NMS_CONFIG:
MULTI_CLASSES_NMS: False
NMS_TYPE: nms_gpu
NMS_THRESH: 0.01
NMS_PRE_MAXSIZE: 4096
NMS_POST_MAXSIZE: 500
BACKBONE_2D: 논문에서는 네트워크 2번째 스텝으로 backbone을 소개하는데 conv layer로 구성되어있는 block series로 feature 뽑아낸 다음, transposed 2d conv로 upsampling한다.
DENSE_HEAD: 논문에서 네트워크 마지막 스텝으로 SSD를 이용해 bbox를 예측한다. 3개의 클래스에 대한 anchorbox 설정을 해준다.
LOSS_CONFIG: 논문에서 3가지의 loss에 대해서 각각의 가중치를 곱해주고 더해주게 된다. cls_weight는 classification weight로써 이전에 RetinaNet에서 제시했던 Focal Loss와 동일한 loss function이다. Foreground와 background의 data imbalance를 해결해줄 수 있는 loss이다. loc_weight는 localization weight로, 3d bbox(x, y, z, dx, dy, dz, yaw)에 대한 loss이다. dir_weight는 direction weight이며 객체가 앞을 보고 있는지 뒤를 보고 있는지 구분해주기 위한 loss이다.
POST_PROCESSING: score threshold, nms와 관련된 파라미터를 설정하는 부분이다.
.
.
OPTIMIZATION:
BATCH_SIZE_PER_GPU: 4
NUM_EPOCHS: 80
OPTIMIZER: adam_onecycle
LR: 0.003
WEIGHT_DECAY: 0.01
MOMENTUM: 0.9
MOMS: [0.95, 0.85]
PCT_START: 0.4
DIV_FACTOR: 10
DECAY_STEP_LIST: [35, 45]
LR_DECAY: 0.1
LR_CLIP: 0.0000001
LR_WARMUP: False
WARMUP_EPOCH: 1
GRAD_NORM_CLIP: 10
해당 optimization 부분은 하이퍼파라미터를 설정해주는 부분이다.
필자는 실내에서 보행자와 의자를 인지해보기 위해 custom config를 만들었다.
기존 pointpillar.yaml을 기반으로 제작하였기 때문에 위에 내용들을 대충 이해했다면 어떻게 수정했는지 확인할 수 있다.
(완벽하게 수정한지는 정확하지 않다... ㅋㅋㅋ)
DATASET: 'CustomDataset'
DATA_PATH: '/home/~~~'
POINT_CLOUD_RANGE: [-75.2, -75.2, -2, 75.2, 75.2, 4]
MAP_CLASS_TO_KITTI: {
# 'Vehicle': 'Car',
# 'Pedestrian': 'Pedestrian',
# 'Cyclist': 'Cyclist',
'Pedestrian': 'Pedestrian',
'Chair': 'Cyclist'
}
DATA_SPLIT: {
'train': train,
'test': val
}
INFO_PATH: {
'train': [custom_infos_train.pkl],
'test': [custom_infos_val.pkl],
}
POINT_FEATURE_ENCODING: {
encoding_type: absolute_coordinates_encoding,
# used_feature_list: ['x', 'y', 'z', 'intensity'],
# src_feature_list: ['x', 'y', 'z', 'intensity'],
used_feature_list: ['x', 'y', 'z'],
src_feature_list: ['x', 'y', 'z'],
}
DATA_AUGMENTOR:
DISABLE_AUG_LIST: ['placeholder']
AUG_CONFIG_LIST:
- NAME: gt_sampling
USE_ROAD_PLANE: False
DB_INFO_PATH:
- custom_dbinfos_train.pkl
PREPARE: {
# filter_by_min_points: ['Vehicle:5', 'Pedestrian:5', 'Cyclist:5'],
filter_by_min_points: ['Pedestrian:5', 'Chair:5'],
}
# SAMPLE_GROUPS: ['Vehicle:20', 'Pedestrian:15', 'Cyclist:15']
SAMPLE_GROUPS: ['Pedestrian:5', 'Chair:5']
# NUM_POINT_FEATURES: 4
NUM_POINT_FEATURES: 3
DATABASE_WITH_FAKELIDAR: False
REMOVE_EXTRA_WIDTH: [0.0, 0.0, 0.0]
LIMIT_WHOLE_SCENE: True
- NAME: random_world_flip
ALONG_AXIS_LIST: ['x', 'y']
- NAME: random_world_rotation
WORLD_ROT_ANGLE: [-0.78539816, 0.78539816]
- NAME: random_world_scaling
WORLD_SCALE_RANGE: [0.95, 1.05]
DATA_PROCESSOR:
- NAME: mask_points_and_boxes_outside_range
REMOVE_OUTSIDE_BOXES: True
- NAME: shuffle_points
SHUFFLE_ENABLED: {
'train': True,
'test': False
}
- NAME: transform_points_to_voxels
VOXEL_SIZE: [0.1, 0.1, 0.15]
MAX_POINTS_PER_VOXEL: 5
MAX_NUMBER_OF_VOXELS: {
'train': 150000,
'test': 150000
}
MAP_CLASS_TO_KITTI: 인지할 객체는 2개였기 때문에 Cyclist를 Chair로 변경할 수 있게 했다.
POINT_FEATURE_ENCODING: 인코딩 과정에서 사용할 feature를 설정하는 부분
DATA_AUGMENTOR 부분에서도 보행자, 의자에 맞게 객체 이름을 변경해주었다.
custom dataset을 이용해 모델을 학습하려면 custom_dataset.py과 같은 파일들도 조금씩 수정이 필요하다. 해보면서 필요한 부분을 수정하면 될것같다.