Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
В условиях пандемия COVID-19 регулирующие государственные органы устанавливают обязательные требования по ношению средств индивидуальной защиты сотрудниками организаций, обслуживающих клиентов.
Несоблюдение сотрудниками защитных мер по ношению масок приводит к штрафным санкциям со стороны регулятора и остановке бизнеса в связи с заболеванием сотрудников.
Методы Computer Vision позволяет эффективно контролировать соблюдение масочного режима в массовых точках обслуживания клиентов.
В одной из прошлых статей мы рассказывали, как с помощью асинхронных http-запросов увеличить скорость загрузки скриншотов с серверов телевизионных систем видеонаблюдения. Сегодня расскажем, как анализировать скриншоты - находить на изображении человека, классифицировать его, как сотрудника, находить лицо и классифицировать – есть на нем маска или нет.
Существующие практики решений похожих задач не учитывают следующие особенности:
· Распознавание маски только на определенной категории людей. В данном случае рассматриваются сотрудники компании, чей стиль одежды визуально отличается от клиентов этой компании.
· Работа с изображениями различного качества, иногда очень низкого.
Решение нашей задачи возможно с использованием нейронных сетей двух типов для детекции и классификации.
Мы использовали комплекс из 4-х нейронных сетей 3-х различных архитектур:
- YOLOv3 - для детекции людей на изображении,
- MTCNN - для детекции лиц,
- ResNet101- для классификации.
Классификаторы архитектуры ResNet101 дополнительно были дообучены на данных, полученных с серверов вендора Trassir. Для обучения классификатора «сотрудник»-«клиент» использовали dataset из 5000 фотографий: по 2500 сотрудников и клиентов. Для обучения классификатора «в маске»-«без маски» dataset из 400 фотографий: 200 – в масках, 200 – без масок.
В качестве loss функции взята кросс энтропия, оптимизатор – Adam с параметрами:
планировщик – StepLR с параметрами: step_size = 20, gamma = 0,5. Значения функций потерь в зависимости от количества эпох приведены на рисунках ниже.
Все приведенные выше модели запускаются скриптом (https://github.com/Vitaliy1234/mask_reco_v1), написанном на языке Python3. Код обучения также расположен по этой ссылке.
Схема работы комплекса следующая:
1. Исходные изображения подвергаются предобработке и подаются на вход YOLOv3. После отработки этой сети мы получим координаты всех обнаруженных объектов, а также разбиение этих объектов на классы. Из всех детектированных объектов нам нужны только объекты класса «человек», поэтому выбираем только изображения с людьми. Ниже показан фрагмент функции detection_loop().
def detection_loop(im_batches,
CUDA,
model,
confidence,
num_classes,
nms_thresh,
im_list,
batch_size,
classes):
write = 0
for i, batch in enumerate(im_batches):
# load the image
start = time.time()
if CUDA:
batch = batch.cuda()
with torch.no_grad():
prediction = model(Variable(batch), CUDA)
2. Следующий этап – классификация людей на сотрудников и клиентов. Эту задачу решает бинарный классификатор, использующий архитектуру ResNet101. На вход ему подается фрагмент исходного изображения, на котором находится человек.
def empl_cl_classifier(model_ft, out_yolo, ims_transformed, data_transforms):
"""
Evaluate employee-client classifier
:param model_ft: employee-client classifier
:param out_yolo: people detections from yolov3
:param ims_transformed: images transformed to pytorch format
:param data_transforms: torch transforms for images
:return result: list with relative (person is employee) yolo detections
"""
result = []
result_cl = 0
with torch.no_grad():
for output in out_yolo:
c1 = tuple(output[1:3].int())
c2 = tuple(output[3:5].int())
img = ims_transformed[int(output[0])]
# crop person from image
person = img[c1[1]:c2[1], c1[0]:c2[0]]
if person.shape[0] == 0 or person.shape[1] == 0:
continue
person_tr = data_transforms(Image.fromarray(person)).unsqueeze(0)
person_tr = person_tr.cuda()
start_empl_cl = time.time()
output_person = model_ft(person_tr)
_, preds = torch.max(output_person, 1)
print('Employee/client classifier took:', time.time() - start_empl_cl, preds)
if preds == 1: # if person is employee
result.append(output)
else:
result_cl += 1
return result, result_cl
3. Дальше изображения сотрудников подаются на вход нейронной сети архитектуры MTCNN, которая ищет лицо. По результатам детекции мы получаем координаты всех обнаруженных лиц, а также соответствующие значения confidence. Фильтруем лица по confidence, чтобы отсеять неверно детектированные объекты
def start_mtcnn(model_mtcnn, employees, ims_transformed):
"""
Starts mtcnn for face detection
:param model_mtcnn: mtcnn model
:param employees: detections from yolo with only employees
:param ims_transformed: images transformed to pytorch format
:return result: list with tuples (detection with employee, bounding box with face)
"""
result = []
with torch.no_grad():
for empl_detection in employees:
c1 = tuple(empl_detection[1:3].int())
c2 = tuple(empl_detection[3:5].int())
employee = ims_transformed[int(empl_detection[0])][c1[1]:c2[1], c1[0]:c2[0]]
start_time = time.time()
try:
result_face_detect = model_mtcnn.detect(employee)
except:
continue
print('MTCNN took', time.time() - start_time)
if result_face_detect[0] is not None: # if face was found
indx = argmax(result_face_detect[1])
max_conf = result_face_detect[1][indx]
b_box_max = result_face_detect[0][indx] # bbox with max confidence
if max_conf >= FACE_CONFIDENCE: # filter faces through threshold
result.append((empl_detection, b_box_max))
return result
4. И, наконец, изображения лиц подаются на вход второму бинарному классификатору архитектуры ResNet101, который определяет – лицо в маске или нет.
def start_mask_classifier(model_mask, empl_faces, ims_transformed, data_transforms):
"""
Starting mask classifier
:param model_mask:
:param empl_faces:
:param ims_transformed:
:return result: list of tuples (employee_detection, face_bounding_box)
"""
result_detections = []
result_b_boxes = []
with torch.no_grad():
for empl_detection, b_box_max in empl_faces:
c1 = tuple(empl_detection[1:3].int())
c2 = tuple(empl_detection[3:5].int())
cur_index = int(empl_detection[0])
employee = ims_transformed[cur_index][c1[1]:c2[1], c1[0]:c2[0]]
x1_face, y1_face = max(int(b_box_max[0]), 0), max(int(b_box_max[1]), 0)
x2_face, y2_face = max(int(b_box_max[2]), 0), max(int(b_box_max[3]), 0)
face_img = employee[y1_face:y2_face, x1_face:x2_face]
face_tr = data_transforms(Image.fromarray(face_img)).unsqueeze(0)
face_tr = face_tr.cuda()
start = time.time()
output_mask = model_mask(face_tr)
print('Mask model took:', time.time() - start)
_, preds_mask = torch.max(output_mask, 1)
if preds_mask == 1: # if class is "no mask"
result_detections.append(empl_detection)
cur_list_b_boxes = [cur_index]
cur_list_b_boxes.extend(b_box_max)
result_b_boxes.append(cur_list_b_boxes)
return result_detections, result_b_boxes
Все четыре сети были реализованы на фреймворке PyTorch. На выходе мы получаем только те изображения, где были детектированы лица сотрудников без масок. На каждом таком изображении лицо сотрудника выделяется красной рамкой с надписью «No_mask». Весь комплекс работает на машине со следующими характеристиками:
Процессор: Intel Core i7-8700
Объем оперативной памяти: 32 ГБ
Видеокарта: Nvidia GeForce 1080Ti
При тестировании системы были получены следующие результаты:
Precision = 0,95
Recall = 0,43
F1 = 0,59
В итоге у нас получилась система, которая обрабатывает 15 скриншотов в секунду, помогая выявлять случаи нарушения масочного режима. Таким образом, не тратятся человеческие ресурсы на просмотр видео с камер наблюдения.