스터디/AI

[Imple] 캐릭터 분류를 위한 컴퓨터비전 신경망 예제 심화 이미지 특성 추출사용

_leezoee_ 2023. 5. 25. 17:44

 

앞선 게시글에서는 이미지의 모든 픽셀을 신경망으로 보내는 실습을 했는데 이번 게시글에서는 이미지의 특성을 추출해 신경망으로 보내는 실습을 하고자 한다.

 

즉 Feature extractor 생성이 가장 큰 특징이라고 볼 수 있다.

 

데이터는 앞선 게시글과 같은 캐릭터 이미지를 활용했다.

 

먼저 필요한 라이브러리를 임포트

import cv2
import numpy as np
import os
import zipfile
from google.colab.patches import cv2_imshow
import tensorflow as tf
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt #그래프

 

다음은 전체 이미지에서 픽셀을 추출한다.

 

구글 드라이브에 마운트해 데이터를 가져온다.

#구글 드라이브 연결
from google.colab import drive
drive.mount('/content/drive')
path = '/content/drive/MyDrive/데이터파일.zip'
zip_object = zipfile.ZipFile(file = path, mode = 'r') #read모드
zip_object.extractall('./')
zip_object.close()
directory = '/content/homer_bart_1'
files = [os.path.join(directory, f) for f in sorted(os.listdir(directory))]
print(files)

 

 

새로운 변수를 생성해 각 특성의 이름을 입력한다. (해당 특성들은 파일의 헤더가 됨)

export = 'mouth,pants,shoes,tshirt,shorts,sneakers,class\n'

 

색상과 관련한 숫자를 입력할 features 변수를 생성한다.

show_images = False
features = []

각각의 이미지 확인

for image_path in files:
  
  #각 이미지 로드
  try:
    original_image = cv2.imread(image_path)
    (H, W) = original_image.shape[:2] #높이, 너비까지만 사용 채널X
  except:
    continue

  image = original_image.copy()
  image_features = []
  mouth = pants = shoes = 0
  tshirt = shorts = sneakers = 0

  image_name = os.path.basename(os.path.normpath(image_path))

  #클래스 정의
  if image_name.startswith('b'):
    class_name = 0
  else:
    class_name = 1

  #각각의 픽셀 살펴보기
  for height in range(0, H):
    for width in range(0, W):
      #세가지 색 채널 BGR
      blue = image.item(height, width, 0)
      green = image.item(height, width, 1)
      red = image.item(height, width, 2)

      # Homer - brown mouth 첫번째 특성
      if (blue >= 95 and blue <= 140 and green >= 160 and green <= 185 and red >= 175 and red <= 200):
        image[height, width] = [0, 255, 255] #brown mouth를 노란색으로 변경
        mouth += 1

      # Homer - blue pants 두번째 특성
      if (blue >= 150 and blue <= 180 and green >= 98 and green <= 120 and red >= 0 and red <= 90):
        image[height, width] = [0, 255, 255]
        pants += 1

      # Homer - gray shoes 세번째 특성
      if height > (H / 2): #이미지 하단부분만 활용
        if (blue >= 25 and blue <= 45 and green >= 25 and green <= 45 and red >= 25 and red <= 45):
          image[height, width] = [0, 255, 255]
          shoes += 1

      # Bart - orange t-shirt 첫번째 특성
      if (blue >= 11 and blue <= 22 and green >= 85 and green <= 105 and red >= 240 and red <= 255):
        image[height, width] = [0, 255, 128]
        tshirt += 1

      # Bart - blue shorts 두번째 특성
      if (blue >= 125 and blue <= 170 and green >= 0 and green <= 12 and red >= 0 and red <= 20):
        image[height, width] = [0, 255, 128]
        shorts += 1

      # Bart - blue sneakers 세번째 특성
      if height > (H / 2): #이미지 하단부분만 활용
        if (blue >= 125 and blue <= 170 and green >= 0 and green <= 12 and red >= 0 and red <= 20):
          image[height, width] = [0, 255, 128]
          sneakers += 1

  #이미지의 총 픽셀 수로 특성을 정규화
  mouth = round((mouth / (H * W)) * 100, 9)
  pants = round((pants / (H * W)) * 100, 9)
  shoes = round((shoes / (H * W)) * 100, 9)

  tshirt = round((tshirt / (H * W)) * 100, 9)
  shorts = round((shorts / (H * W)) * 100, 9)
  sneakers = round((sneakers / (H * W)) * 100, 9)

  image_features.append(mouth)
  image_features.append(pants)
  image_features.append(shoes)
  image_features.append(tshirt)
  image_features.append(shorts)
  image_features.append(sneakers)
  #클래스 추가
  image_features.append(class_name)

  #특성목록에 추가
  features.append(image_features)

  #print('Homer mouth: %s - Homer pants: %s - Homer shoes: %s' % (image_features[0], image_features[1], image_features[2]))
  #print('Bart t-shirt: %s - Bart shorts: %s - Bart sneakers: %s' % (image_features[3], image_features[4], image_features[5]))

  #string으로 연결
  f = (",".join([str(item) for item in image_features]))
  export += f + '\n'

  if show_images == True:
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) #opencv의 채널은 BGR 순서기때문에 이미지를 보려면 RGB로 변환해야한다.
    original_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)
    fig, im = plt.subplots(1, 2)
    im[0].axis('off')
    im[0].imshow(original_image)
    im[1].axis('off')
    im[1].imshow(image)
    plt.show()

 

실행결과 : 입이 노랗게 된 Homer(첫번째 특성 적용)
입,바지,신발색이 변경된 Homer(세가지 특성 다 적용)
입,바지,신발색이 변경된 Bart(세가지 특성 다 적용)

 

모든 특성 확인하기.

export

export 확인 결과

 

특성 결과 csv 로 저장하기.

with open('features.csv', 'w') as file:
  for l in export:
    file.write(l)
#파일이 제대로 생성되고 닫혔는지 확인    
file.closed

결과가 참이면 True 가 리턴 되고 features.csv 가 생성된다 (해당 경우는 구글 드라이브에 데이터 마운트 해두었어서 거기 경로에 생성됨)

 

 

데이터셋 변수를 생성해 해당 데이터 파일을 확인한다.

dataset = pd.read_csv('features.csv')
dataset

dataset 결과

 

 

 

다음으로 학습셋과 검증셋을 만들어준다.

#8:2 비율로 사용
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 1)

 

학습, 검증 데이터의 shape 확인

X_train.shape, y_train.shape

결과로 ((215,6) (215,)) 를 확인 할 수 있다.

X_test.shape, y_test.shape

결과로 ((54,6) (54,)) 를 확인 할 수 있다.

 

 

 

 

다음으로 신경망을 구현한다.

 

# 뉴런 수 : 6 -> 4 -> 4 -> 4 -> 1

network2 = tf.keras.models.Sequential()
#Bart 특성 세개, Homer 특성 세개 = 6
network2.add(tf.keras.layers.Dense(input_shape = (6,), units = 4, activation='relu'))
#입력값 6 + 출력값 2(클래스개수) / 2 = 4
network2.add(tf.keras.layers.Dense(units = 4, activation='relu'))
network2.add(tf.keras.layers.Dense(units = 4, activation='relu'))
network2.add(tf.keras.layers.Dense(units = 1, activation='sigmoid'))

 

신경망(네트워크) 확인하기.

network2.summary()

summary 결과 확인

 

 

모델 컴파일 진행.

network2.compile(optimizer='Adam', loss='binary_crossentropy', metrics = ['accuracy'])
history = network2.fit(X_train, y_train, epochs = 50)
history.history.keys()

리턴 값으로 dict_keys(['loss', 'accuracy']) 를 확인할 수 있다. 

 

 

 

먼저 손실값을 시각화해본다.

plt.plot(history.history['loss']);

손실 그래프

에포크 수에 비례해서 손실이 줄어듦을 볼 수 있다.

 

다음으로는 정확도를 시각화한다.

plt.plot(history.history['accuracy']);

정확도 그래프

에포크 수에 비례해서 정확도가 증가함을 볼 수 있다.

 

모델학습이 완료되었으니 X 검증 데이터셋을 이용해 모델을 평가한다.

predictions = network2.predict(X_test)
predictions

predictions 결과

 

 

 

각 확률값으로 리턴 되므로 임계값을 정의해서 클래스로 변환 시켜준다.

predictions = (predictions > 0.5)
predictions

변환결과

 

해당 결과를 기대출력과 비교해본다.

y_test

y_test 결과

 

 

sklearn 을 이용해 비교값의 정확도를 확인해본다.

from sklearn.metrics import accuracy_score
accuracy_score(y_test, predictions)

정확도 결과

 

이전 게시글에서 전체 픽셀로 비교했을때는 0.68을 기록했었는데 특성만 추출해서 진행하는 이번 실습이 약 20%정도 성능이 좋음을 볼 수 있다!

 

 

오차행렬 생성을 위해 변수를 생성해준다.

from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, predictions)

히트맵으로 시각화를 진행한다.

sns.heatmap(cm, annot=True);

히트맵

 

클래스 0은 Bart를 의미하고, 22개 이미지들이 정확하게 Bart로 분류되었고 6개가 Homer로 잘못분류되었음을 의미한다.

클래스 1은 Homer를 의미하고, 26개 이미지들이 정확하게 Homer로 분류되었고 0개가 Bart로 잘못분류되었음을 의미한다.(다맞았네)

 

다음으로 각 클래스별로 분석해본다.

from sklearn.metrics import classification_report
print(classification_report(y_test, predictions))

분석 결과

 

분석결과로는

클래스 0인 Bart 이미지에 대해 recall 0.79 로 신경망이 79%를 식별해내면서, 그에 대한 정확도 precision은 100%를 기록했고,

클래스 1인 Homer 이미지에 대해 recall 1.0 으로 신경망이 100%를 식별해내면서, 그에 대한 정확도 precision은 81%를 기록하였다.

 

 

이후 모델을 저장하고 필요 시 로드하면서 사용하면 된다.

 

먼저 위 모델을 저장한다.

model_json = network2.to_json()
with open('network2.json','w') as json_file:
  json_file.write(model_json)
from keras.models import save_model
network2_saved = save_model(network2, '/content/weights2.hdf5')

 

 

로드한다.

with open('network2.json', 'r') as json_file:
  json_saved_model = json_file.read()
json_saved_model
network2_loaded = tf.keras.models.model_from_json(json_saved_model)
network2_loaded.load_weights('weights2.hdf5')
network2_loaded.compile(loss = 'binary_crossentropy', optimizer='Adam', metrics=['accuracy'])

로드한 모델을 확인한다.

network2_loaded.summary()

 

summary 결과

 

 

테스트 데이터 구조를 확인한다.

test_image = X_test[0]
test_image.shape

 

(6,) 결과를 확인할 수 있는데, 벡터의 크기와 동일하지만 배치 형식에 맞추기 위해 이미지를 재구성해줘야한다.

 

test_image = test_image.reshape(1,-1)
test_image.shape

 

(1,6) 결과를 확인할 수 있다.

 

 

 

모델 예측(predict)진행.

#network2_loaded.predict(test_image) 행렬 형식이므로 [0][0]으로 출력값 확인
network2_loaded.predict(test_image)[0][0]
#클래스 따라 분류되어 출력되도록 분기
if network2_loaded.predict(test_image)[0][0] < 0.5:
  print('Bart')
else:
  print('Homer')

 

 

 

 

.