Привет, хабровчане! В EastBanc Technologies ведётся большое количество проектов, связанных с мобильной разработкой. В связи с чем необходим целый зоопарк устройств для тестирования на всех этапах. И, что характерно, каждый отдельный девайс постоянно оказывается нужен самым разным людям, а найти его даже в одном отделе мобильной разработки из нескольких десятков человек — это целая история. Не говоря уже о том, что есть тестировщики, дизайнеры, PM’ы, в конце концов!

И чтобы не потерять телефон, а четко знать, где он и с кем, мы используем онлайн-базу, которая распознает сотрудников по лицам. Сейчас расскажем, как мы к этому пришли и реализовали её.

image

Исторический контекст


У нас была доска с «карточками» устройств с основной информацией и местом под магнитик, обозначающим сотрудника. Каждый отмечался о взятии устройства.

image

Эта система имеет свои недостатки — не критичные, но в целом неудобные:

  • Магнитики не так легко переместить с одного места на другое.
  • Чтобы посмотреть на такую доску, обязательно придётся идти в другой кабинет.
  • А ещё кому-то может понадобиться сразу много устройств… А значит, нужно несколько магнитиков на одного сотрудника.
  • Ах да, ещё сотрудники иногда увольняются и приходят новые, на которых тоже нужно сделать магнитики.

Мобильное приложение


В компании, которая занимается автоматизацией бизнес-процессов, использовать описанное выше «аналоговое» решение — не очень здорово. Естественно, мы решили автоматизировать задачу поиска нужного устройства. Первым шагом стало написание мобильного приложения, которое умеет определять и сообщать о своем местоположении в комнатах по Wi-Fi точкам доступа. Попутно для удобства наделили девайсы умением сообщать на сервер о версии ОС, а также показывать такую важную характеристику, как заряд батареи.

Казалось бы, задача решена. Смотришь на список в базе данных, где последний раз устройство видело Wi-Fi, идёшь туда и…

В эксплуатации оказалось, что не всё так просто. Мы установили приложение на тестовые устройства и поработали с ним несколько месяцев. Оказалось, что такой вариант удобен, но тоже не идеален.

Устройства разряжаются, просто выключаются, точки доступа Wi-Fi переставляются из одного места в другое, а геолокация сама по себе говорит только о том, что устройство находится в офисе. Спасибо, капитан!

Можно, конечно, пытаться оптимизировать существующую систему, но почему не переизобрести её на основе технологий ХХI века? Сказано — сделано.

Как мы хотели, чтобы это было


Мы придумали концепт системы, которая бы распознавала сотрудников по лицам, тестовые устройства — по специальным меткам, запрашивала бы подтверждение смены статуса устройства, а потом вносила изменения в онлайн-базу, которую любой сотрудник может посмотреть, не вставая с кресла.

Распознавание лиц


Распознавание лиц в целом решенная задача в 2018 году. Поэтому мы не стали изобретать велосипед и пытаться обучать собственные модели, а воспользовались готовым решением. Самым удобным вариантом показался модуль FaceRecognition, т.к. он не требует дообучения и работает весьма быстро даже без ускорения на GPU.

С помощью функции face_locations на фотографиях сотрудников обнаруживались лица, а с помощью face_encodings из них извлекались признаки лица конкретного сотрудника.

Полученные данные собирались в базу. Для определения конкретного сотрудника с помощью функции face_distance считалась «разница» между кодировкой обнаруженного сотрудника и кодировками из базы.

В целом, на этом этапе можно было пойти дальше и создать классификатор, например, на основе KNN, чтобы система была менее чувствительна к динамике лиц сотрудников. Однако на практике это требует значительно больших временных затрат. Да и банального усреднения кодировки лица человека между той, что сейчас в базе, и той, которую система обнаружила для смены статуса устройства, оказалось уже достаточно, чтобы на практике избегать накопления ошибки.

Код распознавания лиц
face_locations = face_recognition.face_locations(rgb_small_frame)
face_encodings = face_recognition.face_encodings(rgb_small_frame, face_locations)

face_names = []
for face_encoding in face_encodings:
    matches = face_recognition.face_distance(
         known_face_encodings, face_encoding)
    name = "Unknown"

    if np.min(matches) <= 0.45:
         best_match_index = np.argmin(matches)
         name = known_face_info[str(best_match_index)]['name']
    else:
         best_match_index = None


Распознавание устройств


Изначально возникла мысль использовать для распознавания устройств QR-коды, в которые вносить заодно и информацию об устройстве. Однако для устойчивого распознавания QR-кода его приходилось подносить очень близко к камере, что неудобно.

В результате возникла мысль об использовании маркеров дополненной реальности. Они несут меньше информации, зато гораздо более устойчиво распознаются. В ходе экспериментов маркер размером в 30 миллиметров распознавался камерой при небольших отклонениях от вертикали (3-5 градусов) на расстоянии до двух с половиной метров.

Такой вариант выглядел уже куда лучше. Из всего множества маркеров дополненной реальности были выбраны ARuco, т.к. все необходимые инструменты для работы с ними присутствуют в поставке opencv-contrib-python.

Код распознавания маркеров ARuco
self.video_capture = cv2.VideoCapture(0)
ret, frame = self.video_capture.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
markers = cv2.aruco.detectMarkers(gray, self.dictionary)


В итоге, каждому устройству был присвоен числовой индекс, с которым сопоставлялся и маркер с этим индексом.

Дело в шляпе?


Казалось бы, мы научились распознавать устройства и лица, работа сделана. Фанфары, овации! Что ещё может быть нужно?

На самом деле, работа только начинается. Теперь все компоненты системы нужно заставить стабильно и быстро работать «на бою».

Нужно оптимизировать затраты ресурсов сервера в idle’е, продумать юзкейсы и понять, как это вообще должно выглядеть графически.

Интерфейс


Едва ли не самым важным пунктом в разработке подобных систем является интерфейс. Кто-то возможно будет спорить, но пользователь — центральный элемент в такой ситуации.

Максимально быстро можно реализовать фронтенд-часть с помощью Tkinter.

Несколько замечаний о Tkinter
  • Обращайте внимание на то, в каких единицах выставляются отступы/размеры элементов (относительных или абсолютных).
  • Помните, что относительные и абсолютные единицы можно использовать вместе (их значения просто просуммируются).
  • Параметр
    .attributes("-fullscreen", True)
    позволяет сделать однооконное приложение, разворачивающееся на весь экран, чтобы не смущать пользователей элементами интерфейса системы.

Интерфейс состоит из карточек с информацией об устройстве и пользователе, использующем в текущий момент это устройство. БОльшую часть экрана занимает каталог карточек — основной инструмент учета. Сверху находится фильтр, с помощью которого можно отфильтровать каталог по платформе или версии операционной системы.

image

Вот ключевые компоненты интерфейса:


  • Устройство
    На экран выводятся карточки устройств с указанием версии операционной системы, названия и ID устройства, а также пользователя, на котором это устройство сейчас зарегистрировано.
  • Фотофиксирование
    Справа находится блок управления, где выводится изображение с веб-камеры, а также кнопки для регистрации и редактирования персональной информации.

    Изображение выводим, чтобы дать пользователю обратную связь — ты точно попал в экран меткой устройства.
  • Выбор версии ОС
    Мы сделали список с выбором интересующей версии ОС, т.к. часто для тестирования нужен не конкретный девайс, а определенная версия Android или iOS. Фильтр версий сделан горизонтальным, чтобы сэкономить место и чтобы список версий был доступен без прокрутки, на одном экране.

Оптимизация


Один проход любого компонента системы не занимает слишком много времени. Однако, если запустить распознавание маркеров и лиц одновременно, то попытка распознать все 30 кадров в секунду, предоставляемых камерой, приведет к полному исчерпанию ресурсов компьютера без GPU.

При этом понятно, что 99 % времени система будет производить эту работу вхолостую.

Чтобы этого избежать, были приняты следующие оптимизационные решения:

  1. В обработку подаётся лишь каждый восьмой кадр.

    Код
    if self.lastseen + self.rec_threshold > time.time() or self.frame_number != 8:
    	    ret, frame = self.video_capture.read()
    	    self.frame_number += 1
    	    if self.frame_number > 8:
    	          self.frame_number = 8
    	    return frame, None, None, None

    Задержка реакции системы повышается примерно до 8/30 секунды, при этом время реакции человека — примерно одна секунда. Соответственно, такая задержка всё ещё не будет заметной для пользователя. А мы уже в восемь раз снизили нагрузку на систему.
  2. Сначала в кадре ищется маркер устройства, и лишь при его обнаружении запускается распознавание лиц. Так как поиск маркеров в кадре примерно в 300 раз менее затратен, чем поиск лиц, мы решили, что в режиме ожидания будем проверять только наличие маркера.
  3. Чтобы уменьшить «тормоза» при поиске лиц, когда лиц на изображении нет, в функции face_locations был отключен параметр number_of_time_to_upsample.

    face_locations = face_recognition.face_locations(rgb_small_frame, number_of_times_to_upsample=0)

    Благодаря этому время обработки кадра, на котором нет лиц, сравнялось со временем обработки кадра, где лица легко обнаруживаются.

Что в итоге?


На текущий момент, система успешно развернута на подвернувшемся под руку MacMini Late 2009, на двух ядрах Core 2 Duo. В рамках тестирования, она вполне успешно работала даже на одном виртуальном ядре с 1024 мегабайтами оперативной и 4 гигабайтами постоянной памяти в контейнере Docker. К MacMini подключили сенсорный дисплей, чтобы внешний вид стал минималистичным.

Сейчас даже те пользователи, которые не пользовались старой доской, с энтузиазмом и улыбкой регистрируют на себя устройства, а случаев безуспешных поисков стало намного меньше!

Что дальше?


В текущей системе, несомненно, ещё много моментов, которые можно и хочется улучшить:

  • Сделать так, чтобы элементы управления ОС не проявлялись при появлении диалоговых окон (сейчас это messagebox из пакета Tkinter).
  • Разнести вычисления и запросы к серверу в разные потоки с обработкой интерфейса (сейчас они выполняются в основном потоке, mainloop Tkinter’а, что замораживает интерфейс в момент отправки запросов в онлайн-базу).
  • Привести интерфейс к одному дизайну с другими корпоративными ресурсами.
  • Сделать полноценный веб-интерфейс для удаленного просмотра данных.
  • Использовать распознавание голоса для подтверждения/отмены действий и заполнения текстовых полей.
  • Реализовать регистрацию нескольких устройств одновременно.

P.S. А вот так это работает
Видео записано на бета-версии GUI

Комментарии (2)


  1. assusdan
    15.10.2018 21:22

    Galaxy A3 на iOS 6.1 смотрится немного странно. Особенно в этом контексте.


    1. guility Автор
      16.10.2018 05:33

      Как известно — нет ничего более постоянного, чем временное =)
      Видимо, при печати — ошиблись и записали не ту версию ОС, потом зачеркнули временно, с намерением потом перепечатать… да так и прижилось…

      Слава богу, теперь такой проблемы не возникает и изменения вносятся сильно проще.