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 изображений, что на самом деле не так уж и много.