닮은 아이돌 찾기 서비스(https://celebme.net)의 업데이트를 위한 작업 과정을 블로그로 기록합니다!
우선 셀럽미: 닮은 아이돌 찾기 서비스를 개발하기 까지 어려 과정이 있었는데요.
셀럽미 닮은 아이돌 찾기 업데이트 이미지 수집 과정을 시작하기 전에 초기의 셀럽미 서비스 개발 과정을 알아볼게요.
셀럽미: 닮은 아이돌 찾기 서비스 소개
셀럽미: 닮은 아이돌 찾기는 사용자로부터 사진을 입력받아 가장 비슷한 아이돌을 찾아주는 서비스에요.
아래처럼 내 사진을 넣으면 가장 닮은 아이돌을 남녀를 구분하여 빠르게 확인해볼 수 있어요.
셀럽미: 닮은 아이돌 찾기는 사진을 업로드하는 형태가 아니라
인공지능 모델을 내 웹브라우저 메모리에 로드하고, 사진도 메모리에 로드하여 비교를 하고 종료되기 때문에
내 사진이 어딘가에 올라가는거 아니야?하는 걱정없이 편리하게 사용할 수 있어요.
사진은 어디에도 저장되지 않아요!
초기 셀럽미 모델 : Google Teachable Machine
과거에 작성한 블로그 글을 일부 복사해왔어요.
* 과거의 글은 여기에서 확인할 수 있어요: https://blog.naver.com/honth/222920369652
구글의 Teachable Machine을 활용하면 Web UI 상에서 인공지능 모델을 쉽게 구현할 수 있어요.
teachable machine은 웹브라우저에 데이터를 올리고 브라우저상에서 인공지능을 학습하는데,
이런 방식으로는 많은 아이돌의 사진을 일일이 업로드할 수 없는 치명적인 문제점이 있었어요.
학습에 사용된 아이돌 목록
남자: https://raw.githubusercontent.com/kwontaeheon/kceleb_model/main/men_label/labels.txt
여자: https://raw.githubusercontent.com/kwontaeheon/kceleb_model/main/women_label/labels.txt
구글 크롤링을 통해 수집한 아이돌 사진 목록은 무려 5만장을 넘는데,
이걸 브라우저에 올리려하면 크롬이 죽고.. 시스템이 죽는 아주 안타까운 상황을 여러번 만나게 되었어요.
그래서 구글 서비스를 완전히 사용하지 않았느냐 하면 그것은 아니구요!
제가 갖고있는 노트북 GPU머신에서, 구글의 TensorFlow를 사용하여 이미지 분류 모델을 직접 학습시켰습니다.
사실 직접 학습하는게 어려운 일은 아니었는데요.
어려운건.. 수집한 수많은 이미지를 정제하고 동일한 사이즈와 포맷으로 만드는게 가장 어려웠어요!
우선 저 이미지들을 가운데기준 정사각형으로 Crop하는 배치스크립트를 별도로 수행했고
PNG로 수집된 데이터에 대해 Corrupted된 데이터는 삭제시키는 Image File Validation 도 수행했어요.
이 과정에는 linux의 mogrify 스크립트를 사용했어요.
그리고 모델 학습 코드는 TFLite 의 Model Maker를 활용했어요.
셀럽미 모델 개선: TensorFlow Lite Model Maker 활용 인공지능 학습
셀럽미는 이미지 분류 개선을 위해 Tensorflow Lite Model Maker 를 활용했어요.
https://www.tensorflow.org/lite/models/modify/model_maker/image_classification
이미지분류에대한 학습 코드는 상당히 짧게 구현할 수 있는데요.
data = DataLoader.from_folder(image_path)
train_data, validation_data = data.split(0.8)
model = image_classifier.create(train_data, validation_data=validation_data)
model.export(export_dir='.')
이렇게 단순하게봐서는 코드 네줄로 인공지능을 학습해볼 수 있어요!
셀럽미: 닮은 아이돌 찾기 이미지 수집 크롤링 코드
구글에서 이미지를 크롤링 하기 위한 방법은 유튜버 조코딩님의 명강의를 통해 시작할 수 있었는데요.
유튜브에서 배운 내용으로 제가 이미지를 수집할 때 2024.01.21(월) 기준으로 오류없이 동작하는 코드를 첨부할게요.
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
import time
import urllib3
import os
import io
from PIL import Image
import base64
os.environ["PATH"] = os.environ.get("PATH") + ":."
# 평판 https://brikorea.com/rk/ip2312
driver = webdriver.Chrome()
driver.set_window_position(0, 0)
driver.set_window_size(1400, 1000)
driver.get("https://www.google.co.kr/imghp?hl=ko&authuser=0&ogbl")
header = {
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.134 Safari/537.36"
}
http = urllib3.PoolManager()
celeb_list_men = [
"BTS 정국",
"비스트 용준형",
"슈퍼주니어 신동",
"인피니트 엘",
]
celeb_list = [
"하이키 서이",
"하이키 리이나",
"하이키 휘서",
"하이키 옐",
"르세라핌 카즈하",
"르세라핌 홍은채",
"르세라핌 허윤진",
]
def crawl_image(celeb_name, gender):
print(celeb_name)
elem = driver.find_element(by=By.NAME, value="q")
elem.clear()
elem.send_keys(f'allintitle:"{celeb_name}" -닮은 -청량 -단체사진 -다이어트 -살 -레어리 -facebook')
elem.send_keys(Keys.RETURN)
idx = 0
SCROLL_PAUSE_TIME = 3
# Get scroll height
last_height = driver.execute_script("return document.body.scrollHeight")
while True:
# Scroll down to bottom
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
# Wait to load page
time.sleep(SCROLL_PAUSE_TIME)
# Calculate new scroll height and compare with last scroll height
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
try:
driver.find_element(By.CSS_SELECTOR, ".LZ4I").click()
except Exception:
break
last_height = new_height
images = driver.find_elements(By.CSS_SELECTOR, ".rg_i.Q4LuWd")
driver.execute_script("window.scrollTo(0, 0);")
for image_elem in images[:500]:
try:
idx += 1
image_elem.click()
time.sleep(0.3)
down_link = driver.find_element(
by=By.XPATH,
value='//*[@id="Sva75c"]/div[2]/div[2]/div[2]/div[2]/c-wiz/div/div/div/div/div[3]/div[1]/a/img[1]',
).get_attribute("src")
dir_path = os.path.join("images", gender, celeb_name)
if not os.path.exists(dir_path):
os.makedirs(dir_path)
try:
r = http.request("GET", down_link, preload_content=False, headers=header)
with open(os.path.join(dir_path, f"{idx:02d}.png"), "wb") as out:
out.write(r.read())
r.close()
except Exception:
z = down_link[down_link.encode().find(b"/9") :]
Image.open(io.BytesIO(base64.b64decode(z))).save(os.path.join(dir_path, f"{idx:02d}.png"))
except Exception:
print("Error: ", celeb_name, idx)
# celeb_list = ["아이즈원 장원영"]
for celeb_name in celeb_list:
try:
crawl_image(celeb_name, "여자")
except BaseException as e:
print(str(e))
pass
for celeb_name in celeb_list_men:
try:
crawl_image(celeb_name, "남자")
except BaseException as e:
print(str(e))
pass
내용 중 이미지 크롤링에서 저만의 “노하우”가 숨어있는 부분은 바로 구글 검색어 작성 부분이에요.
elem.send_keys(f'allintitle:"{celeb_name}" -닮은 -청량 -단체사진 -다이어트 -살 -레어리 -facebook')
위와 같이 구글 이미지 검색을 실행하게 되면 아래와 같이 해당 연예인의 얼굴 위주의 검색 결과를 볼 수 있어요. 검색어를 잘 작성하는 것 만으로도 괜찮은 크롤링 결과를 얻을 수 있는 첫 걸음이 됩니다!
이 모델 최적화와 이제 개선될 셀럽미에서는 무슨 모델을 사용할지, 다음 글에서 작성해볼게요~