지금까지 배운 내용을 활용하여 규동 이미지를 판정할 것이다 스크래핑으로 이미지르 ㄹ수집하고, 데이터를 전처리/가공하고, 머신러닝으로 분석하는 방법까지 모두 활용할 것이다.
스크래핑부터 시작하기
이번 예제는 포토주(photozou) 라는 사이트에서 이미지를 가져올 것이다. 테스트로 규종 정보를 XML로 가져와보겠다. 포토주의 API는 인증 등이 따로 필요하지 않으므로 웹 브라우저에 입력하면 곧바로 XML을 확인할 수 있다.
https://api.photozou.jp/rest/search_public.xml?keyword=牛丼
포토주의 썸네일 이미지 URL(thumnail_image_url)을 사용한다(120x120 픽셀) 또한 무한 반복으로 API를 계속 실행해 결과가 0이 나올 때까지 돌리겠다.
import sys, os, re, time
import urllib.request as req
import urllib.parse as parse
import json
# API의 URL 지정하기
PHOTOZOU_API = "https://api.photozou.jp/rest/search_public.json"
CACHE_DIR = "./image/cache"
# 포토주 API로 이미지 검색하기 --- (※1)
def search_photo(keyword, offset=0, limit=100):
# API 쿼리 조합하기
keyword_enc = parse.quote_plus(keyword)
q = "keyword={0}&offset={1}&limit={2}".format(keyword_enc, offset, limit)
url = PHOTOZOU_API + "?" + q
# 캐시 전용 폴더 만들기
if not os.path.exists(CACHE_DIR):
os.makedirs(CACHE_DIR)
cache = CACHE_DIR + "/" + re.sub(r'[^a-zA-Z0-9\%\#]+', '_', url)
if os.path.exists(cache):
return json.load(open(cache, "r", encoding="utf-8"))
print("[API] " + url)
req.urlretrieve(url, cache)
time.sleep(1) # --- 1초 쉬기
return json.load(open(cache, "r", encoding="utf-8"))
# 이미지 다운로드하기 --- (※2)
# Function] urllib.parse.quote_plus(string, safe=' ', encoding='utf-8', errors='strict')
: 인자로 주어진 문자열에서 특수문자를 문자열로 변환해서 반환해줍니다.
urllib.parse.quote()와 기능은 같지만, 공백을 '+' 기호로 변환합니다.
quote()에서는 인코딩 방식이 utf-8인 경우 공백을 '%20'으로 변환합니다.
또한, quote()와 다르게 quote_plus()의 safe 인자는 기본 값이 없습니다.
따라서 '/'를 변환하지 않으려면 safe 인자에 '/'를 지정해주어야 합니다.]
※1 에서는 포토주의 검색api를 호출하는 함수 search_photo()를 정의했다.
# 이미지 다운로드하기 --- (※2)
def download_thumb(info, save_dir):
if not os.path.exists(save_dir): os.makedirs(save_dir)
if info is None: return
if not "photo" in info["info"]:
print("[ERROR] broken info")
return
photolist = info["info"]["photo"]
for photo in photolist:
title = photo["photo_title"]
photo_id = photo["photo_id"]
url = photo["thumbnail_image_url"]
path = save_dir + "/" + str(photo_id) + "_thumb.jpg"
if os.path.exists(path): continue
try:
print("[download]", title, photo_id)
req.urlretrieve(url, path)
time.sleep(1) # --- 1초 쉬기
except Exception as e:
print("[ERROR] failed to downlaod url=", url)
※2 에서는 이미지를 내려받는다 api의 리턴값에서 썸네일 이미지를 확인하고 내려받는다.
def download_all(keyword, save_dir, maxphoto = 1000):
offset = 0
limit = 100
while True:
# API 호출
info = search_photo(keyword, offset=offset, limit=limit)
if info is None:
print("[ERROR] no result"); return
if (not "info" in info) or (not "photo_num" in info["info"]):
print("[ERROR] broken data"); return
photo_num = info["info"]["photo_num"]
if photo_num == 0:
print("photo_num = 0, offset=", offset)
return
# 사진 정보가 포함돼 있으면 다운받기
print("*** download offset=", offset)
download_thumb(info, save_dir)
offset += limit
if offset >= maxphoto: break
if __name__ == '__main__':
※3에서는 ※1 과 ※2를 연결하는 역할을 한다 일단api를 호출하고, 이미지 목록 데이터를 얻는다, 그리고 download_thumb()를 사용해 이미지를 내려받는다. 또한 포토주의 검색api에는 전체 이미지 수를 구하는 속성이 없다, 따라서 무한 반복을 적용하고 결과가 0이 될때까지 계속 검색하게 했다.
프로그램을 실행하여 포토주에 있는 규동이미지 (1000개)를 내려받았다.
교사 데이터 만들기 -수작업으로 규동 분류하기
다운한 규동이미지 6000장을 손수 일반규동, 생강규동, 양파 규동, 치즈 규동, 김치규동, 기타 규동으로 나누었다. 각 카테고리에서 이미지를 100개씩 선택하였다( 파이썬을 이용한 ... 실전개발 입문) 책에서는 웹 어플리캐이션을 만들어 스마트폰에서 손쉽게 이미지를 분류할 수 있게 하였다 (그래봤자 일일이 하는거지만..)
이미지 데이터를 숫자 데이터로 변환하기
수작업으로 하나하나 분류한 이미지를 학습 데이터로 읽어 들일 수 있게 Numpy 로 변환하겠다
from PIL import Image
import os, glob
import numpy as np
from sklearn.model_selection import train_test_split
# 분류 대상 카테고리 선택하기 --- (※1)
caltech_dir = "./image/101_ObjectCategories"
categories = ["chair","camera","butterfly","elephant","flamingo"]
nb_classes = len(categories)
# 이미지 크기 지정 --- (※2)
image_w = 64
image_h = 64
pixels = image_w * image_h * 3
caltech_dir 은 이미지가 들어있는 디렉토리, nb_classes 는 카테고리의 개수이다.
# 이미지 데이터 읽어 들이기 --- (※3)
X = []
Y = []
for idx, cat in enumerate(categories):
# 레이블 지정 --- (※4)
label = [0 for i in range(nb_classes)]
label[idx] = 1
# 이미지 --- (※5)
image_dir = caltech_dir + "/" + cat
files = glob.glob(image_dir+"/*.jpg")
for i, f in enumerate(files):
img = Image.open(f) # --- (※6)
img = img.convert("RGB")
img = img.resize((image_w, image_h))
data = np.asarray(img)
X.append(data)
Y.append(label)
if i % 10 == 0:
print(i, "\n", data)
X = np.array(X)
Y = np.array(Y)
# 학습 전용 데이터와 테스트 전용 데이터 구분 --- (※7)
X_train, X_test, y_train, y_test = \
train_test_split(X, Y)
xy = (X_train, X_test, y_train, y_test)
np.save("./image/5obj.npy", xy)
print("ok,", len(Y))
label 은 카테고리를 one-hot-vector 로 변환시킨 형태이다, img_dir 은 각 카테고리에 맞는 디렉토리를 결정한다,
glob 모듈은 카테고리안의 jpg 확장자 파일을 열고 그 이미지를 변환하여 이미지 데이터를 np배열로 변환한다.(머신러닝에서 사용하기 쉽게하기위해) 변환한 데이터를 X에 추가시키고 레이블을 Y에 추가시킨다 마지막으로 저장한다.
CNN으로 학습하기
Tensorflow + Keras 조합으로 CNN을 테스트 할것이다.
from keras.models import Sequential
from keras.layers import Convolution2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras.utils import np_utils
import numpy as np
# 분류 대상 카테고리
root_dir = "./image/"
categories = ["normal", "beni", "negi", "cheese"]
nb_classes = len(categories)
image_size = 50
# 데이터 다운로드하기 --- (※1)
def main():
X_train, X_test, y_train, y_test = np.load("./image/gyudon.npy")
# 데이터 정규화하기
X_train = X_train.astype("float") / 256
X_test = X_test.astype("float") / 256
y_train = np_utils.to_categorical(y_train, nb_classes)
y_test = np_utils.to_categorical(y_test, nb_classes)
# 모델을 훈련하고 평가하기
model = model_train(X_train, y_train)
model_eval(model, X_test, y_test)
※1 에서는 이전에 만든 Numpy 형식의 데이터 "gyudon,npy" 를 읽어 들인다 훈련 전용 데아터, 훈랸 전용 레이블, 테스트 전용 데이터, 테스트 전용 레이블이라는 4개의 데이터가 모두 들어있다.
따라서 데이터를 읽어 들이고 정규화해야 한다. RGB를 나타내는 각 픽셀의 데이터를 256으로 나눠서 데이터를 0에서 1의 범위로 정규화한다, 그리고 카테고리를 나타내는 레이블은 0에서 3사이의 숫자이므로 각각을 [1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]처럼 벡터로 변환한다.
# 모델 구축하기 --- (※2)
def build_model(in_shape):
model = Sequential()
model.add(Convolution2D(32, 3, 3,
border_mode='same',
input_shape=in_shape))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Convolution2D(64, 3, 3, border_mode='same'))
model.add(Activation('relu'))
model.add(Convolution2D(64, 3, 3))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(nb_classes))
model.add(Activation('softmax'))
model.compile(loss='binary_crossentropy',
optimizer='rmsprop',
metrics=['accuracy'])
return model
※2 에서는 CNN모델을 구축하였다. 입력은 50x50 픽셀의 RGB 24비트 컬러 (50,50,3)이고 출력은 카테고리를 나타내는 0에서 3사이의 숫자이다 합성곱, 활성화 함수, 맥스 풀링층을 3개 중첩한 기본 모델이다
# 모델 훈련하기 --- (※3)
def model_train(X, y):
model = build_model(X.shape[1:])
model.fit(X, y, batch_size=32, nb_epoch=30)
# 모델 저장하기 --- (※4)
hdf5_file = "./image/gyudon-model.hdf5"
model.save_weights(hdf5_file)
return model
# 모델 평가하기 --- (※5)
def model_eval(model, X, y):
score = model.evaluate(X, y)
print('loss=', score[0])
print('accuracy=', score[1])
if __name__ == "__main__":
main()
※3 에서는 모델을 훈련한다 ※4에서는 훈련한 모델을 파일로 출력하고 ※5에서는 최종적으로 테스트 데이터를 기반으로 모델을 평가한다.
Using TensorFlow backend.
. . .
loss = 0.828013382327
accuracy = 0.853879241116
정확도는 85%정도로 조금 부족하다고 할수 있다. 이전과 마찬가지로 데이터 수를 늘려 판정 정밀도를 높이겠다.
판정 정밀도 올리기
def add_sample(cat, fname, is_train):
img = Image.open(fname) #읽어들이기
img = img.convert("RGB") # 색상 모드 변경하기
img = img.resize((image_size, image_size)) # 이미지 크기 변경하기
data = np.asarray(img)
X.append(data)
Y.append(cat)
if not is_train: return
# 각도를 조금 변경한 파일 추가하기
# 회전하기
for ang in range(-20, 20, 5):
img2 = img.rotate(ang)
data = np.asarray(img2)
X.append(data)
Y.append(cat)
# img2.save("gyudon-"+str(ang)+".PNG")
# 반전하기
img2 = img2.transpose(Image.FLIP_LEFT_RIGHT)
data = np.asarray(img2)
X.append(data)
Y.append(cat)
def make_sample(files, is_train):
global X, Y
X = []; Y = []
for cat, fname in files:
add_sample(cat, fname, is_train)
return np.array(X), np.array(Y)
※1 은 이미지 데이터를 읽어 들이고 Numpy형식으로 데이터를 변환하는 함수이다, 훈련 전용 데이터를 조금 회전하거나 수평 반정한 뒤 이미지 데이터로 등록하는 것이다. 이부분이 바로 인식의 정밀도를 높이기 위한 데이터 가공 처리의 포인트이다.
# 각 폴더에 들어있는 파일 수집하기 --- (※2)
allfiles = []
for idx, cat in enumerate(categories):
image_dir = root_dir + "/" + cat
files = glob.glob(image_dir + "/*.PG")
for f in files:
allfiles.append((idx, f))
# 섞은 뒤에 학습 전용 데이터와 테스트 전용 데이터 구분하기 --- (※3)
random.shuffle(allfiles)
th = math.floor(len(allfiles) * 0.6)
train = allfiles[0:th]
test = allfiles[th:]
X_train, y_train = make_sample(train, True)
X_test, y_test = make_sample(test, False)
xy = (X_train, X_test, y_train, y_test)
np.save("./image/gyudon2.npy", xy)
print("ok,", len(y_train))
※2 에서는 폴더마다 구분돼 있는 파일을 수집한다, 드리고 ※3에서 파일 목록을 섞고, 훈련 전용 데이터와 테스트 전용 데이터로 구분한다, 그리고 훈련데이터는 정밀도를 높일 수 있게 회전처리 등으로 데이터 수를 늘려준다.
이때 주의를 기울일 점이 훈련 데이터와 테스트 데이터를 함께 수평정렬하고, 변환된 이미지를 기반으로 훈련 데이터와 테스트 데이터로 나누면 과적합이 발생한다.
본 책"파이썬...실전 개발 입문"에서는 웹사이트를 통해 규동판별기를 실행하였다.
'Machine Learning > 영상처리' 카테고리의 다른 글
이미지 OCR (0) | 2020.07.18 |
---|---|
OpenCV로 얼굴인식하기 (0) | 2020.07.17 |
CNN으로 이미지 분류하기 (1) | 2020.07.15 |
유사한 이미지 판별 그리고 Average Hash (0) | 2020.07.15 |