|
따라서 데이터셋 구조와 변환을 위해 아래 코드를 사용하여 맞춰줬음
원본 데이터셋 → OpenPCDet에서 쓸 수 있는 CustomDataset 형식
data2custom.py
---------------------------------------------------------------------------------------------------------------------------------------
DATA_ROOT = Path("./data")
OUTPUT_ROOT = Path("./custom")
POINTS_DIR = OUTPUT_ROOT / "points"
LABELS_DIR = OUTPUT_ROOT / "labels"
IMAGESETS_DIR = OUTPUT_ROOT / "ImageSets"
for d in [POINTS_DIR, LABELS_DIR, IMAGESETS_DIR]:
d.mkdir(parents=True, exist_ok=True)
---------------------------------------------------------------------------------------------------------------------------------------
디렉터리 경로 지정과 생성 부분
---------------------------------------------------------------------------------------------------------------------------------------
CLASS_MAP = {
"3": "Vehicle", # Car
"2": "Vehicle", # Car
"1": "Vehicle", # Bus
"8": "Vehicle", # Car
"30": "Pedestrian",
"4": "Vehicle", # Car
"5": "Cyclist",
"20": "Cyclist",
"22": "Cyclist",
"31": "Vehicle",
"80": "Don't Care", # 도로에 있는 기둥(?)
"7": "Don't Care",
"6": "Don't Care",
"90": "Don't Care",
"21": "Don't Care"
}
---------------------------------------------------------------------------------------------------------------------------------------
클래스 매핑 정의, 원본 json에서 확인해서 임의로 대략적으로 설정해놓음
---------------------------------------------------------------------------------------------------------------------------------------
def convert_pcd_to_npy(pcd_path, npy_path):
pcd = o3d.io.read_point_cloud(str(pcd_path))
points = np.asarray(pcd.points)
if points.shape[0] == 0:
return False
intensities = np.zeros((points.shape[0], 1))
data = np.hstack((points, intensities)).astype(np.float32)
np.save(str(npy_path), data)
return True
---------------------------------------------------------------------------------------------------------------------------------------
포인트 클라우드 변환 함수
.pcd를 .npy로 변환함. Open3D로 읽어서 (x, y, z) 좌표만 추출함
---------------------------------------------------------------------------------------------------------------------------------------
def convert_label_json_to_custom(json_path, txt_path):
with open(json_path, "r") as f:
objs = json.load(f)
lines = []
for obj in objs:
cls_id = str(obj["class"])
name = CLASS_MAP.get(cls_id, "DontCare")
if name == "DontCare":
continue
x = obj["position"]["x"]
y = obj["position"]["y"]
z = obj["position"]["z"]
dx = obj["scale"]["length"]
dy = obj["scale"]["width"]
dz = obj["scale"]["height"]
ry = obj["angle"]
line = f"{x:.4f} {y:.4f} {z:.4f} {dx:.4f} {dy:.4f} {dz:.4f} {ry:.4f} {name}"
lines.append(line)
with open(txt_path, "w") as f:
f.write("\n".join(lines))
---------------------------------------------------------------------------------------------------------------------------------------
라벨 변환 함수
Json 라벨을 KITTI Custom 형식 .txt로 변환하는 과정
"Don't Care" 클래스는 건너뜀, KITTI 포맷인 [x y z dx dz dy angle name] 순서로 저장
---------------------------------------------------------------------------------------------------------------------------------------
def gather_frames():
frames = []
scenes = sorted(DATA_ROOT.glob("scene_*"))
for scene in scenes:
scene_id = scene.name
label_dir = scene / "labels"
pcd_dir = scene / "pcds"
if not label_dir.exists() or not pcd_dir.exists():
continue
for label_file in sorted(label_dir.glob("*.json")):
frame_id = label_file.stem.replace("_all", "")
pcd_file = pcd_dir / f"{frame_id}_all.pcd"
if not pcd_file.exists():
continue
out_id = f"{scene_id}__{frame_id}"
frames.append((scene_id, frame_id, pcd_file, label_file, out_id))
return frames
---------------------------------------------------------------------------------------------------------------------------------------
scene_* 폴더별로 labels/*.json 과 pcds/*_all.pcd 매칭
변환해야 할 리스트를 반환함
---------------------------------------------------------------------------------------------------------------------------------------
def main():
frames = gather_frames()
valid_ids = []
for scene_id, frame_id, pcd_file, label_file, out_id in frames:
npy_path = POINTS_DIR / f"{out_id}.npy"
txt_path = LABELS_DIR / f"{out_id}.txt"
ok = convert_pcd_to_npy(pcd_file, npy_path)
if not ok:
continue
convert_label_json_to_custom(label_file, txt_path)
valid_ids.append(out_id)
train_ids, val_ids = train_test_split(valid_ids, test_size=0.2, random_state=42)
with open(IMAGESETS_DIR / "train.txt", "w") as f:
f.write("\n".join(train_ids))
with open(IMAGESETS_DIR / "val.txt", "w") as f:
f.write("\n".join(val_ids))
if __name__ == "__main__":
main()
---------------------------------------------------------------------------------------------------------------------------------------
메인 실행 함수
80% train / 20% val 랜덤 분할
위 코드를 실행하게 되면 ./custom 디렉터리 내에 labels와 points 디렉터리 생성됨
labels 디렉터리 내부에는 .txt로 변환된 파일, points 디렉터리 내부에는 .npy 파일들이 생성됨
이를 가지고 ImageSets를 생성하면 된다
Imagesett.py
---------------------------------------------------------------------------------------------------------------------------------------
import os
import random
from pathlib import Path
# 데이터 경로
points_dir = Path("/mnt/c/minseo/ctm/custom/points")
imagesets_dir = Path("/mnt/c/minseo/ctm/custom/ImageSets")
imagesets_dir.mkdir(exist_ok=True)
# 분할 비율
train_ratio = 0.8 # 80% train, 20% val
# 파일 목록 불러오기
all_ids = [f.stem for f in points_dir.glob("*.npy")]
all_ids.sort()
# 랜덤 셔플
random.shuffle(all_ids)
# train/val 분리
train_count = int(len(all_ids) * train_ratio)
train_ids = all_ids[:train_count]
val_ids = all_ids[train_count:]
# txt 저장
with open(imagesets_dir / "train.txt", "w") as f:
f.write("\n".join(train_ids))
with open(imagesets_dir / "val.txt", "w") as f:
f.write("\n".join(val_ids))
print(f"총 {len(all_ids)}개 중 Train: {len(train_ids)}, Val: {len(val_ids)}")
print(f"ImageSets 폴더: {imagesets_dir}")
---------------------------------------------------------------------------------------------------------------------------------------
위와 같이 ImageSets까지 생성 후 아래 명령어를 돌려 .pkl 파일들을 생성하면 훈련 준비가 끝남
명령어를 실행하면 .pkl 파일들이 생성됨
이와 같이 최종 구성되면 데이터셋 준비는 끝이 남
원본 데이터셋으로 시각화
위 과정을 진행하기 전에 각 class가 뭔지 명시하지 않아놓으면 진행할 수가 없어서 직접 시각화해서 확인해보기로 함
사용 코드는 아래 링크에 있음
https://kind-slip-86b.notion.site/24b8a2c2bfdb80f3860fe49dfbaa33c2?source=copy_link
위 코드를 돌려서 나오는 RGB값을 찍어서 각각 뭔지 매핑함
https://divmagic.com/ko/tools/color-converter
색상 변환기 사용하면 쉬움
증강 적용
labels의 .txt로 각 클래스가 몇 개 있는지 찍어서 확인 후 데이터 불균형 조정 진행함
datasetCount.py
---------------------------------------------------------------------------------------------------------------------------------------
import os
from collections import Counter
import matplotlib.pyplot as plt
LABELS_DIR = './custom/labels'
IGNORE_CLASSES = {"Care"}
counter = Counter()
for fname in os.listdir(LABELS_DIR):
if not fname.endswith('.txt'):
continue
with open(os.path.join(LABELS_DIR, fname), 'r') as f:
for line in f:
line = line.strip()
if not line:
continue
cls_name = line.split()[-1]
if cls_name in IGNORE_CLASSES:
continue
counter[cls_name] += 1
print('클래스별 개수:')
for cls, count in counter.items():
print(f'{cls}: {count}')
plt.figure(figsize=(6,4))
plt.bar(counter.keys(), counter.values())
plt.title('Class Distribution')
plt.xlabel('Class')
plt.ylabel('Count')
plt.grid(axis='y', linestyle='--', alpha=0.5)
plt.tight_layout()
plt.show()
---------------------------------------------------------------------------------------------------------------------------------------
보면 Pedestrain 다음에 Cyclist가 적은 것을 확인할 수 있음
이를 보고 GT-Sampling에서 적용할 샘플 개수를 늘리면 됨
pv-rcnn (not pretrained)
pv-rcnn (pretrained, backbone freeze)
pv-rcnn (pretrained, backbone freeze) 훈련 진행 중 결과
더 이상 loss가 떨어지지 않는 것을 확인하고 훈련 종료
test.py 코드 결과
추론 시간 측정
test.py를 돌릴 때 인자값으로 --infer_time을 주면 test.py 돌릴 때 같이 측정해줌
결과, cktp 20 확인
pv-rcnn kitti dataset 성능 지표, Pedestrain은 나와있지 않아 제외함
첫댓글 모델은 어떤걸 썼나요
Gpu온도체크하면서 실행할것
pv-rcnn 사용했습니다
테스트 결과는 어딨냐? 논문의 정확도와 경진대회 데이터셋으로 훈련한 결과의 테스트결과를 비교할것
수정했습니다 kitti dataset과의 정확도가 차이가 많이 나서 클래스 매핑이랑 증강 확인 후 계속 돌려보겠습니다