|
실습환경
Os: Ubuntu 20.04
GPU: RTX 3070
그래픽 카드별 cuda버전 확인하는 사이트
https://en.wikipedia.org/wiki/CUDA
RTX 3070은 Ampare 8.0 이상 이므로 11.x~ 12.x 버전까지 지원되기 때문에 11.8버전을 사용
wsl 설치& TensorRT 사용법
https://cafe.daum.net/SmartRobot/RoVa/2009
wsl에서 cuda 11.8을 설치하는 명령어
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/3bf863cc.pub
sudo add-apt-repository "deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/ /"
sudo apt update
sudo apt install -y cuda-toolkit-11-8
이후 nvcc --version 을 사용해 잘 설치되었는지 확인
버전이 안뜨는 상황이 발생하면 직접 환경변수에 추가해 줘야함
echo 'export PATH=/usr/local/cuda-11.8/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=/usr/local/cuda-11.8/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
이후 환경변수 설정을 적용하는 명령어
source ~/.bashrc
환경 변수가 잘 설정되었는지 확인하는 명령어
echo $PATH
11.8버전이 잘 설치된 모습
아나콘다 다운로드
공식 사이트에서 linux를 선택해 다운로드 하는 방법
wget명령어를 사용해 다운로드 하는방법
wget https://repo.anaconda.com/archive/Anaconda3-2023.07-1-Linux-x86_64.sh
이후 다운받은 파일을 실행(버전은 다를수 있음)
bash Anaconda3-2023.07-1-Linux-x86_64.sh
이후 엔터를 누르고 라이선스 동의에 yes를 입력
환경변수를 확인해 Anconda가 추가되었는지 확인하고 없는 경우 수동으로 추가
이후 설치되었는지 확인하는 명령어
conda --version
가상환경 생성
OpenPCDet install가이드
https://github.com/open-mmlab/OpenPCDet/blob/master/docs/INSTALL.md
가상환경 생성 명령어
conda create -n openpcdet python=3.8 -y
가상환경 활성화
conda activate openpcdet
OpenPCDet 래퍼지스토리 클론
git clone https://github.com/open-mmlab/OpenPCDet.git
pytorch 설치 명령어
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
파이토치 버전 보는 코드
python -c "import torch; print(torch.__version__)"
spconv 설치 명령어(11.8버전에 맞게 설치)
pip install spconv-cu118
spconv 버전 보는 코드
python -c "import spconv; print(spconv.__version__)"
OpenPCDet 의존성 설치
pip install -r requirements.txt
OpenPCDet 설치
python setup.py develop
kitti 데이터셋 다운로드 사이트
https://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark=3d
위 배치를 보고 calib, velodyne, label, image2를 다운로드 image2= left
optional에 planes도 있지만 현재는 다운를 받지 않음
데이터셋을 배치후 Kitti 데이터셋 전처리를 하기 위한 명령어
python -m pcdet.datasets.kitti.kitti_dataset create_kitti_infos tools/cfgs/dataset_configs/kitti_dataset.yaml
위 명령어를 사용하면 정보 파일(kitti_infos_train.pkl, kitti_infos_val.pkl 등)을 생성하여 학습에 사용됨
python 버전에 따라 오류가 발생할 수 있음 python버전이 3.8버전 이라면 아래의 명령어를 사용해 버전을 맞춰야함
av2와 kornia에 대한 오류가 발생함
pip install numpy==1.23.4
pip install av2==0.2.0
pip install kornia==0.6.5
pip install protobuf==3.20.3
pip install spconv-cu118
kitti_dbinfos_train.pkl: 학습 데이터셋의 Ground Truth 데이터베이스 정보
kitti_infos_train.pkl: 학습 데이터셋에 대한 정보 파일
kitti_infos_val.pkl: 검증 데이터셋에 대한 정보 파일
kitti_infos_test.pkl: 테스트 데이터셋에 대한 정보 파일
kitti_infos_trainval.pkl: 학습 및 검증 데이터를 합친 데이터셋의 정보 파일(검증 없이 전체 데이터를 사용할때)
학습 단계
학습과 테스트에 대한 정보는 OpenPCDet github에 정리되어있음
https://github.com/open-mmlab/OpenPCDet/blob/master/docs/GETTING_STARTED.md
현재 설정파일에는 voxel_rcnn모델에 대해서 car에 대한 설정파일만 있는 것을 확인하여
https://github.com/djiajunustc/Voxel-R-CNN/blob/main/tools/cfgs/voxel_rcnn/voxel_rcnn_3classes.yaml
Voxel-R-CNN 깃허브에서 설정파일을 그대로 사용해서 진행을 하기로 결정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | CLASS_NAMES: ['Car', 'Pedestrian', 'Cyclist'] DATA_CONFIG: _BASE_CONFIG_: cfgs/dataset_configs/kitti_dataset.yaml 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:10', 'Cyclist:10'] 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] MODEL: NAME: VoxelRCNN VFE: NAME: MeanVFE BACKBONE_3D: NAME: VoxelBackBone8x MAP_TO_BEV: NAME: HeightCompression NUM_BEV_FEATURES: 256 BACKBONE_2D: NAME: BaseBEVBackbone LAYER_NUMS: [4, 4] LAYER_STRIDES: [1, 2] NUM_FILTERS: [64, 128] UPSAMPLE_STRIDES: [1, 2] NUM_UPSAMPLE_FILTERS: [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': 8, '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': 8, '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': 8, '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] } ROI_HEAD: NAME: VoxelRCNNHead CLASS_AGNOSTIC: 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.8 TEST: NMS_TYPE: nms_gpu MULTI_CLASSES_NMS: False USE_FAST_NMS: True SCORE_THRESH: 0.0 NMS_PRE_MAXSIZE: 2048 NMS_POST_MAXSIZE: 100 NMS_THRESH: 0.7 ROI_GRID_POOL: FEATURES_SOURCE: ['x_conv3', 'x_conv4'] PRE_MLP: True GRID_SIZE: 6 POOL_LAYERS: x_conv3: MLPS: [[32, 32], [32, 32]] QUERY_RANGES: [[2, 2, 2], [4, 4, 4]] POOL_RADIUS: [0.4, 0.8] NSAMPLE: [16, 16] POOL_METHOD: max_pool x_conv4: MLPS: [[32, 32], [32, 32]] QUERY_RANGES: [[2, 2, 2], [4, 4, 4]] POOL_RADIUS: [0.8, 1.6] NSAMPLE: [16, 16] POOL_METHOD: max_pool TARGET_CONFIG: BOX_CODER: ResidualCoder ROI_PER_IMAGE: 128 FG_RATIO: 0.5 SAMPLE_ROI_BY_EACH_CLASS: True CLS_SCORE_TYPE: roi_iou CLS_FG_THRESH: 0.75 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 GRID_3D_IOU_LOSS: False LOSS_WEIGHTS: { 'rcnn_cls_weight': 1.0, 'rcnn_reg_weight': 1.0, 'rcnn_corner_weight': 1.0, 'rcnn_iou3d_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.3 OUTPUT_RAW_SCORE: False EVAL_METRIC: kitti NMS_CONFIG: MULTI_CLASSES_NMS: False NMS_TYPE: nms_gpu NMS_THRESH: 0.1 NMS_PRE_MAXSIZE: 4096 NMS_POST_MAXSIZE: 500 OPTIMIZATION: BATCH_SIZE_PER_GPU: 2 NUM_EPOCHS: 80 OPTIMIZER: adam_onecycle LR: 0.01 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 | cs |
OpenPCDet에는 Car에 대한 설정만 있어서 Pedestrian,Cyclist 를 논문에서 제시한 크기로 설정하고 gt_sampling를 사용해 논문에서 제시한 증강기법을 사용함 random_world_flip, random_world_rotation,random_world_scaling
Voxel-R-CNN은 plane를 사용하여 data/kitti/training에 plane파일을 추가함
Voxel-R-CNN 깃허브와 다른점은 Batch size4--->2로 감소한것
아래 주소로 설정하여 모든 네트워크에서 접근하도록 하려고 하였지만 wsl은 window방화벽을 건드려야 하는것 같아서 로컬에서 진행
http://172.22.161.236:6006
훈련 결과
R11과 R40 두가지 지표가 있는데 R40은 40개의 recall포인트를 기반으로 평가하는 지표로 최근 논문의 평가 지표로 사용된다.
논문과 비교했을 때 학습이 잘 된 것을 확인 할 수있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | import open3d import torch import numpy as np import pickle import os # 클래스별 컬러맵 설정 box_colormap = { 1: [0, 1, 0], # Car (초록) 2: [0, 1, 1], # Pedestrian (민트색) 3: [1, 1, 0], # Cyclist (노란색) } def draw_scenes(points, gt_boxes=None, ref_boxes=None, ref_labels=None, ref_scores=None): if isinstance(points, torch.Tensor): points = points.cpu().numpy() if isinstance(gt_boxes, torch.Tensor): gt_boxes = gt_boxes.cpu().numpy() if isinstance(ref_boxes, torch.Tensor): ref_boxes = ref_boxes.cpu().numpy() vis = open3d.visualization.Visualizer() vis.create_window() vis.get_render_option().point_size = 1.0 vis.get_render_option().background_color = np.zeros(3) axis_pcd = open3d.geometry.TriangleMesh.create_coordinate_frame(size=1.0, origin=[0, 0, 0]) vis.add_geometry(axis_pcd) pts = open3d.geometry.PointCloud() pts.points = open3d.utility.Vector3dVector(points[:, :3]) pts.colors = open3d.utility.Vector3dVector(np.ones((points.shape[0], 3))) vis.add_geometry(pts) if gt_boxes is not None and len(gt_boxes) > 0: vis = draw_box(vis, gt_boxes, color=(0, 0, 1)) # ✅ 정답(ground truth) 박스를 파란색으로 표시 if ref_boxes is not None and len(ref_boxes) > 0: vis = draw_box(vis, ref_boxes, ref_labels=ref_labels) # ✅ 예측 박스는 클래스별 색상 적용 vis.run() vis.destroy_window() def draw_box(vis, boxes, color=(0, 1, 0), ref_labels=None): for i in range(boxes.shape[0]): line_set, box3d = translate_boxes_to_open3d_instance(boxes[i]) if ref_labels is not None: color = box_colormap.get(ref_labels[i], (1, 1, 1)) # ✅ ref_labels 값에 따라 색 지정 line_set.paint_uniform_color(color) vis.add_geometry(line_set) return vis def translate_boxes_to_open3d_instance(gt_box): center = gt_box[0:3] lwh = gt_box[3:6] axis_angles = np.array([0, 0, gt_box[6] + 1e-10]) rot = open3d.geometry.get_rotation_matrix_from_axis_angle(axis_angles) box3d = open3d.geometry.OrientedBoundingBox(center, rot, lwh) line_set = open3d.geometry.LineSet.create_from_oriented_bounding_box(box3d) return line_set, box3d # 포인트 클라우드에 바운딩 박스(정답,예측)을 그려주는 함수 def process_single_frame(dataset_path, frame_id): points_path = os.path.join(dataset_path, "velodyne", f"{frame_id}.bin") info_path = os.path.join(dataset_path, "kitti_infos_train.pkl") result_path = os.path.join(dataset_path, "result.pkl") if not os.path.exists(points_path) or not os.path.exists(info_path): print(f"[ERROR] Missing file: {points_path} or {info_path}") return points = np.fromfile(points_path, dtype=np.float32).reshape(-1, 4) #print(f"Point Cloud 범위: Min {points.min(axis=0)}, Max {points.max(axis=0)}") # train_info.pkl에서 GT 박스 가져오기 with open(info_path, 'rb') as f: infos = pickle.load(f) # 현재 frame에 해당하는 데이터 찾기(infos_train에는 lidar_idx로 표시됨) gt_boxes_lidar = None for info in infos: if info["point_cloud"]["lidar_idx"] == frame_id: gt_boxes_lidar = info["annos"]["gt_boxes_lidar"] break if gt_boxes_lidar is None: print(f"[ERROR] GT Box not found for frame {frame_id}") return #print(f"[INFO] GT Boxes Shape: {gt_boxes_lidar.shape}") #print(f"[INFO] GT Box Sample: {gt_boxes_lidar[:3]}") #result.pkl에서 예측된 박스 가져오기 with open(result_path, 'rb') as f: results = pickle.load(f) #프레임별로 예측한 결과 시각화 하는 코드 ref_boxes = results[0]['boxes_lidar'] ref_labels = results[0]['name'] ref_scores = results[0]['score'] label_mapping = {"Car": 1, "Pedestrian": 2, "Cyclist": 3} ref_labels = [label_mapping[label] for label in ref_labels] print(f"[INFO] Visualizing frame {frame_id}...") draw_scenes(points, gt_boxes_lidar, ref_boxes=ref_boxes, ref_labels=ref_labels, ref_scores=ref_scores) if __name__ == "__main__": dataset_path = r'C:\Users\1\Desktop\train'# 데이터셋 path frame_id = "000000" # 원하는 프레임 ID 입력 process_single_frame(dataset_path, frame_id) | cs |
좌표 변환 공식을 사용하여 label_2.txt 데이터를 변환하여 시각화하였으나,
객체가 지면보다 높거나 낮게 배치되는 오차가 계속 발생하여
이를 해결하기 위해 훈련 시 사용했던 kitti_infos_train.pkl을 확인한 결과,
이미 LiDAR 좌표계 기준의 gt_boxes_lidar 정보를 포함하고 있음을 확인하였다.
따라서 변환 없이 kitti_infos_train.pkl 을 활용하여 시각화를 진행하였고,
결과적으로 정확한 GT와 모델 예측 결과를 비교할 수 있도록 코드를 완성하였다.
평가 지표
bbox AP: 2D 이미지에서 바운딩 박스의 평균 정밀도(Average Precision) Easy, Moderate, Hard 난이도 순서
bev AP (Bird’s Eye View AP): BEV에서 예측한 3D 박스가 얼마나 정확하게 위치했는지 평가
3d AP: 3D 공간에서의 AP, 즉 예측된 3D 바운딩 박스와 실제 Ground Truth 간 IoU를 기준으로 평가
aos AP: 객체가 올바른 방향을 예측했는지 평가
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | #point cloud 시각화 코드드 import open3d import torch import numpy as np import pickle import os # 클래스별 컬러맵 설정 box_colormap = { 1: [0, 1, 0], # Car (초록) 2: [0, 1, 1], # Pedestrian (민트색) 3: [1, 1, 0], # Cyclist (노란색) } def draw_scenes(points, gt_boxes=None, ref_boxes=None, ref_labels=None, ref_scores=None): if isinstance(points, torch.Tensor): points = points.cpu().numpy() if isinstance(gt_boxes, torch.Tensor): gt_boxes = gt_boxes.cpu().numpy() if isinstance(ref_boxes, torch.Tensor): ref_boxes = ref_boxes.cpu().numpy() vis = open3d.visualization.Visualizer() vis.create_window() vis.get_render_option().point_size = 1.0 vis.get_render_option().background_color = np.zeros(3) axis_pcd = open3d.geometry.TriangleMesh.create_coordinate_frame(size=1.0, origin=[0, 0, 0]) vis.add_geometry(axis_pcd) pts = open3d.geometry.PointCloud() pts.points = open3d.utility.Vector3dVector(points[:, :3]) pts.colors = open3d.utility.Vector3dVector(np.ones((points.shape[0], 3))) vis.add_geometry(pts) if gt_boxes is not None and len(gt_boxes) > 0: vis = draw_box(vis, gt_boxes, color=(0, 0, 1)) #정답(ground truth) 박스를 파란색으로 표시 if ref_boxes is not None and len(ref_boxes) > 0: vis = draw_box(vis, ref_boxes, ref_labels=ref_labels) #예측 박스는 클래스별 색상 적용 vis.run() vis.destroy_window() def draw_box(vis, boxes, color=(0, 1, 0), ref_labels=None): for i in range(boxes.shape[0]): line_set, box3d = translate_boxes_to_open3d_instance(boxes[i]) if ref_labels is not None: color = box_colormap.get(ref_labels[i], (1, 1, 1)) line_set.paint_uniform_color(color) vis.add_geometry(line_set) return vis def translate_boxes_to_open3d_instance(gt_box): center = gt_box[0:3] lwh = gt_box[3:6] axis_angles = np.array([0, 0, gt_box[6] + 1e-10]) rot = open3d.geometry.get_rotation_matrix_from_axis_angle(axis_angles) box3d = open3d.geometry.OrientedBoundingBox(center, rot, lwh) line_set = open3d.geometry.LineSet.create_from_oriented_bounding_box(box3d) return line_set, box3d # 포인트 클라우드에 바운딩 박스(정답,예측)을 그려주는 함수 def process_single_frame(dataset_path, frame_id): points_path = os.path.join(dataset_path, "velodyne", f"{frame_id}.bin") info_path = os.path.join(dataset_path, "kitti_infos_train.pkl") result_path = os.path.join(dataset_path, "result.pkl") if not os.path.exists(points_path) or not os.path.exists(info_path): print(f"[ERROR] Missing file: {points_path} or {info_path}") return points = np.fromfile(points_path, dtype=np.float32).reshape(-1, 4) #print(f"Point Cloud 범위: Min {points.min(axis=0)}, Max {points.max(axis=0)}") # train_info.pkl에서 GT 박스 가져오기 with open(info_path, 'rb') as f: infos = pickle.load(f) print(f"정답답",infos[1]) # 현재 frame에 해당하는 데이터 찾기(infos_train에는 lidar_idx로 표시됨) gt_boxes_lidar = None for info in infos: if info["point_cloud"]["lidar_idx"] == frame_id: gt_boxes_lidar = info["annos"]["gt_boxes_lidar"] break if gt_boxes_lidar is None: print(f"[ERROR] GT Box not found for frame {frame_id}") return #print(f"[INFO] GT Boxes Shape: {gt_boxes_lidar.shape}") #print(f"[INFO] GT Box Sample: {gt_boxes_lidar[:3]}") #result.pkl에서 예측된 박스 가져오기 with open(result_path, 'rb') as f: results = pickle.load(f) print(f"예측 결과값",results[1]) # 확인용 #프레임별로 예측한 결과 시각화 하는 코드 ref_boxes = results[5]['boxes_lidar'] ref_labels = results[5]['name'] ref_scores = results[5]['score'] label_mapping = {"Car": 1, "Pedestrian": 2, "Cyclist": 3} ref_labels = [label_mapping[label] for label in ref_labels] print(f"[INFO] Visualizing frame {frame_id}...") draw_scenes(points, gt_boxes_lidar, ref_boxes=ref_boxes, ref_labels=ref_labels, ref_scores=ref_scores) if __name__ == "__main__": dataset_path = r'C:\Users\1\Desktop\train'# 데이터셋 path frame_id = "000011" # 원하는 프레임 ID 입력 process_single_frame(dataset_path, frame_id) | cs |
open3d시각화 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | #open3d gui 코드드 import open3d as o3d import numpy as np import pickle import os import open3d.visualization.gui as gui import open3d.visualization.rendering as rendering # 클래스별 컬러맵 설정 box_colormap = { "Car": [0, 1, 0], # 초록색 "Pedestrian": [0, 1, 1], # 민트색 "Cyclist": [1, 1, 0], # 노란색 } GT_COLOR = [0, 0, 1] # 정답(GT) 박스는 파란색 def create_open3d_bounding_box(box, color): """ 3D 바운딩 박스를 Open3D 객체로 변환하는 함수 """ center = box[:3] lwh = box[3:6] axis_angles = np.array([0, 0, box[6]]) rot = o3d.geometry.get_rotation_matrix_from_axis_angle(axis_angles) box3d = o3d.geometry.OrientedBoundingBox(center, rot, lwh) box3d.color = color return box3d def visualize_3d_scene_with_labels(points, gt_boxes, pred_boxes, pred_labels, pred_scores): """ Open3D GUI를 사용하여 바운딩 박스 위에 클래스와 점수를 표시하는 3D 시각화 """ app = gui.Application.instance app.initialize() vis = o3d.visualization.O3DVisualizer("3D Object Detection", 1024, 768) vis.show_settings = True # 설정 창 활성화 vis.scene.set_background([0, 0, 0, 1]) # 포인트 클라우드 추가 pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(points[:, :3]) vis.add_geometry("PointCloud", pcd) # GT 바운딩 박스 추가 for gt_box in gt_boxes: box = create_open3d_bounding_box(gt_box, GT_COLOR) vis.add_geometry(f"GT_{np.random.randint(0, 10000)}", box) # 예측 바운딩 박스 및 라벨 추가 for i, pred_box in enumerate(pred_boxes): color = box_colormap.get(pred_labels[i], [1, 1, 1]) box = create_open3d_bounding_box(pred_box, color) vis.add_geometry(f"Pred_{i}", box) # 바운딩 박스 위에 클래스 및 점수 추가 text_label = f"{pred_labels[i]} ({pred_scores[i]:.2f})" label_pos = pred_box[:3] + np.array([0, 0, pred_box[5] / 2 + 0.5]) # 바운딩 박스 위쪽 vis.add_3d_label(label_pos, text_label) print(f"[INFO] {pred_labels[i]} ({pred_scores[i]:.2f}) at {pred_box[:3]}") vis.reset_camera_to_default() app.add_window(vis) app.run() def process_single_frame(dataset_path, frame_id): """ LiDAR 포인트 클라우드와 예측된 바운딩 박스를 로드하여 시각화 """ points_path = os.path.join(dataset_path, "velodyne", f"{frame_id}.bin") result_path = os.path.join(dataset_path, "result.pkl") if not os.path.exists(points_path) or not os.path.exists(result_path): print(f"[ERROR] Missing file: {points_path} or {result_path}") return points = np.fromfile(points_path, dtype=np.float32).reshape(-1, 4) #result.pkl에서 예측된 바운딩 박스 로드 with open(result_path, 'rb') as f: results = pickle.load(f) pred_boxes = results[5]['boxes_lidar'] pred_labels = results[5]['name'] pred_scores = results[5]['score'] label_mapping = {"Car": "Car", "Pedestrian": "Pedestrian", "Cyclist": "Cyclist"} pred_labels = [label_mapping[label] for label in pred_labels] print(f"[INFO] Visualizing frame {frame_id}...") visualize_3d_scene_with_labels(points, pred_boxes, pred_boxes, pred_labels, pred_scores) if __name__ == "__main__": dataset_path = r'C:\Users\1\Desktop\train' frame_id = "000011" process_single_frame(dataset_path, frame_id) | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | #이미지에 3D바운딩박스 그리는 코드 import numpy as np import os import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D import matplotlib.patches as patches import yaml import pandas as pd from kitti_util import * from matplotlib.lines import Line2D import cv2 import pickle def compute_3d_box_cam2(h, w, l, x, y, z, yaw): """ Return : 3xn in cam2 coordinate """ R = np.array([[np.cos(yaw), 0, np.sin(yaw)], [0, 1, 0], [-np.sin(yaw), 0, np.cos(yaw)]]) x_corners = [l/2,l/2,-l/2,-l/2,l/2,l/2,-l/2,-l/2] y_corners = [0,0,0,0,-h,-h,-h,-h] z_corners = [w/2,-w/2,-w/2,w/2,w/2,-w/2,-w/2,w/2] corners_3d_cam2 = np.dot(R, np.vstack([x_corners,y_corners,z_corners])) corners_3d_cam2 += np.vstack([x, y, z]) return corners_3d_cam2 def read_detection(path): df = pd.read_csv(path, header=None, sep=' ') if df.shape[1] == 15: # 정답 파일에는 score 없음 df.columns = ['type', 'truncated', 'occluded', 'alpha', 'bbox_left', 'bbox_top', 'bbox_right', 'bbox_bottom', 'height', 'width', 'length', 'pos_x', 'pos_y', 'pos_z', 'rot_y'] else: print(f"[ERROR] 파일 컬럼 개수가 예상과 다릅니다: {df.shape[1]}개 컬럼") return None df = df[df['type'].isin(['Car', 'Pedestrian', 'Cyclist'])] # 필요 없는 클래스 제거 df.reset_index(drop=True, inplace=True) print(f"[INFO] 정답 파일 로드 완료! 총 {len(df)}개 객체") return df img_id = 9 calib = Calibration(r'C:\Users\1\Desktop\train\calib\000009.txt') path_img = r'C:\Users\1\Desktop\train\image_2\000009.png' df = read_detection(r'C:\Users\1\Desktop\train\label_2\000000.txt') image = cv2.imread(path_img) df.head() print(len(df)) ##############plot 3D box##################### for o in range(len(df)): corners_3d_cam2 = compute_3d_box_cam2(*df.loc[o, ['height', 'width', 'length', 'pos_x', 'pos_y', 'pos_z', 'rot_y']]) pts_2d = calib.project_rect_to_image(corners_3d_cam2.T) image = draw_projected_box3d(image, pts_2d, color=(255,0,255), thickness=1) cv2.imwrite(str(img_id) + '_test_file_img.png', image) | cs |
kitti_dbinfos_train.pkl : 데이터 증강,샘플링에 사용되는 것을 확인함 label , 3Dbounding box , class
kitti_infos_train.pkl : 학습에 사용되고 dbinfos_pkl과 동일함
kitti_infos_val.pkl : validation 폴더에 있는 KITTI 검증 데이터셋(3,769개 샘플)의 정보를 저장함 kitti_infos_train.pkl과 동일한 구조이며, 모델 평가 시 Ground Truth(GT) 비교를 위해 사용됨
kitti_infos_test.pkl : testing 폴더에 있는 KITTI 테스트 데이터셋(7,518개 샘플)의 정보를 저장하고 LiDAR 및 2D 이미지 정보만 포함하고있음 pointcloud,image,calib정보를 가지고있음
kitti_infos_train.pkl(훈련 정보파일)
num_features: 사용한 특징의 개수 (x,y,z,세타)
lidar_idx: 라이다 데이터 인덱스
image_idx: 해당 프레임 인덱스
image_shape: 2D 이미지 크기
calib: 캘리브레이션 정보(변환 정보)
annos:Ground Truth 데이터
name: 객체 이름
truncated:객체가 이미지에 잘려있는 정도
occluded: 객체가 가려진 정보
bbox: 이미지 내 객체의 위치 [xmin,ymin,xmax,ymax]
gt_boxes_lidar: 3D 공간에서 객체의 위치와 크기[x,y,z,길이,너비,높이,회전]
kitti_infos_train.pkl 파일은 LiDAR 포인트 클라우드, 2D 이미지, 3D 객체 정보(GT), 캘리브레이션 정보를 포함하는 핵심 데이터셋 파일이다.
첫댓글 정리
1. rtx3070에서 KITTI dataset으로 훈련시 약 24시간 걸림
2. 훈련및 추론 코드는 리눅스에서만 실행가능, 데이터 시각화코드는 open3d를 사용하여 윈도우에서도 실행가능
3. 논문에서는 car class만 고려했으나 위 실험에서는 car, pedestrian, cyclist를 고려함
4. 레이블링은 영상에서 수행함(txt파일의 정보는 카메라 좌표계 기준임, pkl파일은 라이다좌표계)
과제
1. 테스트 결과는 포인트클라우드와 영상에 동시에 3d 바운딩 박스 그려줄것
- 객체가 여러 개인 경우도 한번에 모두 그려주도록 코드를 수정할 것
- 훈련데이터 5개(객체가 여러개인걸로) 예측 3d박스, 정답 3d박스 모두 그려줄 것(영상, 포인트클라우드)
- 테스트데이터 5개(객체가 여러개인걸로) 예측 3d박스 (영상,포인트클라우드)
2. 3d 바운딩박스위에 클래스 레이블과 확률값을 같이 출력해줄 것
3. 훈련결과에 사용된 평가지표를 자세히 설명해줄 것
4. 그래프는 텐서보드에서 필터링해서 하나의 선으로 그려진 것으로 캡쳐할것
5. KITTI데이터셋의 파일별로 내용물을 상세히 설명할 것(포인트클라우드, 레이블(gt box), 좌표계정보 등)
1. 논문에서 사용한 모델파일을 사용하여 예측