514 lines
No EOL
16 KiB
C++
514 lines
No EOL
16 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (C) 2017, Carnegie Mellon University and University of Cambridge,
|
|
// all rights reserved.
|
|
//
|
|
// ACADEMIC OR NON-PROFIT ORGANIZATION NONCOMMERCIAL RESEARCH USE ONLY
|
|
//
|
|
// BY USING OR DOWNLOADING THE SOFTWARE, YOU ARE AGREEING TO THE TERMS OF THIS LICENSE AGREEMENT.
|
|
// IF YOU DO NOT AGREE WITH THESE TERMS, YOU MAY NOT USE OR DOWNLOAD THE SOFTWARE.
|
|
//
|
|
// License can be found in OpenFace-license.txt
|
|
//
|
|
// * Any publications arising from the use of this software, including but
|
|
// not limited to academic journal and conference publications, technical
|
|
// reports and manuals, must cite at least one of the following works:
|
|
//
|
|
// OpenFace: an open source facial behavior analysis toolkit
|
|
// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency
|
|
// in IEEE Winter Conference on Applications of Computer Vision, 2016
|
|
//
|
|
// Rendering of Eyes for Eye-Shape Registration and Gaze Estimation
|
|
// Erroll Wood, Tadas Baltrušaitis, Xucong Zhang, Yusuke Sugano, Peter Robinson, and Andreas Bulling
|
|
// in IEEE International. Conference on Computer Vision (ICCV), 2015
|
|
//
|
|
// Cross-dataset learning and person-speci?c normalisation for automatic Action Unit detection
|
|
// Tadas Baltrušaitis, Marwa Mahmoud, and Peter Robinson
|
|
// in Facial Expression Recognition and Analysis Challenge,
|
|
// IEEE International Conference on Automatic Face and Gesture Recognition, 2015
|
|
//
|
|
// Constrained Local Neural Fields for robust facial landmark detection in the wild.
|
|
// Tadas Baltrušaitis, Peter Robinson, and Louis-Philippe Morency.
|
|
// in IEEE Int. Conference on Computer Vision Workshops, 300 Faces in-the-Wild Challenge, 2013.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
#include "PAW.h"
|
|
|
|
// OpenCV includes
|
|
#include <opencv2/core/core.hpp>
|
|
#include <opencv2/imgproc.hpp>
|
|
|
|
#include "Face_utils.h"
|
|
|
|
using namespace FaceAnalysis;
|
|
|
|
// Copy constructor
|
|
PAW::PAW(const PAW& other) : destination_landmarks(other.destination_landmarks.clone()), source_landmarks(other.source_landmarks.clone()), triangulation(other.triangulation.clone()),
|
|
triangle_id(other.triangle_id.clone()), pixel_mask(other.pixel_mask.clone()), coefficients(other.coefficients.clone()), alpha(other.alpha.clone()), beta(other.beta.clone()), map_x(other.map_x.clone()), map_y(other.map_y.clone())
|
|
{
|
|
this->number_of_pixels = other.number_of_pixels;
|
|
this->min_x = other.min_x;
|
|
this->min_y = other.min_y;
|
|
}
|
|
|
|
// A constructor from destination shape and triangulation
|
|
PAW::PAW(const cv::Mat_<float>& destination_shape, const cv::Mat_<int>& triangulation)
|
|
{
|
|
// Initialise some variables directly
|
|
this->destination_landmarks = destination_shape;
|
|
this->triangulation = triangulation;
|
|
|
|
int num_points = destination_shape.rows/2;
|
|
|
|
int num_tris = triangulation.rows;
|
|
|
|
// Pre-compute the rest
|
|
alpha = cv::Mat_<float>(num_tris, 3);
|
|
beta = cv::Mat_<float>(num_tris, 3);
|
|
|
|
cv::Mat_<float> xs = destination_shape(cv::Rect(0, 0, 1, num_points));
|
|
cv::Mat_<float> ys = destination_shape(cv::Rect(0, num_points, 1, num_points));
|
|
|
|
// Create a vector representation of the control points
|
|
std::vector<std::vector<float>> destination_points;
|
|
|
|
for (int tri = 0; tri < num_tris; ++tri)
|
|
{
|
|
int j = triangulation.at<int>(tri, 0);
|
|
int k = triangulation.at<int>(tri, 1);
|
|
int l = triangulation.at<int>(tri, 2);
|
|
|
|
float c1 = ys.at<float>(l) - ys.at<float>(j);
|
|
float c2 = xs.at<float>(l) - xs.at<float>(j);
|
|
float c4 = ys.at<float>(k) - ys.at<float>(j);
|
|
float c3 = xs.at<float>(k) - xs.at<float>(j);
|
|
|
|
float c5 = c3*c1 - c2*c4;
|
|
|
|
alpha.at<float>(tri, 0) = (ys.at<float>(j) * c2 - xs.at<float>(j) * c1) / c5;
|
|
alpha.at<float>(tri, 1) = c1/c5;
|
|
alpha.at<float>(tri, 2) = -c2/c5;
|
|
|
|
beta.at<float>(tri, 0) = (xs.at<float>(j) * c4 - ys.at<float>(j) * c3)/c5;
|
|
beta.at<float>(tri, 1) = -c4/c5;
|
|
beta.at<float>(tri, 2) = c3/c5;
|
|
|
|
// Add points corresponding to triangles as optimisation
|
|
std::vector<float> triangle_points(10);
|
|
|
|
triangle_points[0] = xs.at<float>(j);
|
|
triangle_points[1] = ys.at<float>(j);
|
|
triangle_points[2] = xs.at<float>(k);
|
|
triangle_points[3] = ys.at<float>(k);
|
|
triangle_points[4] = xs.at<float>(l);
|
|
triangle_points[5] = ys.at<float>(l);
|
|
|
|
cv::Vec3f xs_three(triangle_points[0], triangle_points[2], triangle_points[4]);
|
|
cv::Vec3f ys_three(triangle_points[1], triangle_points[3], triangle_points[5]);
|
|
|
|
double min_x, max_x, min_y, max_y;
|
|
cv::minMaxIdx(xs_three, &min_x, &max_x);
|
|
cv::minMaxIdx(ys_three, &min_y, &max_y);
|
|
|
|
triangle_points[6] = (float) max_x;
|
|
triangle_points[7] = (float) max_y;
|
|
|
|
triangle_points[8] = (float) min_x;
|
|
triangle_points[9] = (float) min_y;
|
|
|
|
destination_points.push_back(triangle_points);
|
|
|
|
}
|
|
|
|
double max_x;
|
|
double max_y;
|
|
double min_x_d;
|
|
double min_y_d;
|
|
|
|
minMaxLoc(xs, &min_x_d, &max_x);
|
|
minMaxLoc(ys, &min_y_d, &max_y);
|
|
|
|
min_x = min_x_d;
|
|
min_y = min_y_d;
|
|
|
|
int w = (int)(max_x - min_x + 1.5);
|
|
int h = (int)(max_y - min_y + 1.5);
|
|
|
|
// Round the min_x and min_y for simplicity?
|
|
|
|
pixel_mask = cv::Mat_<uchar>(h, w, (uchar)0);
|
|
triangle_id = cv::Mat_<int>(h, w, -1);
|
|
|
|
int curr_tri = -1;
|
|
|
|
for(int y = 0; y < pixel_mask.rows; y++)
|
|
{
|
|
for(int x = 0; x < pixel_mask.cols; x++)
|
|
{
|
|
curr_tri = findTriangle(cv::Point_<float>(x + min_x, y + min_y), destination_points, curr_tri);
|
|
// If there is a triangle at this location
|
|
if(curr_tri != -1)
|
|
{
|
|
triangle_id.at<int>(y, x) = curr_tri;
|
|
pixel_mask.at<uchar>(y, x) = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Preallocate maps and coefficients
|
|
coefficients.create(num_tris, 6);
|
|
map_x.create(pixel_mask.rows,pixel_mask.cols);
|
|
map_y.create(pixel_mask.rows,pixel_mask.cols);
|
|
|
|
|
|
}
|
|
|
|
// Manually define min and max values
|
|
PAW::PAW(const cv::Mat_<float>& destination_shape, const cv::Mat_<int>& triangulation, float in_min_x, float in_min_y, float in_max_x, float in_max_y)
|
|
{
|
|
// Initialise some variables directly
|
|
this->destination_landmarks = destination_shape;
|
|
this->triangulation = triangulation;
|
|
|
|
int num_points = destination_shape.rows/2;
|
|
|
|
int num_tris = triangulation.rows;
|
|
|
|
// Pre-compute the rest
|
|
alpha = cv::Mat_<float>(num_tris, 3);
|
|
beta = cv::Mat_<float>(num_tris, 3);
|
|
|
|
cv::Mat_<float> xs = destination_shape(cv::Rect(0, 0, 1, num_points));
|
|
cv::Mat_<float> ys = destination_shape(cv::Rect(0, num_points, 1, num_points));
|
|
|
|
// Create a vector representation of the control points
|
|
std::vector<std::vector<float>> destination_points;
|
|
|
|
for (int tri = 0; tri < num_tris; ++tri)
|
|
{
|
|
int j = triangulation.at<int>(tri, 0);
|
|
int k = triangulation.at<int>(tri, 1);
|
|
int l = triangulation.at<int>(tri, 2);
|
|
|
|
float c1 = ys.at<float>(l) - ys.at<float>(j);
|
|
float c2 = xs.at<float>(l) - xs.at<float>(j);
|
|
float c4 = ys.at<float>(k) - ys.at<float>(j);
|
|
float c3 = xs.at<float>(k) - xs.at<float>(j);
|
|
|
|
float c5 = c3*c1 - c2*c4;
|
|
|
|
alpha.at<float>(tri, 0) = (ys.at<float>(j) * c2 - xs.at<float>(j) * c1) / c5;
|
|
alpha.at<float>(tri, 1) = c1/c5;
|
|
alpha.at<float>(tri, 2) = -c2/c5;
|
|
|
|
beta.at<float>(tri, 0) = (xs.at<float>(j) * c4 - ys.at<float>(j) * c3)/c5;
|
|
beta.at<float>(tri, 1) = -c4/c5;
|
|
beta.at<float>(tri, 2) = c3/c5;
|
|
|
|
// Add points corresponding to triangles as optimisation
|
|
std::vector<float> triangle_points(10);
|
|
|
|
triangle_points[0] = xs.at<float>(j);
|
|
triangle_points[1] = ys.at<float>(j);
|
|
triangle_points[2] = xs.at<float>(k);
|
|
triangle_points[3] = ys.at<float>(k);
|
|
triangle_points[4] = xs.at<float>(l);
|
|
triangle_points[5] = ys.at<float>(l);
|
|
|
|
cv::Vec3f xs_three(triangle_points[0], triangle_points[2], triangle_points[4]);
|
|
cv::Vec3f ys_three(triangle_points[1], triangle_points[3], triangle_points[5]);
|
|
|
|
double min_x, max_x, min_y, max_y;
|
|
cv::minMaxIdx(xs_three, &min_x, &max_x);
|
|
cv::minMaxIdx(ys_three, &min_y, &max_y);
|
|
|
|
triangle_points[6] = (float)max_x;
|
|
triangle_points[7] = (float)max_y;
|
|
|
|
triangle_points[8] = (float)min_x;
|
|
triangle_points[9] = (float)min_y;
|
|
|
|
destination_points.push_back(triangle_points);
|
|
|
|
}
|
|
|
|
float max_x;
|
|
float max_y;
|
|
|
|
min_x = in_min_x;
|
|
min_y = in_min_y;
|
|
|
|
max_x = in_max_x;
|
|
max_y = in_max_y;
|
|
|
|
int w = (int)(max_x - min_x + 1.5);
|
|
int h = (int)(max_y - min_y + 1.5);
|
|
|
|
// Round the min_x and min_y for simplicity?
|
|
|
|
pixel_mask = cv::Mat_<uchar>(h, w, (uchar)0);
|
|
triangle_id = cv::Mat_<int>(h, w, -1);
|
|
|
|
int curr_tri = -1;
|
|
|
|
for(int y = 0; y < pixel_mask.rows; y++)
|
|
{
|
|
for(int x = 0; x < pixel_mask.cols; x++)
|
|
{
|
|
curr_tri = findTriangle(cv::Point_<float>(x + min_x, y + min_y), destination_points, curr_tri);
|
|
// If there is a triangle at this location
|
|
if(curr_tri != -1)
|
|
{
|
|
triangle_id.at<int>(y, x) = curr_tri;
|
|
pixel_mask.at<uchar>(y, x) = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Preallocate maps and coefficients
|
|
coefficients.create(num_tris, 6);
|
|
map_x.create(pixel_mask.rows,pixel_mask.cols);
|
|
map_y.create(pixel_mask.rows,pixel_mask.cols);
|
|
|
|
}
|
|
|
|
//===========================================================================
|
|
void PAW::Read(std::ifstream& stream)
|
|
{
|
|
|
|
stream.read ((char*)&number_of_pixels, 4);
|
|
double min_x_d, min_y_d;
|
|
stream.read ((char*)&min_x_d, 8);
|
|
stream.read ((char*)&min_y_d, 8);
|
|
min_x = (float)min_x_d;
|
|
min_y = (float)min_y_d;
|
|
|
|
cv::Mat_<double> destination_landmarks_d;
|
|
ReadMatBin(stream, destination_landmarks_d);
|
|
destination_landmarks_d.convertTo(destination_landmarks, CV_32F);
|
|
|
|
ReadMatBin(stream, triangulation);
|
|
|
|
ReadMatBin(stream, triangle_id);
|
|
|
|
cv::Mat tmpMask;
|
|
ReadMatBin(stream, tmpMask);
|
|
tmpMask.convertTo(pixel_mask, CV_8U);
|
|
|
|
cv::Mat_<double> alpha_d;
|
|
ReadMatBin(stream, alpha_d);
|
|
alpha_d.convertTo(alpha, CV_32F);
|
|
|
|
cv::Mat_<double> beta_d;
|
|
ReadMatBin(stream, beta_d);
|
|
beta_d.convertTo(beta, CV_32F);
|
|
|
|
map_x.create(pixel_mask.rows,pixel_mask.cols);
|
|
map_y.create(pixel_mask.rows,pixel_mask.cols);
|
|
|
|
coefficients.create(this->NumberOfTriangles(),6);
|
|
|
|
source_landmarks = destination_landmarks;
|
|
}
|
|
|
|
//=============================================================================
|
|
// cropping from the source image to the destination image using the shape in s, used to determine if shape fitting converged successfully
|
|
void PAW::Warp(const cv::Mat& image_to_warp, cv::Mat& destination_image, const cv::Mat_<float>& landmarks_to_warp)
|
|
{
|
|
|
|
// set the current shape
|
|
source_landmarks = landmarks_to_warp.clone();
|
|
|
|
// prepare the mapping coefficients using the current shape
|
|
this->CalcCoeff();
|
|
|
|
// Do the actual mapping computation (where to warp from)
|
|
this->WarpRegion(map_x, map_y);
|
|
|
|
// Do the actual warp (with bi-linear interpolation)
|
|
remap(image_to_warp, destination_image, map_x, map_y, CV_INTER_LINEAR);
|
|
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
// Calculate the warping coefficients
|
|
void PAW::CalcCoeff()
|
|
{
|
|
int p = this->NumberOfLandmarks();
|
|
|
|
for(int l = 0; l < this->NumberOfTriangles(); l++)
|
|
{
|
|
|
|
int i = triangulation.at<int>(l,0);
|
|
int j = triangulation.at<int>(l,1);
|
|
int k = triangulation.at<int>(l,2);
|
|
|
|
float c1 = source_landmarks.at<float>(i , 0);
|
|
float c2 = source_landmarks.at<float>(j , 0) - c1;
|
|
float c3 = source_landmarks.at<float>(k , 0) - c1;
|
|
float c4 = source_landmarks.at<float>(i + p, 0);
|
|
float c5 = source_landmarks.at<float>(j + p, 0) - c4;
|
|
float c6 = source_landmarks.at<float>(k + p, 0) - c4;
|
|
|
|
// Get a pointer to the coefficient we will be precomputing
|
|
float *coeff = coefficients.ptr<float>(l);
|
|
|
|
// Extract the relevant alphas and betas
|
|
float *c_alpha = alpha.ptr<float>(l);
|
|
float *c_beta = beta.ptr<float>(l);
|
|
|
|
coeff[0] = c1 + c2 * c_alpha[0] + c3 * c_beta[0];
|
|
coeff[1] = c2 * c_alpha[1] + c3 * c_beta[1];
|
|
coeff[2] = c2 * c_alpha[2] + c3 * c_beta[2];
|
|
coeff[3] = c4 + c5 * c_alpha[0] + c6 * c_beta[0];
|
|
coeff[4] = c5 * c_alpha[1] + c6 * c_beta[1];
|
|
coeff[5] = c5 * c_alpha[2] + c6 * c_beta[2];
|
|
}
|
|
}
|
|
|
|
//======================================================================
|
|
// Compute the mapping coefficients
|
|
void PAW::WarpRegion(cv::Mat_<float>& mapx, cv::Mat_<float>& mapy)
|
|
{
|
|
|
|
cv::MatIterator_<float> xp = mapx.begin();
|
|
cv::MatIterator_<float> yp = mapy.begin();
|
|
cv::MatIterator_<uchar> mp = pixel_mask.begin();
|
|
cv::MatIterator_<int> tp = triangle_id.begin();
|
|
|
|
// The coefficients corresponding to the current triangle
|
|
float * a;
|
|
|
|
// Current triangle being processed
|
|
int k=-1;
|
|
|
|
for(int y = 0; y < pixel_mask.rows; y++)
|
|
{
|
|
float yi = float(y) + min_y;
|
|
|
|
for(int x = 0; x < pixel_mask.cols; x++)
|
|
{
|
|
float xi = float(x) + min_x;
|
|
|
|
if(*mp == 0)
|
|
{
|
|
*xp = -1;
|
|
*yp = -1;
|
|
}
|
|
else
|
|
{
|
|
// triangle corresponding to the current pixel
|
|
int j = *tp;
|
|
|
|
// If it is different from the previous triangle point to new coefficients
|
|
// This will always be the case in the first iteration, hence a will not point to nothing
|
|
if(j != k)
|
|
{
|
|
// Update the coefficient pointer if a new triangle is being processed
|
|
a = coefficients.ptr<float>(j);
|
|
k = j;
|
|
}
|
|
|
|
//ap is now the pointer to the coefficients
|
|
float *ap = a;
|
|
|
|
//look at the first coefficient (and increment). first coefficient is an x offset
|
|
float xo = *ap++;
|
|
//second coefficient is an x scale as a function of x
|
|
xo += *ap++ * xi;
|
|
//third coefficient ap(2) is an x scale as a function of y
|
|
*xp = float(xo + *ap++ * yi);
|
|
|
|
//then fourth coefficient ap(3) is a y offset
|
|
float yo = *ap++;
|
|
//fifth coeff adds coeff[4]*x to y
|
|
yo += *ap++ * xi;
|
|
//final coeff adds coeff[5]*y to y
|
|
*yp = float(yo + *ap++ * yi);
|
|
|
|
}
|
|
mp++; tp++; xp++; yp++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// Helper functions to determine which point a triangle lies in
|
|
// ============================================================
|
|
|
|
// Is the point (x0,y0) on same side as a half-plane defined by (x1,y1), (x2, y2), and (x3, y3)
|
|
bool PAW::sameSide(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3)
|
|
{
|
|
|
|
float x = (x3-x2)*(y0-y2) - (x0-x2)*(y3-y2);
|
|
float y = (x3-x2)*(y1-y2) - (x1-x2)*(y3-y2);
|
|
|
|
return x*y >= 0;
|
|
|
|
}
|
|
|
|
// if point (x0, y0) is on same side for all three half-planes it is in a triangle
|
|
bool PAW::pointInTriangle(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3)
|
|
{
|
|
bool same_1 = sameSide(x0, y0, x1, y1, x2, y2, x3, y3);
|
|
bool same_2 = sameSide(x0, y0, x2, y2, x1, y1, x3, y3);
|
|
bool same_3 = sameSide(x0, y0, x3, y3, x1, y1, x2, y2);
|
|
|
|
return same_1 && same_2 && same_3;
|
|
|
|
}
|
|
|
|
// Find if a given point lies in the triangles
|
|
int PAW::findTriangle(const cv::Point_<float>& point, const std::vector<std::vector<float>>& control_points, int guess)
|
|
{
|
|
|
|
int num_tris = control_points.size();
|
|
|
|
int tri = -1;
|
|
|
|
float x0 = point.x;
|
|
float y0 = point.y;
|
|
|
|
// Allow a guess for speed (so as not to go through all triangles)
|
|
if(guess != -1)
|
|
{
|
|
|
|
bool in_triangle = pointInTriangle(x0, y0, control_points[guess][0], control_points[guess][1], control_points[guess][2], control_points[guess][3], control_points[guess][4], control_points[guess][5]);
|
|
if(in_triangle)
|
|
{
|
|
return guess;
|
|
}
|
|
}
|
|
|
|
|
|
for (int i = 0; i < num_tris; ++i)
|
|
{
|
|
|
|
float max_x = control_points[i][6];
|
|
float max_y = control_points[i][7];
|
|
|
|
float min_x = control_points[i][8];
|
|
float min_y = control_points[i][9];
|
|
|
|
// Skip the check if the point is outside the bounding box of the triangle
|
|
|
|
if( max_x < x0 || min_x > x0 || max_y < y0 || min_y > y0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool in_triangle = pointInTriangle(x0, y0,
|
|
control_points[i][0], control_points[i][1],
|
|
control_points[i][2], control_points[i][3],
|
|
control_points[i][4], control_points[i][5]);
|
|
|
|
if(in_triangle)
|
|
{
|
|
tri = i;
|
|
break;
|
|
}
|
|
}
|
|
return tri;
|
|
} |