Deep Learning through deep learning

# 1 데이터 전처리 - json 파일로 이미지 라벨링 (Python 편) 본문

ML&DL/데이터 전처리

# 1 데이터 전처리 - json 파일로 이미지 라벨링 (Python 편)

NeuroN 2023. 8. 9. 00:06

이미지관련 딥러닝 모델을 다루면서 데이터 전처리에 대한 필요성을 절실히 느끼게 되었다.

이미지 분류와 관련하여 실용화하기 위해서 혹은 각종 대회에서 요구 및 제공하는 데이터셋을 살펴보면 단순 클래스 분류가 아닌,

라벨링을 통한 Object Detection 모델이 주를 이룬다.

하지만 막상 시작에 나서고 모델링에 앞서 우리는 데이터를 다루는 곳에서부터 어려움을 겪게 된다.

모델은 수많은 오픈소스가 존재해 익숙해지는 방법으로 활용이 가능하나, 데이터 전처리는 데이터의 형태 및 구성에 대해 자세히 알고있어야 하며, 어느정도의 알고리즘 실력을 요구한다.

이미지 영상처리에 대한 연구를 진행하고 있는 입장에서 이미지 데이터셋에 대한 데이터 전처리를 먼저 다루고자 한다.


Introduction

순수 이미지 전처리에 대한 내용은 나중에 다룰 예정이니 여기서는 이미지 데이터가 2차원의 배열로 이용할 수 있다는 정보를 제외하고 그저 이미지 그 자체로 활용할 예정이다.

먼저 라벨링이라는 개념부터 살펴보자.

  • 길게 설명할 필요 없이, 라벨링이라는 의미 그대로 이미지의 특정 영역(부분)에 이름을 지어주는 것이다.
  • 예를 들어, 이미지 데이터 폴더를 살펴보니 개와 관련된 사진들이 존재한다고 생각해보자. 우리는 개라는 동물을 알고리즘을 통해 구분해내기 위해 무엇을 해줘야할까?
  • 개라는 이름(라벨링)을 붙여줘야 한다. 이 과정을 라벨링이라고 하며, 컴퓨터에게 주어진 데이터는 이미지뿐이므로 라벨링을 해주기위해 이미지 특정부분(개가 존재하는 주변 영역)을 사각형 혹은 다각형으로 라벨링해주어 컴퓨터에게 이게 개다! 라고 알려줄 수 있다.
  • 하나의 이미지에 여러개의 라벨링이 가능하며, 기존의 클래스 분류와 다르게 클래스가 하나라고 하더라도 (개 뿐일경우) Object Detection 이 가능하다는 점이다.

json 파일을 살펴보자.

{
  "features": [
    {
      "properties": {
        "object_imcoords": "272.905872669815,536.0581250462839,278.516614117026,526.3755651360165,300.5095811686313,539.119803532387,294.8988397214203,548.8023634426544",
        "object_angle": 1.0456101048582784,
        "image_id": "OBJ01822_PS3_K3_NIA0106.png",
        "ingest_time": "2020-11-13T03:16:19.088934Z",
        "type_name": "container"
      }
    },
    {
      "properties": {
        "object_imcoords": "292.07264853041397,480.4515761666071,297.1340083150182,471.71709431551255,319.85600121396,484.88378045046113,314.79464142935575,493.61826230155566",
        "object_angle": 1.0456101048582784,
        "image_id": "OBJ01822_PS3_K3_NIA0106.png",
        "ingest_time": "2020-11-13T03:16:19.088934Z",
        "type_name": "container"
      }
    },
  • json 파일의 구성은 이미지 데이터셋을 라벨링하는 툴이나, 라벨의 정보에 따라 그 구성이 다르다.
  • 소개에 앞서 데이터의 형태 및 구성에 대해 알아야한다는 것이 이 json 파일 구성에 대한 이해가 필요하기 때문이다.
  • 여기서 소개할 json 파일은 AIFACTORY 측에서 주선하는 대회였던 2차 정유탱크 & 컨테이너 대회의 파일을 사용할 예정이니 참고하길 바란다.
  • 이 대회에서 이미지 속 컨테이너를 인식하는 모델을 구축하기 위한 json 파일을 사용한다.
  • json 파일 구성은 다음과 같다.
    • '{','[' 괄호로 감싸져있고, 참고해야 할것은 마치 배열과 같이 괄호로 감싸질때마다 더 작은 차원으로 간다고 생각하면 된다.
      ( a=[[[1],[2]],[[3],[4]]] 라는 배열이 있다면, 첫번째 괄호는 a[i], 두번째 괄호는 a[i][j], 세번째 괄호는 a[i][j][k] 이런식으로 json파일 탐색이 가능하다. )
    • 첫번째 괄호에는 "feature" 라는 이름과 함께 중괄호가 들어간다. 이 중괄호 안에 나머지 라벨링에 대한 정보가 들어있으니 데이터를 활용하기 위해서는 json["feature"] 와 같이 필수로 적고 들어간다.
    • 두번째 괄호부터는 "properties" 라는 이름과 함께 대괄호가 들어간다. "properties" 안에는 하나의 라벨링에 대한 정보가 들어가있다. 이는 즉, 컨테이너 하나에 대한 라벨링이 들어간다는 말이다.
    • 세번째 괄호에는 "object_imcoords", "object_angle", "image_id", "ingest_time", "type_name" 이라는 라벨링에 대한 정보가 들어있다.
      1. "object_imcoords" 에는 이미지 속 사각형으로 라벨링한 x,y 좌표가 들어있다. 이를 통해 이미지 속에 어느 위치에 컨테이너가 있는지 알 수 있다.
      2. "object_angle" 에는 컨테이너가 이미지 속에서 기울어진 정도를 알 수 있다. 위의 x,y 좌표를 polygon 으로 이어 다각형으로 라벨링하였기에 각도를 이용하진 않았다. (사각형으로 한다면 기울기 각도가 필요할 수도 있겠다 생각했다.)
      3. "image_id" 에는 해당 컨테이너가 어느 이미지에 있는지 이미지 이름에 대한 정보가 나와있다. 컨테이너 위치를 알았다해도 수많은 이미지중에서 어떤 이미지에 해당하는건지 이를 통해 알 수 있다.
      4. "ingest_time" 에는 시간에 대한 정보가 나와있었으나 사용하지는 않았고 필요하지 않아 분석하지 않았다.
      5. "type_name" 에는 그래서 너가 라벨링한게 무엇이냐? 라고 했을 때 컨테이너다! 라고 할 수 있는 이름에 대한 정보가 담겨있다.
    • 이 정보들을 이해했으면 이제 우리는 알고리즘을 프로그래밍해야한다. 파이썬으로 진행할 예정이니 참고하길 바란다.

Image dataset 을 살펴보자.

  • 해당 json 파일에 해당하는 이미지파일이다.
  • 이미지를 대강 살펴보아도 중간부분에 많은 컨테이너가 있음을 알 수 있다.
  • 여기서 생각해야 될 점은, 우리가 직접 라벨링을 해주는 간편한 사이트들이 많은데 굳이 json파일이 필요할까? 라고 생각할 수 있으나 이미지를 살펴보면 알듯이 컨테이너가 너무 조밀하고 작으며 또한 많다.
  • 이제 우리는 json파일을 이용해 이 이미지파일에 컨테이너의 위치에 라벨링을 하여 컴퓨터에게 알려줄 것이다.

Python Coding

import os 
import json 
import cv2
import pandas as pd


json_folder_path = "./label"

png_folder_path = "./image"

output_folder_path = "./out"

json_file_list = os.listdir(json_folder_path)

for json_file_name in json_file_list: 
	if json_file_name.endswith(".json"): # JSON 파일 경로 
	    json_file_path = os.path.join(json_folder_path, json_file_name)

    # JSON 파일 읽기
	    with open(json_file_path, "r") as json_file:
	        json_data = json.load(json_file)
	        features = json_data["features"]
	        image_id = features[0]["properties"]["image_id"]
	        
	        
	        
	    # PNG 파일 경로
	    png_file_name = image_id
	    png_file_path = os.path.join(png_folder_path, png_file_name)

	    # 결과 이미지 저장 경로
	    output_image_path = os.path.join(output_folder_path, "result_" + image_id[:-4] + ".png")

	    # PNG 파일 읽기
	    image = cv2.imread(png_file_path)

	    # 원하는 작업 수행: object_imcoords 값을 순서대로 점을 연결하여 직선 그리기
	    for feature in features:
	    
	        object_imcoords = feature["properties"]["object_imcoords"].split(",")
	        points = [(int(float(object_imcoords[i])), int(float(object_imcoords[i+1]))) for i in range(0, len(object_imcoords), 2)]
	        
	        a = [points[0][0],points[1][0],points[2][0],points[3][0]]
	        b = [points[0][1],points[1][1],points[2][1],points[3][1]]
	        left = min(a)
	        top = min(b)
	        width = max(a) - min(a)
	        height = max(b) - min(b)
            
	        cv2.rectangle(image, (min(a),min(b)), (max(a), max(b)),(0.,255.,255.),1)
	        
	    # 결과 이미지 저장
	    cv2.imwrite(output_image_path, image)

	    print("Saved output image:", output_image_path)

> json 파일을 통해 해당 이미지의 모든 컨테이너를 라벨링하여 새로운 이미지로 저장하는 전체코드이다.

코드를 부분부분 살펴보자.

import os 
import json 
import cv2
import pandas as pd


json_folder_path = "./label"

png_folder_path = "./image"

output_folder_path = "./out"
  • import 불러오는 부분은 없다면 구글링을 통해 설치를 바란다.
  • json_folder_path, png_folder_path, output_folder_path 라는 변수를 할당한다. 이 부분은 위의 json파일을 저장할 경로,  위의 이미지파일을 저장할 경로, 새로 생성한 이미지를 저장한 폴더의 경로이다. (3개의 폴더를 만들어줘야한다.)
json_file_list = os.listdir(json_folder_path)

for json_file_name in json_file_list: 
	if json_file_name.endswith(".json"): # JSON 파일 경로 
	    json_file_path = os.path.join(json_folder_path, json_file_name)

    # JSON 파일 읽기
	    with open(json_file_path, "r") as json_file:
	        json_data = json.load(json_file)
	        features = json_data["features"]
	        image_id = features[0]["properties"]["image_id"]
  • json_file_list 를 선언하여 os를 통해 여러개의 json 파일을 넣은경우 list 형태로 파일 이름을 저장해준다.
  • for 문을 이용하여 json 파일을 하나씩 살펴본다.
  • if 문에서 with 구문까지 해당 json파일 이름에서 json 파일을 불러온 후, json_data 에 파일 내용을 저장한다.
  • 위의 json파일 구문에서 feature는 반드시 쓰이므로 features라고 정의해두고 사용하자.
  • image_id 에는 이미지 경로를 불러오기 위해 features[0]["properties"]["image_id"] 를 통해 첫번째 컨테이너에서 image_id 정보를 가져온다. (나머지 컨테이너도 이미지 경로는 같기때문에 [0] 인덱스인 첫번째 컨테이너에서 가져오면 된다.)
# PNG 파일 경로
png_file_name = image_id
png_file_path = os.path.join(png_folder_path, png_file_name)

# 결과 이미지 저장 경로
output_image_path = os.path.join(output_folder_path, "result_" + image_id[:-4] + ".png")

# PNG 파일 읽기
image = cv2.imread(png_file_path)
  • png_file_path 에서는 json 파일의 image_id 경로에 맞는 이미지 이름을 찾아 이미지파일 이름을 저장한다.
  • output_image_path 에서는 새로 생성할 이미지를 저장할 때, 이름을 원래 이미지 이름으로 저장해준다.
  • cv2.imread 를 통해 이미지를 읽어온다.
# 원하는 작업 수행: object_imcoords 값을 순서대로 점을 연결하여 직선 그리기
for feature in features:
	    
    object_imcoords = feature["properties"]["object_imcoords"].split(",")
    points = [(int(float(object_imcoords[i])), int(float(object_imcoords[i+1]))) for i in range(0, len(object_imcoords), 2)]
	        
    a = [points[0][0],points[1][0],points[2][0],points[3][0]]
    b = [points[0][1],points[1][1],points[2][1],points[3][1]]
    left = min(a)
    top = min(b)
    width = max(a) - min(a)
    height = max(b) - min(b)
    
    cv2.rectangle(image, (min(a),min(b)), (max(a), max(b)),(0.,255.,255.),1)
	        
# 결과 이미지 저장
cv2.imwrite(output_image_path, image)

print("Saved output image:", output_image_path)
  • 이제 위에서 정의한  features 속 json 정보를 이용해 이미지에 라벨링을 한다.
  • object_imcoords 에서는 x,y 좌표값을 ',' 를 기준으로 쪼개어 리스트로 저장한다.
  • points 는 위의 좌표값을 2차원 배열로 재정의하여 이용하기 편하게 만든다. (그냥 바로 이용해도 되어서 굳이 할필요는 없지만 해주었다.)
  • a,b 에 각각 x좌표, y좌표를 따로 담아준다. 이를 통해 x의 최소 최대값, y의 최소 최대값을 구하기 위함이다.
  • cv2.rectangle 을 통해 좌측 상단 x,y 좌표부터 좌측 하단 x,y 좌표까지 지점을 입력해주어 사각형으로 라벨링을 하는데, 선의 색은 초록색으로하며 (0,255,255) 선 굵기는 1로 한다.
  • cv2.imwrite 를 통해 라벨링을 마친 이미지를 output_image_path 라는 결과폴더 경로로 저장해준다.
for i in range(len(points) - 1):
    cv2.line(image, points[i], points[i+1], (0, 255, 0), 1)
# 마지막 점과 첫 번째 점을 연결하여 폐쇄 도형 그리기
cv2.line(image, points[-1], points[0], (0, 255, 0), 1)
  • 위의 결과를 살펴보면 아쉽게도 사각형으로 라벨링이 되어 기울어진 컨테이너를 정확하게 라벨링하지 못한다.
  • 사각형을 라벨링하는 cv2.rectangle() 코드를 위의 코드로 바꾸면 우리가 원하는 컨테이너 형태에 들어맞는 라벨링이 가능하다.

else

이번 글에서는 json 파일을 가지고 이미지에 객체를 라벨링하는 방법을 알아보았다.

이를 통해 라벨링된 데이터를 바로 모델에 학습시킬 수 있지만, 다음 글에서는 이 라벨링 정보를 csv파일로 저장하여 이용할 수 있는 방법에 대해 알아보고자 한다.

참고로 본인의 json 파일 형식과 맞게 코드를 수정하길 바란다. opencv 에서는 사각형 뿐만 아니라 다각형, 원 등도 표현이 가능하니 이를 잘 이용해서 라벨링을 할 수 있다.