본문 바로가기

Machine Learning/영상처리

CNN으로 이미지 분류하기

https://codegongbang.tistory.com/2?category=864829

이전에 CNN에 대해 살펴 봤는데, CNN은 신경망의 단점인 '층이 늘어나면 제대로 학습하지 못한다' 라는 문제를 입력층과 출력층 사이에 합성곱층과 풀링층을 넣어 보완한 것이다.

 

이번에는 Caltech 101데이터 세트를 사용해 색상 있는 이미지를 분류 할 것이다. Caltech 101 에는 101가지 종류의 카테고리로 분류된 이미지가 들어 있다. 분류하고 학습하는데 시간이 오래걸리므로 5가지 종류의 카테고리를 학습시키고 정확하게 분류할 수 있느지 테스트 해보자

 

랜덤으로 chair, camera, butterfly, elephant, flamingo 카테고리 이미지를 이용하겠다. 각 카테고리에는 이미지가 60장정도 있고 전체 337장의 사진으로 대상을 분류한다.


이미지 데이터를 파이썬 데이터로 변환하기

이미지 Caltech101 의 이미지는 크기가 모두 달라 머신러닝으로 다루기에 불편한 면이 있다. 따라서 이미지를 일정한 크기로 리사이즈하고, 24비트 RGB형식으로 변환할 것이다 그리고 머신러닝 할때 쉽게 사용할 수 있게 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

# 이미지 데이터 읽어 들이기 --- (※3)
X = []
Y = []
for idx, cat in enumerate(categories):
    # 레이블 지정 --- (※4)
    label = [0 for i in range(nb_classes)]
    label[idx] = 1

nb_classes 분류될 클래스를 개수 만큼 변수에 저장해 둔다. pixel은 rgb값이 모두 들어가니 3을 곱해준다.

 

변수 X에는 실제 이미지 데이터를, 변수Y에는 이미지가 어떤 것을 나타내는지 설명하는 레이블 데이터가 들어간다.※4에서는 이 레이블을 생성한다.

 

  # 이미지 --- (※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))

 

※5에서는 이미지 파일을 찾는다. glob()함수를 사용하면 확정자가 ".jpg"인 것만을 찾을 수 있다. ※에서는 이미지 파일을 읽고 색상모드를 RGB로 변환하고 64X64 픽셀로 리사이즈한다. 이후 Numpy 배열 데이터로 변환한다.

 

※7에서는 무작위로 데이터를 학습전용과 테스트 전용으로 구분한다, 그리고 Numpy의 save()메소드로 파일을 저장한다.

 

 CNN으로 분류해보기


from keras.models import Sequential
from keras.layers import Convolution2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
import numpy as np

# 카테고리 지정하기
categories = ["chair","camera","butterfly","elephant","flamingo"]
nb_classes = len(categories)
# 이미지 크기 지정하기
image_w = 64 
image_h = 64

# 데이터 불러오기 --- (※1)
X_train, X_test, y_train, y_test = np.load("./image/5obj.npy")
# 데이터 정규화하기
X_train = X_train.astype("float") / 256
X_test  = X_test.astype("float")  / 256
print('X_train shape:', X_train.shape)

 

머신러닝을 하는데 있어 keras모델을 사용한다. 

 

# 모델 구축하기 --- (※2)
model = Sequential()
model.add(Convolution2D(32, 3, 3, 
    border_mode='same',
    input_shape=X_train.shape[1:]))
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()) # --- (※3) 
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'])

# 모델 훈련하기 --- (※4)
model.fit(X_train, y_train, batch_size=32, nb_epoch=50)
    
# 모델 평가하기--- (※5)
score = model.evaluate(X_test, y_test)
print('loss=', score[0])
print('accuracy=', score[1])

 

필터로 특징을 뽑아주는 컨볼루션(Convolution) 레이어

Conv2D(32, (5, 5), padding='valid', input_shape=(28, 28, 1), activation='relu')

주요 인자는 다음과 같습니다.
첫번째 인자 : 컨볼루션 필터의 수 입니다.
두번째 인자 : 컨볼루션 커널의 (행, 열) 입니다.

padding : 경계 처리 방법을 정의합니다.
‘valid’ : 유효한 영역만 출력이 됩니다. 따라서 출력 이미지 사이즈는 입력 사이즈보다 작습니다
‘same’ : 출력 이미지 사이즈가 입력 이미지 사이즈와 동일합니다.
input_shape : 샘플 수를 제외한 입력 형태를 정의 합니다. 모델에서 첫 레이어일 때만 정의하면 됩니다.(행, 열, 채널 수)로 정의합니다. 흑백영상인 경우에는 채널이 1이고, 컬러(RGB)영상인 경우에는 채널을 3으로 설정합니다.
activation : 활성화 함수 설정합니다.‘linear’ : 디폴트 값, 입력뉴런과 가중치로 계산된 결과값이 그대로 출력으로 나옵니다.

‘relu’ : rectifier 함수, 은익층에 주로 쓰입니다
.‘sigmoid’ : 시그모이드 함수, 이진 분류 문제에서 출력층에 주로 쓰입니다.‘softmax’ : 소프트맥스 함수, 다중 클래스 분류 문제에서 출력층에 주로 쓰입니다.
입력 형태는 다음과 같습니다.

 

CNN모델을 구축한다 이떄 합성곱층,활성화함수, 맥스 풀링층을 3개씩 쌓은 모델을 사용하였다. ※3이후 부분부터 2개의 전결합층을 배치해서 최종적으로 5클래스가 되게하였다. 

 

프로그램 ※4에서는 데이터를 넣어 모델을 학습하고, 최종적으로 ※5에서 테스트 데이터로 모델을 평가한다

 

Using TensorFlow backend.

X_train shape: (252, 64, 64, 3)


loss = 0.615852470258
accuracy= 0.865882349716

 

 


정밀도를 높이는 방법

 

cnn을 사용해 정답률 0.87을 얻었다는 것은 만족스러운 결과라 볼 수 없다. 정밀도가 낮은 이유로는 샘플의 수가 너무 부족하다는 점이 있다. 따라서 샘플을 늘리기 위해서 이미지의 각도를 약간 바꾸거나, 반전하거나, 확대 축소하거나 .평균화하거나, 노이즈를 넣거나, 명암대비와 감마값을 바꾼 이미지를 만들어 사용할 수 있다.

 

이미지 수를 늘릴때 활용할 수 있는 PIL(Imange) 메서드는 

메서드 설명
Image.transpose(v) 90도 단위로 이미지를 회전하거나 반전한다.
Image.rotate(angle) 이미지를 angle도 만큼 회전한다.

 

현재 모델의 predict() 메서드를 호출해서 예측에 실패한 이미지를 image/error폴더에 저장하는 프로그램이다.

pre = model.predict(X_test)

for i,v in enumerate(pre):
    pre_ans = v.argmax()#에측한 레이블
    ans = y_test[i].argmax()#정답레이블
    dat = X_test[i]#이미지 데이터
    if ans == pre_ans: continue
    print("[NG]", categories[pre_ans], "!=", categories[ans])
    print(v)
    #이미지 출력하기
    fname = "image/error/" + str(i) + "-" + categories[pre_ans] + \
        "-ne-" + categories[ans] + ".png"
    dat *= 256
    img = Image.fromarray(np.uint8(dat))
    img.save(fname)

 

위 프로그램에서 주목할 점은 [0,0,1,0,0]과 같은 데이터를 카테고리 번호 2로 변환하는 argmax()함수이다. 실제로 예측한 데이터를 살펴보면 [0.22208455 0.17484865 0.23227191 0.18457697 0.186421778] 과 같은 데이터가 들어있고, 이를 기반으로 카테고리를 구분하고 있다. 가장 큰값이 들어있는 레이블로 답을 내는 것이다.

 

hdf5_file = "./image/5obj-model.hdf5"
if os.path.exists(hdf5_file):
	#기존에 학습된 모델 읽어 들이기
    model.load_weights(hdf5_file)
else:
	#학습한 모델을 파일로 저장하기
    model.fit(X_train, y_train, batch_size=32, nb_epoch=50)
    model.save_weights(hdf5_file)

 

keras로 학습한 모델을 저장하는 방법이다. Keras로 모델을 저장할때는 HDF5 형식의 데이터를 다루는 h5py 모듈을 사용한다.

'Machine Learning > 영상처리' 카테고리의 다른 글

이미지 OCR  (0) 2020.07.18
OpenCV로 얼굴인식하기  (0) 2020.07.17
규동 메뉴 이미지 판정하기  (0) 2020.07.16
유사한 이미지 판별 그리고 Average Hash  (0) 2020.07.15