AI hub에서 받은 데이터 셋 중 가장 첫번째 사진이다. 우선 AI hub에서 제공하는 Dataset의 XML은 자체 양식대로 되어 있는 듯 했는데, 처음에 Roboflow에 올려서 전부 변환하려다가 좀 형식이 다르기도 하고 불안해서 직접 코딩해서 포맷을 YoloV5 라벨링용으로 바꾸기로 했다. 이 사진 파일에 대한 Annotation은 다음과 같았다.
1
2
3
4
5
6
7
8
9
10
|
<image id="0" name="MP_SEL_000001.jpg" width="1920" height="1080">
<box label="tree_trunk" occluded="0" xtl="1272.39" ytl="451.90" xbr="1331.40" ybr="670.79" z_order="0">
</box>
<box label="movable_signage" occluded="1" xtl="1082.78" ytl="356.68" xbr="1147.89" ybr="491.46" z_order="0">
</box>
<box label="tree_trunk" occluded="0" xtl="1157.03" ytl="277.90" xbr="1210.30" ybr="565.71" z_order="0">
</box>
<box label="tree_trunk" occluded="0" xtl="1094.21" ytl="331.56" xbr="1131.90" ybr="493.75" z_order="0">
</box>
...
|
cs |
image 태그 안에 attribute로 사진 파일 이름, 가로폭과 세로폭이 저장되어 있고 자식 태그로 Bounding box( <box> ) 가 있음을 확인 할 수 있었다. 바운딩 박스 태그에는 가려짐 여부(occluded = "1" 일시 가려짐), 좌상단 우하단 좌표가 들어가 있고 z_order까지 있었는데 이건 나중에 활용하도록 하고(단순 Classification으로 활용 할 수 있을 듯) 우선 Yolo 포맷에 맞추기 위해 코드를 작성했다. 최종적으로 2480개의 폴더의 xml파일을 전부 불러와서(데이터 구축 활용에는 2520개가 있다 그러던데 압축 풀고나니까 40개가 빈다...) 하나하나 해야겠지만, 우선 클래스 넘버나 좌표등이 꼬이면 골치아프기에 파일 한개를 불러와서 테스트 해봤다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
import xml.etree.ElementTree as ET
import os
tree = ET.parse(xml 파일 주소)
root = tree.getroot()
Class_index = {'bicycle' : 0
,'bus':1
,'car':2
,'carrier':3
,'cat':4
,'dog':5
,'motorcycle':6
,'movable_signage':7
,'person':8
,'scooter':9
,'stroller':10
,'truck':11
,'wheelchair':12
,'barricade':13
,'bench':14
,'bollard':15
,'chair':16
,'fire_hydrant':17
,'kiosk':18
,'parking_meter':19
,'pole':20
,'potted_plant':21
,'power_controller':22
,'stop':23
,'table':24
,'traffic_light':25
,'traffic_light_controller':26
,'traffic_sign':27
,'tree_trunk':28} #Class Annotation을 위한 {클래스 이름 : 라벨 넘버} 1:1 대응 Dictionary
def normalization(center_x, center_y,width,height,img_width,img_height):
center_x = round(center_x / img_width, 6)
center_y = round(center_y / img_height, 6)
width = round(width / img_width, 6)
height = round(height / img_height, 6)
return [center_x,center_y,width,height]
for image in root.findall('image'):
#Image 이름 불러오기
image_name = image.attrib['name']
print("파일 이름 : ", image.attrib['name'])
# Image size 정보(기본 1920*1080) 이지만 Just in case
image_width = int(image.attrib['width']) # 1920(픽셀)
image_height = int(image.attrib['height']) # 1080(픽셀)
for box in image.findall('box'):
center_x = round((float(box.attrib['xtl']) + float(box.attrib['xbr'])) / 2 , 6)
center_y = round((float(box.attrib['ytl']) + float(box.attrib['ybr'])) / 2 , 6)
width = round((float(box.attrib['xbr']) - float(box.attrib['xtl'])) , 6)
height = round((float(box.attrib['ybr']) - float(box.attrib['ytl'])) , 6)
center_x, center_y, width, height = normalization(center_x,center_y,width,height,image_width,image_height)
print("{} {} {} {} {}".format(str(Class_index[box.attrib['label']]), center_x,center_y,width,height))
|
cs |
결과는 성공적!
Yolo는 라벨링 형식이 [Class_no, x_center, y_center, width, height] 이기에 기존 형식을 바꿔줄 필요도 있었다. 여기에 더해 각 단위가 픽셀이 아니라 전체 이미지 사이즈를 Normalize한 값을 쓰기에 이미지 사이즈(1920*1080 고정이지만 혹시 몰라 태그에서 가져오는걸로 처리함)로 나누어 1보다 작게 나온다면 성공이다. 또 신경쓴 부분은 x,y축이 좌상단이 원점이고 x축은 오른쪽, y축은 아래쪽으로 가야 양수이다! 따라서 AI hub에서 제공한 Annotation 값이 이와 동일한지 확인해봤는데 그림 그려서 좌표 찍어보니까 이건 똑같다. y축이 아래에서 위로 가는 방향이 양수였다면 아마 뒤집어야 했을 것 같은데 그런 수고로움은 덜었다.
추가로 공식 Git을 뒤져가며 폐색된 객체(Occluded Bounding box) 에 대해서도 라벨링을 해야하는지 확인했다. 제공된 데이터셋은 바운딩 박스가 가려져 있는지 아닌지 occluded 어트리뷰트를 이용해 알려주고 있는데, 이를 라벨링시에도 추가해서 학습을 시켜야 하는지 고민이 되었다. 공식 Git에 답변이 시원한게 없어서 ChatGPT한테 물어봤다.
결론은 사용해도 될듯! 내가 일일히 라벨링 할것도 아니니까..ㅋㅋ 아무튼 코드도 정상 작동하는걸 확인했으니 images, test, validate 폴더 나누어서 분배하고(7:1:2 정도의 비율 생각중) 학습을 돌려봐야겠다. 예전에 학습시킬때 이미지는 있는데 대응되는 txt가 없을 경우 사진안에 Bounding Box가 한개도 없다고 학습한다는 답변을 본 적 있다. 지금 경우에 코랩에서 구글드라이브에 압축 푸는 과정에서 이미지가 좀 사라지기도 했으니, 크게 상관 없을 듯! 예전에 실험했을땐 이미지와 라벨.txt가 1:1 대응해야 직접 학습 셋에 넣는걸 봤었는데(Train 시작할때 "이미지 ~~개 인식") 확실하지 않아서 다음에 꼭 사진 몇장으로 실험해보고 전체 학습을 돌리려고 한다.
끗!
+ 전체 코드 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
import xml.etree.ElementTree as ET
import os
import glob
Class_index = {'bicycle' : 0
,'bus':1
,'car':2
,'carrier':3
,'cat':4
,'dog':5
,'motorcycle':6
,'movable_signage':7
,'person':8
,'scooter':9
,'stroller':10
,'truck':11
,'wheelchair':12
,'barricade':13
,'bench':14
,'bollard':15
,'chair':16
,'fire_hydrant':17
,'kiosk':18
,'parking_meter':19
,'pole':20
,'potted_plant':21
,'power_controller':22
,'stop':23
,'table':24
,'traffic_light':25
,'traffic_light_controller':26
,'traffic_sign':27
,'tree_trunk':28} #Class Annotation을 위한 {클래스 이름 : 라벨 넘버} 1:1 대응 Dictionary
def normalization(center_x, center_y,width,height,img_width,img_height):
center_x = round(center_x / img_width, 6)
center_y = round(center_y / img_height, 6)
width = round(width / img_width, 6)
height = round(height / img_height, 6)
return [center_x,center_y,width,height]
import glob
import time
empty_list = []
folder_number = 2480 #폴더 갯수, 2480개지만 안전을 위해 저장하고 보관할떈 0으로 적음
for i in range(folder_number):
target = "H:\내 드라이브\Capstone1\Dataset_zip_original\Bbox_{}\*.xml".format(str(i + 1).zfill(4))
xml_list = glob.glob(target)
print("폴더 : Bbox_{}".format(str(i + 1).zfill(4)), xml_list)
# if len(xml_list) == 0 : empty_list.append(str(i+1).zfill(4))
if len(xml_list) != 0:
tree = ET.parse(xml_list[0]) #xml_list[0] 에는 xml 주소가 적혀있음.
root = tree.getroot()
# folder = "H:/내 드라이브/Capstone1/labels/Bbox_{}".format(str(i+1).zfill(4))
# os.mkdir(folder) 무조건 주석 처리 할 것!!!!!!!!!!!!!!! 폴더 생성하는 코드
# time.sleep(1)
for image in root.findall('image'):
#Image 이름 불러오기
image_fullname = image.attrib['name']
image_name, ext = os.path.splitext(image_fullname)
print("파일 이름 : ", image.attrib['name'])
# Image size 정보(기본 1920*1080) 이지만 Just in case
image_width = int(image.attrib['width']) # 1920(픽셀)
image_height = int(image.attrib['height']) # 1080(픽셀)
#label 위치할 주소 받아오기
label_context=""
for box in image.findall('box'): # 라벨 내용 한줄 한줄 작성하는 코드
center_x = round((float(box.attrib['xtl']) + float(box.attrib['xbr'])) / 2 , 6)
center_y = round((float(box.attrib['ytl']) + float(box.attrib['ybr'])) / 2 , 6)
width = round((float(box.attrib['xbr']) - float(box.attrib['xtl'])) , 6)
height = round((float(box.attrib['ybr']) - float(box.attrib['ytl'])) , 6)
center_x, center_y, width, height = normalization(center_x,center_y,width,height,image_width,image_height)
label_context += "{} {} {} {} {}\n".format(str(Class_index[box.attrib['label']]), center_x,center_y,width,height)
print("label : {}\n{}".format(image_name.rstrip('jpg')+'.txt',label_context))
label_name = "{}.txt".format(image_name)
label_location = "H:/내 드라이브/Capstone1/labels/Bbox_{}/{}".format(str(i + 1).zfill(4),label_name)
file = open(label_location,'w')
file.write(label_context)
file.close()
else:
empty_list.append(str(i + 1).zfill(4))
print("empty folder : ",empty_list)
|
cs |
'프로젝트 : CV를 활용한 시각장애인용 어플리케이션' 카테고리의 다른 글
Part3 : 모델 학습 & Google Cloud Platform 환경 이전 (0) | 2023.05.10 |
---|---|
Part 1 : 캡스톤디자인(1) 과목 프로젝트 기획 & Colab을 활용하여 Dataset을 Google drive에 옮기기 (2) | 2023.03.12 |