Published on

코끼리공장 x 코어닷투데이

Authors
  • avatar
    Name
    Dongju Lee
    Twitter

장난감 인식 AI 개발 배경

코끼리공장공정

자원 순환 기업인 코끼리 공장에서는 사용하지 않는 장난감을 기부받고 재사용 가능한 장난감은 나눠주고, 재사용이 불가능한 장난감은 플라스틱 소재를 재활용해서 자원화합니다.

해당 작업 과정의 수거, 분류, 세척, 수리 과정 등은 시니어 근로자가 맡아서 하고있습니다.

코끼리 공장의 좋은 취지에 맞춰 전국에서 많은 사람들이 장난감을 기부하고 있는데, 사람의 손으로 일일이 장난감을 분류하는 일에 시간이 많이 소요되어 기부의 속도를 따라잡지 못해 장난감이 창고에 쌓여가고 있는 상황입니다.

코끼리공장창고

시간이 많이 소요되는 분류 작업을 자동화하여 노동력을 장난감의 세척과 수리잡업에 집중할 수 있도록 하는것이 이번 과제의 목표였습니다.

장난감이 재활용되는 프로세스를 자세하게 살펴보면 다음과 같습니다.

  1. 기부받은 택배를 뜯어 장난감을 꺼냅니다.
  2. 해당 장난감이 재사용 가능한지 판단합니다.
    • 재사용 가능한 장난감은 수리, 세척 과정을 거칩니다.
    • 재사용 불가능한 장난감은 폐장난감으로 분류합니다.
  3. 수리 및 세척과정을 마친 장난감은 어린이들에게 나눠줍니다.
  4. 폐장난감은 플라스틱 소재를 추출해서 자원화합니다.

문제정의

택배상자를 열어 장난감을 확인하는 과정에서 시간이 많이 소요되다 보니, 프로세스 뒷 단의 장난감을 나눠주고 폐플라스틱을 자원화하는 공정이 원할하게 진행되지 않고 있습니다.

장난감 분류를 자동화해서 병목을 해결한다면, 수리·세척이 필요하지 않은 폐장난감은 앞의 작업과 별개로 빠르게 처리해서 자원화할 수 있습니다.

해당 문제를 해결하기 위해서 이미지 인식 기술을 활용한 장난감 인식 과제를 진행했습니다.

현황파악

현재 코끼리공장의 경우 폐장난감의 플라스틱 소재를 파쇄하여 자원화하는 작업을 제외하고는 모두 사람의 인력으로 작업을 하다보니 저장된 데이터가 없는 상황입니다.

그래서 데이터 수집과 동시에 저장을 고려하는 설계가 필요했습니다.

또 실제 현장에 대해 파악하기 위해 작업자를 대상으로 인터뷰를 진행했습니다.

인터뷰 진행을 통해 파악한 사실은 다음과 같습니다.

  • 장난감을 분류하는 기준으로 구성품 확인, 작동 확인, 외관 확인 등이 있다.
  • 장난감 분류를 위해 확인하는데 오래걸리는 경우 10분 정도 소요된다.
  • velcro가 붙은 장난감의 경우 스티커 제거제로 제거하는데 오래걸려서 오염된 장난감으로 분류한다. 해당 인터뷰 내용을 바탕으로 AI를 어떻게 도입할지 고민했습니다.

설계

코끼리공장설계

자동화 시스템 설계는 위의 사진과 같습니다.

  1. 컨베이어 벨트 위로 장난감 통과
  2. 카메라를 통해 장난감 이미지를 수집
  3. 수집한 장난감 정보를 AI가 추론 해당 결과로 장난감을 분류하는 솔루션을 설계했습니다.

위의 과정에서 AI가 장난감 객체를 인식해서 구성품을 알려주고, 오염이나 파손된 장난감을 인식해서 자동으로 분류하는 솔루션입니다.

모델선정

장난감 객체 인식의 경우, pytorch로 PyramidNet을 구현해서 이미지 분류를 하는 방법과 YOLOv8의 Object Detection을 사용하는 방법 등이 있었고

장난감 오염 및 파손 인식의 경우, pytorch로 U-Net을 구현해서 오염 및 파손에 대해 Sementic Segmentation하는 것과 YOLOv8 Object Segmentation을 사용하는 방법이 있습니다.

해당 과제에서는 기존에 데이터가 없는 상황인 점과 컨베이어 벨트를 지나는 장난감을 실시간으로 빠르게 인식해야 한다는 점에서 YOLOv8 모델을 사용하는 것으로 결정했습니다.

코끼리공장욜로

데이터

YOLO 모델을 학습시키기 위해 수집할 데이터를 정의하고 정의한 데이터를 수집하는 과정을 거쳤습니다.

데이터 정의

장난감 객체 인식의 경우, 장난감의 구성품을 확인하고 모든 구성품이 존재하는지 파악하기 위해서 각 장난감마다 이름을 지정해서 학습했습니다.

코끼리공장장난감샘플

오염 및 파손에 대한 장난감의 경우, 로봇 장난감에서 팔 하나가 없거나 다리가 부서진 경우, 자동차 장난감의 바퀴가 없거나 창문이 깨진 경우 등 많은 경우의 수가 존재합니다. 많은 경우의 수에 대해 데이터를 수집하고 학습 시키는 것에 제한이 있었기 때문에 문제의 범위를 더욱 좁히게 되었습니다.

코끼리공장풍경

오염 및 파손된 폐장난감에 대해서 범위를 좁혀 정의내리고자, 폐장난감을 모아둔 공장에 방문해서 뚜렷한 특징이 있는 장난감을 탐색했습니다. 직접 장난감을 살펴보고, 인터뷰 내용을 조합해서 velcro가 붙은 장난감을 오염 장난감으로 정의하고 건전지 덮개에 이상이 있는 장난감을 파손 장난감으로 정의했습니다.

코끼리공장장난감샘플2

데이터 수집

Object Detection

학습에 사용할 장난감을 선택해서 사진을 촬영하고 class name 지정을 위해서 구글 이미지 검색 기능을 활용해 해당 장난감의 이름을 지정했습니다.

약 100개의 장난감을 스마트폰 카메라로 촬영했습니다.

촬영할 때 장난감이 컨베이어 벨트를 지날 때 정확하게 세워져 지나가는 것을 전재로 아랫면을 제외한 면을 촬영했습니다.

장난감 더미에서 10개의 장난감을 고르고 촬영하는데 약 10분~12분 정도 소요됐습니다.

코끼리공장사진샘플 코끼리공장구글이미지검색 코끼리공장폴더예시

Sementic Segmentation

오염 및 파손 장난감에 대해서는 velcro가 붙은 장난감와 건전지 덮개에 문제가 있는 장난감으로 한정했기 때문에 class name을 'velcro'화 'no_battery_cover'로 지정했습니다.

해당 장난감은 검은 배경에 장난감의 각도를 변경하며 CZUR Aura 스캐너를 사용해서 촬영했습니다.

오염 장난감 약 100개, 파손 장난감 약 100개에 대해서 장난감 1개에 약 12초에 걸쳐서 촬영했습니다.

코끼리공장데이터예시

데이터 레이블링

데이터 레이블링 도구로 Labelimg, Anylabeling, Roboflow, CVAT 등의 후보가 있었습니다. 이 중에서 데이터 전처리와 후처리 작업, Smart polygon(자동 레이블링)등을 지원하는 Roboflow를 사용해서 데이터를 레이블링했습니다.

Roboflow 사용법

해당 과정을 진행하면서 바운딩 박스, 폴리곤을 지정할 때 기준이 명확하지 않아서 작업을 수정한 적이 있습니다. 데이터 레이블링을 하기전에 명확한 annotation 기준을 정하는 것이 중요합니다.

모델 학습

yolo train 해당 링크에서 coco 데이터셋을 사용해서 yolo 모델을 학습하는 예제를 볼 수 있습니다.

# YOLO train 사용예제
from ultralytics import YOLO

# Load a model
model = YOLO('yolov8n.yaml')  # build a new model from YAML
model = YOLO('yolov8n.pt')  # load a pretrained model (recommended for training)
model = YOLO('yolov8n.yaml').load('yolov8n.pt')  # build from YAML and transfer weights

# Train the model
results = model.train(data='coco128.yaml', epochs=100, imgsz=640)

Custom Data 학습

YOLO 모델에 개인의 데이터를 학습하기 위해서 yaml 파일을 수정하고 해당 파일의 경로에 맞춰 폴더를 구성해야합니다.

Roboflow 플랫폼에서 레이블링을 진행하고 yolov8 형식으로 데이터셋을 추출한 뒤 압축을 해제하면 아래의 형식으로 폴더가 생성됩니다.

  • 데이터셋 폴더 구조
.
├── train
│   ├── images
│   └── labels
└── valid
    ├── images
    └── labels

images 폴더에는 이미지를, labels 폴더에는 레이블링된 좌표가 적힌 txt 파일을 위치 시킵니다.

학습 시킬 이미지와 label의 확장자를 제외한 파일명이 같아야합니다.

  • yaml 파일 형식
train: ../train/images
val: ../valid/images
test: ../test/images

nc: 1
names: ['class name']

roboflow:
  workspace: workspace name
  project: project name
  version: 3
  license: Private
  url: roboflow project url
  • Roboflow를 사용하지 않는 경우 yaml 파일 생성
import yaml

data = {
    'train':'/yolov8/datasets/train/images/',
    'val':'/yolov8/datasets/validation/images/',
    'test':'/yolov8/datasets/test/images/',
    'names':['class_name_1','class_name_2','class_name_3'],
    'nc':3
}

# yaml 파일 생성
with open('./datasets/data.yaml','w') as f:
    yaml.dump(data, f)

# yaml 파일 불러오기
with open('./datasets/data.yaml','r') as f:
    yaml = yaml.safe_load(f)
    display(yaml)

Object Detection

  • 학습 코드
import os
from ultralytics import YOLO

os.environ['NUMEXPR_NUM_THREADS'] = '8'
model = YOLO('yolov8n.pt')
model.train(data='datasets/data.yaml', epochs=200, workers=0)
  • 결과 확인 코드
import os
import cv2
from glob import glob
from ultralytics import YOLO

from PIL import Image
import matplotlib.pyplot as plt

os.environ['NUMEXPR_NUM_THREADS'] = '8'

# 테스트 이미지 경로
f = glob('./datasets/test/images/*')
# 테스트할 모델
model = YOLO('./runs/detect/train44/weights/best.pt')
results = model(f)

for result in results:
    img = cv2.cvtColor(result.plot(), cv2.COLOR_BGR2RGB)
    plt.imshow(img)
    plt.show()

  • 결과 코끼리공장영상예시 코끼리공장폴더예시1 코끼리공장폴더예시2

Sementic Segmentation

  • 학습코드
# jupyter notebook에서 학습했습니다.
!yolo task=segment mode=train model=yolov8m-seg.pt data=./datasets/data.yaml epochs=300 imgsz=640 workers=0
  • 결과 확인
from IPython import display
from IPython.display import display, Image

Image(filename=f'./runs/segment/train/confusion_matrix.png', width=600)
Image(filename=f'./runs/segment/train/results.png', width=600)
Image(filename=f'./runs/segment/train/val_batch0_pred.jpg', width=600)

겪을 수 있는 에러

  • Ultralytics/settings.yaml 파일에서 datasets의 경로가 잘못된 경우 에러가 발생할 수 있습니다.
  • ultralytics 버전이 학습 때와 model을 load할 때 다른경우 에러가 발생할 수 있습니다.
  • 모델을 학습할 때 컴퓨터의 성능에 따라 worker의 개수, NUMEXPR_NUM_THREADS를 조절해야 합니다.

모델 평가

mask IoU 구하기

클래스 분류 정확도 구하기

실제 결과 값

위에서 생성한 모델을 평가하는 함수로 출력한 값을 가지고 dataframe으로 생성했습니다.

코끼리공장폴더예시

실시간 객체 탐지

Nvidia 컴퓨터와 웹캠을 사용해서 학습한 모델로 실시간 객체 탐지를 진행했습니다.

Jetson xavier 세팅

유튜브를 참고했습니다.

  • object detection 모델
import cv2
import torch
import numpy as np
from time import time
import supervision as sv
from ultralytics import YOLO
from supervision import Detections, BoxAnnotator


class ObjectDetection:

    def __init__(self, capture_index):

        self.capture_index = capture_index

        self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
        print("Using Device: ", self.device)

        self.model = self.load_model()

        self.CLASS_NAMES_DICT = self.model.model.names

        self.box_annotator = BoxAnnotator(color=sv.ColorPalette.default(),thickness=3, text_thickness=3, text_scale=1.5)


    def load_model(self):

        model = YOLO("best.pt")  # load a pretrained YOLOv8n model
        # model.fuse()

        return model


    def predict(self, frame):

        results = self.model(frame)

        return results


    def plot_bboxes(self, results, frame):

        xyxys = []
        confidences = []
        class_ids = []

        # Setup detections for visualization
        detections = Detections(
                    xyxy=results[0].boxes.xyxy.cpu().numpy(),
                    confidence=results[0].boxes.conf.cpu().numpy(),
                    class_id=results[0].boxes.cls.cpu().numpy().astype(int),
                    )

        print("detections : ",detections)

        # Format custom labels
        self.labels = [f"{self.CLASS_NAMES_DICT[class_id]} {confidence:0.2f}"
        for _, mask, confidence, class_id, tracker_id
        in detections]

        # Annotate and display frame
        frame = self.box_annotator.annotate(scene=frame, detections=detections, labels=self.labels)

        return frame

    def __call__(self):

        cap = cv2.VideoCapture(self.capture_index)
        assert cap.isOpened()
        cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
        cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)

        while True:

            start_time = time()

            ret, frame = cap.read()
            assert ret

            results = self.predict(frame)
            frame = self.plot_bboxes(results, frame)

            end_time = time()
            fps = 1/np.round(end_time - start_time, 2)

            cv2.putText(frame, f'FPS: {int(fps)}', (20,70), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0,255,0), 2)

            cv2.imshow('YOLOv8 Detection', frame)

            if cv2.waitKey(5) & 0xFF == 27:

                break

        cap.release()
        cv2.destroyAllWindows()



detector = ObjectDetection(capture_index=0)
detector()
  • segment 모델
import cv2
from ultralytics import YOLO

model = YOLO('yolov8n-seg.pt')

# 연결된 웹캡 지정
cap = cv2.VideoCapture(0)

while cap.isOpened():
    success, frame = cap.read()

    if success:
        results = model(frame)
        annotated_frame = results[0].plot()

        cv2.imshow("YOLOv8 Inference", annotated_frame)

        if cv2.waitKey(1) & 0xFF == ord("q"):
            break

    else:
        break

cap.release()
cv2.destroyAllWindows()

Next

데이터를 계속 축적해 나아가면서 데이터 파이프라인을 만들고 오염·파손 장난감에 대해 더 일반적인 인식이 가능하도록 할 계획입니다. 실제 장난감을 인식하는 장소에 맞춰서 background image를 보완해서 학습하고 현장 테스트를 진행해야 합니다.