Object Detection 과 Segmentation 을 위한 데이터는 많지만.. 그 중에서도 유명하다 할 수 있는 것은
(Annotation 이란? 바운딩박스의 위치나 Object 이름, 즉 정답을 특정 포맷으로 제공한다.)
PASCAL VOC Dataset 을 보자.
Detection 이라면 JPEGImages - Annotations 와 매핑할 수 있다.
데이터를 직접 다운로드 받고, 데이터를 살펴보자. (annotation 파일을 파싱하는 것까지 해보자.)
다운받는 경로는 아까 그곳이다! 다운을 받고, 디렉도리를 설정한 다음 그 안에다가 압축파일을 풀어주자.
# pascal voc 2012 데이터를 다운로드 후 /content/data 디렉토리에 압축 해제
# DOWNLOAD시 약 3분정도 시간 소요. 아래 디렉토리가 잘 동작하지 않을 경우 https://web.archive.org/web/20140815141459/http://pascallin.ecs.soton.ac.uk/challenges/VOC/voc2012/VOCtrainval_11-May-2012.tar
!mkdir ./data
!wget http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar
# 다운받은 압축파일을 /content/data 밑에 풀자.
!tar -xvf VOCtrainval_11-May-2012.tar -C /content/data
.tar 는 밖에다 잘 다운받아졌고, 이를 /content/data 밑에다가 풀어줬다. 드라이브 마운트를 해놓지 않은 상태이므로 드라이브에는 올라가진 않는다.
content/data/ 밑에 위와 같은 이름의 파일들이 잔뜩있다. 구조를 좀 살펴보면
위와 같은데, 이중 지금 사용해볼 것은 Annotations 와 JPEGImages 이다. 5개 정도 확인해보자.
!ls /content/data/VOCdevkit/VOC2012
!ls /content/data/VOCdevkit/VOC2012/JPEGImages | head -n 5
잘 들어간 것을 코드로도 확인할 수 있다.
✅ JPEGImages 디렉토리에 있는 임의의 이미지 보기
디폴트 디렉토리를 루트 파일로 잘 설정해주고, 원하는 jpg 파일의 주소를 os.path.join
함수로 잘 묶은 다음 cv2.imread
로 읽어온다.
cv2.cvtColor
에는 해당 이미지 주소와 cv2.COLOR_BGR2RGB 를 넣는다.
import cv2
import matplotlib.pyplot as plt
import os
%matplotlib inline
# 코랩 버전은 상대 경로를 사용하지 않습니다. /content 디렉토리를 기준으로 절대 경로를 이용합니다.
# default_dir 은 /content/data 로 지정하고 os.path.join()으로 상세 파일/디렉토리를 지정합니다.
default_dir = '/content/data'
img = cv2.imread(os.path.join(default_dir, 'VOCdevkit/VOC2012/JPEGImages/2007_000032.jpg'))
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
print('img shape:', img.shape)
plt.figure(figsize=(8, 8))
plt.imshow(img_rgb)
plt.show()
✅ Annotations 디렉토리에 있는 임의의 annotation 파일 보기
좀 전 이미지 파일 5개를 찍어봤을 때 2007_000032.jpg 이름의 사진이 존재했는데, 해당 Annotation 파일이 존재할까? 어떤 형태일지 !cat 을 통해 보자.
!cat /content/data/VOCdevkit/VOC2012/Annotations/2007_000032.xml
구조는 다음과 같다.
✅ Annotation xml 파일에 있는 요소를 파싱하기
xml 파일 위의 태그들을 쉽게 파싱하여 접근할 수 있다. ElementTree 를 이용한다면!(다행이도 이를 불러오기 위한 lxml 은 이미 코랩에 설치되어 있다.)
먼저 주소를 각각 ANNO_DIR 과 IMAGE_DIR 로 주자.
import os
import random
# 코랩 버전 절대경로 수정.
VOC_ROOT_DIR ="/content/data/VOCdevkit/VOC2012/"
ANNO_DIR = os.path.join(VOC_ROOT_DIR, "Annotations")
IMAGE_DIR = os.path.join(VOC_ROOT_DIR, "JPEGImages")
다음은 그 중에서도 하나의 xml 파일 (아까봤던 비행기2, 사람2 사진) 을 파싱해보자. 주소를 조인하여 인자로 전달하고, ET.parse 메서드를 이용한다! 트리를 반환해준다.
# !pip install lxml
import os
import xml.etree.ElementTree as ET # 이걸 이용해 파싱.
xml_file = os.path.join(ANNO_DIR, '2007_000032.xml')
# XML 파일을 Parsing 하여 Element 생성
tree = ET.parse(xml_file) # 부모-자식 트리가 생성 (root 밑에 object 들이 있는 구조)
root = tree.getroot() # 루트를 가져올 수 있음 (annotation)
# image 관련 정보는 root의 자식으로 존재
image_name = root.find('filename').text # -> 자식 중 filename 이라는 노드를 찾음.
full_image_name = os.path.join(IMAGE_DIR, image_name) # 이미지까지 포함한 절대경로
image_size = root.find('size') # -> 자식 중 size 찾음
image_width = int(image_size.find('width').text) # size 를 찾고 그 자식노드인 width 를 찾아야 함.
image_height = int(image_size.find('height').text) # size 를 찾고 그 자식노드인 height 를 찾아야 함.
# 파일내에 있는 모든 object Element를 찾음.
objects_list = []
for obj in root.findall('object'): # -> 모든 object 를 찾고 반복문 돌림 (지금은 4개)
# object element의 자식 element에서 bndbox를 찾음.
xmlbox = obj.find('bndbox')
# bndbox element의 자식 element에서 xmin,ymin,xmax,ymax를 찾고 이의 값(text)를 추출
x1 = int(xmlbox.find('xmin').text)
y1 = int(xmlbox.find('ymin').text)
x2 = int(xmlbox.find('xmax').text)
y2 = int(xmlbox.find('ymax').text)
bndbox_pos = (x1, y1, x2, y2) # 바운딩박스와
class_name=obj.find('name').text # 이름을 찾고
object_dict={'class_name': class_name, 'bndbox_pos':bndbox_pos} # 딕셔너리를 만들어
objects_list.append(object_dict) # 리스트에 더해줌. (object 하나에 딕셔너리 하나)
print('full_image_name:', full_image_name,'\n', 'image_size:', (image_width, image_height))
for object in objects_list:
print(object)
xml.etree.ElementTree as ET 라는 걸 가져온다. 이제 ET는 파싱하여 트리를 가져올 수 있게 해줄 것이다.
ET.parse 에 인자로 파싱하고 싶은 xml 파일의 주소를 준다.
만들어진 트리에 .getroot 메서드를 쓰면 루트를 가져올 수 있다.
전에 말했듯이 자식 노드를 찾고 값까지 가져올 때 .find('자식노드이름').text
를 쓰면 된다.
좌표는 object 아래에 있다고 했다. .findall
을 하게되면 모든 해당 태깅을 잡게되는데, 모든 오브젝트를 잡으면서 그 오브젝트의 좌표를 가져오자. (리스트에 딕셔너리로 정리해서 더한다.)
✅ Bounding box 시각화
annotation 은 정답이므로 이미 정답인 바운딩박스가 잘 그려져있다. 시각화해보자.
import cv2
import os
import xml.etree.ElementTree as ET
xml_file = os.path.join(ANNO_DIR, '2007_000032.xml')
tree = ET.parse(xml_file)
root = tree.getroot()
image_name = root.find('filename').text
full_image_name = os.path.join(IMAGE_DIR, image_name)
img = cv2.imread(full_image_name)
# opencv의 rectangle()는 인자로 들어온 이미지 배열에 그대로 사각형을 그려주므로 별도의 이미지 배열에 그림 작업 수행.
draw_img = img.copy()
# OpenCV는 RGB가 아니라 BGR이므로 빨간색은 (0, 0, 255)
green_color=(0, 255, 0)
red_color=(0, 0, 255)
blue_color=(255, 0, 0)
purple_color=(128, 0, 127)
# 파일내에 있는 모든 object Element를 찾음.
objects_list = []
for obj in root.findall('object'):
xmlbox = obj.find('bndbox')
left = int(xmlbox.find('xmin').text)
top = int(xmlbox.find('ymin').text)
right = int(xmlbox.find('xmax').text)
bottom = int(xmlbox.find('ymax').text)
class_name=obj.find('name').text
# draw_img 배열의 좌상단 우하단 좌표에 녹색으로 box 표시
# 네모 그리는 인자 (사진, 좌상단, 우하단, 색, thickness)
cv2.rectangle(draw_img, (left, top), (right, bottom), color=green_color, thickness=1)
# draw_img 배열의 좌상단 좌표에 빨간색으로 클래스명 표시
# text 삽입 인자 (사진, 쓸 텍스트, 텍스트 위치, 폰트.. )
cv2.putText(draw_img, class_name, (left, top - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, purple_color, thickness=1)
img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(10, 10))
plt.imshow(img_rgb)
cv.rectangle
은 네모그리는 인자이고, cv.putText
는 텍스트 삽입인자이다. 시험삼아 블루컬러와 퍼플컬러 (BGR로)를 만들고 인자로 주니까 진짜 보라색으로 떴다 ㅎㅎ.