0. 마스크 착용/ 미착용 구분
지금은 많은 매장에서 체온측정 등의 입장절차가 자동화 되었다.
지난 1년간 마스크 착용이 의무화되고 당연시 되었지만, 아직까지 턱스크 등 부적절하게 마스크를 착용하는 사람들을 적지 않게 볼 수 있다.
개인 프로젝트로 사람 얼굴 이미지를 입력했을때 각각 마스크 착용/미착용을 구분하는 모델을 구축해 보았다.
1. 데이터
Quantity and Quality
데이터의 '양'과 함께 학습에 사용되는 데이터의 '질'역시 매우 중요하다.
모델 학습에 있어서 데이터의 양은 그만큼 모델에게 충분한 학습이 가능하게 해준다.
처음 모델을 구성할때 단순히 데이터의 '양'에 집중해서 이미지 데이터를 수집하였다.
구글에 단순히 face, man, masked face, KOVID mask 등의 키워드를 중심으로 데이터를 수집했다.
착용 500장, 미착용 500장 정도의 이미지를 준비했다.
이것 역시 너무나도 적은 양이 였지만, 나름의 시간과 비용의 타협점이었다.
처음으로 시도한 모델의 정확도는 60%정도의 낮은 정확도를 보였다.
그리고 이런 문제의 원인을 데이터의 질에 있다고 생각했다.
왼쪽이 기존에 500장 가량 수집한 데이터이다.
마구잡이로 수집하다보니 광고 이미지 등 마스크 착용/미착용이 확실히 구분되지 않는 이미지가 많이 포함되어 있었다.
다시 이미지들을 하나하나 확인하며 사람 얼굴과 마스크 착용 여부가 뚜렷히 구분되는 이미지들로 데이터를 구성했다.
데이터의 양은 300장으로 절반 가까이 양이 줄었지만, 모델의 성능은 보다 안정적이고 높은 정확도를 보였다.
Data Agumentation
앞서 데이터를 정제하며 줄어든 데이터의 양을 Keras의 ImageDataGenerator로 그 양을 보완하였다.
이 모듈을 사용하여 사진을 조금씩 회전시키거나 줌인을 하는 등 기존의 데이터를 이용하여 비슷한 이미지데이터들을 만들어 낼 수 있다.
* ImageDataGenerator로 어떤 이미지들이 만들어지는지 확인하기 위해 두개의 이미지를 입력하여 만들어지는 이미지들을 저장하는 코드이다.
import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img
import numpy as np
sample_gen = ImageDataGenerator(horizontal_flip = True,\
rotation_range = 35,
rescale = 1./255,
zoom_range = [0.7,1.5],
brightness_range = (0.7,1.0),
width_shift_range = 0.1,
height_shift_range = 0.1)
img_mask = load_img('image_path_1')
img_non = load_img('image_path_2')
mask_array = img_to_array(img_mask)
non_array = img_to_array(img_non)
print(mask_array.shape, non_array.shape)
non_array = non_array.reshape((1,)+non_array.shape)
mask_array = mask_array.reshape((1,)+mask_array.shape)
path = '이미지 저장 위치'
i= k = 0
for batch in sample_gen.flow(non_array, batch_size = 2, shuffle = False, save_to_dir = path, save_prefix = 'sample', save_format = 'jpg'):
i += 1
if i >= 10:
break
for batch in sample_gen.flow(mask_array, batch_size = 2, shuffle = False, save_to_dir = path, save_prefix = 'sample', save_format = 'jpg'):
k += 1
if k >= 10:
break
2. 모델링
모델은 VGG16의 전이학습을 통해 구현했다. Functional api를 사용하여 직접 CNN 모델을 구성해보기도 하였지만, 결과적으로 정확도는 전이학습 모델의 압승이였다.
CNN은 학습시키지 않고, 구성한 FC layer만 학습시키도록 하였다.
optimizer는 sgd를 사용하여 각 batch 단위로 학습하여 가중치값들을 갱신시켰다.
마지막 Dense 에서는 sigmoid 함수로 이미지를 분류하여 1에 가까운 값일 수록 마스크를 착용하지 않았다는 예측이다.
학습은 google colab을 통해 진행하였다.
import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras import layers, models
from tensorflow.keras.applications import VGG16
base = VGG16(weights= 'imagenet', include_top=False, input_shape = (150,150,3))
base.trainable = False
for layer in base.layers:
print('{} : {}'.format(layer, layer.trainable))
train_gen = ImageDataGenerator(horizontal_flip = True,\
rotation_range = 35,
rescale = 1./255,
zoom_range = [0.7,1.5],
brightness_range = (0.7,1.0),
width_shift_range = 0.1,
height_shift_range = 0.1)
#val, gen generator
VT_gen = ImageDataGenerator(rescale = 1./255)
batch_size = 20
#generator.flowfromdirectory
train_genorator = train_gen.flow_from_directory('/content/drive/My Drive/datamining/기말고사/Data/train',
target_size = (150,150), batch_size = batch_size, class_mode = 'binary')
val_genorator = VT_gen.flow_from_directory('/content/drive/My Drive/datamining/기말고사/Data/val', shuffle = False,
target_size = (150,150), batch_size = batch_size ,class_mode = 'binary')
test_genorator = VT_gen.flow_from_directory('/content/drive/My Drive/datamining/기말고사/Data/test', shuffle = False,
target_size = (150,150), batch_size = batch_size ,class_mode = 'binary')
model = models.Sequential()
model.add(base)
model.add(layers.BatchNormalization())
model.add(layers.Flatten())
model.add(layers.Dense(256, activation = 'relu'))
model.add(layers.Dropout(0.4))
model.add(layers.Dense(1, activation = 'sigmoid'))
sgd = tf.keras.optimizers.SGD(learning_rate= 1.e-4, momentum = 0.9)
model.compile(optimizer = sgd, loss = 'binary_crossentropy',metrics = ['accuracy'])
epochs = 100
hist = model.fit(train_genorator, batch_size = batch_size, epochs= epochs , validation_data = val_genorator,
steps_per_epoch= 300//batch_size)
3. Check Featuremaps
CNN은 이미지의 특징들을 추출하고 이러한 특징들을 통해서 FC layer를 통해 이미지 분류가 가능하다.
이미지가 CNN을 통과하며 어떤 특징들이 추출되는지 궁굼하여 확인해 보았다.
import tensorflow as tf
from tensorflow.keras.preprocessing.image import load_img, img_to_array, array_to_img
from tensorflow.keras.applications.vgg16 import preprocess_input, VGG16
from tensorflow.keras import models, layers
mask_img = load_img('image path', target_size = (150,150))
mask_array = img_to_array(mask_img)
mask_array = mask_array.reshape((1,)+mask_array.shape)
mask = preprocess_input(mask_array)
먼저 샘플로 사용할 이미지를 준비해준다.
cnn_base = VGG16(weights = 'imagenet',include_top=False, input_shape = (150,150,3))
layer_dict = dict([(layer.name, layer) for layer in cnn_base.layers])
find_layer = 'block1_conv2' #어디까지 통과된 featuremaps 확인할지 설정
model = models.Model(inputs = cnn_base.inputs, outputs = layer_dict[find_layer].output )
feature_maps = model.predict(mask)
CNN으로 사용할 VGG 모델을 가져온다.
각각의 layer의 이름을 dictionary 형태로 layer_dict에 저장하고, feature map을 확인하고자 하는 layer의 이름을 저장해놓는다. 여기서는 'block1_conv2' 첫 번째 블록의 2번째 layer을 선택하였다.
VGG16 모델은 총 5개의 블록이 있고 각각 2~3개의 합성곱층이 있다.
준비된 이미지를 설정된 layer에서 출력하면, 150 x 150 x 64의 feature maps를 받을 수 있다.
이제 이것을 pyplot으로 출력해주면 된다.
import matplotlib.pyplot as plt
filter_num = feature_maps.shape[-1]
step = filter_num // 4
idx = 1
for _ in range(0,filter_num,step): # 이미지 4개 추출
plt.figure()
plt.imshow(feature_maps[0,:,:,idx-1],cmap = 'gray')
idx+=1
plt.show()
64개 이미지를 다 출력하지않고 총 4개의 이미지만을 추출하였다.
이런식으로 layer를 바꿔가면서 각 블록의 각 layer에서의 feature map을 시각화 할 수 있다.
마스크 낀 내 사진을 넣어봤을때, 처음에는 사람 형상이 보이고 마지막에 갈수록 이미지에서 마스크의 특징만 남는것을 확인 할 수 있었다.
4. 임계값 조정
모델의 출력층은 sigmoid로 0~1사이의 확률값을 반환한다.
예측값 $\hat{Y}$가 0에 가까울수록 마스크를 착용한것, 1에 가까울수록 마스크를 착용하지 않은 것으로 판단한다.
하지만 모델의 출력값(predict proba)를 확인했을때, 마스크 착용에 대한 확률값은 0.3 이하의 값이 많은 안정된 분포를 보이지만, 마스크 미착용에 대한 값들은 0.4x 값을 포함해 확률 값의 분포가 더욱 넓었다.
따라서 마지막 결과를 출력해주는 코드에서 마스크 미착용에대한 임계값을 0.4로 조정하였다.
import numpy as np
def print_res(prob):
if prob >= 0.4: # 0.5로 조정햇을때, 미착용에 대한 오차가 많다...
print('predict proba: {}'.format(prob[0]))
print('마스크 미착용')
else:
print('predict proba: {}'.format(1-prob[0]))
print('마스크 정상 착용')
5. 결과출력
임계값 조정을 통해 마스크 착용과 미착용을 각각 잘 구분해 냈다...
애매한 상황에서의 실험을 해보았다.
턱스크는 마스크 미착용으로 분류를 했다. 하지만 코스크에서는 마스크 착용으로 구분하는 것 같다.
코밑으로 착용한 마스크도 얼굴의 많은 부분을 마스크가 가려서 오분류가 있는것 같다.
이를 보완하기 위해서는 많은 코스크 이미지를 마스크 미착용 학습데이터로 준비하고 모델을 재학습 시켜야 할 것이다.
턱수염 많은 하든 선수도 턱수염이 얼굴에서 차지하는 부분이 마스크와 같아서인지 마스크로 착용으로 분류를 하였다.
얼굴이 그려진 마스크를 착용해도 마스크를 착용한것으로 정상적으로 분류할 수 있었다.
6. 추가 사항
이제는 시중에 혹은 인터넷에 이러한 분류 코드가 많이 올라와있다.
그리고 YOLO를 사용하여 객체탐지를 활용한다면 실시간 영상에 대한 마스크 착용/미착용 분류또한 가능하다.
'Study > 프로젝트' 카테고리의 다른 글
중국어 성조 분석-학습 프로그램 (3) | 2021.02.24 |
---|