Распознавание лиц на основе OpenCV для C++ (Facial Recognition based on OpenCV C++)

Автор: | 31.10.2018

Введение
Захват видеопотока с камеры и выделение лица
Выделение особых точек лица (Facial Landmark Detection)
Выбор признаков для фильтрации изображений и распознавания лиц
Алгоритм распознавания лиц по 2D-каркасу точек 
Программа сравнения лиц по одному признаку
3D- реконструкция ASM (Active Shape Models)
Идентификация по множеству признаков. Основы
Идентификация по множеству признаков при помощи нейросети
Машинное обучение распознавать поворот лица
Полезные ссылки

Введение

Как может работать базовый процесс распознавания лиц:

  1. Обнаружение лица на изображении
  2. Выделение особенностей на лице
  3. Преобразования для сравнения с лицами в базе данных
  4. Заключение (определение соответствия между лицами)

Для обнаружения лиц, как правило, используется метод  Виолы-Джонса.

Для идентификации лиц можно использовать модель ASM (Active Shape Models). Основная идея заключается в учете статистических связей между расположением антропометрических точек лица. На каждом изображении лица точки пронумерованы в одинаковом порядке. По их взаимному расположению осуществляется сравнение лиц.

Для сравнения лиц можно использовать точечный 2D-каркас одного и того же положения лица относительно камеры. Более предпочтителен для этого точечный 3D-каркас.

Захват видеопотока с камеры и выделение лица

На основе фрагментов программ получил приложение, которое, обеспечивает захват видеопотока с камеры и выделение лица (используя Haar-Cascade классификатор).

 

#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
 // Load Face cascade (.xml file)
 CascadeClassifier face_cascade;
 face_cascade.load("haarcascade_frontalface_alt2.xml");

Mat img;
 VideoCapture cap(0);
 while (true)
 {
 cap >> img;

//cvtColor(img, img, CV_BGR2GRAY);

// Detect faces
 std::vector<Rect> faces;
 face_cascade.detectMultiScale(img, faces, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30));


 // Draw circles on the detected faces
 for (int i = 0; i < faces.size(); i++)
 {
 Point center(faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5);
 ellipse(img, center, Size(faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, Scalar(255, 0, 255), 4, 8, 0);
 }
 
 imshow("Detected Face", img);
 waitKey(1);
 }
 return 0;
}

Файлы каскадов находятся в директории   c:\opencv\build\etc\… Нужный каскад размещаете в директории проекта, там же, где и исходный файл main.cpp.

Выделение особых точек лица

Приложение создано на основе C++ code for OpenCV Facemark

 

#include <iostream>
#include <conio.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/face.hpp>
#include "drawLandmarks.hpp"

using namespace std;
using namespace cv;
using namespace cv::face;

int main(int argc, char** argv)
{
 // Load Face Detector
 CascadeClassifier faceDetector("haarcascade_frontalface_alt2.xml");
// Create an instance of Facemark
 Ptr<Facemark> facemark = FacemarkLBF::create();
// Load landmark detector
 facemark->loadModel("lbfmodel.yaml");
// Set up webcam for video capture
 VideoCapture cam(0);
// Variable to store a video frame and its grayscale 
 Mat frame, gray;
// Read a frame
 while (cam.read(frame))
 {
// Find face
 vector<Rect> faces;
 // Convert frame to grayscale because
 // faceDetector requires grayscale image.
 cvtColor(frame, gray, COLOR_BGR2GRAY);
// Detect faces
 faceDetector.detectMultiScale(gray, faces);
// Variable for landmarks. 
 // Landmarks for one face is a vector of points
 // There can be more than one face in the image. Hence, we 
 // use a vector of vector of points. 
 vector< vector<Point2f> > landmarks;
// Run landmark detector
 bool success = facemark->fit(frame, faces, landmarks);
if (success)
 {
 // If successful, render the landmarks on the face
 for (size_t i = 0; i < faces.size(); i++)
 {
 cv::rectangle(frame, faces[i], Scalar(0, 255, 0), 3);
 }
for (int i = 0; i < landmarks.size(); i++)
 {
 drawLandmarks(frame, landmarks[i]);
/*for (size_t j = 0; j < landmarks[i].size(); j++)
 circle(frame, Point(landmarks[i][j].x, landmarks[i][j].y), 1, Scalar(255, 0, 0), 2);*/
}
 }
// Display results 
 imshow("Facial Landmark Detection", frame);
// Exit loop if ESC is pressed
 if (waitKey(1) == 27) break;
}
 return 0;
}

В проекте приложения, там же где и файл main.cpp,  разместил файлы haarcascade_frontalface_alt2.xml, drawLandmarks.hpp и lbfmodel.yaml , на которые есть ссылки в коде. Файлы каскадов находятся в директории   c:\opencv\build\etc\… Файлы  drawLandmarks.hpp и lbfmodel.yaml есть в архиве Facemark_LBF.rar.

После вставки кода появились ошибки из-за того, что  в OpenCV 3.4.3-vc14-vc15 отсутствуют ряд библиотек, необходимых для запуска приложения. Скомпоновал свою библиотеку OpenCV с добавлением новых модулей  (скачать opencv_new.zip) и установил ее в  корень диска C (C:\opencv-new).

Теперь, все настройки, которые выполнялись при инстилляции  OpenCV 3.4.3-vc14-vc15, необходимо выполнить и для opencv-new:

Выполняю настройки в Windows. Выхожу на окно «Изменить переменную среды» (кнопки Windows->Служебные->Панель управления -> Система и безопасность -> Система -> Дополнительные параметры системы -> Переменные среды -> Path ->Изменить).  В этом окне  создаю переменную C:\opencv-new\x64\vc14\bin. Перезагружаю Windows.

В свойствах проекта также ссылаюсь на библиотеку opencv_new (вместо opencv). В окне «Property Pages» выполняю действия:

  • C/C++ -> General -> Additional Include Directories -> C:\opencv-new\include
  • Linker -> General -> Additional Library Directories -> C:\opencv-new\x64\vc14\lib
  • Linker -> Input -> Additional Dependencies -> opencv_core400.lib; opencv_face400.lib; opencv_videoio400.lib; opencv_objdetect400.lib; opencv_imgproc400.lib; opencv_highgui400.lib

При запуске программа выдает ошибку, если в установках проекта Debug. Для Release, запуск успешный.


Выбор признаков для фильтрации изображений и распознавания лиц

Точечный каркас лица отображается по разному в зависимости от объективных и субъективных факторов.

Объективные факторы — положение лица относительно камеры.

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

При  видеозахвате  иногда проскакивают и такие изображения. Их нужно отфильтровывать — как  при обучении так и распознавании.

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

Для сравнения лиц можно использовать точечный 2D каркас одного и того же положения лица. Какое положение лица относительно камеры есть наиболее информативным? Очевидно, что фронтальное. Не зря в криминалистике делают фото в анфас и профиль. Пока ограничимся анфасом.

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

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

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

Оставляем только те точечные каркасы, которые проходят по следующим признакам:

  • Прямая, которая проходит через крайние точки глаз (линия глаз), перпендикулярна прямой, которая проходит через крайние точки носа (линия носа).
  • Линия глаз параллельна прямой, которая проходит через точки уголков рта (линия рта).
  •  Соблюдается симметрия указанных выше точек относительно линии носа.
  • Угловые точки глаз (внешние и внутренние) находятся на одной прямой.

Пример фронтальных изображений, которые проходят по всем признакам:

Пример изображений, которые отфильтровываются:

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

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

Алгоритм распознавания лиц по 2D-каркасу точек

Координаты точек каркаса лица изначально задаются в системе координат, которая привязана к верхней левой точке окна. При этом ось Y направлена вниз.

Для удобства определения признаков используем пользовательскую систему координат (ПСК), ось X которой проходит через отрезок между серединами глаз, а ось Y — перпендикулярно этому отрезку через его середину в направлении вверх. Координаты  ПСК (от -1 до +1) нормализованы — соотнесены с расстоянием между  средними точками  глаз.

ПСК обеспечивает удобство и простоту определения признаков. Например,  положение лица в анфас определяется признаком симметрии соответствующих точек глаз относительно линии носа.  Этот признак  формализуется совпадением линии носа с осью Y, т.е  X1=X2=0, где X1 и X2 — координаты крайних точек носа (27 и 30) в ПСК.

Определяем относительно оконной СК 

Координаты средних точек левого и правого глаз (Left и Right):

XL = (X45 + X42) /2 ;      YL = (Y45 + Y42) /2 ;    XR = (X39 + X 36) /2;  YR = (Y39 + Y 36) /2;

Начало ПСК:

X0 =( XL + XR)/2;    Y0 =( YL + YR)/2;

Расстояния между средними точками глаз вдоль осей Х и Y:

DX = XR — XL;    DY = YR — YL;

Действительное расстояние L между средними точками глаз (по теореме Пифагора):

L = sqrt ( DX** 2 + DY**2)

Тригонометрические функции угла поворота ПСК:

sin_AL = DY/L

cos_AL = DX/L

Переходим от координат в оконной СК к координатам в ПСК, используя параметры X0,Y0, L, sin AL, cos AL:

X_User_0 = 2 (X_Window — X0 ) / L;

Y_User_0 = — 2 (Y_Window — Y0 ) / L ;

X_User =  X_User_0 * cos_AL  — Y_User_0  * sin_AL;

Y_User =  X_User_0 * sin_AL  + Y_User_0  * cos_AL;

Реализуем фильтрацию изображений последовательно проверяя признаки:

1.Признак перпендикулярности линий носа и глаз, а также симметрии угловых точек глаз. Линия носа определяется точками 27 и 30 (см. рисунок во Введении).  Оба признака выполняются, если в ПСК координаты этих точек X1 = X2= 0 (т.е., линия носа совпадает с осью Y).

2.Признак параллельности линии глаз и линии рта. Линия рта определяется точками 48 и 54 (см. рисунок во Введении). Признак выполняется, если в ПСК Y1-Y2=0.

3. Признак симметрии угловых точек рта. Линия рта определяется точками 48 и 54 (см. рисунок во Введении).  Признак выполняется, если в ПСК X1+X2 =0

4. Признак «Угловые точки глаз находятся на одной прямой». Прямые определяются парами точек: (36 и 45), а также (39 и 42). Поскольку тест по признаку 1 уже пройден, достаточно лишь определить в ПСК признак  Y2-Y1 =0 лишь для точек 36 и 39.

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

Программа сравнения лиц по одному признаку

В качестве признака берется расстояние между  точками переносицы и подбородка (Landmarks точками 27 и 8, см. рисунок во Введении). Признак, нормализован, определяется в ПСК отношением:   (Y1 — Y2)/L , где L — расстояние между центрами глаз. При обучении программы признак для конкретного лица определяется числом, которое высвечивается рядом с отслеживаемым лицом (эта часть кода в программе закомментирована). При распознавании значение признака сравнивается с введенным в программу конкретным признаком для каждого лица. При положительном результате сравнения  рядом с лицом появляется его идентификатор.

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

Контрольные задания:

  1. Ознакомиться с программой.
  2. Определить значение признака для своего лица и нескольких своих коллег.
  3. Протестировать программу на предмет идентификации лиц (своего и коллег).

 

 

#include <iostream>
#include <conio.h>

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect/objdetect.hpp>

#include <opencv2/face.hpp>
#include "drawLandmarks.hpp"

using namespace std;
using namespace cv;
using namespace cv::face;

int main(int argc, char** argv)
{
 // Load Face Detector
 CascadeClassifier faceDetector("haarcascade_frontalface_alt2.xml");
 // Create an instance of Facemark
 Ptr<Facemark> facemark = FacemarkLBF::create();
 // Load landmark detector
 facemark->loadModel("lbfmodel.yaml");
 // Set up webcam for video capture
 VideoCapture cam(0);
 // Variable to store a video frame and its grayscale 
 Mat frame, gray;
 // Read a frame
 while (cam.read(frame))
 {
 // Find face
 vector<Rect> faces;
 // Convert frame to grayscale because
 // faceDetector requires grayscale image.
 cvtColor(frame, gray, COLOR_BGR2GRAY);
 // Detect faces
 faceDetector.detectMultiScale(gray, faces);
 // Variable for landmarks. 
 // Landmarks for one face is a vector of points
 // There can be more than one face in the image. Hence, we 
 // use a vector of vector of points. 
 vector< vector<Point2f> > landmarks;
 // Run landmark detector
 bool success = facemark->fit(frame, faces, landmarks);

if (success)
 {
 // If successful, render the landmarks on the face
 for (size_t i = 0; i < faces.size(); i++)
 {
 cv::rectangle(frame, faces[i], Scalar(0, 255, 0), 3);
 }

for (int i = 0; i < landmarks.size(); i++)
 {
 //if((i >=30)&&(i <= 35))
 drawLandmarks(frame, landmarks[i]);

for (size_t j = 0; j < landmarks[i].size(); j++) {
 circle(frame, Point(landmarks[i][j].x, landmarks[i][j].y), 1, Scalar(255, 0, 0), 2);
 }
 line(frame, Point(landmarks[i][27].x, landmarks[i][27].y), Point(landmarks[i][8].x, landmarks[i][8].y), Scalar(0, 0, 255), 2);
 float XL = (landmarks[i][45].x + landmarks[i][42].x) / 2;
 float YL = (landmarks[i][45].y + landmarks[i][42].y) / 2;
 float XR = (landmarks[i][39].x + landmarks[i][36].x) / 2;
 float YR = (landmarks[i][39].y + landmarks[i][36].y) / 2;
 line(frame, Point(XL, YL), Point(XR, YR), Scalar(0, 0, 255), 2);
 float DX = XR - XL;
 float DY = YR - YL;
 float L = sqrt(DX * DX + DY * DY);
 float X1 = (landmarks[i][27].x);
 float Y1 = (landmarks[i][27].y);
 float X2 = (landmarks[i][8].x);
 float Y2 = (landmarks[i][8].y);
 float DX1 = abs(X1 - X2);
 float DY1 = abs(Y1 - Y2);
 float L1 = sqrt(DX1 * DX1 + DY1 * DY1);
 float X0 = (XL + XR) / 2;
 float Y0 = (YL + YR) / 2;
 float sin_AL = DY / L;
 float cos_AL = DX / L;

float X_User_0 = (landmarks[i][27].x - X0) / L;
 float Y_User_0 = -(landmarks[i][27].y - Y0) / L;
 float X_User27 = X_User_0 * cos_AL - Y_User_0 * sin_AL;
 float Y_User27 = X_User_0 * sin_AL + Y_User_0 * cos_AL;

X_User_0 = (landmarks[i][30].x - X0) / L;
 Y_User_0 = -(landmarks[i][30].y - Y0) / L;
 float X_User30 = X_User_0 * cos_AL - Y_User_0 * sin_AL;
 float Y_User30 = X_User_0 * sin_AL + Y_User_0 * cos_AL;

if (abs(X_User27 - X_User30) <= 0.1)
 {
 //putText(frame, std::to_string(abs(L1 / L)), Point(landmarks[i][27].x, landmarks[i][27].y), 1, 2, Scalar(0, 0, 255), 2);
 if (abs((L1 / L) - 1.6) < 0.1)
 {
 putText(frame, "Roman", Point(landmarks[i][27].x, landmarks[i][27].y), 1, 2, Scalar(0, 0, 255), 2);
 }
 if (abs((L1 / L) - 1.9) < 0.1)
 {
 putText(frame, "Pasha", Point(landmarks[i][27].x, landmarks[i][27].y), 1, 2, Scalar(0, 0, 255), 2);
 }
 if (abs((L1 / L) - 2.1) < 0.1)
 {
 putText(frame, "Svirnesvkiy", Point(landmarks[i][27].x, landmarks[i][27].y), 1, 2, Scalar(0, 0, 255), 2);
 }
 
 }
 putText(frame, "Incorrect", Point(landmarks[i][27].x, landmarks[i][27].y), 1, 2, Scalar(0, 0, 255), 2);
 }
 }

// Display results 
 imshow("Facial Landmark Detection", frame);

// Exit loop if ESC is pressed
 if (waitKey(1) == 27) break;

}
 return 0;
}

Далее смотри:

 Полезные ссылки

 

 

 

Авторы: Николай Свирневский и Сергей Иващенко