[아이펠 X 쏘카] VIAI 차량 파손 탐지 프로젝트와 회고

Jieun Jeon
23 min readJan 9, 2022

Link to English Version

2021.11.10 ~ 2021.12.15 약 한달간 진행된 VIAI (Vehicle Inspection A.I.) 프로젝트를 하면서 저희 팀이 어떻게 일했는지, 결과를 내기 위해 고민했던 것들을 공유합니다. 마음이 맞는 네명의 팀원이 모여 Semantic Segmentation 모델 구현과 ML model의 Human-In-The-Loop를 구현하기 위한 노력을 담았습니다.

모두의 연구소 AIFFEL과 SOCAR의 기업 협력 과제인 차량 파손 탐지를 주제로 프로젝트를 진행하였습니다.

Team

Team VIAI

해커톤 프로젝트에 앞서 아이펠 AI 교육과정을 통해 서로의 공부/코딩 스타일, 집중하고 싶은 분야를 미리 알 수 있었습니다. 이를 바탕으로 어떻게 팀을 구성해야 프로젝트 완성을 위해 최고의 결과를 낼 수 있을지 고민하고, 팀을 구성할 수 있었습니다.

팀의 이름인 VIAI는 차량 파손을 자동으로 탐지하는 A.I. 제품을 서비스한다는 뜻 (Vehicle Inspection)을 담아 븨-AI (VIAI) 로 정했습니다.

Project Overview

쏘카의 기업 두번째 기업 협력 과제였던 차량 파손 탐지의 문제 정의와 VIAI팀이 세운 프로젝트 목표는 다음과 같습니다:

Problem Definition

🛠 SOCAR의 차량 파손 탐지 시스템의 문제 정의는 이렇게 정의했습니다.

  1. 차량의 파손 상태 (파손 영역, 파손 종류) 검수의 인력 부담 (일 평균 7–8만장을 직접 한장한장 검수)
  2. 차량의 파손 시점 추적의 어려움 (역시 모든 사진을 역추적하는 인력 부담)
  3. 차량 파손 탐지 모델 성능 고도화의 필요성

Goal of VIAI

✔ 위 문제 해결을 위해 VIAI팀이 세운 목표는 다음과 같습니다.

  1. 차량 이미지의 파손 영역과 종류 검출/모니터링 자동화
  2. Dashboard UI를 통해 Annotator가 마스킹 이미지를 생성할 수 있도록 Tool 제공
  3. Human-In-The-Loop를 구현해 Data-centered AI를 구현

Tech Stack

Model

  • PyTorch
  • mmSegmentation
  • mmDetection

DataSet

  • Transfer Learning — ImageNet, Stanford Cars Dataset

Data Pipeline

  • Kubernetes
  • kubeflow

Serving

  • TorchServe
  • Google Cloud Platform
  • GKE (Google Kubernetes Engine)
  • Cloud Function
  • Cloud Storage
  • AI Platform

Client

  • Flask
  • PostgresSQL
  • VIA

Dataset

데이터셋은 쏘카에서 제공받은 차량 이미지 총 7000여장을 사용했습니다. 파손 종류별 각각 Dent, Scratch, Spacing 타입 별로 train/, test/, valid/ 형태의 폴더 아키텍쳐로 제공받았습니다.

Project Milestone

프로젝트 준비

10월 ~ 11월 초

프로젝트의 주제인 Semantic Segmentation에 대해 팀원 모두가 공부가 필요했습니다. 모델의 측면 뿐만 아니라 모델의 설계로 결정된 GCP Cloud 환경과 Kubernetes, kubeflow 전반에 관한 지식이 부족했기 때문에, 팀의 결성이 완료된 9월 중후반부터 모델과 GCP, Docker, Kubernetes에 관한 공부를 미리 시작했습니다.

쏘카의 데이터를 제공받은것이 11월 중순 즈음이었기 때문에, 그 전에는 캐글의 다른 차량 이미지 데이터셋을 참고해 모델 베이스라인을 만드는 것으로 모델 개발을 시작하였습니다.

11월 중순

스터디를 바탕으로 베이스라인 모델을 구축하고, 여러 모델로 모델 성능을 향상시키기 위한 다양한 기법을 실험했습니다. 주어진 차량 사진과 사고 부위가 마스킹된 사진을 통해 데이터를 먼저 확인하고, 데이터의 전처리를 위한 기법들도 정리했습니다. 다양한 모델 실험을 위해 mmDetection, mmSegmentation 등의 프레임워크를 이용해 다양한 모델 결과를 빠르게 확인하고, 이를 바탕으로 최종 모델을 구축할 수 있었습니다.

동시에, 모델 서빙을 위한 미니멈 아키텍쳐를 구상하였습니다. 중간 발표를 위해 GCP의 Cloud Native 솔루션을 이용해 사고 종류별 차량 이미지 마스킹 결과를 보여주는 UI를 완성하고, 중간 발표에서 시연을 할 수 있었습니다.

한달 여 정도 되는 기간 동안 프로젝트를 진행하면서 실제 해커톤 기간동안 목표로 잡은 것은 다음과 같습니다.

  1. 살아 움직이는 end-to-end 머신러닝 파이프라인 구축
  2. 더 많은 모델 연구 및 아이디어 적용
  3. 데이터셋 정제 + augmentation 적용 이었습니다.

본 프로젝트

1) 문제 정의 및 구현

위 프로젝트 문제 정의에서 언급했듯이, VIAI 팀의 문제 해결을 위한 목표 중 하나는 Data-centered AI를 구현하는 것이었습니다. 저희는 반복된 Human-In-The-Loop 실행으로 데이터의 품질을 높여 모델의 성능을 향상시키는 방법을 택했습니다.

제공받은 데이터의 양이 적었고, 데이터의 품질이 정제되지 않은 상태가 많았기 때문에, 모델의 마스킹 결과가 좋지 않을 경우 사람이 직접 차량 사고 부위 마스킹을 따서 재학습을 시키는 파이프라인을 구축함으로써 데이터의 품질을 높이고, 모델의 성능을 향상시키는 결과를 확인할 수 있었습니다.

이를 구현하기 위해, 본 해커톤 기간 전에는 Cloud 리소스 (Cloud Function, Compute Engine, Cloud Bucket) 등을 이용해 작은 데모를 만들었다면, 앞서 언급한 Torchserve 서빙과 kubeflow pipeline을 포함한 최종 데이터 파이프라인은 다음과 같습니다.

2. 데이터셋과 모델 향상을 위한 노력

데이터셋 실험 과정

1) Masked Only vs All Data

먼저 데이터셋의 분포를 확인한 결과, 마스킹 되어 있는 이미지들의 분포가 파손 종류별로 차이가 큰 것을 확인할 수 있었습니다. 이에 따라서 마스킹이 존재하는 차량 이미지들로만 학습하는 방안 1, 그리고 마스킹이 존재하지 않는 차량 이미지들 전체로 학습하는 방안 2를 실험해보았습니다.

그 결과, 마스킹이 존재하는 차량 이미지들로만 학습했을 때에 모델이 다음과 같은 상황에서 오류를 발생시키는 것을 확인할 수 있었습니다.

A) 차 표면의 빛을 Dent로 판별하거나,

B) 그림자를 Dent로 판별하거나,

C) Scratch를 Dent 로 판별하는 경우가 있었습니다.

이 결과, 마스킹이 있는 데이터로만 학습시키는것보다는 모든 데이터를 포함시켜 학습 시키자는 결론을 낼 수 있었습니다.

2) Data Augmentation

Augmentation을 적용했을 때의 Parameter importance with respect to eval_iou로 판단하여 Augmentation을 결정했습니다.

수치는 WanDB를 이용한 실험으로 확인했습니다.

3) Data의 양을 늘려서 모델의 성능 향상 시도

그리고, 결국 모델의 성능을 쉽고 빠르게 향상시킬 수 있는 효과적인 방법인 데이터의 양을 늘리는 노력을 하게 되었습니다. 크게 두 가지 방법을 시도해보았는데요, 첫번째는 파손 이미지를 합성해 이미지를 합성하는 코드를 이용해 이미지를 생성하는 방법, 두번째는 구글링으로 차량 사진들을 모아 한땀한땀 마스킹을 해주어 데이터를 추가해주는 방법을 시도했습니다.

a) 파손 이미지를 정상인 차 이미지에 합성해서 파손 차량 이미지를 직접 만들어보는 시도

먼저, 차량의 각 부위를 추출하고 bbox를 만들었습니다. 그리고 각 부위에 중간지점을 점으로 찍은 후에, 그 부분에 사고 이미지를 잘라내 옮겨붙여보았습니다.

하지만 육안으로도 보이는것처럼, 사고 이미지와 찍힌 이미지 간의 차이점이 분명히 보였습니다 (밝기, 차량 배경의 차이, 빛번짐과 찍은 카메라의 종류의 차이로 인한 픽셀의 다름 등)

사람이 봐도 이상한 합성은 역시나 모델 결과를 오히려 떨어뜨리는 결과를 낳았고, 결국 데이터의 양보다는 질에 집중하기로 결정하였습니다.

b) CVAT 이미지 마스킹 툴을 이용해 한땀한땀 마스킹

두번째로는 CVAT툴을 이용해 직접 한땀한땀 마스킹을 따고, 그 추가한 이미지까지 train set에 부어주었습니다.

데이터를 추가한 후, 모델의 성능이 좋아진것을 확인할 수 있었습니다. 200+여장의 이미지를 직접 마스킹 해 준 태원님 정말 감사합니다 👍🏻

Model 실험 과정

Baseline model 실험 과정

초기 베이스라인 모델은 U-Net과 U-Net++ (Nested U-Net), 그리고 DeepLabv3으로 설정하고 실험해보았습니다.

Baseline model 실험 결과 test IOU는 다음과 같습니다.

성능을 더 향상시키기 위해, mmDetection, mmSegmentation 과 같은 모델 패키지 툴을 이용해 다양한 모델들을 빠르게 많이 실험할 수 있었고, 나아가 poly2mask, mask2poly 와 같은, polygon 데이터를 마스크 이미지로 변환하는 코드, 그리고 반대로 마스크 이미지를 데이터로 변환하는 utility 코드를 짜고 이것을 활용해 더 다양한 모델을 실험했습니다.

패키지 제공 모델의 학습 결과로 성능이 좋게 나온 것은 Mask R-CNNDeepLabv3+ 였습니다.

그리고 여러 실험을 통해, 모델을 U-Net with Efficient encoder로 결정하게 되었습니다.

Transfer Learning

모델을 좀더 고도화하기 위해서, transfer learning을 적용해보는 실험도 진행했습니다. Transfer Learning은 특정 도메인에서 학습한 모델을 다른 도메인이나 태스크에 적용하는 기술입니다. 이전에 학습한 모델의 가중치를 가져와서, 분류하고 싶은 다른 이미지 데이터셋에 적용해 모델 성능 향상을 실험했습니다.

Transfer Learning을 하기 위해 실험한 데이터셋은 총 3가지로, ImageNet, Stanford Car Dataset, SOCAR Datset 입니다. (세번째 SOCAR 데이터셋은 이전에 쏘카의 차량 부위 classification 과제에서 사용했던 차량 이미지를 활용했습니다.)

Transfer Learning 실험 결과, Scratch 판별에는 socar 데이터셋이, 그리고 Dent 와 Spacing의 판별에는 Stanford Car 데이터셋이 가장 성능이 좋았습니다.

그리고 나서, 데이터 추가 실험 후에 추가된 이미지들을포함시켜 학습 한 후 나온 결과입니다. 이 결과가 저희의 모델의 최종 성능 결과입니다.

3. 파이프라인 구축 과정

저희가 구축한 앱은 크게 두개로 나눌 수 있습니다.

  1. 유저가 사진을 업로드하는 user client
  2. 쏘카의 Administrator가 각 차량의 파손을 탐지하고 사고를 추적하고, Inference 결과와 모델 결과를 검수, 직접 mask image를 annotate 할 수 있는 tool 을 제공하는 Admin Dashboard 입니다.

위의 두 app을 만들기 위해서 고안한 data flow는 다음과 같습니다.

Cloud Function, Cloud Storage, AI Platform

유저가 사진을 업로드 했을 때의 flow는 다음과 같습니다:

  1. 유저가 웹 클라이언트를 이용해 차량 외부 사진을 Cloud Storage images-original에 업로드
  2. Cloud Bucket 의 trigger 기능을 이용해 Cloud Function run-upload 실행
  3. Cloud Function run-upload 의 inference 결과를 Cloud Storage images-inferred, Cloud SQL viai-images, kubeflow pipeline을 시동합니다.

Cloud Function run-upload의 역할은 다음과 같습니다:

  1. create new field & update the data on the DB
  2. take the uploaded image and hit the inference API AI Platform Prediction (TorchServe) for dent, scratch, spacing
  3. if something went wrong or errors returned while the inference, move the inferred images from <original-images> originals/ to issues/ folder. (error scenario: — ckpt file not found)
  4. take the results (mask images as b64 format) and 1. store images to the Cloud Storage <images-inferred> & 2. update the data on the DB

Database — PostgresSQL X Cloud SQL

데이터베이스는 PostgresSQL을 이용해 Cloud SQL에 배포하였습니다.

차량 이용 내역, 사용자 정보, 그리고 차량 정보를 엮어 Relational Database로 구축하는것이 좋겠다고 생각했습니다. 또한, Relational Database 중 MySQL이 아니라 PostgresSQL을 선택한 이유는, PostgresSQL은 저희가 필요로하는 데이터 타입 (boolean, arrays)를 지원했고, 무엇보다 중요한 것은 inference result update에 db read & write 둘 다가 빈번했으므로, high volumes of reads가 더 적합한 MySQL보다는 PostgresSQL을 선택하게 되었습니다.

Model Serving For Inference — TorchServe X AI Platform

Model Serving 또한 kubeflow 위에 배포할 수도 있었지만, GCP에서 지원받은 크레딧이 점점 바닥나고 있었기 때문에 AI Platform의 AI Prediction에서 Dockerized TorchServe model을 배포하는 형태로 진행하였습니다.

또, 저희는 각각의 파손 종류별로 모델을 학습했고 이를 위한 inference endpoint가 각각 필요했기 때문에, TorchServe를 이용한 효율적인 모델 서빙 서비스를 이용했습니다. TorchServe는 Pytorch용 open source model server로, AWS와 Facebook이 공동으로 개발한 모델입니다. TorchServe를 사용하면, 빠른 예측 API와 저희에게 필요했던 다중 모델 서비스가 내장되어 있고, A/B 테스트를 위한 모델 버전 관리, 모니터링 메트릭을 위한 RESTful endpoint가 포함되어 있었기 때문에 선택하게 되었습니다.

Model Retrain Pipeline — Kubernetes, Kubeflow Pipeline

재학습을 위한 retraining pipeline은 Kubernetes와 kubeflow fairing, kubeflow pipeline으로 구현하였습니다.

검수자가 직접 검수한 이미지들이 어느 정도 모이는것을 모델 재학습의 trigger로 설정했는데요, 그 외에도 model의 inference 결과 이미지들의 confidence score을 계산하는 모듈을 구성해 재학습 기준 파이프라인에 추가했습니다.

원래 목표는 inference가 완료된 이미지의 사고 판별 유무를 통한 기준을 적용하려고 했으나 classifier모델의 성능이 좋지 않아서 임시로 마스크 이미지에서 픽셀값이 255인 영역 (즉, 사고라고 판별된 범위)의 확률값들의 평균을 이용했습니다.

*이는 임시로 사용된 방법으로, 각 픽셀의 confidence score을 구한 것이지 이미지 자체의 사고 유무 confidence score을 계산하는 데에는 적합하지 않습니다.

파이프라인의 Flow는 다음과 같이 설정해주었습니다.

  1. images-annotated 버킷 내 새로 추가된 이미지 개수 count
  • 일정 개수 이하라면 파이프라인 중단
  • 일정 개수 이상이면 계속 진행

2. 새로 추가된 이미지들로 재학습 진행

  • checkpoint naming conventions
  • data_class : scratch, dent, spacing
  • test_IoU : 2.2f
  • train_date : yy_mm_dd
<data_class>_<test_IoU>_yy_mm_dd.pth

3. 새로 학습한 모델이 현재 돌고 있는 모델에 비해 test IoU가 높은지 평가 진행

  • test IoU로 체크포인트 이름 변경
  • 높다면 체크포인트를 교체해 배포 자동화하는 cloud function 실행
  • 높지 않다면 파이프라인 종료

위의 파이프라인을 Recurring Job으로 실행해, 새로 마스킹된 이미지 count 를 주기적으로 확인할 수 있게 하였습니다.

그리고 모든 파이프라인의 Dockerfile에 WanDB 로그인 코드를 추가해, 모델의 결과를 확인할 수 있게 하였습니다.

Retraining Pipeline Component — GCS X Docker X Kubeflow Pipeline

Retraining Pipeline Component는 다음과 같이 구축하였습니다.

PVC를 생성한 후 GCS로 접근

A) kubeflow pipeline의 GCS 접근 권한 설정

B) DSL Component 구성

dent, spacing, scratch 와 같은 모델 data type을 argument로 받아서 training 시작 할 수 있도록 했습니다

import argparse
parser = argparse.ArgumentParser(description="Define metadata")
parser.add_argument('-t','--type', requir, help="Choose Data Type - dent, scratch, spacing")args = parser.parse_args()args.type

train 함수를 작성한 뒤 Dockerize, 이미지 빌드 후 GCR에 업로드 한 뒤 사용했습니다.

C) Pipeline 코드 작성

import osimport kfp
from kfp import dsl
from kubernetes.client.models import V1EnvVar, V1VolumeMount, V1Volume, \\\\
V1SecretVolumeSource
if __name__ == '__main__':
my_run = kfp.Client().create_run_from_pipeline_func(pipeline_gcs, arguments={},
experiment_name='Sample Experiment')

GKE 와 on-premise 환경에서 gcsfuse로 mount해서 storage bucket에 접근해서 사용했습니다.

kubeflow는 GKE에 배포하여 pipeline을 구성했습니다.

Pipeline Code References:

Annotation Tool

VIA의 오픈 소스 image annotation tool을 이용해 Flask를 통해 구축한 웹과 연결해, 쏘카의 차량 관리 Administrator가 손쉽게 차량 검수와 마스킹을 한번에 진행할 수 있게 하였습니다.

annotate가 필요한 원본 이미지를 GCS Storage Bucket images-annotated 에서 가져온 후 exported .json 파일을 Flask web으로 전달한 후, category_id를 설정에 해당하는 Bucket에 검수한 json 파일을 마스크 이미지로 변환한 후에 저장해주었습니다.

from io import BytesIO
from google.cloud import storage
client = storage.Client()
bucket = client.bucket("images-annotated")
blob = bucket.blob(f'data/dent/masks/{img_name}')
bs64 = BytesIO()
seg_img.save(bs64, format="png")
img_as_str = bs64.getvalue()
blob.upload_from_string(img_as_str, content_type='image/png')

VIA annotation tool을 이용해 생성된 Exported JSON입니다.

4. 결과물

쏘카 이용자가 차량 대여 시작 전 차량 외관 이미지를 업로드할 수 있는 User Client를 Flask 를 이용해 구현하였습니다

쏘카의 검수 직원이 차량 파손 탐지와 사고 역추적, 살아있는 머신러닝 파이프라인을 위한 데이터 검수 툴을 마찬가지로 플라스크와 CVAT을 이용해 구현하였습니다.

Demo — Dashboard

자세한 Admin Dashboard의 역할은 다음과 같습니다.

  • 쏘카의 검수 직원이 사용하는것을 가정
  • 로그인 기능으로 검수자를 구분
  • Cars, Users, Events 탭으로 각각의 리스트 뷰로 차량별, 사용자별, 차량공유 이벤트별 상세 정보를 조회
  • 파손이 검출된 이벤트를 색상을 통해 강조하고, 파손 시점을 역추적
  • 이벤트를 통해 사용자가 해당 예약(이벤트) 에서 찍은 사진과 모델 inference결과를 확인하고, 마스킹이 잘못되었을 경우 바로 다시 annotate을 할 수 있는 툴을 제공
  • Annotate이 완료된 경우 해당 버킷에 새로 생성된 마스크 이미지를 저장하고, 모델 재학습에 활용
  • 검수가 완료된 이미지의 경우 검수 일시와 검수자 정보 표시

이렇게 한달 반 간의 해커톤 프로젝트를 마무리할 수 있었습니다! 👏🏻

4. 향후 연구 계획

프로젝트는 마무리되었지만, 저희의 향후 연구 계획은 크게 세가지입니다.

  1. 모델 배포 자동화 파이프라인 추가 (CD)
  2. Inference 전 입력 이미지 전처리 (crop, classifier)
  3. 재검수 대상 이미지 기준 변경

모델 재학습과 validation 과정을 거쳐 모델의 성능이 향상되면, 같은 도커파일로 GCP AI Platform 에 자동으로 재배포가 되는 파이프라인을 추가해 자동화를 하는 부분과,

유저가 업로드한 이미지를 Infernece하기 전, 입력된 이미지를 전처리 함으로써 쏘카의 차량만 crop, 사고 유무를 classifier로 미리 판별함으로써 모델의 부담을 덜어주는 부분,

그리고 재검수 기준이었던 임시 방법인 confidence score 대신 classifier accuracy를 사용하는 방법 등을 향후 연구 계획으로 잡았습니다.

쏘카 피드백

프로젝트가 끝난 후 쏘카의 데이터 그룹장님과 각 팀장님들 앞에서 최종 발표를 통해 더 생각해볼 수 있는 피드백을 들을 수 있었습니다.

  • 비즈니스 차원에서, 현업에서 차량 관리자가 성능 향상만을 위해 이미지를 한장한장 annotation 하는 것이 매우 번거로운 작업이라는 점
  • 임시로 사용된 confidence score 계산 방법의 개선점
  • IOU를 metric으로 사용하는 방법의 장/단점, 개선점
  • 차 파츠별 클러스터링 모델의 고도화 방법

등에 대한 피드백을 받을 수 있었습니다. 여기에서 마무리되는것이 아닌, 저희가 개발 단계에서 고민하고 의심했던 부분들을 정확히 짚어주시고, 그에 맞는 향후 실험/연구 키워드를 정확히 짚어주셨습니다. 쏘카 데이터그룹 DK님, KP님, 카일님, 윤님, 치즈님 자세한 피드백 정말 감사드립니다.

마무리

프로젝트가 끝나고, 저는 이 경험을 살려 이직을 하게 되었습니다. 프로젝트 진행을 할 당시에는 몰랐지만 새로 일을 시작하고 보니 이 프로젝트에서 한 경험이 얼마나 값진 것이었는지를 더 크게 느끼게 되는 것 같습니다. 이미 만들어져 있는 것이 아닌, 밑바닥에서부터 end-to-end ML workflow를 구축한다는것이 처음에는 너무 크게 느껴졌지만, 팀으로써 각자의 장점을 살려 함께 결과물을 만든 것이 정말 뿌듯합니다.

단순히 프로젝트에서 사용한 기술과 ML 지식을 얻어가는 것이 아닌, 한 제품을 만들어내기 위한 사이클을 짧은 시간 안에 모든 힘을 쏟아내서 만들어 낸 것이 저와 팀원들 모두에게 특별함을 주었다고 생각합니다. 또, 앞으로 배울 것이 무궁무진 하다는것과 이 분야에서 한발짝을 내딛었다는 것이 아주 큰 의미로 다가오는것 같습니다.

프로젝트가 끝나고 팀 모두가 각자의 새로운 커리어를 쌓기 시작했는데, 이 두 달간의 프로젝트가 저에게는 아주 큰 터닝 포인트가 되었고 이렇게 몰입해서 결과를 낼 수 있었던 것은 팀의 모두가 열정적으로 밤낮없이 (막바지에는 주말도 없이!) 노력한 결과였던 것 같습니다. VIAI팀 다시 한 번 모두 수고 많으셨고, 감사합니다! 👍🏻

VIAI Github Repository

--

--