Первое, что нам понадобится это датасет (выборка фотографий) людей с масками и без для дообучения нейросети MobileNetV2, которая находится в открытом доступе. У меня был датасет в количестве 981 фотографий людей в масках и столько же без, одних и тех же людей.
Хотелось бы отметить один важный момент, что нейросеть MobileNetV2, можно использовать практически для любой классификации, например, можно было дообучить её для определения пола, или попробовать автоматически определять в очках человек или нет, именно поэтому мы замораживаем все базовые слои модели, а в верхний слой подаём то, что нужно классифицировать. Но мы остановимся на поиске медицинской маски, как наиболее актуальной в настоящее время.
Итак, разместим наш датасет из 1962 фотографий в двух каталогах в папке dataset в масках в WithMask и без маски в Withoutmask соответственно. В каждой по 981 фотографии. Ещё одно важное замечание, это то, что дообучаем мы именно на лицах, а не просто, что человек на изображении в маске или без, хотя можно было и так.
Далее импортируем необходимые библиотеки:
from tensorflow.keras.preprocessing.image import ImageDataGeneratorfrom tensorflow.keras.applications import MobileNetV2from tensorflow.keras.layers import AveragePooling2Dfrom tensorflow.keras.layers import Dropoutfrom tensorflow.keras.layers import Flattenfrom tensorflow.keras.layers import Densefrom tensorflow.keras.layers import Inputfrom tensorflow.keras.models import Modelfrom tensorflow.keras.optimizers import Adamfrom tensorflow.keras.applications.mobilenet_v2 import preprocess_inputfrom tensorflow.keras.preprocessing.image import img_to_arrayfrom tensorflow.keras.preprocessing.image import load_imgfrom tensorflow.keras.utils import to_categoricalfrom sklearn.preprocessing import LabelBinarizerfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import classification_reportfrom imutils import pathsimport matplotlib.pyplot as pltimport numpy as npimport argparseimport os
# Указываем начальные гиперпараметры
Первый это скорость обучения, он означает, с какой скоростью мы двигаемся по направлению к минимуму функции потерь
INIT_LR = 0,004
Второй это количество эпох, одна эпоха это один проход обучения на всем наборе данных
EPOCHS = 20
Третий это размер пакета или батча, означает количество данных в одной партии.
BS = 32
Подбор гиперпараметров лежит за пределами данной статьи, скажем только то, что нет универсального способа, и в каждом случае надо их подбирать эмпирически.
imagePaths = list(paths.list_images (r'C:\dataset')) # В этой папке хранятся два каталога с масками и безdata , labels = [] , []for imagePath in imagePaths:# Извлечение класса из директории (с маской или без)label = imagePath.split(os.path.sep)[-2]# Загружам входное изображение 224х224 и обрабатываем егоimage = load_img(imagePath, target_size = (224, 224))image = img_to_array(image)image = preprocess_input(image)# Обновляем список файлов и классовdata.append(image)labels.append(label)# Переводим в NumPy массивdata = np.array(data, dtype="float32")labels = np.array(labels)# Переводим классы в бинарный вид, т.е. 0 без маски 1 с маскойlb = LabelBinarizer()labels = lb.fit_transform(labels)labels = to_categorical(labels)# Разобьём датасет на тренировочный и тестовый 80% на 20%;(trainX, testX, trainY, testY) = train_test_split(data, labels, test_size = 0.20, stratify = labels, random_state = 42)# Аугментация датасета путем поворота изображенийaug = ImageDataGenerator(rotation_range = 20, zoom_range = 0.15,width_shift_range = 0.2, height_shift_range = 0.2, shear_range=0.15, horizontal_flip = True, fill_mode = "nearest")# Загружаем базовую модель c предварительно обученными весамиpath_weights = mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224_no_top.h5' baseModel = MobileNetV2(weights=path_weights, include_top=False, input_tensor=Input(shape=(224, 224, 3))Запишем в нашу модель внешний слой из базовой моделиheadModel = baseModel.outputheadModel = AveragePooling2D(pool_size = (7, 7))(headModel)headModel = Flatten(name = "flatten")(headModel)headModel = Dense(128, activation = "relu")(headModel)headModel = Dropout(0.5)(headModel)headModel = Dense(2, activation = "softmax")(headModel)model = Model(inputs = baseModel.input, outputs = headModel)# Заморозка слоев базовой модели for layer in baseModel.layers:layer.trainable = False# Скомпилируем нашу модельopt = Adam(lr = INIT_LR, decay = INIT_LR / EPOCHS)model.compile(loss = "binary_crossentropy", optimizer = opt, metrics = ["accuracy"])# Тренируем нашу сетьH = model.fit( aug.flow(trainX, trainY, batch_size = BS), steps_per_epoch = len(trainX) // BS,validation_data = (testX, testY), validation_steps = len(testX) // BS, epochs = EPOCHS)# Делаем предсказание на тестовой выборкеpredIdxs = model.predict(testX, batch_size = BS)# Для каждого изображения в тестовом наборе, ищем максимальную вероятностьpredIdxs = np.argmax(predIdxs, axis=1)# Показать отчет обучения print(classification_report(testY.argmax(axis = 1), predIdxs, target_names = lb.classes_))
# Сохраняем модель на диск и загружаем её
model.save('model_mask_FACE', save_format = "h5")model_mask = tf.keras.models.load_model('model_mask_FACE)
Поиск маски на лице, на примере
# Найдем лицо на изображении, используя библиотеку MTCNN
frame = cv2.cvtColor(cv2.imread(house.png'), cv2.COLOR_BGR2RGB)frame_image = Image.fromarray(frame)boxes, probs, landmarks = mtcnn.detect(frame_image, landmarks = True)x1, y1, x2, y2 = [int(bx) for bx in boxes[0]]image = Image.fromarray(frame[y1:y2, x1:x2]).resize((224,224))face = img_to_array(image)
# Обработка изображения для загрузки в модель
face = preprocess_input(face)face = np.expand_dims(face, axis=0)
# Загрузка лица в нашу модель
(mask, withoutMask) = model_mask.predict(face)[0]image = cv2.imread(house.png)
# Прорисовка рамки и вероятности
if mask > withoutMask and max(mask, withoutMask) > 0.8: # уверенность label = "Mask" if mask > withoutMask else "No Mask" color = (0, 122, 0) if label == "Mask" else (0, 0, 122) label = "{}: {:.2f}%".format(label, max(mask, withoutMask) * 100) cv2.putText(image, label, (x1, y1 - 10),cv2.FONT_HERSHEY_SIMPLEX, 2, color, 5) cv2.rectangle(image, (x1, y1), (x2, y2), color, 5) y = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
Итак, в данной статье мы показали, как дообучить нейросеть MobileNetV2, чтобы классифицировать изображения лиц людей на находящихся в медицинских масках и без них.