# 구조에 맞는 디렉토리 생성
!mkdir /content/Dataset/images;
!mkdir /content/Dataset/images/train; mkdir /content/Dataset/images/val
!mkdir /content/Dataset/labels;
!mkdir /content/Dataset/labels/train; mkdir /content/Dataset/labels/val
# Dataset metadata 입력
won_list = [디렉토리 리스트]
# 파일 담아오기
image_files = dict()
for won in won_list:
image_files[won] = glob.glob(data_path+won+'/*.jpg')
json_files = dict()
for won in won_list:
json_files[won] = glob.glob(data_path+won+'/*.json')
# 랜덤 샘플링 (난수고정)
random.seed(23)
random_num = dict()
for won in won_list:
test_num = round(len(image_files[won]) * 0.2)
random_num[won] = random.sample((range(len(image_files[won]))), test_num)
# 파일 이동
for won in won_list:
for i in random_num[won]:
shutil.move(image_files[won][i], data_path+'images/val/')
shutil.move(json_files[won][i], data_path+'labels/val/')
# 남아있는 파일 다시 담아오기 (train data)
image_files = dict()
for won in won_list:
image_files[won] = glob.glob(data_path+won+'/*.jpg')
json_files = dict()
for won in won_list:
json_files[won] = glob.glob(data_path+won+'/*.json')
# 남아있는 파일을 전부 train으로 복사
for won in won_list:
for image in image_files[won]:
shutil.copy2(image, data_path+'images/train/')
for json in json_files[won]:
shutil.copy2(json, data_path+'labels/train/')
# 더 최신 버전이 있지만 오류가 발생할 가능성이 있어 9.0.0 버전 사용
!pip install Pillow==9.0.0
import os
from PIL import Image
# crop 함수 설정
"""
이미지를 1:1 비율이 되도록 자르는 함수입니다.
"""
def crop(file):
img = Image.open(file)
w, h = img.size
crop_len = abs(w - h) // 2
if w == h:
img_cropped = img
elif w > h:
img_cropped = img.crop((crop_len, 0, w-crop_len, h))
else:
img_cropped = img.crop((0, crop_len, w, h-crop_len))
return img_cropped
# 파일 불러오기
train_path = '/content/Dataset/images/train/*'
test_path = '/content/Dataset/images/val/*'
train_files = glob.glob(train_path)
test_files = glob.glob(test_path)
# 이미지 처리 (기존 이미지 덮어씌움)
for file in train_files:
img_cropped = crop(file)
img_cropped.save(file)
for file in test_files:
img_cropped = crop(file)
img_cropped.save(file)
class x_center y_center width, height
순서로 저장되어야 한다. 또한 0~1의 범위로 normalization이 이루어져 있어야 한다.yolo document에서 제공하는 가이드
def json_to_txt(file, classes):
result = dict()
with open(file, mode='r') as f:
data = json.load(f)
# 이미지 처리를 위한 변수 설정
points = data['shapes'][0]['points']
x1, y1 = points[0]
x2, y2 = points[1]
width, height = data['imageWidth'], data['imageHeight']
# 라벨 획득 (앞면, 뒷면 구분 제거)
text = data['shapes'][0]['label']
idx = text.rfind('_')
label = text[:idx]
# crop 반영
crop_len = abs(width-height) // 2
if width > height:
width = height
x1 = max(x1-crop_len, 0)
x2 = min(x2-crop_len, width)
elif width < height:
height = width
y1 = max(y1-crop_len, 0)
y2 = min(y2-crop_len, height)
# 정규화 (0 ~ 1 min-max)
x1 = x1 / width
x2 = x2 / width
y1 = y1 / height
y2 = y2 / height
# x, y, w, h 계산
x = (x1 + x2) / 2
y = (y1 + y2) / 2
w = (x2 - x1)
h = (y2 - y1)
# 정해진 형식의 문자열로 반환
return f'{classes[label]} {x} {y} {w} {h}'
for file in train_files:
annot = json_to_txt(file, classes)
txt_file = file.replace('.json', '.txt')
with open(txt_file, mode='w') as f:
text = annot
f.write(text)
for file in test_files:
annot = json_to_txt(file, classes)
txt_file = file.replace('.json', '.txt')
with open(txt_file, mode='w') as f:
text = annot
f.write(text)
마지막으로 yolo 모델에게 파일 위치와 클래스 종류 등을 전달하는 yaml 파일을 작성하였다.
yolo document에서 알려주는 yaml파일의 형식은 다음과 같다.
# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: ../datasets/coco128 # dataset root dir
train: images/train2017 # train images (relative to 'path') 128 images
val: images/train2017 # val images (relative to 'path') 128 images
test: # test images (optional)
# Classes (80 COCO classes)
names:
0: person
1: bicycle
2: car
...
77: teddy bear
78: hair drier
79: toothbrush
document = {
'path' : '/content/drive/MyDrive/my_data/220320_mini_project_money/images/',
'train' : 'train',
'val' : 'val',
'nc' : 8,
'names' : won_dict
}
# dump 함수를 이용하여 저장
with open('/content/drive/MyDrive/my_data/220320_mini_project_money/money.yaml', 'w') as f :
yaml.dump(document, f)
만약 이 모델을 실제로 사용한다면 object가 회전되어 있거나, 여러 각도에서 사진이 찍혔거나, 어느 한쪽으로 치우치고 일부만 보이는 등의 사례가 있을 것이다. 그런데 학습 데이터는 대부분 정중앙에 object 하나만 찍혀있는 형태여서 일반적인 성능이 잘 나올 수 있을지는 의문이다.
때문에 Data Augmentation을 시도해보고 싶었으나, yolo 모델 자체에 적용하는 generator는 찾을 수 없었다.
별도로 변형된 이미지를 따로 생성하면 가능하겠지만, 이는 시간 부족으로 시도하지 못했다.
train.py 파일을 뜯어보니 yolo 자체에서 augmentation을 적용하는 것을 발견하였다. 이를 적절히 조절하면 원하는 augmentation이 가능할 것으로 보인다.