Step by step object detection, STEP 1. ๋ฐ์ดํฐ์ ์ค๋นํ๊ธฐ
๊ฐ์ฒด ๊ฒ์ถ ํ์ต ๋ชจ๋ธ ๊ตฌํ์ ๋จ๊ณ๋ณ๋ก ์ ๋ฆฌํ๋ ํฌ์คํธ๋ฅผ ์์ฑํด๋ณด๋ ค๊ณ ํ๋ค.
์๊ทผ ์์ํ๊ฒ ์ ๊ฒฝ์ธ๊ฒ ๋ง์์ ์ ๋ฆฌํด๋๋ฉด ๋์ค์ ํ์ฉํ๊ธฐ์ ์ข์ ๊ฒ ๊ฐ๋ค.
๋จผ์ ์ด ๊ณผ์ ์ sgrvinod ๊นํ๋ธ์ 'Deep Tutorials for PyTorch' ํํ ๋ฆฌ์ผ์ ์ฐธ๊ณ ํด์ ์งํ๋์๋ค.
๋ด๊ฐ ์ฌ์ฉํ ๊ฐ์ฒด ๊ฒ์ถ ๋ฐ์ดํฐ์ ์ AI HUB ์ '๊ฑด๊ฐ๊ด๋ฆฌ๋ฅผ ์ํ ์์ ์ด๋ฏธ์ง ๋ฐ์ดํฐ' ์ด๋ค.
์ด ๋ฐ์ดํฐ์ ์๋ ์์๊ณผ ๊ด๋ จ๋ 500์ฌ๊ฐ ์นดํ ๊ณ ๋ฆฌ๊ฐ ์กด์ฌํ๋๋ฐ, ์ปดํจํ ์ ๋ฆฌ์์ค๋ฅผ ๊ณ ๋ คํ์ฌ, ๊ณผ์ผ๊ณผ ์ฑ์ ๋๋ถ๋ฅ ์ค ์ผ๋ถ ์นดํ ๊ณ ๋ฆฌ๋ง์ ์ ์ ํ์ฌ ๊ฐ์ฒด ๊ฒ์ถ์ ์งํํ๊ธฐ๋ก ํ๋ค.
๊ทธ๋ฆฌํ์ฌ ์ ์ ๋ ์นดํ ๊ณ ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ๋ค.
- ๊ณผ์ผ: ๋ง๊ณ (1), ๋ฉ๋ก (2), ๋ธ๊ธฐ(3), ๋ธ๋ฃจ๋ฒ ๋ฆฌ(4), ์ฌ๊ณผ(5), ์๋ชฝ(6), ์๋(7), ์ฒ๋๋ณต์ญ์(8), ์ฒญํฌ๋(9), ์ฒด๋ฆฌ(10)
- ์ฑ์: ๊ตฐ๊ณ ๊ตฌ๋ง(11), ๊ตฐ๋ฐค(12), ๊ณ ์ถ(13), ์น์ปค๋ฆฌ(14), ์ฝ๋ผ๋น(15), ํํ๋ฆฌ์นด(16), ํ ๋งํ (17), ํ๊ณ ๋ฒ์ฏ(18), ๋จํธ๋ฐ(19), ํผ๋ง(20)
STEP 1. ๋ฐ์ดํฐ์ ์ค๋นํ๊ธฐ
1) ๋๋ค ์ํ๋ง
๊ฐ ์นดํ ๊ณ ๋ฆฌ ๋ณ๋ก raw data ๋ฅผ ๋ค์ด๋ฐ์ ๋ณด๋ฉด ํ ์นดํ ๊ณ ๋ฆฌ ์์๋ ์ด๋ฏธ์ง๊ฐ ๋๋ฌด ๋ง๋ค. ๊ทธ๋์ ํ์ต ๋ฐ์ดํฐ๋ก 400์ฅ, validation ๋ฐ์ดํฐ๋ก 50์ฅ, ํ ์คํธ ๋ฐ์ดํฐ๋ก 50์ฅ์ ๋ฝ์์ ํ์ฉํ ๊ฒ์ด๋ค.
์ด๋ฅผ ์ํด ๋๋ค ์ํ๋ง์ 500๊ฐ ๋งํผ ์งํํ์๋ค.
๋ชจ๋ ์นดํ ๊ณ ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์ ๊ตฌ์ฑ์ผ๋ก ์ด๋ฏธ์ง์ ๋ผ๋ฒจ์ด ์ ์ฅ๋์ด์๋ค. ์ด๊ณณ (source) ์์ 500์ฅ์ ๋๋ค ์ํ๋งํด์ ๋ณต์ฌํ๊ณ ์ธ๋ถ ํด๋๋ก ๋ถ์ฌ๋ฃ๊ธฐ๋ฅผ ํ ๊ฒ์ด๋ค. ๋ฐ๋ผ์ ์ธ๋ถ์ ๋ธ๋ฃจ๋ฒ ๋ฆฌ๋ผ๋ ํด๋๋ฅผ ๋ง๋ค๊ณ ํ์ ํด๋๋ก images์ labels ๋ฅผ ๋ง๋ ๋ค.
< ๊ธฐ์กด ๋ฐ์ดํฐ์ - ๋ธ๋ฃจ๋ฒ ๋ฆฌ ํด๋ >
๐ฆ [์์ฒ] ์์204_Tra
โโ ๋ธ๋ฃจ๋ฒ ๋ฆฌ
โโ A220121XX_03592.jpg
โโ A220121XX_03593.jpg
โโ (์ดํ ์๋ต)
๐ฆ [๋ผ๋ฒจ] ์์204_Tra
โโ ๋ธ๋ฃจ๋ฒ ๋ฆฌ
โโ A220121XX_03592.json
โโ A220121XX_03593.json
โโ (์ดํ ์๋ต)
< ์๋ก ๋ง๋ ํด๋ >
๐ฆ ๋ธ๋ฃจ๋ฒ ๋ฆฌ
โโ images
โโ labels
๋๋ค ์ํ๋งํ์ฌ ๋ณต์ฌ ๋ถ์ฌ๋ฃ๊ธฐ๋ฅผ ํ๋ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค. (500_random_sampling.ipynb)
source_img = "/Users/jeongin/Desktop/แแ
กแผแแ
ฅแแ
ฎ์ด image"
destin_img = "/Users/jeongin/Desktop/แแ
กแผแแ
ฅแแ
ฎแแ
ต/images"
source_json = "/Users/jeongin/Desktop/แแ
กแผแแ
ฅแแ
ฎแแ
ต json"
destin_json = "/Users/jeongin/Desktop/แแ
กแผแแ
ฅแแ
ฎแแ
ต/labels"
file_list = os.listdir(source_img)
image_list = [f for f in file_list if f.endswith('.jpg')]
# ์ด๋ฏธ์ง ํ์ผ 500๊ฐ ๋๋ค ์ํ๋ง
random_images = random.sample(image_list, 500)
print("random",random_images)
# ์ด๋ฏธ์ง ํ์ผ ๋ณต์ฌ ๋ถ์ฌ๋ฃ๊ธฐ
copied_files = []
for image in random_images:
copy_this = os.path.join(source_img, image)
paste_here = os.path.join(destin_img, image)
shutil.copy(copy_this, paste_here)
copied_files.append(image)
print("copied", copied_files)
json_list = []
for img in copied_files:
new = img.replace('.jpg', '.json')
json_list.append(new)
print(json_list)
for json in json_list:
copy_from = os.path.join(source_json, json)
paste_to = os.path.join(destin_json, json)
shutil.copy(copy_from, paste_to)โ
์ด ์์ ์ ์๋ฃํ๊ณ ํด๋๋ฅผ ์ฐํด๋ฆญํด '์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ'๋ฅผ ํ๋ฉด 501๊ฐ์ ํ์ผ์ด ๋ค์ด์๋ค๊ณ ๋จ๋๋ฐ, ์ด ํ๋์ ์ ์ฒด๋ '.DS_Store' ๋ผ๋ ํ์ผ๋ก macOS ์ด์ ์ฒด์ ์์ ์ฌ์ฉ๋๋ ์จ๊ฒจ์ง ์์คํ ํ์ผ์ด๋ค. ์ด ํ์ผ์ ํน์ ํด๋์ ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ์ ํด๋ ๋ด๋ถ์ ํ์ผ ๋ฐ ํด๋์ ๋ ์ด์์์ ์ ์ฅํ๋ ๋ฐ ์ฌ์ฉ๋๋ฉฐ ๋ฌด์ํด๋ ๋๋ค.
์์ ๊ณผ์ ์ ์ ์ ํ 20๊ฐ์ ์นดํ ๊ณ ๋ฆฌ์ ๋ํด ๋ฐ๋ณตํ๋ฉฐ ์ด 500x20=10,000์ฅ์ ์ฌ์ง๊ณผ ๋ผ๋ฒจ ํ์ผ์ด ์ ์ฅ๋๋ค.
๋๋ค์ผ๋ก ๋ฝ์ 500๊ฐ์ ์ฌ์ง๊ณผ ๋ผ๋ฒจ์ค 50, 50 ๊ฐ๋ฅผ ๋ผ์ด๋ด์ test, validation ์ ๋๋ ์ฃผ๋ฉด ๋๋ค.
์ด์ ๋ค์ ์๋ก์ด ํด๋๋ฅผ ๋ง๋ค์ด ์ ์ฒด ์นดํ ๊ณ ๋ฆฌ์ ๋ํด train, validation, test ์ ์ผ๋ก ๋ถ๋ฆฌ์ํฌ ๊ฒ์ด๋ค.
๐ฆ all
โโ train (8000)
โ โโ images
โ โโ labels
โโ validation (1000)
โ โโ images
โ โโ labels
โโ test (1000)
โโ images
โโ labels
2) ์ด๋ฏธ์ง resize & json ํ์ผ ์์ (label, resolution ํค ์ถ๊ฐ)
๋ชจ๋ ์ด๋ฏธ์ง๊ฐ ๊ฐ์ ์ฌ์ด์ฆ๋ฅผ ๊ฐ์ง๋๋ก ๋ฆฌ์ฌ์ด์ง ํด์ค๋ค. ๋ฆฌ์ฌ์ด์ง ํด์๋๋ 300x300 ์ด๋ค.
์๋ image ํ์ผ์ ๊ฐ๊ธฐ ๋ค๋ฅธ ํด์๋๋ฅผ ๊ฐ์ง๊ณ ์๋ค. ์ด์ ๋ํ ์ ๋ณด๋ฅผ resolution ์ผ๋ก ๋ฃ์ด์ค๋ค.
๋ํ label_map ์ผ๋ก ๋งคํ๋ label ์ซ์๋ฅผ ๋ฃ์ด์ค๋ค.
์ฝ๋:
import os
import random
import shutil
import json
import cv2
source_dir = "/Users/jeongin/Desktop/all/training/images"
destin_dir = "/Users/jeongin/Desktop/all/training/destin_images"
def resize_and_update_json(image, json_data, target_size=(300,300)):
resized_image = cv2.resize(image, target_size)
updated_json = []
for obj in json_data:
obj["label"] = 20 # ๋ฐ๊ฟ์ค ๋ถ๋ถ
updated_json.append(obj)
updated_json = [{'label': obj['label'], **obj} for obj in updated_json]
resolution = {
'width': image.shape[1],
'height': image.shape[0]
}
return resized_image, updated_json, resolution
for file in source_dir:
# ์ด๋ฏธ์ง ํ์ผ ๋ณต์ฌ
source_img = os.path.join(file)
destin_img = os.path.join(file)
# JSON ํ์ผ ๋ณต์ฌ
json_file = os.path.splitext(file)[0] + ".json"
source_json = os.path.join(source_dir, "labels", json_file)
destin_json = os.path.join(destin_dir, "labels", json_file)
# Load the image and JSON data
image = cv2.imread(source_img)
with open(source_json, 'r') as json_f:
json_data = json.load(json_f)
# Resize image and adjust bounding boxes
resized_image, updated_json, resolution = resize_and_update_json(image, json_data)
# Save the resized image and updated JSON data
cv2.imwrite(destin_img, resized_image)
# Group the updated object annotations and resolution into a single dictionary
output_data = {
'objects': updated_json,
'resolution': resolution
}
with open(destin_json, 'w') as json_f:
json.dump(output_data, json_f, indent=2)
์ ๋ฐ์ดํธ๋ json ํ์ผ:
3) ์ ๋์ขํ๋ก ๋ณํํด์ค๋ค.
์๋ json ํ์ผ ์์๋ ์ค์ฌ์ขํ (c_x, c_y)์ ํญ (width), ๋์ด(height) ๊ฐ ์๋์ขํ๋ก ๋ค์ด์๋ค.
์ด๊ฒ์ ์ถ์ถํด์ ์ ๋์ขํ๋ก ๋ฃ์ด์ค ๊ฒ์ด๋ค. ์ ๋์ขํ๋ก ๋ณํํ๋ ์ฝ๋๋ ์๋์ ๊ฐ๋ค.
์ฝ๋:
import os
import json
from PIL import Image, ImageDraw
import torch
import torchvision.transforms.functional as FT
def convert(json_dir):
for filename in os.listdir(json_dir):
if filename.endswith(".json"):
json_file_path = os.path.join(json_dir, filename)
with open(json_file_path, 'r') as json_file:
data = json.load(json_file)
for item in data["objects"]:
w = float(item["W"])
h = float(item["H"])
x, y = map(float, item["Point(x,y)"].split(','))
resized_width = 300
resized_height = 300
x_min = (x - w / 2) * resized_width
y_min = (y - h / 2) * resized_height
x_max = (x + w / 2) * resized_width
y_max = (y + h / 2) * resized_height
new_xmin = x_min
new_ymin = y_min
new_xmax = x_max
new_ymax = y_max
print(new_xmin, new_ymin, new_xmax, new_ymax)
item["boxes"] = [new_xmin, new_ymin, new_xmax, new_ymax]
with open(json_file_path, 'w') as updated_json_file:
json.dump(data, updated_json_file, indent=4)
# ์
๋ฐ์ดํธ๋ JSON ํ์ผ ๊ฒฝ๋ก ์ถ๋ ฅ
print(f"์
๋ฐ์ดํธ๋ JSON ํ์ผ: {json_file_path}")
# JSON ํ์ผ์ด ๋ค์ด ์๋ ๋๋ ํ ๋ฆฌ ๊ฒฝ๋ก
json_dir = "/Users/jeongin/Desktop/all/train/labels"
# JSON ํ์ผ ๋ณํ ํจ์ ํธ์ถ
convert(json_dir)
์ต์ข json ํ์ผ ํํ
ํ๋์ ๊ธ์จ ๋ถ๋ถ์ด ์๋กญ๊ฒ ์ ๋ฐ์ดํธ๋ ๋ถ๋ถ์ด๋ค.
4) train_images.json, train_objects.json, test_images.json, test_objects.json ๋ง๋ค๊ธฐ
ํ์ต์ ํ ๋ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํด์์ ํ์ต ํจ์์ ์ ๋ ฅํด์ฃผ๋ ค๋ฉด CustomDataset ํด๋์ค ์ ์์ DataLoader ๊ฐ ํ์ํ๋ค.
CustomDataset ์์ ์ด๋ฏธ์ง์ ๋ ์ด๋ธ์ ๋ถ๋ฌ์์ผ ํ๋ค. ์ด๋ ํ์ํ ๋ ์ด๋ธ์ json ํ์ผ ๋ด๋ถ์์ "label" ๊ฐ๊ณผ "boxes" ๋ฟ์ด๋, ํด๋นํ๋ key๋ค๋ง ๊ฐ์ ธ์์ ์๋ก์ด 'train_objects.json' ํ์ผ์ ๋ฆฌ์คํธ๋ก ์ ์ฅํ ๊ฒ์ด๋ค.
๋ํ, ๋ชจ๋ ์ด๋ฏธ์ง์ ์ ๋ ๊ฒฝ๋ก๋ ์๋ก์ด 'train_images.json' ํ์ผ์ ๋ง๋ค์ด ๋ฆฌ์คํธ๋ก ์ ์ฅํ๋ ค๊ณ ํ๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ด๋ฏธ์ง์ ๊ฒฝ๋ก์, ๊ทธ ์ด๋ฏธ์ง์ ๋ํ label&boxes ๊ฐ ์์๋๋ก ์ ์ฅ๋์ด ์ธ๋ฑ์ค๋ก ๋ถ๋ฌ์ฌ ์ ์์ ๊ฒ์ด๋ค.
< train_images.json ๊ณผ train_objects.json ๋ง๋ค๊ธฐ >
์ดํ ๋ชจ๋ ์ฝ๋๋ ๋ ํฌ์งํ ๋ฆฌ์ utils.py ๋ด๋ถ์ ์กด์ฌํ๋ ์ฝ๋์ด๋ค.
1) label_map ์ง์
๋จผ์ label map ์ ์ ์ํด์ผ ํ๋ค. 20๊ฐ์ ์นดํ ๊ณ ๋ฆฌ๋ฅผ 1๋ถํฐ 20๊น์ง์ ๋ ์ด๋ธ๋ก ์ง์ ํ ๊ฒ์ด๋ค.
๋ ์ด๋ธ 0์ ์ฌ์ง ์์ ์๋ฌด๋ฐ ๊ฐ์ฒด๊ฐ ์๋ ๊ฒฝ์ฐ๋ก 'background' ํด๋์ค๋ก ์ง์ ํ ๊ฒ์ด๋ค.
food_labels = ('mango', 'melon', 'strawberry', 'blueberry', 'apple', 'grapefruit',
'plum', 'peach', 'grape', 'cherry', 'yam', 'chestnuts', 'pepper',
'chicory', 'Kohlrabi', 'Paprika', 'Tomato', 'Mushroom', 'Pumpkin',
'Pimento')
label_map = {k: v+1 for v, k in enumerate(food_labels)}
label_map['background'] = 0
print(len(label_map))
rev_label_map = {v: k for k, v in label_map.items()} # inverse mapping
distinct_colors = ['#e6194b', '#3cb44b', '#ffe119', '#0082c8', '#f58231',
'#911eb4', '#46f0f0', '#f032e6', '#d2f53c', '#fabebe',
'#008080', '#000080', '#aa6e28', '#fffac8', '#800000',
'#aaffc3', '#808000', '#ffd8b1', '#e6beff', '#808080', '#FFFFFF']
label_color_map = {k: distinct_colors[i] for i, k in enumerate(label_map.keys())}
print("label map:",label_map) # 'mango':
21
label_map: {
'mango': 1,
'melon': 2,
'strawberry': 3,
'blueberry': 4,
'apple': 5,
'grapefruit': 6,
'plum': 7,
'peach': 8,
'grape': 9,
'cherry': 10,
'yam': 11,
'chestnuts': 12,
'pepper': 13,
'chicory': 14,
'Kohlrabi': 15,
'Paprika': 16,
'Tomato': 17,
'Mushroom': 18,
'Pumpkin': 19,
'Pimento': 20,
'background': 0
}
2) json ํ์ผ์ label๊ณผ boxes ํค์ ๋ํ value ๊ฐ์ ธ์ค๊ธฐ
def parse_annotation(json_path):
with open(json_path, 'r') as json_file:
data = json.load(json_file)
objects = data.get('objects', [])
boxes = []
labels = []
for obj in objects:
label = obj.get('label') + 1
box = obj.get('boxes')
if label is not None and box is not None:
xmin, ymin, xmax, ymax = box
boxes.append([xmin, ymin, xmax, ymax])
labels.append(label)
return {'labels': labels, 'boxes': boxes}
json ํ์ผ ์์๋ ๋ ์ด๋ธ์ด 0~19 ๊น์ง์ ๋งคํ๋๋ก ๋ผ๋ฒจ๋ง๋์ด ์์ผ๋ train_objects.json ์ ๊ฐ์ ธ์ฌ ๋๋ label์ 1์ ๋ํด์ ๊ฐ์ ธ์์ผ ํ๋ค. boxes๋ xmin, ymin, xmax, ymax ๋ก ๊ทธ๋๋ก ๊ฐ์ ธ์จ๋ค.
์ด์ ๊ฐ์ ธ์จ ๊ฐ๋ค์ ์๋ก์ด json ํ์ผ์ write ํด์ ์ ์ฅํ๋ฉด ๋๋ค. ์ด๋ฏธ์ง์ ๊ฒฝ๋ก๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด์ ์ด๋ฏธ์ง์ ๋ชจ๋ ํ์ผ ์ ๋ชฉ์ด ๋ด๊ฒจ์๋ train.txt ์ custom_path (๋ ํฌ์งํ ๋ฆฌ ์ ๋ ๊ฒฝ๋ก) ๋ฅผ ํ์ฉํ ๊ฒ์ด๋ค.
def create_data_lists(custom_path, output_folder):
train_images = list()
train_objects = list()
n_objects = 0
# Training data
for path in [custom_path]:
with open(os.path.join(path, 'train.txt')) as f:
ids = f.read().splitlines()
for id in ids:
objects = parse_annotation(os.path.join(path, 'train/labels', id + '.json'))
if len(objects['boxes']) == 0:
continue
n_objects += len(objects['labels']) # Use objects['labels'] here
train_objects.append(objects)
train_images.append(os.path.join(path, 'train/images', id + '.jpg'))
assert len(train_objects) == len(train_images)
# Save to file
with open(os.path.join(output_folder, 'train_images.json'), 'w') as j:
json.dump(train_images, j)
with open(os.path.join(output_folder, 'train_objects.json'), 'w') as j:
json.dump(train_objects, j)
print('\nThere are %d test images containing a total of %d objects. '
'Files have been saved to %s.' % (
len(train_images), n_objects, os.path.abspath(output_folder)))
print("length of test_objects", len(train_objects))
print("length of test_images", len(train_images))
์ด์ ์ด ํจ์๋ฅผ ์คํํ๊ธฐ ์ํด custom_path ์ train๊ณผ test ํด๋๋ฅผ ํฌํจํ๊ณ ์๋ ํ์ฌ ๋ ํฌ์งํ ๋ฆฌ์ ๊ฒฝ๋ก๋ฅผ ๋ฃ์ด์ฃผ๊ณ , output_folder์๋ json ํ์ผ๋ค์ ์ ์ฅํ ๊ฒฝ๋ก๋ฅผ ๋ฃ์ด์ค๋ค.
from utils import create_data_lists
if __name__ == '__main__':
create_data_lists(custom_path = '/Users/jeongin/PycharmProjects/21class_food_detection',
output_folder='/Users/jeongin/PycharmProjects/21class_food_detection/train/json_files')
test ๋ฐ์ดํฐ์ ๋ํด์๋ ๋์ผํ๊ฒ create_data_list ํจ์๋ฅผ ์ ์ฉ์์ผ ์ฃผ๋ฉด ๋๋ค.