데이터 전처리 1

조훈·2025년 6월 14일

1. Labeled Data Format Transform

  • AIHUB 자율주차용 데이터셋을 보면 1프레임의 이미지 당 1개의 Segmentation Annotation JSON 형식의 Labeled data가 매칭 되어 있다.

  • 이를 YOLOv8-seg 학습 형태로 바꾸기 위한 Python 코드를 작성한다.

import os
import json
from glob import glob
import shutil

CLASS_NAMES = [
    'Disabled Icon',' Women Icon', 'Compact Car Icon', 'No Parking Sign', 'Traffic Cone', 'Fire Extinguisher', 'Undefined Object', 
    'Two-wheeled Vehicle', 'Vehicle', 'Wheelchair', 'Stroller', 'Shopping Cart', 'Human'
    ]
            
CLASS_MAP = {name: idx for idx, name in enumerate(CLASS_NAMES)}

IMG_WIDTH = 4032
IMG_HEIGHT = 3040

json_root = "YOUR_JSON_PATH"
output_temp_dir = "YOUR_OUTPUT_PATH"
train_dir = "YOUR_TRAIN_PATH"
val_dir = "YOUR_VAL_PATH"
os.makedirs(output_temp_dir, exist_ok=True)
os.makedirs(train_dir, exist_ok=True)
os.makedirs(val_dir, exist_ok=True)

all_json_files = sorted(glob(os.path.join(json_root, "*.json")))
selected_json_files = all_json_files[::4]

def normalize_point(point):
    try:
        x = float(point['x']) / IMG_WIDTH
        y = float(point['y']) / IMG_HEIGHT
        return [x, y]
    except (KeyError, TypeError):
        return None

def process_polygon(polygon):
    flat_coords = []
    for point in polygon:
        if isinstance(point, dict) and 'x' in point and 'y' in point:
            norm = normalize_point(point)
            if norm:
                flat_coords.extend(norm)
        else:
            print(f"point 형식 이상: {point}")
            return None
    return flat_coords if len(flat_coords) >= 6 else None

def convert_json_to_yolo_seg(json_path):
    with open(json_path, 'r', encoding='utf-8') as f:
        data = json.load(f)

    image_name = os.path.splitext(os.path.basename(data['data_key']))[0]

    if image_name.startswith("R") or image_name.startswith("L"):
        print(f"좌우 카메라 제외: {image_name}")
        return

    label_lines = []

    for obj in data.get("objects", []):
        class_name = obj.get("class_name")
        if class_name not in CLASS_MAP:
            continue

        class_id = CLASS_MAP[class_name]
        annotations = obj.get("annotation", [])

        for polygon_group in annotations:
            if not polygon_group:
                continue

            if isinstance(polygon_group[0], dict):
                flat_coords = process_polygon(polygon_group)
                if flat_coords:
                    label_lines.append(f"{class_id} " + " ".join(f"{c:.6f}" for c in flat_coords))
            elif isinstance(polygon_group[0], list):
                for polygon in polygon_group:
                    flat_coords = process_polygon(polygon)
                    if flat_coords:
                        label_lines.append(f"{class_id} " + " ".join(f"{c:.6f}" for c in flat_coords))

    if label_lines:
        label_path = os.path.join(output_temp_dir, f"{image_name}.txt")
        with open(label_path, 'w', encoding='utf-8') as f:
            f.write("\n".join(label_lines))
            print(f"{image_name}.txt 저장 완료")

for json_file in selected_json_files:
    convert_json_to_yolo_seg(json_file)

print(f"총 {len(selected_json_files)}개의 JSON 파일을 변환 완료")

image_files = sorted([
    f for f in os.listdir(output_temp_dir)
])

total = len(image_files)
split_idx = int(total * 0.9)

train_files = image_files[:split_idx]
val_files = image_files[split_idx:]

for f in train_files:
    shutil.move(os.path.join(output_temp_dir, f), os.path.join(train_dir, f))

for f in val_files:
    shutil.move(os.path.join(output_temp_dir, f), os.path.join(val_dir, f))

os.rmdir(os.path.join(output_temp_dir))

print(f"샘플링된 총 {total}개 중 {len(train_files)}개는 train, {len(val_files)}개는 val로 복사 완료.")

1) Class 선별

CLASS_NAMES = [
    'Disabled Icon',' Women Icon', 'Compact Car Icon', 'No Parking Sign', 'Traffic Cone', 'Fire Extinguisher', 'Undefined Object', 
    'Two-wheeled Vehicle', 'Vehicle', 'Wheelchair', 'Stroller', 'Shopping Cart', 'Human'
    ]
              
CLASS_MAP = {name: idx for idx, name in enumerate(CLASS_NAMES)}
  • Annotation.json 파일에는 29개의 Class가 라벨링 되어 있지만 우리가 학습 할 Class만 선별하여 순차적으로 Class_index를 부여

2) 이미지 크기 및 경로 설정

IMG_WIDTH = 4032
IMG_HEIGHT = 3040

json_root = "YOUR_JSON_PATH"
output_temp_dir = "YOUR_OUTPUT_PATH"
train_dir = "YOUR_TRAIN_PATH"
val_dir = "YOUR_VAL_PATH"
os.makedirs(output_temp_dir, exist_ok=True)
os.makedirs(train_dir, exist_ok=True)
os.makedirs(val_dir, exist_ok=True)

all_json_files = sorted(glob(os.path.join(json_root, "*.json")))
selected_json_files = all_json_files[::4]
  • 이미지의 너비와 높이, Format 변환 후 결과물이 저장될 디렉토리 경로를 설정
  • 4프레임 당 1개로 이미지를 제한 했기 때문에 Labeled Data 또한 4개 당 하나로 선별
    (전체 라벨 : all_json_files / 선별된 라벨 : selected_json_files)

3) 좌표 정규화

def normalize_point(point):
    try:
        x = float(point['x']) / IMG_WIDTH
        y = float(point['y']) / IMG_HEIGHT
        return [x, y]
    except (KeyError, TypeError):
        return None
  • JSON 파일에 있는 x, y 좌표를 0~1 사이로 정규화

4) 폴리곤 처리

def process_polygon(polygon):
    flat_coords = []
    for point in polygon:
        if isinstance(point, dict) and 'x' in point and 'y' in point:
            norm = normalize_point(point)
            if norm:
                flat_coords.extend(norm)
        else:
            print(f"point 형식 이상: {point}")
            return None
    return flat_coords if len(flat_coords) >= 6 else None
  • 하나의 폴리곤은 점들(dict들의 리스트)로 구성

  • 각 점을 정규화하여 [x1, y1, x2, y2, ...] 형식으로 변환

  • 폴리곤 점이 3개 이상(즉 6개 값 이상)일 때만 유효


5) JSON → YOLO Segmentation 포맷으로 변환 함수

def convert_json_to_yolo_seg(json_path):
    with open(json_path, 'r', encoding='utf-8') as f:
        data = json.load(f)

    image_name = os.path.splitext(os.path.basename(data['data_key']))[0]

    if image_name.startswith("R") or image_name.startswith("L"):
        print(f"좌우 카메라 제외: {image_name}")
        return

    label_lines = []

    for obj in data.get("objects", []):
        class_name = obj.get("class_name")
        if class_name not in CLASS_MAP:
            continue

        class_id = CLASS_MAP[class_name]
        annotations = obj.get("annotation", [])

        for polygon_group in annotations:
            if not polygon_group:
                continue

            if isinstance(polygon_group[0], dict):
                flat_coords = process_polygon(polygon_group)
                if flat_coords:
                    label_lines.append(f"{class_id} " + " ".join(f"{c:.6f}" for c in flat_coords))
            elif isinstance(polygon_group[0], list):
                for polygon in polygon_group:
                    flat_coords = process_polygon(polygon)
                    if flat_coords:
                        label_lines.append(f"{class_id} " + " ".join(f"{c:.6f}" for c in flat_coords))

    if label_lines:
        label_path = os.path.join(output_temp_dir, f"{image_name}.txt")
        with open(label_path, 'w', encoding='utf-8') as f:
            f.write("\n".join(label_lines))
            print(f"{image_name}.txt 저장 완료")
            
for json_file in selected_json_files:
    convert_json_to_yolo_seg(json_file)

print(f"총 {len(selected_json_files)}개의 JSON 파일을 변환 완료")
  • JSON 파일을 열어

  • 필터링

    • 이미지 이름이 "R" 또는 "L"로 시작하면 좌우 카메라 이미지로 판단하고 제외 (정면 카메라 이미지만 사용하기 때문)

    • 클래스가 CLASS_MAP에 없으면 제외

  • 객체 안에 annotation 필드에 다각형(polygons) 존재 시

  • 이미지에 포함된 객체의 세그멘테이션 정보를 .txt로 임시 디렉토리에 저장


6) 라벨 파일을 train/val로 분할하여 이동

image_files = sorted([
    f for f in os.listdir(output_temp_dir)
])

total = len(image_files)
split_idx = int(total * 0.9)

train_files = image_files[:split_idx]
val_files = image_files[split_idx:]

for f in train_files:
    shutil.move(os.path.join(output_temp_dir, f), os.path.join(train_dir, f))

for f in val_files:
    shutil.move(os.path.join(output_temp_dir, f), os.path.join(val_dir, f))

os.rmdir(os.path.join(output_temp_dir))

print(f"샘플링된 총 {total}개 중 {len(train_files)}개는 train, {len(val_files)}개는 val로 이동 완료.")
  • 임시 디렉토리 내 변환된 .txt 파일들 90%는 train, 10%는 val로 분할 후 각각 지정된 디렉토리로 이동

0개의 댓글