Распознавание лиц с использованием библиотеки DLIB С++
Развитие средств видеомониторинга определяет необходимость внедрения методов распознавания лиц людей для решения различных задач: от аутентификации до поиска преступников. В данной работе рассматривается вопрос применения достаточного эффективного метода распознавания лиц людей.
Процесс распознавания лица человека делится на следующие основные этапы:
- выделение контура лица на изображении;
- извлечение характерных признаков текущего образа лица человека;
- сравнение признаков с эталоном.
Все данные этапы реализованы в библиотеки dlib и могут быть использованы в «коробочном» режиме, используя готовые функции и объекты:
- get_frontal_face_detector. Возвращает объект для поиска контура лица изображения. В данной работе используется простой метод поиска лица при помощи HOG и SVM;
- shape_predictor. Выделяет ключевые точки на лице. В рамках работы используется модель лица, задающаяся 68 точками. Пример расположения данных точек представлен на рисунке 1. В библиотеке dlib используется быстродействующий алгоритм определения ключевых точек с учетом ориентации лица на изображении;
- get_face_chip_details. Функция, которая возвращает изображения лица с правильной ориентаций и его центрированием;
- нейронная сеть net, которая вычисляет вектор характерных признаков для образа лица. Вектор представляет собой 128 чисел;
- length. Функция определения дистанции между двумя дескрипторами образов.
Рисунок 1 – Ключевые точки лица
В данной работе рассматривается задача распознавания лиц методом сравнения с эталоном. Код разрабатывается на C++. В качестве примера решается следующая задача: поиск лица человека, представленного на входном рисунке, в списке эталонных. Для простоты рассматриваем случай, когда на каждом рисунке только одно лицо.
Первоначально скачиваем библиотеку Dlib по адресу http://dlib.net/. При компиляции библиотеки для повышения производительности отмечаем необходимость применения CUDA. Первоначально подключаются необходимые библиотеки для разрабатываемого приложения:
#include <vector>
//===============dlib
#include <dlib/dnn.h>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/opencv/cv_image.h>
#include <dlib/opencv/to_open_cv.h>
//===============OpenCV
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
//=====Поиск файлов
#include "src/FindFilesToVector.h"
В первом блоке указаны модули библиотеки dlib, во втором – OpenCV и в третьем – модуль поиска файлов в папке.
Далее определяются основные структуры сети RestNet:
using namespace dlib;
using namespace std;
template <template <int, template<typename>class, int, typename> class block, int N, template<typename>class BN, typename SUBNET>
using residual = add_prev1<block<N, BN, 1, tag1<SUBNET>>>;
template <template <int, template<typename>class, int, typename> class block, int N, template<typename>class BN, typename SUBNET>
using residual_down = add_prev2<avg_pool<2, 2, 2, 2, skip1<tag2<block<N, BN, 2, tag1<SUBNET>>>>>>;
template <int N, template <typename> class BN, int stride, typename SUBNET>
using block = BN<con<N, 3, 3, 1, 1, relu<BN<con<N, 3, 3, stride, stride, SUBNET>>>>>;
template <int N, typename SUBNET> using ares = relu<residual<block, N, affine, SUBNET>>;
template <int N, typename SUBNET> using ares_down = relu<residual_down<block, N, affine, SUBNET>>;
template <typename SUBNET> using alevel0 = ares_down<256, SUBNET>;
template <typename SUBNET> using alevel1 = ares<256, ares<256, ares_down<256, SUBNET>>>;
template <typename SUBNET> using alevel2 = ares<128, ares<128, ares_down<128, SUBNET>>>;
template <typename SUBNET> using alevel3 = ares<64, ares<64, ares<64, ares_down<64, SUBNET>>>>;
template <typename SUBNET> using alevel4 = ares<32, ares<32, ares<32, SUBNET>>>;
using anet_type = loss_metric<fc_no_bias<128, avg_pool_everything<
alevel0<
alevel1<
alevel2<
alevel3<
alevel4<
max_pool<3, 3, 2, 2, relu<affine<con<32, 7, 7, 2, 2,
input_rgb_image_sized<150>
>>>>>>>>>>>>;
На первом шаге программы определяются основные объекты и производится их инициализация:
shape_predictor sp;//сеть для определения ключевых точек лица
frontal_face_detector detector;// распознаватель лица на основе HOG
anet_type net;//сеть для выявления дексриптора изображения
detector = get_frontal_face_detector();
deserialize("shape_predictor_68_face_landmarks.dat") >> sp;
deserialize("dlib_face_recognition_resnet_model_v1.dat") >> net;
Детектор характерных точек лица использует модель с 68 точками. Также неплохие результаты показывает модель только с 5 точками (shape_predictor_5_face_landmarks.dat).
На следующем этапе определяем дескриптор текущего образа. Так как структуры данных библиотеки dlib и OpenCV отличаются, то первоначально производим преобразование кадра из cv::Mat в matrix<rgb_pixel>:
//преобразование исходного изображения в формат dlib
matrix<rgb_pixel> dlib_img1;
cv_image<bgr_pixel> dlibIm(frame1);
assign_image(dlib_img1, dlibIm);
После этого осуществляем поиск лиц в изображении dlib_img1.и выделение прямоугольником:
std::vector<rectangle> dets; //вектор для хранения прямоугольников,
//обрамляющих найденные лица
dets= detector(dlib_img1); //непосредственный поиск лиц
if (dets.size() < 1)
{
printf("Not found faces in image!\n");
return -1;
}
//прорисовка прямоугольника первого лица
cv::Rect rect(int(dets[0].left()), int(dets[0].top()), int(dets[0].width()), int(dets[0].height()));
cv::rectangle(frame1, rect, CV_RGB(255, 0, 0), 2);
Так как лицо на изображении может быть наклонено, то требуется выполнить его восстановление и центрирование. Для определения подобных искажений используются данные о ключевых точках лица. Ниже представлен код определения данных точек и их прорисовки:
auto shape = sp(dlib_img1, dets[0]); //определение ключевых точек лица
//прорисовка данных точек
for (int i = 0; i < 68; i++)
{
int x = shape.part(i).x();
int y = shape.part(i).y();
cv::circle(frame1, cv::Point(x, y), 4, CV_RGB(0, 255, 0),2);
}
На рисунке 2 представлены результаты выделения лица и ключевых точек.
Рисунок 2 – Схема размещения ключевых точек лица
Далее осуществляется выделение участка лица с учетом искажений:
std::vector<matrix<rgb_pixel>> faces; //вектор восстановленных изображений
matrix<rgb_pixel> face_chip;
//формируется определяется восстановленное изображение face_chip размером 150x150
//на основе модели лица на базе ключевых точек
extract_image_chip(dlib_img1, get_face_chip_details(shape, 150, 0.25), face_chip);
faces.push_back(std::move(face_chip));
На рисунке 3 отображен пример восстановленного изображения face_chip.
Рисунок 3 – Выделение лица с учетом модели на базе ключевых точек
Далее производится вычисление дескрипторов для вектора изображений (в данном случае вектор дескрипторов будет содержать один элемент):
std::vector<matrix<float, 0, 1>> face_descriptors1;
face_descriptors1 = net(faces);
Для сравнения с эталонным образом аналогично рассчитываем его дескриптор. В качестве разделяющей функции используется length. Ниже приведен пример сравнения дескрипторов двух