|
|
POINT_CLOUD_RANGE 조정이유
이전에 첨부한 논문에서 말했듯이 각 센서마다 장착 위치와 관측 범위는 데이터셋 마다 다르다. 모든 데이터셋이 같은 범위를 사용한다면 성능이 떨어질 것을 안내하고 있는데 예를 들어 waymo 와 KITTI로 비교하자면 voxel_rcnn의 경우 KITTI와 waymo의 point_cloud_range가 서로 다르고 그에 따라 voxel_size도 다르다 이는 데이터셋의 레이블 분포에 따라 다른 것인데 실제로 객체가 존재하는 영역에 맞는 범위를 설정을 한 것으로 우리가 point_cloud_range를 조정해야 하는 이유로 뽑힌다.
(KITTI는 카메라에서 레이블링을 진행하여 x축이 0부터 시작하여 70m이고 waymo는 point_cloud에서 라벨링을 진행하여서 -75m에서 75m로 설정되어있는 것을 알 수 있다.)
또한 포인트 클라우드 범위를 넓게 설정하면 관심 영역 밖의 포인트까지 모두 입력되기 때문에 보셀 개수와 연산량이 증가
합니다. HVNet 연구는 작은 보셀 크기와 넓은 범위는 작은 객체 검출에는 유리하지만 추론 시간이 길어지고 메모리 부담이 커진다
고 말하고 있다. 너무 넓은 범위를 사용하면 불필요한 배경 포인트가 많은 학습 샘플이 생성되어 오탐율이 증가할 수 있으며 범위를 label_data가 존재하는 영역으로 축소하면 오탐을 줄이고 모델이 중요한 영역에 집중하기 때문에 label_data에 맞는 point_cloud_range를 조정해야한다.
#label_data_range.py
---------------------------------------------------------------------------------------------------------------------------------------------
import pickle, numpy as np, os
INFO_T = '/home/linux/OpenPCDet/data/custom_av/custom_av_infos_train.pkl'
INFO_V = '/home/linux/OpenPCDet/data/custom_av/custom_av_infos_val.pkl'
CLASSES = {'Vehicle', 'Pedestrian', 'Cyclist'}
P_LO, P_HI = 0.5, 99.5
Z_CLAMP = (-3.5, 4.0)
XY_MARGIN = 1.0
def collect_boxes(pkl):
if not os.path.exists(pkl): return np.empty((0,7)), []
infos = pickle.load(open(pkl, 'rb'))
all_boxes, all_names = [], []
for it in infos:
ann = it.get('annos', {})
boxes = ann.get('gt_boxes_lidar', None) #[x,y,z,dx,dy,dz,yaw]
names = ann.get('name', None)
if boxes is None or names is None:
continue
mask = np.array([n in CLASSES for n in names], dtype=bool)
if mask.any():
all_boxes.append(boxes[mask])
all_names.extend([n for n in names[mask]])
if not all_boxes:
return np.empty((0,7)), []
return np.concatenate(all_boxes, axis=0), all_names
def robust_range():
b1, _ = collect_boxes(INFO_T)
b2, _ = collect_boxes(INFO_V)
boxes = np.concatenate([b1,b2], axis=0) if b2.size else b1
if boxes.size == 0:
raise RuntimeError("No GT boxes found for selected classes.")
# 박스 중심과 크기 min/max 근사
centers = boxes[:, :3]
dims = boxes[:, 3:6]
half = dims / 2
mins = centers - half
maxs = centers + half
# 이상치 억제
lo = np.percentile(mins, P_LO, axis=0) # (x_min, y_min, z_min) 쪽
hi = np.percentile(maxs, P_HI, axis=0) # (x_max, y_max, z_max) 쪽
# XY는 대칭으로 통일
x_abs = max(abs(lo[0]), abs(hi[0])) + XY_MARGIN
y_abs = max(abs(lo[1]), abs(hi[1])) + XY_MARGIN
z_min = max(lo[2] - 0.5, Z_CLAMP[0])
z_max = min(hi[2] + 0.5, Z_CLAMP[1])
rec = np.array([-x_abs, -y_abs, z_min, x_abs, y_abs, z_max], dtype=float)
return rec
if __name__ == "__main__":
rec = robust_range()
print("권장 POINT_CLOUD_RANGE:", np.round(rec, 2))
---------------------------------------------------------------------------------------------------------------------------------------------
라벨에 있는 3D 박스들을 전부 훑어보고, 각 축(x,y,z)의 전역 최소/최대 좌표를 구한 뒤, 여기에 ±2m 마진을 더해서
POINT_CLOUD_RANGE = [xmin, ymin, zmin, xmax, ymax, zmax]를 지정했다.
설정한 범위가 라벨 데이터를 얼마나 포함하는지
---------------------------------------------------------------------------------------------------------------------------------------------
import pickle, numpy as np
info_pkl = '/home/linux/OpenPCDet/data/custom_av/custom_av_infos_train.pkl'
# 새 범위 입력
rng = np.array([-70.80, -70.0, -4.0, 70.80, 70.80, 4.0]) #
lo, hi = rng[:3], rng[3:]
tot = {'Vehicle':0,'Pedestrian':0,'Cyclist':0}
miss = {'Vehicle':0,'Pedestrian':0,'Cyclist':0}
infos = pickle.load(open(info_pkl,'rb'))
for info in infos:
annos = info['annos']
boxes = annos['gt_boxes_lidar'] # [x,y,z,dx,dy,dz,heading]
names = annos['name'] # 클래스 문자열 리스트
c = boxes[:, :3]; half = boxes[:,3:6]/2
bmin = c - half; bmax = c + half
inside = np.all((bmin>=lo)&(bmax<=hi), axis=1)
for cls, ok in zip(names, inside):
if cls in tot:
tot[cls]+=1
miss[cls]+= (not ok)
print("총/범위밖/비율(%) =", {k:(tot[k], miss[k], round(100*miss[k]/max(1,tot[k]),2)) for k in tot})
---------------------------------------------------------------------------------------------------------------------------------------------
맨 위의 결과= 조정할 point_cloud_range의 결과
아래의 결과= 기존 point_cloud_range의 결과
조정할 point_cloud_range는 기존의 range를 비교해 보았을때 x축으로 ±5m 정도 더 크고 y축은 ±5m 더 적다. 이를 보았을 때 x축으로 차량,자전거 클래스 label들이 더 분포하여 있는 것을 알수 있고 반대로 y축을 줄였는데도 비율이 떨어지지 않은 것을 보았을 때 y축±5m 에는 중요한 정보는 없다고 판단이 되어서 최종 POINT_CLOUD_RANGE=[-74.80, -65.92, -3.0, 74.80, 65.92, 4.0]로 결정 하였다.
이전 보고서의 Voxel_RCNN 60epoch->75까지 진행한 결과
저번 loss그래프를 보고 추가 학습을 하여 이득을 취할수 있을거라 판단하여 추가학습을 15epoch정도 돌린 결과
차량,자전거 클래스는 소폭 상승하였으나 보행자 클래스는 1점 이상의 점수가 올랐다.
POINT_CLOUD_RANGE 조정후 학습중인 Voxel_RCNN
#custom_av_dataset.yaml
---------------------------------------------------------------------------------------------------------------------------------------------
DATASET: 'CustomAvDataset'
DATA_PATH: '../data/custom_av'
POINT_CLOUD_RANGE: [-74.80, -65.92, -3.0, 74.80, 65.92, 4.0]
MAP_CLASS_TO_KITTI: {
'Vehicle': 'Car',
'Pedestrian': 'Pedestrian',
'Cyclist': 'Cyclist',
}
DATA_SPLIT: {
'train': train,
'test': val
}
INFO_PATH: {
'train': [custom_av_infos_train.pkl],
'test': [custom_av_infos_val.pkl],
}
POINT_FEATURE_ENCODING: {
encoding_type: absolute_coordinates_encoding,
used_feature_list: ['x', 'y', 'z'],
src_feature_list: ['x', 'y', 'z', 'intensity'],
}
DATA_AUGMENTOR:
DISABLE_AUG_LIST: []
AUG_CONFIG_LIST:
- NAME: gt_sampling
USE_ROAD_PLANE: False
DB_INFO_PATH:
- custom_av_dbinfos_train.pkl
PREPARE: {
filter_by_min_points: ['Vehicle:5', 'Pedestrian:5', 'Cyclist:5'],
}
SAMPLE_GROUPS: ['Vehicle:2', 'Pedestrian:16', 'Cyclist:14']
NUM_POINT_FEATURES: 4
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]
- NAME: random_world_translation
NOISE_TRANSLATE_STD: [0.2, 0.2, 0.2]
- NAME: random_points_dropout
DROP_RATE: 0.15
ENSURE_MIN_KEEP: 80000
PROB: 0.2
- NAME: line_downsample
TARGET_LINES: 64
LINE_BINS: 128
TOLERANCE: 6
MIN_BIN_OCCUPANCY: 120
ELEVATION_FROM: 'auto'
PROB: 1.0
DATA_PROCESSOR:
- NAME: mask_points_and_boxes_outside_range
REMOVE_OUTSIDE_BOXES: True
- NAME: shuffle_points
SHUFFLE_ENABLED: {
'train': True,
'test': True
}
- NAME: transform_points_to_voxels
VOXEL_SIZE: [0.08, 0.08, 0.15]
MAX_POINTS_PER_VOXEL: 5
MAX_NUMBER_OF_VOXELS: {
'train': 150000,
'test': 150000
}
----------------------------------------------------------------------------------------------------------------------------------------------------------
# voxel_rcnn_custom.yaml
CLASS_NAMES: ['Vehicle', 'Pedestrian', 'Cyclist']
DATA_CONFIG:
_BASE_CONFIG_: cfgs/dataset_configs/custom_av_dataset.yaml
DATA_PROCESSOR:
- NAME: mask_points_and_boxes_outside_range
REMOVE_OUTSIDE_BOXES: True
STRICT_MASK: True
- NAME: shuffle_points
SHUFFLE_ENABLED: {
'train': True,
'test': True
}
- NAME: transform_points_to_voxels_placeholder
VOXEL_SIZE: [ 0.08, 0.08, 0.15 ] #0.1 0.15
MODEL:
NAME: VoxelRCNN
VFE:
NAME: DynMeanVFE
BACKBONE_3D:
NAME: VoxelBackBone8x
MAP_TO_BEV:
NAME: HeightCompression
NUM_BEV_FEATURES: 256
BACKBONE_2D:
NAME: BaseBEVBackbone
LAYER_NUMS: [5, 5]
LAYER_STRIDES: [1, 2]
NUM_FILTERS: [128, 256]
UPSAMPLE_STRIDES: [1, 2]
NUM_UPSAMPLE_FILTERS: [256, 256]
DENSE_HEAD:
NAME: CenterHead
CLASS_AGNOSTIC: False
CLASS_NAMES_EACH_HEAD: [
[ 'Vehicle', 'Pedestrian', 'Cyclist' ]
]
SHARED_CONV_CHANNEL: 64
USE_BIAS_BEFORE_NORM: True
NUM_HM_CONV: 2
SEPARATE_HEAD_CFG:
HEAD_ORDER: [ 'center', 'center_z', 'dim', 'rot' ]
HEAD_DICT: {
'center': { 'out_channels': 2, 'num_conv': 2 },
'center_z': { 'out_channels': 1, 'num_conv': 2 },
'dim': { 'out_channels': 3, 'num_conv': 2 },
'rot': { 'out_channels': 2, 'num_conv': 2 },
}
TARGET_ASSIGNER_CONFIG:
FEATURE_MAP_STRIDE: 8
NUM_MAX_OBJS: 500
GAUSSIAN_OVERLAP: 0.2
MIN_RADIUS: 1
LOSS_CONFIG:
LOSS_WEIGHTS: {
'cls_weight': 2.3, #1.0 2.0
'loc_weight': 2.3, #2.0
'code_weights': [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ]
}
CLASS_WEIGHTS: # heatmap 분류 손실용
Vehicle: 1.0
Pedestrian: 1.8
Cyclist: 2.5
LOC_CLASS_WEIGHTS:
Vehicle: 1.0
Pedestrian: 1.2
Cyclist: 1.5
POST_PROCESSING:
SCORE_THRESH: 0.12 # 0.1
POST_CENTER_LIMIT_RANGE: [ -75.2, -75.2, -2, 75.2, 75.2, 4 ]
MAX_OBJ_PER_SAMPLE: 500 #500
NMS_CONFIG:
NMS_TYPE: nms_gpu
NMS_THRESH: 0.6 #0.7
NMS_PRE_MAXSIZE: 4096
NMS_POST_MAXSIZE: 500
DEBUG_LABEL_HIST: True
ROI_HEAD:
NAME: VoxelRCNNHead
CLASS_AGNOSTIC: True #True
SHARED_FC: [256, 256]
CLS_FC: [256, 256]
REG_FC: [256, 256]
DP_RATIO: 0.3
NMS_CONFIG:
TRAIN:
NMS_TYPE: nms_gpu
MULTI_CLASSES_NMS: False
NMS_PRE_MAXSIZE: 9000
NMS_POST_MAXSIZE: 512
NMS_THRESH: 0.65 # 0.8
TEST:
NMS_TYPE: nms_gpu
MULTI_CLASSES_NMS: False
NMS_PRE_MAXSIZE: 4096
NMS_POST_MAXSIZE: 512
NMS_THRESH: 0.6
ROI_GRID_POOL:
FEATURES_SOURCE: ['x_conv2', 'x_conv3', 'x_conv4']
PRE_MLP: True
GRID_SIZE: 8
POOL_LAYERS:
x_conv2:
MLPS: [ [ 64, 64 ] ]
QUERY_RANGES: [ [ 3, 3, 2 ] ]
POOL_RADIUS: [ 0.4 ]
NSAMPLE: [ 16 ]
POOL_METHOD: max_pool
x_conv3:
MLPS: [ [ 64, 64 ] ]
QUERY_RANGES: [ [ 3, 3, 2 ] ]
POOL_RADIUS: [ 0.8 ]
NSAMPLE: [ 16 ]
POOL_METHOD: max_pool
x_conv4:
MLPS: [ [ 64, 64 ] ]
QUERY_RANGES: [ [ 3, 3, 2 ] ]
POOL_RADIUS: [ 1.6 ]
NSAMPLE: [ 16 ]
POOL_METHOD: max_pool
TARGET_CONFIG:
BOX_CODER: ResidualCoder
ROI_PER_IMAGE: 192
FG_RATIO: 0.5
CLS_SCORE_TYPE: roi_iou
CLS_FG_THRESH: 0.70
CLS_BG_THRESH: 0.25
CLS_BG_THRESH_LO: 0.1
HARD_BG_RATIO: 0.8
REG_FG_THRESH: 0.55
LOSS_CONFIG:
CLS_LOSS: BinaryCrossEntropy
REG_LOSS: smooth-l1
CORNER_LOSS_REGULARIZATION: True
LOSS_WEIGHTS: {
'rcnn_cls_weight': 1.0,
'rcnn_reg_weight': 1.0,
'rcnn_corner_weight': 1.0,
'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.12 #0.1 #0.2
OUTPUT_RAW_SCORE: False
EVAL_METRIC: waymo
NMS_CONFIG:
MULTI_CLASSES_NMS: False #False
NMS_TYPE: nms_gpu
NMS_THRESH: 0.6 #0.7
NMS_PRE_MAXSIZE: 4096
NMS_POST_MAXSIZE: 500
OPTIMIZATION:
BATCH_SIZE_PER_GPU: 2
NUM_EPOCHS: 80
OPTIMIZER: adam_onecycle
LR: 0.006
WEIGHT_DECAY: 0.001
MOMENTUM: 0.9
MOMS: [0.95, 0.85]
PCT_START: 0.35
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
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 성능지표 (2024) | 베이스라인 | voxelNext | Voxel_RCNN_60epoch | Voxel_RCNN_75epoch |
| VEHICLE_AP/L1 | 0.8911 | 0.8255 | 0.8492 | 0.8560 |
| VEHICLE_AP/L2 | 0.8801 | 0.8069 | 0.8366 | 0.8389 |
| PEDESTRIAN_AP/L1 | 0.9023 | 0.6777 | 0.7389 | 0.7501 |
| PEDESTRIAN_AP/L2 | 0.8920 | 0.6562 | 0.7231 | 0.7357 |
| CYCLIST_AP/L1 | 0.8962 | 0.7923 | 0.8173 | 0.8231 |
| CYCLIST_AP/L2 | 0.8829 | 0.7732 | 0.8042 | 0.8146 |
| 추론시간(4070SUPERTi) | 47ms | 연산중 오류 발생 | 48ms | 48ms |
| 성능지표 (2025) | 베이스라인 | voxelNext | Voxel_RCNN_60epoch | Voxel_RCNN_75epoch |
| VEHICLE_AP/L1 | 0.8611 | 0.7980 | 0.8250 | 0.8290 |
| VEHICLE_AP/L2 | 0.8433 | 0.7763 | 0.8051 | 0.8091 |
| PEDESTRIAN_AP/L1 | 0.8396 | 0.6245 | 0.6391 | 0.6551 |
| PEDESTRIAN_AP/L2 | 0.8185 | 0.6113 | 0.6145 | 0.6298 |
| CYCLIST_AP/L1 | 0.8784 | 0.7743 | 0.8108 | 0.8145 |
| CYCLIST_AP/L2 | 0.8600 | 0.7642 | 0.7911 | 0.7945 |
| 추론시간(4070SUPERTi) | 47ms | 연산중 오류 발생 | 48ms | 48ms |
현재 학습이 65epoch을 지나고 있으며 loss는 계속해서 하락하는 추세를 보여주고 있다. point_cloud_range를 조정하여 얻은 결과를 성능지표(2025,2024)와 비교하여 점수가 오른다면 centerpoint-pillar,pv_rcnn++ 모델에 적용을 고려해 좋은 점수를 받을수 있을 것이다.

첫댓글 성능지표 (2024), 성능지표 (2025)의 차이는 뭔가요?
베이스라인 모델의 성능이 좋은 이유는 뭔가요? 훈련조건이 공개된건 없나?
성능지표_2024는 작년 경진대회 데이터셋에서 64ch(현재 테스트 환경과 유사한)학습 데이터만 추출하여 만든 모의 테스트 데이터세트 이며 , 성능지표_2025 는 대회 데이터 세트의 train_dataset에서 20%를 추출하여 val세트로 분류한 데이터셋 입니다.(학습과 유사한)
@이승현 2025년 테스트 데이터는 128ch+64ch 이고 훈련데이터와 유사한건데 왜 성능이 2024년 데이터로 한거보다 낮게 나오는가?
@Sungryul Lee 훈련을 진행할때 down_sampling 기법을 통해 테스트 환경과 유사한 환경으로 만들어서 학습을 진행하는 방향으로 학습을 했기 때문에 2024년 데이터셋으로 한 결과가 높게 나오는게 의도에 맞게 학습이 된 것 입니다.
@이승현 64채널데이터만으로 학습해보면 어떻겠냐?
지금까지 사용해서 효과가 있다고 생각되는 방법은 무엇인가요?
voxel_size와 클래스별 가중치, voxel_size와 메모리 상황에 따라서 center_head의 GAUSSIAN_OVERLAP과 MIN_RADIUS를 조정하여 소형 객체(보행자,자전거)의 recall값을 확보하는 방법은 확실한 효과가 있습니다.