창의설계프로젝트를 진행하면서 딥러닝 학습을 위해 구축 데이터의 전처리가 필요했다. 구축 데이터는 Aihub의 "공사장 안전장비 인식 이미지" 데이터이고 원본 데이터 이미지파일과 라벨링데이터인 json 파일이 존재했다.
우리에게 필요한 데이터인 안전보호구와 이상행동과 관련된 데이터를 추려야 했다. 팀원들이 일일이 직접 처리하는 것 보다는 코드를 이용해 자동으로 하는 것이 효율적이라 생각해서 파인썬을 이용해 만들어보기로 했다.
목차
0. 아이디어
1. json 파일 접근
2. json 파일 읽어들이기
3. 파일 지우기
4. 진행상황 표시하기
5. 마무리
아이디어
구축데이터의 파일들이 라벨링 데이터에 클래스를 통해 구별되어 있었다. 따라서 해당 클래스를 찾아 필요한 클래스만 추리고 필요한 클래스를 포함하지 않는 데이터들은 삭제시키도록 하여 해결할 수 있을 것이라 생각했다.
json 파일 접근
현재 구축데이터(샘플)은 다음과 같은 폴더 구조를 가지고 있다.
공사장현장안전장비인식
ㄴ라벨링데이터
ㄴ 1.공동주택
ㄴ 지역1
ㄴ 현장사진1.json
ㄴ 지역2
ㄴ ...
ㄴ 2.공연장
ㄴ ...
ㄴ원천데이터
ㄴ (라벨링데이터와 동일)
아이디어를 수행하기 위해서 루트폴더에서 가장 아래에 존재하는 json파일까지 접근해야 했다. 구글링을 통해 os 모듈을 이용하여 현재 폴더의 하위 목록들을 불러 들이고 접근하는 방법을 찾았다.
def search_file(dir):
files = os.listdir(dir)
for data in files:
if os.path.isdir(dir + r"/"+data) == True:
search_file(dir+r"/"+data)
else:
print(dir+r"/"+data)
처음 받은 경로의 하위목록을 os.listdir(dir)로 확인하고 files 변수에 저장한다. 이후 for 반복문을 통해 가장 아래까지 이동하게 되는데 os.path.isdir() 메서드를 통해 현재 경로가 폴더인지 확인하고 폴더라면 재귀호출을 하고 아니라면 파일이기 때문에 작업을 수행하도록 하면된다.
json파일 읽어들이기
json 파일까지 접근했기 때문에 이제 읽어들여 class를 확인하면 된다.
def read_json(path):
with open(path, 'r', encoding="utf-8") as file:
data = json.load(file)
print(data)
read_json 함수를 만들고 json 파일을 읽어들이기 위해 json 모듈이 필요했다. json.load로 특정 경로의 json파일을 읽어들여 data에 저장하도록 했다. 이때 json파일이 "uft-8"로 인코딩 되어있었기 때문에 open시 'encoding'옵션을 "utf-8"로 설정해주었다.
json 파일들은 다음과 같은 구조를 가지고 있다.
{
"image": {
"date": "20201014",
"path": "S2-N0002M00076",
"filename": "S2-N1802M02772.jpg",
"copyrighter": "회사명",
"location": "18",
"H_DPI": 96,
"V_DPI": 96,
"bit": 24,
"resolution": [
1920,
1080
]
},
"annotations": [
{
"data ID": "S2",
"middle classification": "06",
"box": [
1381,
5,
1920,
584
],
"class": "31",
"flags": ","
},
{...},
...
]
}
여기서 우리가 필요한 데이터는 "annotations"배열안 객체의 "class" 값이다.
del_list = []
class_keys = ['01', '02', '03', '04', '05', '06', '07', '08']
def read_json(path):
with open(path, 'r', encoding="utf-8") as file:
data = json.load(file)
len_data = len(data["annotations"])
flag = False
for idx in range(len_data):
if any(class_key in data["annotations"][idx]["class"] for class_key in class_keys):
flag = True
break
if flag == False:
del_path = path.rstrip('.json')
del_list.append(del_path)
"annotations"의 길이를 받고 반복문에 사용한다. 우리에게 필요한 클래스가 하나라도 포함되어 있다면 더 이상 반복문을 진행할 필요가 없기 때문에 flag 변수를 사용하여 빠져나와 필요한 클래스를 포함하는지 확인했다. 이때 필요한 클래스를 따로 class_key 배열에 저장하고 any() 메서드를 통해 포함되어있는지 확인한다.
이후 만약 flag가 0이라면 즉, 필요한 클래스를 포함하지 않고 있다면 삭제해야하기 때문에 파일명 뒤의 확장자를 지우고 해당 값을 del_list 배열에 집어 넣었다.
파일 지우기
이제 필요없는 파일들을 파악했기 때문에 지우는 과정이 필요하다. 지우는 것은 os모듈을 사용하면 된다.
def del_file(path):
#label
if os.path.exists(path + ".json"):
os.remove(path + ".json")
#source
if os.path.exists(source_path + r"/" + path.lstrip(label_path) + ".jpg"):
os.remove(source_path + r"/" + path.lstrip(label_path) + ".jpg")
del_file 함수를 만들고 지우려한 파일들의 경로를 받는다. 이때 경로는 뒤의 확장자명이 빠져있다. 따라서 label데이터와 원본데이터를 삭제할 때 확장자를 넣어 지우도록 하였다.
처음 경로를 저장할 때 라벨링데이터를 기준으로 하였기 때문에 label데이터를 지울 때 뒤에 확장자명만 지워주면 되지만 원본데이터는 가장 상위폴더명이 다르기때문에 문자열메소드 lstrip을 사용하여 앞 경로를 지워주고 원본데이터 경로를 추가하여 지울 수 있도록 하였다.
진행상황 표시하기
지금은 샘플데이터라 대략 2000개의 데이터가 존재하지만 실제 구축데이터는 대략 203만개가 존재하기 때문에 진행상황을 표시하는 것이 사용자에게 편할 것이라 생각했다.
찾아보니 프로그레스바 모듈인 tqdm을 사용하여 만들 수 있었다.
label_path = "./라벨링데이터"
source_path = "./원천데이터"
def main():
top_path = label_path
search_file(top_path)
for path in tqdm(del_list, unit="항목", desc="전처리 중"):
del_file(path)
if __name__ == "__main__":
main()
tqdm 메서드를 반복문을 통해 호출하여 인자로 반복하여 호출될 리스트나 반복횟수를 넣어주면 된다. 이때 프로그레스바가 표시될 때 초당 처리되는양을 표시할때 "항목"으로 표시하기 위해 unit속성을 설정해주었고, 가장 앞 표시될 말을 desc 속성을 통해 "전처리 중"으로 표시하였다.
마무리
from tqdm import tqdm
from encodings import utf_8
import sys
import os
import os.path
import time
import json
label_path = "./라벨링데이터"
source_path = "./원천데이터"
del_list = []
class_keys = ['01', '02', '03', '04', '05', '06', '07', '08']
def read_json(path):
with open(path, 'r', encoding="utf-8") as file:
data = json.load(file)
len_data = len(data["annotations"])
flag = False
for idx in range(len_data):
if any(class_key in data["annotations"][idx]["class"] for class_key in class_keys):
flag = True
break
if flag == False:
del_path = path.rstrip('.json')
del_list.append(del_path)
def search_file(dir):
files = os.listdir(dir)
for data in files:
if os.path.isdir(dir + r"/"+data) == True:
search_file(dir+r"/"+data)
else:
read_json(dir+r"/"+data)
def del_file(path):
#label
if os.path.exists(path + ".json"):
os.remove(path + ".json")
#source
if os.path.exists(source_path + r"/" + path.lstrip(label_path) + ".jpg"):
os.remove(source_path + r"/" + path.lstrip(label_path) + ".jpg")
def main():
top_path = label_path
search_file(top_path)
for path in tqdm(del_list, unit="항목", desc="전처리 중"):
del_file(path)
if __name__ == "__main__":
main()
위에서 만들었던 코드들을 다음과 같이 만들어 사용하였다. 샘플데이터에는 무리없이 동작하였다.
실제 구축데이터에 적용하기에는 부족한 점이 많아 보인다. 특히 실제 데이터에는 .jpg파일 외에 이상행동클래스는 영상으로 되어있기 때문에 해당 부분을 예외처리 해주어야하고 또한 현재 코드는 모든 폴더의 가장 아래까지 접근하여 파일을 처리하고 그 진행상황을 표시한다. 하지만 실제 데이터에서는 한 폴더당 데이터 수가 굉장히 많고 상위 폴더아래 8개 폴더가 존재하므로 상위폴더와 8개폴더 기준으로 진행상황을 보여주는 것이 좋아보인다.
처음으로 본인이 작성한 코드를 블로그 형식으로 써보았다. 최대한 문제를 해결하기 위해서 한 사고의 흐름대로 글을 보여주고 싶어 그렇게 작성했다. 작성하다보니 코드를 만들 때 미쳐 보지 못한 부분도 보이고 다른 코드 구조를 해도 되겠다는 생각이 들기도 했다.
현재는 창의설계프로젝트를 진행중에 있어 전공인 웹프로그래밍에 대해 쓰기 힘든 상황이지만 프로젝트 진행상황에 따라 웹 프로그래밍에 관한 내용도 계속해서 써나갈 계획이다.