본문 바로가기
딥러닝

AI 기반 아동 미술심리 진단을 위한 그림 분석 - YOLOv5(최종)

by 짱태훈 2024. 12. 17.
728x90

두 개의 게시물을 통해 여러 모델을 이용해서 이미지 디텍션을 진행했다. 이번에는 Yolov5를 이용해서 '여자 사람'에 대해 이미지 디텍션 모델 학습을 진행했다.

현재까지 학습에 사용한 모델은 '나무', '집'은 Faster R-CNN을 이용해서 이미지 디텍션을 진행했다. 그리고 '남자 사람'은 SSD를 이용해서 이미지 디텍션을 진행했다. 그리고 이번에 이용한 Yolov5를 이용해서 '여자 사람'에 대해서 이미지 디텍션을 진행했다. Faster R-CNN, SSD, Yolov5 모두 사전 훈련된 가중치를 모델에 이용했다. 하지만 모델의 모든 가중치가 학습 과정에서 업데이트되도록 설정했다. 또한, Faster R-CNN과 SSD는 MMDetection을 사용하여 COCO 데이터셋 형식으로 처리하였지만, YOLO의 경우 별도의 YOLO 데이터셋 형식을 사용하므로 이에 맞게 수정했다. 

AI hub에서 다운받은 'AI 기반 아동 미술심리 진단을 위한 그림 분석' 데이터셋은 json파일을 통해 annotation을 제공하기 때문에 아래와 같이 처리했다. 뿐만 아니라 YOLO는 yaml 파일과 특정한 디렉토리 구조를 요구하기 때문에 yaml 파일을 만들고 파일의 디레고리 구조 역시 수정을 했다. 

특히, 데이터셋의 사진, json 모두 한글이 포함되어 있어서 한글을 영어로 교체해야 제대로 load가 되기 때문에 이름을 바꾸는 작업도 진행했다.

 

1. YOLO 데이터셋 형식으로 수정

2. yaml 파일 생성

3. 파일 디렉토리 구조 수정

# YOLO 데이터셋 형식으로 수정
def json_to_yolo(json_path, output_dir, cat2label):
    with open(json_path, 'r', encoding='utf-8') as f:
        data = json.load(f)
    
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    
    img_meta = data.get("meta", {})
    img_name = img_meta.get("img_path").split('/')[-1]
    img_width, img_height = map(int, img_meta.get("img_resolution").split('x'))
    annotations = data.get("annotations", {}).get("bbox", [])
    
    yolo_file_path = os.path.join(output_dir, img_name.replace(".jpg", ".txt"))
    with open(yolo_file_path, 'w') as f:
        for bbox in annotations:
            x, y, w, h = bbox["x"], bbox["y"], bbox["w"], bbox["h"]
            label = bbox["label"]
            
            if label not in cat2label:
                continue  # 매핑되지 않은 클래스는 건너뜀
            class_id = cat2label[label]
            
            # 바운딩 박스를 YOLO 형식으로 변환
            center_x = (x + w / 2) / img_width
            center_y = (y + h / 2) / img_height
            norm_w = w / img_width
            norm_h = h / img_height
            f.write(f"{class_id} {center_x:.6f} {center_y:.6f} {norm_w:.6f} {norm_h:.6f}\n")


def process_json_directory(json_dir, output_dir, cat2label):
    for root, _, files in os.walk(json_dir):
        for file in files:
            if file.endswith(".json"):
                json_path = os.path.join(root, file)
                json_to_yolo(json_path, output_dir, cat2label)

# yaml 파일 생성
yaml_content = f"""
# 학습 및 검증 이미지 경로
train: /kaggle/working/images/train
val: /kaggle/working/images/val

# 클래스 수
nc: {num_classes}

# 클래스 이름
names: {class_names}
"""
yaml_path = "/kaggle/working/art.yaml"
with open(yaml_path, "w") as file:
    file.write(yaml_content)

# 레이블, train/val txt 생성
json_dir = "/kaggle/input/art-picture/Training/label/TL_여자사람"
output_dir = "/kaggle/working/label"
label_dir = os.path.join(output_dir, "train")
if not os.path.exists(label_dir):
    os.makedirs(label_dir)
# JSON 파일 디렉터리 처리
process_json_directory(json_dir, label_dir, cat2label)

json_dir = "/kaggle/input/art-picture/Validation/label/VL_여자사람"
output_dir = "/kaggle/working/label"
label_dir = os.path.join(output_dir, "val")
if not os.path.exists(label_dir):
    os.makedirs(label_dir)
# JSON 파일 디렉터리 처리
process_json_directory(json_dir, label_dir, cat2label)
print('finish')
# 파일 디렉토리 수정 및 image, train/val 복사
folder_path = "/kaggle/working/image"
folder_path1 = "/kaggle/working/image/train"
folder_path2 = "/kaggle/working/image/val"
os.makedirs(folder_path, exist_ok=True)
os.makedirs(folder_path1, exist_ok=True)
os.makedirs(folder_path2, exist_ok=True)

src_image_path = '/kaggle/input/art-picture/Training/image/TS_여자사람'
tgt_images_dir = '/kaggle/working/image/train'
os.makedirs(tgt_images_dir, exist_ok=True)
shutil.copytree(src_image_path, tgt_images_dir, dirs_exist_ok=True)

src_image_path = '/kaggle/input/art-picture/Validation/image/VS_여자사람'
tgt_images_dir = '/kaggle/working/image/val'
os.makedirs(tgt_images_dir, exist_ok=True)
shutil.copytree(src_image_path, tgt_images_dir, dirs_exist_ok=True)
import os

target_base_dir = "/kaggle/working/images"
target_train_dir = os.path.join(target_base_dir, "train")
target_val_dir = os.path.join(target_base_dir, "val")
os.makedirs(target_base_dir, exist_ok=True)
os.makedirs(target_train_dir, exist_ok=True)
os.makedirs(target_val_dir, exist_ok=True)


source_train_dir = "/kaggle/working/image/train"
source_val_dir = "/kaggle/working/image/val"
image_dir1 = "/kaggle/working/images/train"
image_dir2 = "/kaggle/working/images/val"


def rename_and_copy_images(source_dir, target_dir):
    for file_name in os.listdir(source_dir):
        if file_name.endswith(".jpg"):
            parts = file_name.split("_")
            if len(parts) >= 4:  # 예상되는 파일 이름 구조일 경우
                prefix = "woman"  # 앞부분 고정
                gender = parts[2]  # "남" 또는 "여" 추출
                number = parts[3]  # 번호 부분 추출

                # gender에 따라 "man" 또는 "woman"으로 설정
                if gender == "남":
                    gender_eng = "man"
                elif gender == "여":
                    gender_eng = "woman"
                else:
                    print(gender)
                    continue

                new_name = f"{prefix}_{parts[1]}_{gender_eng}_{number}"
                
                src_path = os.path.join(source_dir, file_name)
                dst_path = os.path.join(target_dir, new_name)
                shutil.copy(src_path, dst_path)
    print('finish')


rename_and_copy_images(source_train_dir, target_train_dir)
rename_and_copy_images(source_val_dir, target_val_dir)
kaggle/working/
│
├── images/
│   ├── train/          # 학습용 이미지
│   │   ├── img1.jpg
│   │   ├── img2.jpg
│   │   └── ...
│   ├── val/            # 검증용 이미지
│   │   ├── img1.jpg
│   │   ├── img2.jpg
│   │   └── ...
│
├── labels/
│   ├── train/          # 학습용 레이블 파일
│   │   ├── img1.txt    # img1.jpg의 바운딩 박스 정보
│   │   ├── img2.txt
│   │   └── ...
│   ├── val/            # 검증용 레이블 파일
│   │   ├── img1.txt    # img1.jpg의 바운딩 박스 정보
│   │   ├── img2.txt
│   │   └── ...
│
├── art.yaml           # 데이터 설정 파일

결과

전체 epoch: 11

11/11      6.02G    0.02866    0.07369    0.01132        296        640
  • 사용 중인 GPU 메모리: 6.02GB
  • 총 손실값(Total Loss): 0.02866
  • IoU Loss: 0.07369
  • 객체 손실(Objectness Loss): 0.01132
  • 이미지 처리 속도(Images per Second): 296(클수록 학습 속도가 빠름)
  • 입력 이미지 크기: 640

  • 훈련 및 검증 손실 (Loss)

1. train/box_loss, val/box_loss

  • 예측된 바운딩 박스와 실제 박스의 위치 차이를 나타내는 손실
  • 훈련과 검증 손실이 모두 안정적으로 감소

2. train/obj_loss, val/obj_loss

  • 객체가 존재하는지 여부를 예측하는 손실
  • 훈련과 검증 손실이 모두 지속적으로 감소

3. train/cls_loss, val/cls_loss

  • 객체의 클래스 예측에 대한 손실
  • 초기에는 급격하게 감소하고 이후 안정적으로 낮아진다. 모델이 점점 정확하게 객체 클래스를 예측
  • 평가 지표 (Metrics)
  1. metrics/precision
  • 모델이 탐지한 객체 중 실제로 객체인 비율
  • 초반에는 낮지만 학습이 진행되면서 0.95 수준으로 수렴
  • 정확도가 높아지고 있다.

2. metrics/recall

  • 실제 객체를 얼마나 잘 탐지했는가
  • 초반에 빠르게 상승하여 0.95에 도달, 모델이 대부분의 객체를 잘 찾아내고 있다.

3. metrics/mAP_0.5

  • IoU 임계값이 0.5일 때의 평균 정확도
  • 값이 점진적으로 상승하여 0.95 근처에 도달, 모델이 대부분의 객체를 정확하게 탐지

4. metrics/mAP_0.5:0.95

  • IoU 임계값을 0.5에서 0.95까지 단계적으로 올렸을 때의 평균 정확도
  • 초기에는 낮지만, 이후 0.80 수준까지 상승

5. 종합

  • 손실 (Loss)이 지속적으로 감소
  • 훈련 및 검증 간 큰 차이가 없어 과적합은 아니다.
  • 정확도(Precision)와 재현율(Recall)이 모두 높다. mAP 지표도 좋은 성능을 보여주고 있다.
  • 전체적으로 모델이 객체 탐지를 정확하게 수행하고 있으며, 학습이 잘 진행되었다.

 

결론

Faster R-CNN, SSD, YOLOv5를 사용해서 미술심리 진단을 위한 그림 분석 프로젝트를 진행했다. 3개의 모델 모두 높은 정확도를 보여주었다.

훈련 데이터셋이 충분하다면 속도와 정확도에서 균형을 이루는 YOLOv5를 이용해 학습하는 것이 더욱 효육적이기에 YOLOv5를 이용하는 것이 더 좋은 것으로 판단한다.

728x90