Создать систему распознавания лиц у входной двери за $150 с помощью одноплатного компьютера и кода на Python

Закупка оборудования, 200 строчек кода — и готова простейшая система, которая отслеживает и запоминает гостей.

Перевод материала Адама Гитги подготовлен командой онлайн-школы английского языка Skyeng.

Компьютер Nvidia Jetson Nano позволяет при небольшом бюджете создавать автономные аппаратные системы, работающие на моделях глубокого обучения с GPU-ускорением. Это как мини-компьютер Raspberry Pi, но намного быстрее.

Можно легко отслеживать тех, кто подходит к двери

Я расскажу, как создать настоящий аппаратный проект с Jetson Nano: простую версию камеры для дверного звонка, отслеживающую всех, кто подходит к входной двери. Благодаря распознаванию лиц система мгновенно узнаёт человека, даже если он приходит в разной одежде. Камера подсказывает, когда этот гость приходил к вам и как часто.

Что такое Nvidia Jetson Nano и чем он отличается от Raspberry Pi

Долгое время Raspberry Pi был самым простым способом для разработчика программного обеспечения попробовать самостоятельно создать аппаратное устройство. Raspberry Pi — это одноплатный компьютер за $35, работающий на Linux и полностью поддерживающий Python.

А если подключить к нему фирменную камеру-модуль за $20, вы сможете использовать его для создания автономных систем компьютерного зрения. C 2012-го по 2017 год Raspberry Pi продала более 12 млн устройств, познакомив новое поколение программистов с разработкой аппаратных средств.

The Raspberry Pi 3B — полноценный компьютер с Linux на борту
The Raspberry Pi 3B — полноценный компьютер с Linux на борту

Несмотря на то что Raspberry Pi — отличный продукт, он едва ли годится для глубокого обучения. В компьютере нет видеокарты, а CPU медленно проводит операции с матрицами, поэтому модели глубокого обучения тоже, как правило, работают медленно.

Raspberry Pi просто создан для других задач. Многие разработчики компьютерного зрения всё равно пытались его использовать, но обычно приложения работали с частотой менее одного кадра в секунду.

Nvidia заметила нишу на рынке и спроектировала Jetson Nano. Jetson Nano похож на Raspberry Pi, но оборудован графическим процессором и предназначен именно для запуска моделей глубокого обучения.

Nvidia Jetson Nano концептуально похож на Raspberry Pi — это компьютер с Linux на одной плате. Но он имеет встроенный 128-ядерный графический процессор Nvidia для ускорения моделей глубокого обучения и поддерживает ускорение CUDA
Nvidia Jetson Nano концептуально похож на Raspberry Pi — это компьютер с Linux на одной плате. Но он имеет встроенный 128-ядерный графический процессор Nvidia для ускорения моделей глубокого обучения и поддерживает ускорение CUDA

Другое достоинство Jetson Nano: он поддерживает те же библиотеки CUDA для ускорения, которые уже используются почти всеми фреймворками глубокого обучения на основе Python. Это означает, что вы можете взять существующее приложение глубокого обучения на основе Python и запустить его на Jetson Nano с минимальными изменениями, причём зачастую — с приличной производительностью.

Это огромный шаг вперёд по сравнению с глубоким обучением на Raspberry Pi.

Что купить

В любом аппаратном проекте первый шаг — покупка необходимых для построения системы компонентов. Вот минимальный набор, который вам нужен.

1. Nvidia Jetson Nano Board ($99)

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

Если честно, я получил свой Jetson Nano бесплатно от человека, работающего в Nvidia (тогда они были распроданы везде), но Nvidia никак не спонсировала эту статью.

2. Разъём питания microUSB (около $10)

Если есть возможность, найдите адаптер питания, который поддерживает именно Jetson Nano, поскольку некоторые USB-штекеры не дают достаточного напряжения. Но подойти может и старое зарядное устройство для мобильного телефона.

3. Raspberry Pi Camera Module v2.x (около $30)

Вы не сможете использовать модуль камеры Raspberry Pi v1.x, Jetson Nano не поддерживает этот чипсет. Для работы вам нужен модуль камеры v2.x.

3. Быстрая карта microSD с объёмом не менее 32 ГБ (около $10–25)

Я потратил чуть больше и купил карту на 128 ГБ на Amazon. Рекомендую брать побольше памяти, чтобы вам точно хватило места. Если у вас есть лишняя microSD, смело используйте её.

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

  • Картридер microSD для компьютера, чтобы загрузить и установить ПО Jetson.
  • Проводная USB-клавиатура и проводная USB-мышь для управления Jetson Nano.
  • Любой монитор или телевизор с HDMI-входом (не через конвертер HDMI-DVI), чтобы следить за работой. Монитор понадобится для первоначальной настройки Jetson Nano, даже если позже вы будете обходиться без него.
  • Кабель Ethernet и место, куда его можно подключить. Странно, но у Jetson Nano нет встроенного Wi-Fi. При желании можно добавить USB-адаптер Wi-Fi, но Jetson Nano поддерживает не все модели, поэтому проверьте это перед покупкой.

Соберите всё это вместе — и вы готовы к работе. Надеюсь, в общем вы потратите не больше $150. Дороже всего обойдутся сама плата Jetson Nano и модуль камеры.

Конечно, вы можете купить или смастерить чехол для оборудования Jetson Nano и камеры. Но это полностью зависит от того, куда вы хотите установить систему.

Загрузка ПО Jetson Nano

Прежде чем вы начнёте подключать устройства к Jetson Nano, необходимо загрузить образ программного обеспечения.

Образ ПО Nvidia по умолчанию просто отличный. Он включает в себя Ubuntu Linux 18.04 с предварительно установленным Python 3.6 и OpenCV, что экономит много времени.

Установка программного обеспечения Jetson Nano на SD-карту:

  1. Загрузите образ SD-карты Jetson Nano Developer Kit от Nvidia.
  2. Загрузите Etcher, программу, которая записывает образ программного обеспечения Jetson на SD-карту.
  3. Запустите Etcher и используйте его для записи образа SD-карты Jetson Nano Developer Kit. Это займёт около 20 минут.

Теперь, когда у вас есть SD-карта с программным обеспечением Jetson Nano по умолчанию, время распаковывать остальное оборудование.

Подключение

Достаньте Jetson Nano из коробки.

Создать систему распознавания лиц у входной двери за $150 с помощью одноплатного компьютера и кода на Python

Внутри — Jetson Nano и маленький поддон, который можно использовать в качестве подставки. Больше ничего — ни проводов, ни инструкций.

Сначала вставляем карту microSD. Слот для неё очень хорошо спрятан, он находится с обратной стороны под радиатором.

Создать систему распознавания лиц у входной двери за $150 с помощью одноплатного компьютера и кода на Python

Затем нужно подключить модуль камеры Raspberry Pi v2.x. Он соединяется со шлейфом. Найдите слот для ленточного кабеля, откройте разъём, вставьте кабель и снова закройте его. Убедитесь, что металлические контакты на ленточном кабеле направлены внутрь к радиатору.

Создать систему распознавания лиц у входной двери за $150 с помощью одноплатного компьютера и кода на Python

Теперь подключите всё остальное:

  • мышь и клавиатуру к портам USB;
  • монитор с помощью кабеля HDMI;
  • кабель Ethernet к сетевому порту (и убедитесь, что другой конец подключён к маршрутизатору);
  • шнур питания microUSB.

Получится примерно так.

Создать систему распознавания лиц у входной двери за $150 с помощью одноплатного компьютера и кода на Python

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

Первая загрузка и настройка учётной записи пользователя

При первой загрузке Jetson Nano нужно пройти стандартный процесс регистрации нового пользователя Ubuntu Linux. Вы выбираете тип используемой клавиатуры, создаёте учётную запись пользователя и выбираете пароль. Когда закончите, увидите пустой рабочий стол Ubuntu Linux.

Python 3.6 и OpenCV уже установлены. Вы можете открыть окно терминала и запустить программы на Python прямо сейчас, как и на любом другом компьютере. Но есть ещё несколько библиотек, которые следует установить, прежде чем запустить приложение камеры дверного звонка.

Установка необходимых библиотек Python

Хотя в Jetson Nano множество полезных вещей уже предустановлено, есть и некоторые странные упущения. Например, OpenCV устанавливается с привязками Python, но pip и numpy не установлены, а они необходимы для работы с OpenCV. Давайте исправим это.

На рабочем столе Jetson Nano откройте окно терминала и выполните следующие команды. Каждый раз, когда система запрашивает ваш пароль, используйте тот же пароль, который вы ввели при создании учётной записи пользователя.

sudo apt-get update sudo apt-get install python3-pip cmake libopenblas-dev liblapack-dev libjpeg-dev

Во-первых, мы обновляем apt, стандартный инструмент установки ПО Linux, который мы будем использовать для установки всего остального. Далее устанавливаем некоторые базовые библиотеки с apt, которые понадобятся нам позже для компиляции numpy и dlib.

Прежде чем идти дальше, нужно создать файл подкачки. У Jetson Nano только 4 ГБ оперативной памяти, этого недостаточно для компиляции dlib. Чтобы решить проблему, настроим файл подкачки, который позволит использовать дисковое пространство в качестве дополнительной оперативной памяти. К счастью, на Jetson Nano есть простой способ создать такой файл. Просто запустите эти две команды.

git clone https://github.com/JetsonHacksNano/installSwapfile ./installSwapfile/installSwapfile.sh

Про этот способ я узнал благодаря веб-сайту JetsonHacks.

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

После входа в систему открываем новое окно терминала — и можно продолжать. Во-первых, давайте установим numpy, библиотеку Python, которая используется для математических вычислений.

pip3 install numpy

Выполнение команды займёт 15 минут, так как она компилирует numpy с нуля. Просто подождите и не волнуйтесь, даже если вам покажется, что всё зависло.

Теперь мы готовы установить dlib, библиотеку глубокого обучения, созданную Дэвисом Кингом, выполняющая сложную работу для библиотеки face_recognition.

Однако сейчас в собственных библиотеках CUDA от Nvidia для Jetson Nano есть ошибка, которая мешает правильной работе. Чтобы обойти эту ошибку, нам нужно скачать dlib, отредактировать строку кода и перекомпилировать её. Не волнуйтесь, это не так сложно.

В терминале выполните эти команды.

wget http://dlib.net/files/dlib-19.17.tar.bz2 tar jxvf dlib-19.17.tar.bz2 cd dlib-19.17

Это загрузит и распакует исходный код для dlib. Прежде чем мы скомпилируем его, нужно превратить одну строку в комментарий. Запустите эту команду.

gedit dlib/cuda/cudnn_dlibapi.cpp

Это откроет файл, который нам нужно отредактировать в текстовом редакторе. Найдите в файле следующую строку кода (она должна иметь номер 854).

forward_algo = forward_best_algo;

И прокомментируйте её, добавив два слэша перед ней.

//forward_algo = forward_best_algo;

Теперь сохраните файл, закройте редактор и вернитесь в окно терминала. Затем выполните эти команды для компиляции и установки dlib.

cd dlib sudo python3 setup.py install

Это займёт около 30–60 минут. Jetson Nano может нагреться, но не волнуйтесь, пусть он работает.

Наконец, установим библиотеку Python face_recognition с помощью следующей команды.

sudo pip3 install face_recognition

Теперь ваш Jetson Nano готов распознавать лица с полным ускорением графического процессора CUDA. Переходим к самому интересному.

Запуск демо-приложения камеры с распознаванием лиц для дверного звонка

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

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

Для начала давайте скачаем код. Я разместил здесь полный код с комментариями, но вот более простой способ загрузки его на свой Jetson Nano из командной строки.

wget -O doorcam.py tiny.cc/doorcam

Затем вы можете запустить код и протестировать его.

python3 doorcam.py

Вы увидите всплывающее окно видео на рабочем столе. Всякий раз, когда перед камерой появляется новый человек, система регистрирует его лицо и начинает отслеживать, как долго он находится у двери. Если тот же самый человек уходит и возвращается более чем через 5 минут, камера регистрирует новое посещение и отслеживает его снова. Вы можете закрыть видео в любой момент, нажав «q».

Приложение автоматически сохраняет информацию обо всех посетителях в файл с known_faces.dat. Когда вы снова запустите программу, она будет использовать эти данные для распознавания предыдущих посетителей. Если вы хотите очистить список известных лиц, просто выйдите из программы и удалите этот файл.

Камера для дверного звонка Python Code Walkthrough

Хотите знать, как работает этот код? Давайте посмотрим.

Код начинается с импорта библиотек. Самые важные:

  • OpenCV (в Python называется cv2) — его мы будем использовать для чтения изображений с камеры.
  • face_recognition необходим для обнаружения и сравнения лиц.
import face_recognition import cv2 from datetime import datetime, timedelta import numpy as np import platform import pickle

Далее создадим несколько переменных для хранения данных о людях перед камерой. Переменные будут работать как простая база данных известных посетителей.

known_face_encodings = [] known_face_metadata = []

Это приложение — всего лишь демо, поэтому мы храним известные лица в обычном списке Python. В настоящем приложении (с большой базой лиц) вы можете использовать вместо этого реальную базу данных.

У нас есть функция сохранения и загрузки известных данных лиц.

def save_known_faces(): with open("known_faces.dat", "wb") as face_data_file: face_data = [known_face_encodings, known_face_metadata] pickle.dump(face_data, face_data_file) print("Known faces backed up to disk.")

Она записывает известные лица на диск, используя встроенную в Python операцию pickle. Данные загружаются обратно таким же образом, но я здесь этого не показал.

Я хотел, чтобы эта программа работала на стационарном компьютере или на Jetson Nano без каких-либо изменений. Поэтому добавил простую операцию для определения, на какой платформе она сейчас работает.

def running_on_jetson_nano(): return platform.machine() == "aarch64"

Эта операция нужна, потому что на разных платформах разный доступ к камере. На ноутбуке мы можем просто передать номер камеры в OpenCV, и он будет извлекать изображение. Но на Jetson Nano мы должны использовать gstreamer для потоковой передачи изображений, для чего требуется пользовательский код.

Определив платформу, выберем подходящий для неё способ доступа к камере. Это единственная настройка, необходимая для запуска программы на Jetson Nano вместо обычного компьютера.

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

def register_new_face(face_encoding, face_image): known_face_encodings.append(face_encoding) known_face_metadata.append({ "first_seen": datetime.now(), "first_seen_this_interaction": datetime.now(), "last_seen": datetime.now(), "seen_count": 1, "seen_frames": 1, "face_image": face_image, })

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

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

def lookup_known_face(face_encoding): metadata = None if len(known_face_encodings) == 0: return metadata face_distances = face_recognition.face_distance( known_face_encodings, face_encoding ) best_match_index = np.argmin(face_distances) if face_distances[best_match_index] < 0.65: metadata = known_face_metadata[best_match_index] metadata["last_seen"] = datetime.now() metadata["seen_frames"] += 1 if datetime.now() - metadata["first_seen_this_interaction"] > timedelta(minutes=5): metadata["first_seen_this_interaction"] = datetime.now() metadata["seen_count"] += 1 return metadata

Здесь нужно сделать несколько важных вещей:

  1. Используя библиотеку face_recogntion, мы проверяем, насколько неизвестное лицо похоже на всех предыдущих посетителей. Операция face_distance () представляет сходство между неизвестным лицом и всеми известными лицами в виде числа — чем меньше число, тем больше сходство.
  2. Если лицо очень похоже на одного из известных посетителей, мы предполагаем, что этот человек уже приходил. В этом случае обновляем время, когда видели его в последний раз, и увеличиваем количество раз, когда видели его, в строке информации на видео.
  3. Наконец, если этот человек был замечен перед камерой за последние пять минут, мы предполагаем, что он всё ещё здесь в рамках одного и того же визита. В противном случае предполагаем, что это новый визит, поэтому сбрасываем метку времени, отслеживающую последнее посещение.

Остальная часть программы представляет собой основной цикл. Это бесконечный цикл, в котором мы выбираем кадр видео, ищем лица на изображении и обрабатываем каждое лицо. Это центр всей программы. Давайте посмотрим, как он работает.

def main_loop(): if running_on_jetson_nano(): video_capture = cv2.VideoCapture( get_jetson_gstreamer_source(), cv2.CAP_GSTREAMER ) else: video_capture = cv2.VideoCapture(0)

Первый шаг — получить доступ к камере любым способом, подходящим для оборудования. Вне зависимости от того, работаем ли мы на обычном компьютере или на Jetson Nano, video_capture позволяет захватывать кадры видео с камеры компьютера.

Начнём захват кадров.

while True: # Grab a single frame of video ret, frame = video_capture.read() # Resize frame of video to 1/4 size small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25) # Convert the image from BGR color rgb_small_frame = small_frame[:, :, ::-1]

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

Ещё нужно иметь в виду, что OpenCV извлекает изображения с камеры, так что каждый пиксель сохраняется в виде значения «синий-зелёный-красный» вместо стандартного порядка «красный-зелёный-синий». Прежде чем запустить распознавание лица на изображении, нужно преобразовать формат изображения.

Теперь мы можем обнаруживать все лица на изображении и преобразовать каждое лицо в кодировку лица. Это можно сделать с помощью всего пары строк кода.

face_locations = face_recognition.face_locations(rgb_small_frame) face_encodings = face_recognition.face_encodings( rgb_small_frame, face_locations )

Затем пройдёмся по каждому обнаруженному лицу и решим, кого видели раньше, а кто из них — новый посетитель.

for face_location, face_encoding in zip( face_locations, face_encodings): metadata = lookup_known_face(face_encoding) if metadata is not None: time_at_door = datetime.now() - metadata['first_seen_this_interaction'] face_label = f"At door {int(time_at_door.total_seconds())}s" else: face_label = "New visitor!" # Grab the image of the the face top, right, bottom, left = face_location face_image = small_frame[top:bottom, left:right] face_image = cv2.resize(face_image, (150, 150)) # Add the new face to our known face data register_new_face(face_encoding, face_image)

Если человека уже видели, извлечём метаданные о его предыдущих посещениях. Если нет, добавим их в базу данных лиц и извлечём изображение лица с кадра, чтобы добавить в базу данных.

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

for (top, right, bottom, left), face_label in zip(face_locations, face_labels): # Scale back up face location # since the frame we detected in was 1/4 size top *= 4 right *= 4 bottom *= 4 left *= 4 # Draw a box around the face cv2.rectangle( frame, (left, top), (right, bottom), (0, 0, 255), 2 ) # Draw a label with a description below the face cv2.rectangle( frame, (left, bottom - 35), (right, bottom), (0, 0, 255), cv2.FILLED ) cv2.putText( frame, face_label, (left + 6, bottom - 6), cv2.FONT_HERSHEY_DUPLEX, 0.8, (255, 255, 255), 1 )

Я также хотел, чтобы в верхней части экрана отображался список недавних посетителей и количество их посещений.

Графический список знаков, представляющих каждого человека у двери
Графический список знаков, представляющих каждого человека у двери

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

number_of_recent_visitors = 0 for metadata in known_face_metadata: # If we have seen this person in the last minute if datetime.now() - metadata["last_seen"] < timedelta(seconds=10): # Draw the known face image x_position = number_of_recent_visitors * 150 frame[30:180, x_position:x_position + 150] = metadata["face_image"] number_of_recent_visitors += 1 # Label the image with how many times they have visited visits = metadata['seen_count'] visit_label = f"{visits} visits" if visits == 1: visit_label = "First visit" cv2.putText( frame, visit_label, (x_position + 10, 170), cv2.FONT_HERSHEY_DUPLEX, 0.6, (255, 255, 255), 1 )

Наконец, мы можем отобразить текущий кадр видео на экране со всеми комментариями поверх него.

cv2.imshow('Video', frame)

И чтобы не потерять данные в случае сбоя программы, будем сохранять список известных лиц на диск каждые 100 кадров.

if len(face_locations) > 0 and number_of_frames_since_save > 100: save_known_faces() number_of_faces_since_save = 0 else: number_of_faces_since_save += 1

Почти всё, осталось только написать пару строк кода очистки, чтобы выключить камеру при выходе из программы.

Код запуска программы находится в самом низу программы.

if __name__ == "__main__": load_known_faces() main_loop()

Просто загружаем известные лица (если они есть), а затем запускаем основной цикл, который считывает данные с камеры и отображает результаты на экране.

Вся программа состоит из примерно 200 строк, но делает интересные вещи: обнаруживает посетителей, идентифицирует их и отслеживает каждый раз, когда они возвращаются к двери. Это забавное приложение, хотя оно может быть и жутковатым, если им злоупотреблять.

Интересный факт: подобный код для отслеживания лиц используется во многих рекламных объявлениях на улицах и автобусных остановках, чтобы собирать информацию о том, кто смотрит на рекламу и как долго. Возможно, раньше это казалось вам чем-то невероятным, но теперь вы сами можете сделать то же самое всего за $150.

Расширение программы

Эта программа — пример использования небольшого количества кода Python 3, работающего на плате Jetson Nano за $100, для создания мощной системы.

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

Можно поэкспериментировать и превратить программу в нечто совершенно иное. Схема считывания кадра видео, поиска объектов на изображении и прочие операции — основа всех видов систем компьютерного зрения. Попробуйте изменить код и придумать что-то совсем другое. Например, включать любимую музыку, когда вы подходите к своей двери. Посмотрите на другие примеры Python face_recognition, чтобы понять, как сделать что-то подобное.

6363
35 комментариев

А где «Привет, Хаброжители»?

Но статья крутая, спасибо!

27
Ответить

ХаброжителиВисяне

3
Ответить

Круто. Хабр плачет от потерянной статьи.

9
Ответить

Хабр переезжает на vc! До vc “сидел” на хабре, сейчас только vc

9
Ответить

Да, 2 раза перепроверял, не на хабре ли я )

7
Ответить

Пару лет назад опробовал dlib, но точность не очень понравилась, иногда путал меня с моим бородатым коллегой

2
Ответить