Определение принадлежности лица живому человеку (3D Liveness)
В этом туториале Вы узнаете, как определить, принадлежит ли лицо на видеопотоке реальному человеку, при помощи Face SDK и RGDB-сенсора. Для удобства мы будем называть эту функцию Liveness. Как правило, liveness-проверка используется для предотвращения злонамеренных действий с использованием фото вместо реального лица.
В Face SDK есть возможность определять принадлежность лица реальному человеку посредством анализа карты глубины, либо RGB-изображения с камеры. Первый способ наиболее точный, поэтому в данном туториале мы рассмотрим только его.
В качестве основы для данного проекта был взят туториал Распознавание лиц на видеопотоке. В данном проекте, как и в предыдущем, используется готовая база лиц для распознавания. После запуска готового проекта на экране будут отображаться цветное изображение и карта глубины, при помощи которой Вы сможете скорректировать свое положение относительно сенсора: для корректной работы liveness-детектора требуется, чтобы лицо находилось на достаточном расстоянии от сенсора, при этом качество карты глубины с сенсора должно быть приемлемым. На цветном изображении задетектированное и распознанное лицо будет выделяться зеленым прямоугольником, рядом с ним будет отображаться фото и имя из базы, а также liveness-статус REAL
. В случае, если человек не распознан, liveness-статус будет REAL
, но ограничивающий прямоугольник будет красным. Если перед камерой будет лицо с изображения или видео, то прямоугольник будет красным и распознавание не будет производиться, в этом случае liveness-статус будет FAKE
.
Помимо Face SDK и Qt для данного проекта Вам потребуется:
- RGBD-камера с поддержкой OpenNI2 или RealSense2 (например, ASUS Xtion или RealSense D415);
- дистрибутив OpenNI2 или RealSense2.
Готовый демо-проект вы можете найти в Face SDK: examples/tutorials/depth_liveness_in_face_recognition
#
Подключение библиотек OpenNI2 и RealSense2- Прежде всего, нам нужно подключить необходимые библиотеки для работы с камерой глубины. Вы можете использовать как OpenNI2-камеру (например, ASUS Xtion) так и камеру RealSense2. В зависимости от используемой камеры, Вам нужно прописать условие
WITH_OPENNI2=1
илиWITH_REALSENSE=1
.
face_recognition_with_video_worker.pro
- [Для камер OpenNI2] Указываем путь до дистрибутива OpenNI2 и пути, по которым можно найти необходимые OpenNI2-библиотеки и заголовочные файлы.
Примечание: Для Windows необходимо выполнить установку дистрибутива и указать путь до директории установки. Для Linux достаточно указать путь до распакованного дистрибутива.
face_recognition_with_video_worker.pro
- [Для камер RealSense2] Указываем путь до дистрибутива RealSense2 и пути, по которым можно найти необходимые RealSense2-библиотеки и заголовочные файлы. В блоке win32 определяется битность платформы для формирования корректных путей до библиотек RealSense.
Примечание: Для Windows необходимо выполнить установку дистрибутива и указать путь до директории установки. Для Linux необходимо выполнить установку дистрибутива, как указано на сайте Intel RealSense. Пути до дистрибутива в pro-файле указывать не требуется.
face_recognition_with_video_worker.pro
#
Получение карты глубины через OpenNI2 API или RealSense2 API- На данном этапе нам необходимо получить кадры глубины с RGBD-камеры при помощи OpenNI2 API или RealSense2 API, в зависимости от используемой камеры. В данном проекте мы не будем подробно рассматривать получение кадров глубины. Мы используем заголовочные файлы, которые входят в состав одной из демо-программ Face SDK (video_recognition_demo). В pro-файле проекта указываем путь до папки examples/cpp/video_recognition_demo/src, которая находится в Face SDK.
face_recognition_with_video_worker.pro
- Указываем необходимые заголовочные файлы для работы с камерами OpenNI2 и RealSense2. Подробную информацию о получении кадров глубины Вы можете найти в указанных файлах (
OpenniSource.h
иRealSenseSource.h
).
face_recognition_with_video_worker.pro
- Для использования математических констант определяем
_USE_MATH_DEFINES
(cmath
подключен в файлахOpenniSource.h
иRealSenseSource.h
).
face_recognition_with_video_worker.pro
Подключение сенсора глубины для обработки кадров
- В предыдущих проектах мы получали изображение с веб-камеры, используя объект
QCameraCapture
. Однако в этом проекте нам требуется получать не только цветные кадры, но и кадры глубины. Для этого создадим новый классDepthSensorCapture
: Add New > C++ > C++ Class > Choose… > Class name – DepthSensorCapture > Base class – QObject > Next > Project Management (настройки по умолчанию) > Finish. - В файле
depthsensorcapture.h
подключим заголовочный файлImageAndDepthSource.h
. Также подключаемQSharedPointer
для работы с указателями,QThread
для обработки потоков,QByteArray
для работы с массивами байтов,memory
иatomic
для использования умных указателей и атомарных типов соответственно. В файлеdepthsensorcapture.cpp
подключаем заголовочные файлыOpenniSource.h
иRealSenseSource.h
для получения кадров глубины, а такжеworker.h
иdepthsensorcapture.h
. Для обработки ошибок используетсяassert.h
, для отображения сообщения об ошибке используетсяQMessageBox
.
depthsensorcapture.h
depthsensorcapture.cpp
- Определяем
RGBFramePtr
– указатель на цветное изображение иDepthFramePtr
– указатель на кадр глубины. Конструктор классаDepthSensorCapture
принимает родительский виджет (parent
), а также указатель наworker
. Данные с сенсора будут приходить в бесконечном цикле. Чтобы главный поток, в котором происходит отрисовка интерфейса, не ждал завершения цикла, мы создаем отдельный поток и перемещаем в него объектDepthSensorCapture
.
depthsensorcapture.h
depthsensorcapture.cpp
- В методе
DepthSensorCapture::start
запускаем поток получения данных, в методеDepthSensorCapture::stop
останавливаем его.
depthsensorcapture.h
depthsensorcapture.cpp
- В методе
DepthSensorCapture::frameUpdatedThread
обрабатываем новый кадр с камеры в бесконечном цикле и передаем его вWorker
черезaddFrame
. В случае возникновения ошибки появится окно с сообщением об ошибке.
depthsensorcapture.h
depthsensorcapture.cpp
- Объект
VideoFrame
должен содержать в себе RGB кадр с камеры.
depthsensorcapture.cpp
- В файле
videoframe.h
подключаем заголовочный файлdepthsensorcapture.h
для работы с камерой глубины.
videoframe.h
- Интефейс
IRawImage
позволяет получить указатель на данные изображения, его формат, высоту и ширину.
videoframe.h
- В методе
runProcessing
запускаем камеру, в методеstopProcessing
– останавливаем.
viewwindow.h
viewwindow.cpp
- В файле
worker.h
подключаем заголовочный файлdepthsensorcapture.h
. СтруктураSharedImageAndDepth
содержит в себе указатели на RGB-кадр и кадр глубины с камеры, а также структуруpbio::DepthMapRaw
с информацией о параметрах карты глубины (ширина, высота и т.д.). Указатели используются вWorker
. Из-за некоторой задержки в обработке кадров определенное количество кадров будет складываться в очередь на отрисовку, поэтому для экономии памяти вместо самих кадров мы храним указатели на них.
worker.h
- В структуру
DrawingData
передаемSharedImageAndDepth frame
- кадры для отрисовки (цветное изображение и карта глубины). В коллбэкеTrackingCallback
из очереди кадров извлекается изображение, соответствующее последнему полученному результату.
worker.h
worker.cpp
#
Обработка карты глубины при помощи VideoWorker- В конструкторе
Worker::Worker
переопределяем значения некоторых параметров объектаVideoWorker
для обработки карты глубины. а именно:
depth_data_flag
("1"
– включен режим работы с кадрами глубины для подтверждения liveness);weak_tracks_in_tracking_callback
(“1”
– вTrackingCallback
передаются все сэмплы, в том числе сэмплы с флагомweak = true
).
Значение флага weak
становится true
в том случае, если сэмпл не прошел определенные проверки, например, если:
- на лице слишком много теней (недостаточное освещение);
- изображение нечеткое (размытое);
- лицо находится под большим углом;
- размер лица в кадре слишком маленький;
- лицо не прошло liveness-проверку (взято с фото).
Более подробное описание требований к условиям освещения, размещения камер и т.д. Вы можете найти в пункте Рекомендации по камерам. Как правило, сэмплы, которые не прошли проверку, не передаются для дальнейшей обработки. Однако в данном проекте даже лица на фото (не прошедшие liveness-проверку) должны выделяться ограничивающим прямоугольником, следовательно, мы должны передавать в TrackingCallback
в том числе сэмплы с флагом weak = true
.
worker.cpp
- В файле
worker.h
в структуреFaceData
указываем перечислениеpbio::DepthLivenessEstimator
- результат определения принадлежности лица живому человеку. Доступно четыре варианта определения liveness:
- NOT_ENOUGH_DATA – недостаточно информации. Такая ситуация может возникнуть, если качество карты глубины неудовлетворительное, либо пользователь находится слишком близко/слишком далеко от сенсора.
- REAL – лицо принадлежит реальному человеку.
- FAKE – лицо было взято с фото/видео.
- NOT_COMPUTED – лицо не было проверено. Такая ситуация может возникнуть, например, если кадры с сенсора не синхронизированы (цветной кадр получен, но соответствующий ему кадр глубины не найден в определенном временном диапазоне).
Результат liveness-проверки сохраняется в переменную face.liveness_status
для дальнейшей отрисовки.
worker.h
worker.cpp
- В методе
Worker::addFrame
подаем последний кадр глубины в FaceSDK через методVideoWorker::addDepthFrame
и сохраняем его для дальнейшей отрисовки.
worker.h
worker.cpp
- Подготавливаем и подаем в FaceSDK цветное изображение через метод
VideoWorker::addVideoFrame
. В случае, если формат полученного цветного изображения – не RGB, а BGR, порядок байтов меняется, чтобы цвета отображались корректно. Если вместе с цветным кадром не пришел кадр глубины, используется последний полученный кадр глубины. Пара из кадра глубины и цветного изображения сохраняется в очередь_frames
, чтобы в коллбэкеTrackingCallback
найти данные, соответствующие результату обработки.
worker.cpp
#
Визуализация RGB и карты глубины. Отображение информации о 3D Liveness- Модифицируем метод рисования
DrawFunction::Draw
в файлеdrawfunction.cpp
. В полеframe
структурыWorker::DrawingData
содержатся указатели на данные цветного изображения и карты глубины, а также параметры кадра глубины (ширина, высота и др.). Для удобства обращения к этим данным заведем на них ссылкиconst QImage& color_image
,const QByteArray& depth_array
иconst pbio::DepthMapRaw& depth_options
. Цветное изображение и карта глубины будут отображаться наQImage result
, который представляет собой своего рода “фон” и включает в себя оба изображения (цветное – сверху, карта глубины – снизу). Перед этим нам необходимо конвертировать 16-битные значения глубины в 8-битные для корректного отображения карты глубины (в серых тонах). В переменнойmax_depth_mm
указываем максимальное расстояние от сенсора до пользователя (как правило, для RGBD-камер это 10 метров).
drawfunction.cpp
- Из сконвертированных значений формируем изображение глубины. Создаем объект
result
, на котором будет отображаться цветное изображение (в верхней части) и карта глубины (в нижней части). Рисуем эти изображения.
drawfunction.cpp
- Отображаем liveness-статус рядом с лицом, в зависимости от информации, полученной от liveness-детектора. Зададим параметры надписи (цвет, линия, размер). На карте глубины будет отображаться ограничивающий прямоугольник, чтобы Вы могли убедиться, что кадр глубины не смещен относительно цветного кадра.
drawfunction.cpp
- Запускаем проект. На экране будут отображаться цветное изображение с камеры и карта глубины. На цветном изображении будет отображаться информация о найденном лице:
- статус распознавания (цвет ограничивающего прямоугольника: зеленый - пользователь есть в базе, красный - нет в базе/лицо с изображения или видео);
- информация о распознанном пользователе (его фотография из базы и имя);
- liveness-статус: real - реальный человек, fake - лицо взято с фото или видео, not_enough_data - качество карты глубины неудовлетворительное/пользователь слишком близко или далеко от сенсора, not_computed - рассинхронизация кадра глубины и цветного кадра.