import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten, Conv2D, MaxPooling2D from tensorflow.keras.callbacks import EarlyStopping import keras import numpy as np import os import cv2 import matplotlib.pyplot as plt import random import pandas as pd
Using TensorFlow backend.
Распознавание маски лица
За последние несколько лет одна вещь, которая оказала огромное влияние на поведение и развитие нашего общества, — это самая последняя пандемия COVID-19. Для того, чтобы контролировать эту пандемию, представляет наибольший интерес и необходимость принятия ряда мер биобезопасности в повседневной жизни каждого из нас как члена этого общества. Одним из самых основных и важных из этих действий является добавление маски для лица в нашу повседневную одежду, чтобы остановить скорость распространения вируса.
Несмотря на то, насколько простым может показаться ношение маски, крайне удивительно, как много людей не желают сотрудничать с протоколами биобезопасности в этом отношении.
По этой причине очень удобно легко идентифицировать людей, которые неправильно соблюдают протокол маски для лица, например, в частных местах, таких как банки или рестораны, без необходимости проверки реальными людьми правильности ее использования. Исходя из этой предпосылки, мы собираемся построить нейронную сеть, которая попытается классифицировать, носит ли человек маску правильно или неправильно, или вообще ничего не носит.
Набор данных
Для этой цели мы будем использовать набор данных, содержащий 2079 фотографий людей в этих трех ситуациях. 703 из них носят маску неправильно, 690 человек носят маску правильно, а остальные 686 вообще не носят маски. Это будет использоваться для обучения нашей модели.
Метод
Хорошим первым подходом является использование указанных изображений в оттенках серого для простоты.
В общем, идея состоит в том, чтобы использовать значение интенсивности света для каждого пикселя изображения, чтобы найти распознаваемые шаблоны, которые идентифицируют каждый из классов для прогнозирования.
Зная это, мы перейдем к созданию глубокой нейронной сети, которая будет служить цели модели классификации, которая использует любое изображение человека и возвращает прогноз того, как он носит маску для лица в этот конкретный момент времени.
Предварительная обработка данных
Первым шагом для этого проекта будет загрузка 2079 изображений. Как мы уже упоминали, в качестве первого подхода мы не будем беспокоиться о цветах самого изображения, чтобы сделать модель максимально простой.
Здесь мы можем взглянуть на пример одной из картинок, которая будет использоваться для обучения нашей модели.
data_dir = "E:/Estudio/Python/dataset"
categories = ["incorrect_mask","with_mask","without_mask"]
for category in categories:
path = os.path.join(data_dir,category)
for img in os.listdir(path):
imgArray = cv2.imread(os.path.join(path,img),cv2.IMREAD_GRAYSCALE)
plt.imshow(imgArray,cmap = "gray")
plt.show()
break
break
Также важно отметить, что необходимо изменить форму каждого изображения до одинакового размера. В нашем случае мы изменим их размер до изображения 70x70.
Кроме того, необходимо идентифицировать каждый из возможных классов номерами для модели; исходя из этого, неправильное ношение маски будет представлено 0, правильное ношение маски будет представлено 1 и, наконец, отсутствие маски будет обозначено 2.
imgSize = 70
new_array = cv2.resize(imgArray, (imgSize, imgSize))
plt.imshow(new_array,cmap = "gray")
plt.show()
training_data = []
for category in categories:
path = os.path.join(data_dir,category)
labelNum = categories.index(category)
for img in os.listdir(path):
imgArray = cv2.imread(os.path.join(path,img),cv2.IMREAD_GRAYSCALE)
new_array = cv2.resize(imgArray, (imgSize, imgSize))
training_data.append([new_array,labelNum])
Теперь все изображения были успешно загружены. Однако важно отметить, что изображения были извлечены по порядку по каждой из категорий. Это нежелательно, поскольку мы не хотим, чтобы модель была смещена из-за порядка, в котором она получает точки данных.
По этой причине мы будем случайным образом перемешивать положение данных.
random.shuffle(training_data)
Для построения модели нам также необходимо разделить данные на прогностические или независимые переменные, которые в данном случае представляют собой просто ранее упомянутую интенсивность света каждого пикселя в каждом изображении, которое необходимо преобразовать в пригодные для использования размеры. И ответ или зависимая переменная, которая будет заданной меткой (0, 1 или 2).
X = [] y = []
for feats, label in training_data: X.append(feats) y.append(label) X = np.array(X).reshape(-1,imgSize,imgSize,1) y = np.array(y)
ERROR! Session/line number was not unique in database. History logging moved to new session 531
Построение модели в градациях серого
Теперь, когда наши данные хорошо обработаны и представлены в удобном для использования формате, мы приступим к построению исходной нейронной сети для классификации.
Нейронная сеть будет состоять из 490 входных переменных, по одной на каждый пиксель любого изображения; три скрытых слоя, два из которых будут сверточными слоями с 64 нейронами, а оставшийся скрытый слой будет простым плотным слоем, также образованным 64 отдельными нейронами. Наконец, у него будет 3 выходных нейрона, по одному для каждого класса.
В качестве функции активации будет использоваться «relu» для скрытых слоев и «softmax» для выходного слоя. Кроме того, мы решили использовать «адам» в качестве оптимального оптимизатора.
modelGrayScale = Sequential()
modelGrayScale.add(Conv2D(64,(4,4),input_shape = X.shape[1:]))
modelGrayScale.add(Activation("relu"))
modelGrayScale.add(MaxPooling2D(pool_size = (2,2)))
modelGrayScale.add(Conv2D(64,(3,3),input_shape = X.shape[1:]))
modelGrayScale.add(Activation("relu"))
modelGrayScale.add(MaxPooling2D(pool_size = (2,2)))
modelGrayScale.add(Flatten())
modelGrayScale.add(Dense(64))
modelGrayScale.add(Activation("relu"))
modelGrayScale.add(Dense(3))
modelGrayScale.add(Activation("softmax"))
modelGrayScale.compile(loss = "sparse_categorical_crossentropy",
optimizer = "adam",
metrics = ["accuracy"])
Отметим, что мы не разделяли данные заранее на обучение и тестирование. Вместо этого мы просто создадим новый проверочный набор данных с 20% доступных данных, чтобы рассчитать точность и потери нашей модели, чтобы проверить, насколько хороша производительность указанной модели, поскольку мы соответствуем модели.
Чтобы избежать переобучения нашей модели по данным обучения, будет реализован обратный вызов ранней остановки, чтобы остановить обучение, когда прошло 8 итераций модели с момента достижения наилучшей зарегистрированной потери проверки.
early_stop = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=8) modelGrayScale.fit(X, y, batch_size = 50, validation_split = 0.2, epochs = 100, callbacks = [early_stop] )
Train on 1663 samples, validate on 416 samples Epoch 1/100 1663/1663 [==============================] - 3s 2ms/sample - loss: 16.2333 - accuracy: 0.4540 - val_loss: 0.7993 - val_accuracy: 0.5962 Epoch 2/100 1663/1663 [==============================] - 1s 371us/sample - loss: 0.6225 - accuracy: 0.7450 - val_loss: 0.7508 - val_accuracy: 0.7043 Epoch 3/100 1663/1663 [==============================] - 1s 370us/sample - loss: 0.4193 - accuracy: 0.8509 - val_loss: 0.5937 - val_accuracy: 0.7933 Epoch 4/100 1663/1663 [==============================] - 1s 363us/sample - loss: 0.2542 - accuracy: 0.9038 - val_loss: 0.4802 - val_accuracy: 0.8413 Epoch 5/100 1663/1663 [==============================] - 1s 370us/sample - loss: 0.1256 - accuracy: 0.9627 - val_loss: 0.5305 - val_accuracy: 0.8678 Epoch 6/100 1663/1663 [==============================] - 1s 372us/sample - loss: 0.0797 - accuracy: 0.9753 - val_loss: 0.5680 - val_accuracy: 0.8606 Epoch 7/100 1663/1663 [==============================] - 1s 370us/sample - loss: 0.0702 - accuracy: 0.9814 - val_loss: 0.5371 - val_accuracy: 0.8822 Epoch 8/100 1663/1663 [==============================] - 1s 370us/sample - loss: 0.0918 - accuracy: 0.9717 - val_loss: 0.5455 - val_accuracy: 0.8774 Epoch 9/100 1663/1663 [==============================] - 1s 368us/sample - loss: 0.0449 - accuracy: 0.9904 - val_loss: 0.6188 - val_accuracy: 0.8630 Epoch 10/100 1663/1663 [==============================] - 1s 370us/sample - loss: 0.0333 - accuracy: 0.9904 - val_loss: 0.6191 - val_accuracy: 0.8726 Epoch 11/100 1663/1663 [==============================] - 1s 367us/sample - loss: 0.0925 - accuracy: 0.9790 - val_loss: 0.6516 - val_accuracy: 0.8726 Epoch 12/100 1663/1663 [==============================] - 1s 364us/sample - loss: 0.0475 - accuracy: 0.9904 - val_loss: 0.6291 - val_accuracy: 0.8678 Epoch 00012: early stopping <tensorflow.python.keras.callbacks.History at 0x1fe23b37748>
Похоже, что модель работает относительно хорошо со значением точности, превышающим 86%. Это означает, что всякий раз, когда наша модель получает новое изображение человека, очень вероятно, что она успешно распознает фактическое состояние человека в отношении лицевой маски.
Построение модели в цветовом масштабе
Напомним, что до этого момента мы реализовывали модель, которая не учитывала цвет при прогнозировании класса изображения. Однако разумно полагать, что цвет в целом будет представлять собой очень важную характеристику, позволяющую определить, правильно ли человек носит маску или нет.
for category in categories:
path = os.path.join(data_dir,category)
for img in os.listdir(path):
imgArray = cv2.imread(os.path.join(path,img))
plt.imshow(imgArray)
plt.show()
break
break
new_array = cv2.resize(imgArray, (imgSize, imgSize))
plt.imshow(new_array)
plt.show()
На этих изображениях мы видим, насколько заметен цветовой контраст между кожей человека и маской, которую он носит. По этой причине добавление цвета может быть хорошей идеей для улучшения нашей модели.
В качестве примечания: цвета выглядят странно из-за позиции, которую python внутренне присваивает значениям RGB.
Сам процесс аналогичен тому, что мы видели ранее для шкалы серого. Однако теперь данных будет больше, поскольку нам также нужно хранить значения RGB для каждой фотографии.
training_data = [] for category in categories: path = os.path.join(data_dir,category) labelNum = categories.index(category) for img in os.listdir(path): imgArray = cv2.imread(os.path.join(path,img)) new_array = cv2.resize(imgArray, (imgSize, imgSize)) training_data.append([new_array,labelNum])
random.shuffle(training_data)
X = [] y = []
for feats, label in training_data: X.append(feats) y.append(label) X = np.array(X).reshape(-1,imgSize,imgSize,3) y = np.array(y)
modelColorScale = Sequential() modelColorScale.add(Conv2D(64,(3,3),input_shape = X.shape[1:])) modelColorScale.add(Activation("relu")) modelColorScale.add(MaxPooling2D(pool_size = (2,2))) modelColorScale.add(Conv2D(64,(3,3),input_shape = X.shape[1:])) modelColorScale.add(Activation("relu")) modelColorScale.add(MaxPooling2D(pool_size = (2,2))) modelColorScale.add(Flatten()) #modelColorScale.add(Dense(64)) #modelColorScale.add(Activation("relu")) modelColorScale.add(Dense(3)) modelColorScale.add(Activation("softmax")) modelColorScale.compile(loss = "sparse_categorical_crossentropy", optimizer = "adam", metrics = ["accuracy"])
early_stop = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=8) modelColorScale.fit(X, y, batch_size = 50, validation_split = 0.2, epochs = 100, callbacks = [early_stop] )
Train on 1663 samples, validate on 416 samples Epoch 1/100 1663/1663 [==============================] - 1s 412us/sample - loss: 1.7818e-04 - accuracy: 1.0000 - val_loss: 0.3980 - val_accuracy: 0.9255 Epoch 2/100 1663/1663 [==============================] - 1s 366us/sample - loss: 1.1254e-04 - accuracy: 1.0000 - val_loss: 0.3961 - val_accuracy: 0.9303 Epoch 3/100 1663/1663 [==============================] - 1s 369us/sample - loss: 9.2857e-05 - accuracy: 1.0000 - val_loss: 0.3959 - val_accuracy: 0.9327 Epoch 4/100 1663/1663 [==============================] - 1s 379us/sample - loss: 7.9931e-05 - accuracy: 1.0000 - val_loss: 0.3964 - val_accuracy: 0.9279 Epoch 5/100 1663/1663 [==============================] - 1s 366us/sample - loss: 7.0189e-05 - accuracy: 1.0000 - val_loss: 0.3962 - val_accuracy: 0.9303 Epoch 6/100 1663/1663 [==============================] - 1s 405us/sample - loss: 6.1895e-05 - accuracy: 1.0000 - val_loss: 0.3962 - val_accuracy: 0.9303 Epoch 7/100 1663/1663 [==============================] - 1s 410us/sample - loss: 5.5847e-05 - accuracy: 1.0000 - val_loss: 0.3961 - val_accuracy: 0.9327 Epoch 8/100 1663/1663 [==============================] - 1s 411us/sample - loss: 5.0953e-05 - accuracy: 1.0000 - val_loss: 0.3960 - val_accuracy: 0.9327 Epoch 9/100 1663/1663 [==============================] - 1s 451us/sample - loss: 4.5965e-05 - accuracy: 1.0000 - val_loss: 0.3959 - val_accuracy: 0.9327 Epoch 10/100 1663/1663 [==============================] - 1s 387us/sample - loss: 4.2279e-05 - accuracy: 1.0000 - val_loss: 0.3960 - val_accuracy: 0.9327 Epoch 11/100 1663/1663 [==============================] - 1s 395us/sample - loss: 3.9289e-05 - accuracy: 1.0000 - val_loss: 0.3961 - val_accuracy: 0.9327 Epoch 00011: early stopping <tensorflow.python.keras.callbacks.History at 0x1fe23fc2710>
Похоже, что модель уже работает немногим лучше, чем первоначальная, которая работала с серой шкалой, с точностью проверки около 93%. Тем не менее, мы хотим визуализировать и сравнить производительность как для моделей в градациях серого, так и для моделей в цветовой гамме, используя точность и потери, чтобы сделать вывод, какой из них на самом деле является лучшим вариантом для нас.
metrics_color = pd.DataFrame(modelColorScale.history.history) metrics_gray = pd.DataFrame(modelGrayScale.history.history)
from matplotlib.lines import Line2D plt.figure(figsize = (12,8)) plt.plot(metrics_color[["val_accuracy"]],"-b") plt.plot(metrics_gray[["val_accuracy"]],"--b") plt.plot(metrics_color[["val_loss"]],"-r") plt.plot(metrics_gray[["val_loss"]],"--r") lines = [Line2D([0], [0], color="blue", linewidth=3, linestyle='--'), Line2D([0], [0], color="blue", linewidth=3, linestyle='-'), Line2D([0], [0], color="red", linewidth=3, linestyle='--'), Line2D([0], [0], color="red", linewidth=3, linestyle='-')] labels = ["Val_accuracy_gray_scale","Val_accuracy_color_scale", "Val_loss_gray_scale","Val_loss_color_scale"] plt.legend(lines,labels)
<matplotlib.legend.Legend at 0x1fe1d4aeeb8>
Пунктирные линии представляют показатели производительности для модели в шкале серого, а сплошные линии представляют показатели производительности для модели в цветовой шкале. В общем, желательно иметь более высокое значение точности при максимально возможном снижении потерь.
Исходя из этого, мы можем видеть, что, во-первых, точность проверки для модели в цветовой шкале выше, а, во-вторых, ее потери при проверке ниже, чем для модели в шкале серого. По этой причине мы можем с уверенностью заключить, что лучшая модель из этих двух — это та, которая также реализует цвет изображения.
Выводы
- Как мы уже обсуждали, кажется, что использование цвета для этой конкретной модели значительно увеличивает прогностическую силу модели.
- Несмотря на то, что модель работает достаточно хорошо, вполне возможно, что ее можно продвинуть еще дальше, чтобы получить лучшие результаты; рекомендуется подобрать набор разных моделей с разными параметрами, чтобы окончательно выбрать лучшую (например, с помощью тензорной доски).
- В то же время может быть полезно увеличить количество изображений, которые мы используем для обучения модели. Давайте вспомним, что в настоящее время у нас есть около 2000 изображений, что на самом деле не так уж и много.