Распознавание лиц. 3D- реконструкция Facemarks модели

Автор: | 28.04.2019

Постановка задачи
Пространственная система координат, привязанная к лицу
Алгоритм определения 3D-координат
Программа для тестирования предложенного метода

Постановка задачи

В статье Распознавание лиц на основе OpenCV для C++  изложены концепции распознавания лиц  на основе Facemarks модели. Признаки для идентификации определяются из статистических связей (расстояний) между расположением антропометрических точек лица.

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

Из рисунка видно, как изменяются значения 2-х признаков при повороте головы.  Первый признак — это расстояние по вертикали от переносицы до подбородка. Второй признак — расстояние по горизонтали  между 2-я крайними точками лица. Оба размера нормализованы — отнесены к расстоянию между серединой глаз.

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

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

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

Для нормализации можно выбрать точки на 3-х взаимно-перпендикулярных (в 3D пространстве) линиях, которые по  определяют  на изображении проекцию пространственной системы координат, привязанной к лицу.

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

Координаты Facemarks-точек обычно определяются в оконной системе координат (ОСК), у которой начало в верхнем левом углу окна, ось Y направлена вниз.

Необходимо Facemarks-точки отнести к пространственной СК, привязанной к лицу. Зная направление осей, а также единичные отрезки на них, можно  определить 3D координаты любой из Facemarks-точек.  Рассмотрим это на следующем примере.

На рисунке показана модель СК, оси которой проходят через Facemarks-точки:

  • Ось x проходит через центры глаз (определяются на основе точек 36, 39, 42 и 45).
  • Ось y  проходит через точку 33 и среднюю точку между центрами глаз.
  • Ось z  проходит через точку 27 и среднюю точку между центрами глаз.

Точки, которые определяют СК, выбраны из условий взаимной перпендикулярности осей координат, а также симметрии пар Facemarks-точек  относительно плоскости yz.

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

На рисунке показан пример, как определить координаты симметрично расположенных  Facemarks-точек 3 и 13.  Соединяем точки отрезком. Определяем середину отрезка. Из него проводим параллельную оси z линию до пересечения с осью y.  Отрезки ломанной от точки 3 до начала координат отображают координаты (x,z,y) для точки 3.

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

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

Здесь ось X остается прежней, ось Y определяется точкой 8, а ось Z — серединой между точками 0 и 16. Справа на рисунке показано как определяются координаты пар симметричных точек (3, 13) и (17, 26), а также координаты (z, y) точки 30. Координаты нормализуются единичными отрезками, определяемыми точками на осях (центр между глаз на оси x;  точка 8 на оси y;  центр между точками 0 и 16 оси z).

Алгоритм определения 3D-координат

Прежде всего перейдем от оконной системы координат (ОСК) к более удобной для восприятия системе координат  с началом посредине между глаз и с направлением оси Y вверх:

СК определяется на основе Facemarks-точек 36, 39, 42 и 45.

 

XL = (X36 + X 39) /2;  YL =  (Y36 + Y39) /2;  XR =  (X42 + X45) /2;      YR = (Y42 + Y45) /2 ;   

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

 

Определяем направления осей координат СК, привязанной к точкам лица через угловые коэффициенты прямой в плоскости (у =K*X+B):

Kx = dyx/dxx;  Ky= dyy/dxy;  Kz= dyz/dxz;

где смещения (d…) определяются через координаты точек PR,  33 и 27:

dxx= XR — X0; dyx= — (YR — Y0);

  dxy= — (X33 — X0);   dyy= Y33 — Y0;

dxz= X27 — X0;    dyz= — (Y27 — Y0);

Смещения определены в СК с центром в точке (X0,Y0) и осью Y, направленной вверх. Координаты точек в этой СК определяются из выражений:

X = Xоск — Xо;    Y = — (Yоск — Y0);

где Xоск  и  Yоск —  текущие координаты  Facemarks-точек в оконной системе координат (ОСК).

Далее в цикле определяем проекции отрезков координатной ломаной (xzy) для каждой пары симметричных Facemarks-точек. Рассмотрим на примере точек 3 и 13

Определяем расстояние между парой точек по теореме Пифагора:

DX12 = abs (X1-X2);

DY12 = abs (Y1-Y2);

D = sqrt (DX12 * DX12 + DY12 * DY12);

Определяем проекции координат x точек 13 и 3:

x13p = D/2;   x3p = — x13p;

Теперь переходим к определению проекций координаты z точек. Для этого сначала находим середину отрезка между парой точек:

Xmid=X3+(X13 — X3)/2;   Ymid= Y3+(Y13 -Y3)/2;

Переходим в СК с центром в точке (X0,Y0) и осью Y, направленной вверх:

Xmid= Xmid -X0;   Ymid= — (Ymid -Y0);

Через точку (Xmid, Ymid) проводим прямую параллельно проекции оси z до пересечения с проекцией оси y.   Находим координаты точки пересечения этих прямых:

Xint=(Ymid — Kz*Xmid)/(Ky-Kz);

Yint= Ky * Xint;

Координаты точки находим решая систему уравнений, определяющих прямые. Уравнение прямой, параллельной проекции оси z:   Y= Kz (X — Xmid) + Ymid. Уравнение прямой, проходящей по оси у:    Y= Ky * X.

Третья точка ломанной находится в начале координат: Xo=0 и Y0=0. По 3-м точкам можно определить проекции координат z и y для точек 13 и 3  —  по теореме Пифагора (также как и проекцию координаты x).

y3p = sqrt (Xint * Xint + Yint * Yint);

z3p = sqrt ((Xint -Xmid)  * (Xint -Xmid) + (Yint — Ymid) * (Yint -Ymid));

От проекций переходим к нормализованным признакам-координатам:

x13 = x13p/Lx;   x3 = — x13;   y3 = y3p/Ly;    z3 = z3p/Lz;

 Здесь Lx, Ly и Lz — отрезки на осях, отсекаемые точками PR, 33 и 27. Определяются по теореме Пифагора:

Lx= sqrt (dyx * dyx + dxx * dxx);

Ly= sqrt (dyy * dyy + dxy * dxy);

 Lz= sqrt (dxz * dxz + dyz * dyz);

В цикле аналогично определяются координаты остальных пар симметричных точек.

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

Программа для тестирования предложенного метода

Приведенная ниже программа позволяет выбрать наиболее подходящие настройки и оценить эффективность предложенного метода 3D- реконструкция Facemarks модели.

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

Значение между координатами точек  должны быть приблизительно одинаковыми. В противном случае расстояния между точками будут зависеть лишь от одной координаты. Например, из рисунка видно, что значение координаты Z тестируемой точки 3 существенно больше  по сравнению с координатами X и Y. Какой выход? Можно уменьшать ее делением на какой-то коэффициент.

В конечном итоге, для оценки эффективности метода необходимо определить, как влияет поворот головы на стабильность признаков по сравнению с 2-мерной Facemarks моделью.

 

#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]);

 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;
 // The coord. center
 float X0 = (XL + XR) / 2;
 float Y0 = (YL + YR) / 2;
 
 //line(frame, Point(X0, Y0), Point(XL, YL), Scalar(0, 0, 255), 2); // Draw x
 //line(frame, Point(X0, Y0), Point(landmarks[i][33].x, landmarks[i][33].y), Scalar(0, 255, 0), 2); // Draw y 
 //line(frame, Point(X0, Y0), Point(landmarks[i][27].x, landmarks[i][27].y), Scalar(255, 0, 0), 2); // Draw z

float dxx = XR - X0; float dyx = -(YR - Y0);
 float dxy = -(landmarks[i][33].x - X0); float dyy = landmarks[i][33].y - Y0;
 float dxz = landmarks[i][27].x - X0; float dyz = -(landmarks[i][27].y - Y0);

float Kx = dyx/dxx; float Ky = dyy/dxy; float Kz = dyz/dxz;

 float Lx = sqrt(dxx * dxx + dyx * dyx);
 float Ly = sqrt(dxy * dxy + dyy * dyy);
 float Lz = sqrt(dxz * dxz + dyz * dyz);

 float X1 = (landmarks[i][3].x); // координаты 2-х симметричных точек
 float Y1 = (landmarks[i][3].y);
 float X2 = (landmarks[i][13].x);
 float Y2 = (landmarks[i][13].y);
 float DX1 = (X2 - X1);
 float DY1 = (Y2 - Y1);


 float Xmid = X1 + DX1 / 2; float Ymid = Y1 + DY1 / 2;
 
 Xmid = Xmid - X0; Ymid = -(Ymid - Y0); // переход из оконной системы координат

float Xint = (Ymid-Kz*Xmid)/(Ky - Kz); float Yint = Ky*Xint; // определяем точку пересечения с осью y.

 Xmid = Xmid + X0; Ymid = -(Ymid - Y0); // возврат в оконную систему координат
 Xint = Xint + X0; Yint = -(Yint - Y0);


 // рисуем отрезки координатной ломанной
 line(frame, Point(X1, Y1), Point(Xmid, Ymid), Scalar(0, 0, 255), 2); // Draw X3
 line(frame, Point(X0, Y0), Point(Xint, Yint), Scalar(0, 255, 0), 2); // Draw Y3
 line(frame, Point(Xmid, Ymid), Point(Xint, Yint), Scalar(255, 0, 0), 2); // Draw Z3

 float x1 = 0.5*sqrt(DX1 * DX1 + DY1 * DY1) / Lx; // нормализованная координата х1 
 float x2 = -x1; //нормализованная координата х2 (для симметричной точки)

 DX1 = (Xint - X0);
 DY1 = (Yint - Y0);
 float y1 = sqrt(DX1 * DX1 + DY1 * DY1) / Ly; //нормализованные координаты y1 и y2

 DX1 = (Xint - Xmid);
 DY1 = (Yint - Xmid);
 float z1 = sqrt(DX1 * DX1 + DY1 * DY1) / Lz; //нормализованные координаты z1 и z2

 putText(frame, std::to_string(x1), Point(X1 + 150, Y1 + 10), 1, 2, Scalar(0,0, 255), 2);
 putText(frame, std::to_string(z1), Point(X1 + 150, Y1 - 30), 1, 2, Scalar(255, 0, 0), 2);
 putText(frame, std::to_string(y1), Point(X1 + 150, Y1 - 70), 1, 2, Scalar(0, 255, 0), 2);
 
 }
 }

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

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

}
 return 0;
}

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *