TFJS로 아이콘 ‘맞춤법 검사기’ 만들기(2)

Joo Hyung Park (Jude)
8 min readMar 27, 2022

--

이전 포스팅

Discover

프로토타이핑 프로젝트를 완료한 다음 프로젝트를 도와준 분들과 함께 회고해봤습니다. 회고를 통해서 프로젝트의 개선 아이디어들을 많이 얻었는데, 그 중에서 가장 도전적인 과제는 ‘내 아이콘을 검출’할 수 있도록 커스터마이징 하는 기능이었습니다.

Experiments

커스터마이징을 위해서는 디자인시스템에서 추출하는 부족한 데이터를 가지고도 훌륭한 성능의 모델을 만들 수 있도록 데이터셋을 구축하는 기능을 만들어야 했습니다. 이를 위해서 다양한 가설을 내보고 실험을 반복했습니다.

Generate and export from Figma

첫번째 실험으로는 아이콘 라이브러리의 컴포넌트들을 이용해서 자동으로 png 이미지와 xml 파일을 생성해봤습니다.

먼저 Figma API의 기능을 이용해서 위와 같이 컴포넌트 목록을 추출하는 기능을 만들었고, 그 아이콘들을 자동으로 붙여넣어서 Figma에서 360x640px 사이지의 프레임 노드이미지를 만들었습니다.

이렇게 생성된 프레임들을 Light / Dark로 바꾸거나 아이콘의 색상을 변경해서 다양한 예시를 만들기도 했고, 그 프레임들에서 PNG와 XML 파일을 추출했습니다.

Light / Dark / Primary color icon 케이스 생성

Result

자동으로 추출한 데이터를 기반으로 모델을 학습시켰지만 안타깝게도 성능은 기대에 한참 못미쳤습니다. 예상해보면 흰색 또는 검은색 배경에서 아이콘을 검출하는 것이 컴퓨터 입장에서는 너무 단순한 작업이라 학습 효과가 매우 떨어져서 그런것이 아닐까.. 생각됩니다.

Screen overlay

앞선 실험에서의 교훈은 단순한 배경과 단순한 아이콘 배열 만으로는 학습효율이 떨어진다는 것이었습니다. 그래서 다음으로는 실제 스크린샷 배경에서 아이콘 이미지만 덮어쓰는 방식으로 데이터셋을 구축하는 것을 시도해봤습니다.

먼저 학습에 활용했던 RICO의 스크린샷과 XML을 다시 가져왔습니다. 다음으로는 Figma의 아이콘 라이브러리에서 아이콘 이미지를 모두 추출했습니다. 이번 실험에서는 Microsoft가 운영하는 오픈소스 디자인 시스템인 Fluent Iconography에서 약 11,000개의 아이콘 이미지들을 추출해서 사용했습니다.

다음으로는 아래의 파이썬 코드를 OpenCV를 이용해서 스크린샷의 아이콘이 들어간 위치마다 배경색을 칠해서 기존 아이콘을 지우고, 새 아이콘을 합성하는 작업을 반복했습니다. 보다 상세한 내용은 아래의 코드와 Colab 노트북을 참고해주시기 바랍니다.

import os
import cv2
import numpy as np
import xml.etree.ElementTree as ET
import random
from scipy.stats import mode
icons = os.listdir("./icon/")
def selectRandom(array):
icon = random.choice(array)
return icon
def getfreq(roi):
colors=[]
for y in range(len(roi)):
for x in range(len(roi[y])):
if(sum(roi[y][x])!=0 and sum(roi[y][x])!=255*3): #흰색이거나 검은색은 제외
colors.append(roi[y][x])
if len(colors)>0:
result = mode(colors).mode[0]
else:
result = [255,255,255]
return result
for filename in os.listdir("./xml/"):
xml = ET.parse(os.path.join("./xml/", filename))
root = xml.getroot()
jpg = filename.replace(".xml", ".jpg")

src1 = cv2.imread("./image/" + jpg) #스크린샷 파일 읽기

for object in root.iter("object"):
src2 = cv2.imread("./icon/" + selectRandom(icons)) #아이콘파일 읽기
boxes = object.find("bndbox")
xmin = round(float(boxes.findtext("xmin")))
xmax = round(float(boxes.findtext("xmax")))
ymin = round(float(boxes.findtext("ymin")))
ymax = round(float(boxes.findtext("ymax")))
roi = src1[ymin:ymax, xmin:xmax] #아이콘이 들어갈 픽셀값을 관심영역(ROI)으로 저장함.
rows, cols, channels = roi.shape #관심영역의 픽셀값 저장
# print(cols, rows)
if (cols > 0 and rows > 0):
resize_src2 = cv2.resize(src2, (cols, rows)) # 합성할 이미지 리사이징
else:
resize_src2 = cv2.resize(src2, (1,1))

bgcolor=getfreq(roi) #아이콘의 배경색 추출

luma = bgcolor[0] * 299/1000 + bgcolor[1] * 587/1000 + bgcolor[2] * 114/1000
if luma < 128:
fgcolor=[255,255,255]
else:
fgcolor=[0,0,0] #아이콘색 추출

print(bgcolor, fgcolor)
src1[ymin:ymax, xmin:xmax]=bgcolorfor y in range(len(resize_src2)):
for x in range(len(resize_src2[y])):
if((y+ymin<len(src1) and (x+xmin)<len(src1[0]))):
if(sum(resize_src2[y][x])>0):
src1[y+ymin][x+xmin]=fgcolor

# 이미지 저장
data_path = './output/'
if not os.path.exists(data_path):
os.mkdir(data_path)
cv2.imwrite(data_path + jpg, src1)

작업 결과 아래와 같이 각 스크린샷마다 기존에 원래 있던 아이콘 대신에 Fluent Design system에서 추출한 아이콘이 합성됐습니다. 기존에 사용하던 XML 파일과 함께 사용하므로서 새로운 아이콘을 기반으로 사용 가능한 학습 데이터셋을 구축하는데 성공했습니다.

Result

결과까지 좋았으면 금상첨화였을 것 같습니다만, 해당 데이터셋으로 학습시킨 커스텀 모델은 아직 원본 RICO/CLAY로 학습시킨 모델의 성능에는 미치지 못하고 있습니다. 임의로 합성시키는 과정에서 불가피하게 사이즈, 색상 조작이 발생하는데 이 과정에서 PNG 이미지의 픽셀이 깨지면서 문제가 생긴 것이 아닐지 추측만 하고 있습니다.

그럼에도 불구하고 첫번째 실험보다는 훨씬 진일보한 성능을 보여주고 있으므로 나름 희망을 봤다가 자축했습니다.

다음 포스팅…

다음 포스팅에서는 마지막으로 이 아이콘 검출기 프로젝트를 브랜딩하고, 오픈소스로 공개하는 과정을 기술해볼 생각입니다.

기존에 하던 프로젝트의 곁다리? 프로젝트에서 시작했는데…진행 과정에서 얻은게 많아서 그것들을 하나하나 기록하다보니 3편에 나눠서 쓰게 됐네요. 긴 글 읽어주셔서 감사하고 혹시 도움이 됐다면 박수👏로 응원 부탁드립니다!

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Joo Hyung Park (Jude)
Joo Hyung Park (Jude)

No responses yet

Write a response