Commit 21807b946114f4e0616a112e461d226061e99e80

Authored by Josh Klontz
1 parent 3b38780b

added some alignment transforms

Showing 1 changed file with 613 additions and 0 deletions
openbr/plugins/core/align.cpp 0 → 100644
  1 +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2 + * Copyright 2015 Noblis *
  3 + * *
  4 + * Licensed under the Apache License, Version 2.0 (the "License"); *
  5 + * you may not use this file except in compliance with the License. *
  6 + * You may obtain a copy of the License at *
  7 + * *
  8 + * http://www.apache.org/licenses/LICENSE-2.0 *
  9 + * *
  10 + * Unless required by applicable law or agreed to in writing, software *
  11 + * distributed under the License is distributed on an "AS IS" BASIS, *
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
  13 + * See the License for the specific language governing permissions and *
  14 + * limitations under the License. *
  15 + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  16 +
  17 +#include <opencv2/opencv.hpp>
  18 +#include "openbr/plugins/openbr_internal.h"
  19 +#include "openbr/core/qtutils.h"
  20 +#include "openbr/core/opencvutils.h"
  21 +#include "openbr/core/eigenutils.h"
  22 +#include "openbr/core/common.h"
  23 +#include <QString>
  24 +#include <Eigen/SVD>
  25 +#include <Eigen/Dense>
  26 +
  27 +using namespace std;
  28 +using namespace cv;
  29 +using namespace Eigen;
  30 +
  31 +namespace br
  32 +{
  33 +
  34 +/*!
  35 + * \ingroup transforms
  36 + * \brief Improved procrustes alignment of points, to include a post processing scaling of points
  37 + * to faciliate subsequent texture mapping.
  38 + * \author Brendan Klare \cite bklare
  39 + * \param width Width of output coordinate space (before padding)
  40 + * \param padding Amount of padding around the coordinate space
  41 + * \param useFirst whether or not to use the first instance as the reference object
  42 + */
  43 +class ProcrustesAlignTransform : public Transform
  44 +{
  45 + Q_OBJECT
  46 +
  47 + Q_PROPERTY(float width READ get_width WRITE set_width RESET reset_width STORED false)
  48 + Q_PROPERTY(float padding READ get_padding WRITE set_padding RESET reset_padding STORED false)
  49 + Q_PROPERTY(bool useFirst READ get_useFirst WRITE set_useFirst RESET reset_useFirst STORED false)
  50 + BR_PROPERTY(float, width, 80)
  51 + BR_PROPERTY(float, padding, 8)
  52 + BR_PROPERTY(bool, useFirst, false)
  53 +
  54 +
  55 + Eigen::MatrixXf referenceShape;
  56 + float minX;
  57 + float minY;
  58 + float maxX;
  59 + float maxY;
  60 + float aspectRatio;
  61 +
  62 + void init() {
  63 + aspectRatio = 0;
  64 + }
  65 +
  66 + static MatrixXf getRotation(MatrixXf ref, MatrixXf sample) {
  67 + MatrixXf R = sample.transpose() * ref;
  68 + JacobiSVD<MatrixXf> svd(R, ComputeFullU | ComputeFullV);
  69 + R = svd.matrixU() * svd.matrixV().transpose();
  70 + return R;
  71 + }
  72 +
  73 + //Converts x y points in a single vector to two column matrix
  74 + static MatrixXf vectorToMatrix(MatrixXf vector) {
  75 + int n = vector.rows();
  76 + MatrixXf matrix(n / 2, 2);
  77 + for (int i = 0; i < n / 2; i++) {
  78 + for (int j = 0; j < 2; j++) {
  79 + matrix(i, j) = vector(i * 2 + j);
  80 + }
  81 + }
  82 + return matrix;
  83 + }
  84 +
  85 + static MatrixXf matrixToVector(MatrixXf matrix) {
  86 + int n2 = matrix.rows();
  87 + MatrixXf vector(n2 * 2, 1);
  88 + for (int i = 0; i < n2; i++) {
  89 + for (int j = 0; j < 2; j++) {
  90 + vector(i * 2 + j) = matrix(i, j);
  91 + }
  92 + }
  93 + return vector;
  94 + }
  95 +
  96 + void train(const TemplateList &data)
  97 + {
  98 + MatrixXf points(data[0].file.points().size() * 2, data.size());
  99 +
  100 + // Normalize all sets of points
  101 + int skip = 0;
  102 + for (int j = 0; j < data.size(); j++) {
  103 + QList<QPointF> imagePoints = data[j].file.points();
  104 + if (imagePoints.size() == 0) {
  105 + skip++;
  106 + continue;
  107 + }
  108 +
  109 + float meanX = 0,
  110 + meanY = 0;
  111 + for (int i = 0; i < imagePoints.size(); i++) {
  112 + points(i * 2, j - skip) = imagePoints[i].x();
  113 + points(i * 2 + 1, j - skip) = imagePoints[i].y();
  114 +
  115 + meanX += imagePoints[i].x();
  116 + meanY += imagePoints[i].y();
  117 + }
  118 +
  119 + meanX /= imagePoints.size();
  120 + meanY /= imagePoints.size();
  121 +
  122 + for (int i = 0; i < imagePoints.size(); i++) {
  123 + points(i * 2, j - skip) -= meanX;
  124 + points(i * 2 + 1, j - skip) -= meanY;
  125 + }
  126 + }
  127 +
  128 + points = MatrixXf(points.leftCols(data.size() - skip));
  129 +
  130 + //normalize scale
  131 + for (int i = 0; i < points.cols(); i++)
  132 + points.col(i) = points.col(i) / points.col(i).norm();
  133 +
  134 + //Normalize rotation
  135 + if (!useFirst) {
  136 + referenceShape = vectorToMatrix(points.rowwise().sum() / points.cols());
  137 + } else {
  138 + referenceShape = vectorToMatrix(points.col(0));
  139 + }
  140 +
  141 + for (int i = 0; i < points.cols(); i++) {
  142 + MatrixXf p = vectorToMatrix(points.col(i));
  143 + MatrixXf R = getRotation(referenceShape, p);
  144 + points.col(i) = matrixToVector(p * R);
  145 + }
  146 +
  147 + //Choose crop boundaries and adjustments that captures most data
  148 + MatrixXf minXs(points.cols(),1);
  149 + MatrixXf minYs(points.cols(),1);
  150 + MatrixXf maxXs(points.cols(),1);
  151 + MatrixXf maxYs(points.cols(),1);
  152 + for (int j = 0; j < points.cols(); j++) {
  153 + minX = FLT_MAX,
  154 + minY = FLT_MAX,
  155 + maxX = -FLT_MAX,
  156 + maxY = -FLT_MAX;
  157 + for (int i = 0; i < points.rows(); i++) {
  158 + if (i % 2 == 0) {
  159 + if (points(i,j) > maxX)
  160 + maxX = points(i, j);
  161 + if (points(i,j) < minX)
  162 + minX = points(i, j);
  163 + } else {
  164 + if (points(i,j) > maxY)
  165 + maxY = points(i, j);
  166 + if (points(i,j) < minY)
  167 + minY = points(i, j);
  168 + }
  169 + }
  170 +
  171 + minXs(j) = minX;
  172 + maxXs(j) = maxX;
  173 + minYs(j) = minY;
  174 + maxYs(j) = maxY;
  175 + }
  176 +
  177 + minX = minXs.mean() - 0 * EigenUtils::stddev(minXs);
  178 + minY = minYs.mean() - 0 * EigenUtils::stddev(minYs);
  179 + maxX = maxXs.mean() + 0 * EigenUtils::stddev(maxXs);
  180 + maxY = maxYs.mean() + 0 * EigenUtils::stddev(maxYs);
  181 + aspectRatio = (maxX - minX) / (maxY - minY);
  182 + }
  183 +
  184 + void project(const Template &src, Template &dst) const
  185 + {
  186 + QList<QPointF> imagePoints = src.file.points();
  187 + if (imagePoints.size() == 0) {
  188 + dst.file.fte = true;
  189 + qDebug() << "No points for file " << src.file.name;
  190 + return;
  191 + }
  192 +
  193 + MatrixXf p(imagePoints.size() * 2, 1);
  194 + for (int i = 0; i < imagePoints.size(); i++) {
  195 + p(i * 2) = imagePoints[i].x();
  196 + p(i * 2 + 1) = imagePoints[i].y();
  197 + }
  198 + p = vectorToMatrix(p);
  199 +
  200 + //Normalize translation
  201 + p.col(0) = p.col(0) - MatrixXf::Ones(p.rows(),1) * (p.col(0).sum() / p.rows());
  202 + p.col(1) = p.col(1) - MatrixXf::Ones(p.rows(),1) * (p.col(1).sum() / p.rows());
  203 +
  204 + //Normalize scale
  205 + p /= matrixToVector(p).norm();
  206 +
  207 + //Normalize rotation
  208 + MatrixXf R = getRotation(referenceShape, p);
  209 + p = p * R;
  210 +
  211 + //Translate and scale into output space and store in output list
  212 + QList<QPointF> procrustesPoints;
  213 + for (int i = 0; i < p.rows(); i++)
  214 + procrustesPoints.append( QPointF(
  215 + (p(i, 0) - minX) / (maxX - minX) * (width - 1) + padding,
  216 + (p(i, 1) - minY) / (maxY - minY) * (qRound( width / aspectRatio) - 1) + padding));
  217 +
  218 + dst = src;
  219 + dst.file.setList<QPointF>("ProcrustesPoints", procrustesPoints);
  220 + dst.file.set("ProcrustesBound", QRectF(0, 0, width + 2 * padding, (qRound(width / aspectRatio) + 2 * padding)));
  221 + dst.file.set("ProcrustesPadding", padding);
  222 + }
  223 +
  224 + void store(QDataStream &stream) const
  225 + {
  226 + stream << referenceShape;
  227 + stream << minX;
  228 + stream << minY;
  229 + stream << maxX;
  230 + stream << maxY;
  231 + stream << aspectRatio;
  232 + }
  233 +
  234 + void load(QDataStream &stream)
  235 + {
  236 + stream >> referenceShape;
  237 + stream >> minX;
  238 + stream >> minY;
  239 + stream >> maxX;
  240 + stream >> maxY;
  241 + stream >> aspectRatio;
  242 + }
  243 +};
  244 +BR_REGISTER(Transform, ProcrustesAlignTransform)
  245 +
  246 +/*!
  247 + * \ingroup transforms
  248 + * \brief Maps texture from one set of points to another. Assumes that points are rigidly transformed
  249 + * \author Brendan Klare \cite bklare
  250 + * \author Scott Klum \cite sklum
  251 + */
  252 +class TextureMapTransform : public UntrainableTransform
  253 +{
  254 + Q_OBJECT
  255 +
  256 +public:
  257 + static QRectF getBounds(const QList<QPointF> &points, int dstPadding)
  258 + {
  259 + float srcMinX = FLT_MAX;
  260 + float srcMinY = FLT_MAX;
  261 + float srcMaxX = -FLT_MAX;
  262 + float srcMaxY = -FLT_MAX;
  263 + foreach (const QPointF &point, points) {
  264 + if (point.x() < srcMinX) srcMinX = point.x();
  265 + if (point.y() < srcMinY) srcMinY = point.y();
  266 + if (point.x() > srcMaxX) srcMaxX = point.x();
  267 + if (point.y() > srcMaxY) srcMaxY = point.y();
  268 + }
  269 +
  270 + const float padding = (srcMaxX - srcMinX) / 80 * dstPadding;
  271 + return QRectF(qRound(srcMinX - padding), qRound(srcMinY - padding), qRound(srcMaxX - srcMinX + 2 * padding), qRound(srcMaxY - srcMinY + 2 * padding));
  272 + }
  273 +
  274 + static int getVertexIndex(const QPointF &trianglePts, const QList<QPointF> &pts)
  275 + {
  276 + for (int i = 0; i < pts.size(); i++)
  277 + // Check points using single precision accuracy to avoid potential rounding error
  278 + if ((float(trianglePts.x()) == float(pts[i].x())) && (float(trianglePts.y()) == float(pts[i].y())))
  279 + return i;
  280 + qFatal("Couldn't identify index of requested point!");
  281 + return -1;
  282 + }
  283 +
  284 + static QList<QPointF> addBounds(QList<QPointF> points, const QRectF &bound)
  285 + {
  286 + points.append(bound.topLeft());
  287 + points.append(QPointF(bound.right() - 1, bound.top()));
  288 + points.append(QPointF(bound.left(), bound.bottom() - 1));
  289 + points.append(QPointF(bound.right() - 1, bound.bottom() - 1));
  290 + return points;
  291 + }
  292 +
  293 + static QList<QPointF> removeBounds(const QList<QPointF> &points)
  294 + {
  295 + return points.mid(0, points.size() - 4);
  296 + }
  297 +
  298 + //Expand out bounds placed at end of point list by addBounds
  299 + static QList<QPointF> expandBounds(QList<QPointF> points, int pad)
  300 + {
  301 + const int n = points.size();
  302 + points[n-4] = QPointF(points[n-4].x() - pad, points[n-4].y() - pad);
  303 + points[n-3] = QPointF(points[n-3].x() + pad, points[n-3].y() - pad);
  304 + points[n-2] = QPointF(points[n-2].x() - pad, points[n-2].y() + pad);
  305 + points[n-1] = QPointF(points[n-1].x() + pad, points[n-1].y() + pad);
  306 + return points;
  307 + }
  308 +
  309 + //Contract in bounds placed at end of point list by addBounds
  310 + static QList<QPointF> contractBounds(QList<QPointF> points, int pad)
  311 + {
  312 + const int n = points.size();
  313 + points[n-4] = QPointF(points[n-4].x() + pad, points[n-4].y() + pad);
  314 + points[n-3] = QPointF(points[n-3].x() - pad, points[n-3].y() + pad);
  315 + points[n-2] = QPointF(points[n-2].x() + pad, points[n-2].y() - pad);
  316 + points[n-1] = QPointF(points[n-1].x() - pad, points[n-1].y() - pad);
  317 + return points;
  318 + }
  319 +
  320 + static QList<QList<int> > getTriangulation(const QList<QPointF> &points, const QRectF &bound)
  321 + {
  322 + Subdiv2D subdiv(OpenCVUtils::toRect(bound));
  323 + foreach (const QPointF &point, points) {
  324 + if (!bound.contains(point))
  325 + return QList<QList<int> >();
  326 + subdiv.insert(OpenCVUtils::toPoint(point));
  327 + }
  328 +
  329 +
  330 + vector<Vec6f> triangleList;
  331 + subdiv.getTriangleList(triangleList);
  332 +
  333 + QList<QList<int> > triangleIndices;
  334 + foreach (const Vec6f &triangle, triangleList) {
  335 + bool valid = true;
  336 + const QPointF vertices[3] = { QPointF(triangle[0], triangle[1]),
  337 + QPointF(triangle[2], triangle[3]),
  338 + QPointF(triangle[4], triangle[5]) };
  339 + for (int j = 0; j < 3; j++)
  340 + if (vertices[j].x() > bound.right() || vertices[j].y() > bound.bottom() || vertices[j].x() < bound.left() || vertices[j].y() < bound.top()) {
  341 + valid = false;
  342 + break;
  343 + }
  344 +
  345 + if (valid) {
  346 + QList<int> tri;
  347 + for (int j = 0; j < 3; j++)
  348 + tri.append(getVertexIndex(vertices[j], points));
  349 + triangleIndices.append(tri);
  350 + }
  351 + }
  352 +
  353 + return triangleIndices;
  354 + }
  355 +
  356 +private:
  357 + void project(const Template &src, Template &dst) const
  358 + {
  359 + QList<QPointF> dstPoints = dst.file.getList<QPointF>("ProcrustesPoints");
  360 + QList<QPointF> srcPoints = dst.file.points();
  361 + if (dstPoints.empty() || srcPoints.empty()) {
  362 + dst = src;
  363 + if (Globals->verbose) {
  364 + qWarning("Delauney triangulation failed because points or rects are empty.");
  365 + dst.file.fte = true;
  366 + }
  367 + return;
  368 + }
  369 +
  370 + QRectF dstBound = dst.file.get<QRectF>("ProcrustesBound");
  371 + dstPoints = addBounds(dstPoints, dstBound);
  372 +
  373 + /*Add a wider bound for triangulation to prevent border triangles from being missing*/
  374 + QRectF srcBound = getBounds(srcPoints, dst.file.get<int>("ProcrustesPadding") + 20);
  375 + srcPoints = addBounds(srcPoints, srcBound);
  376 + QList<QList<int> > triIndices = getTriangulation(srcPoints, srcBound);
  377 +
  378 + /*Remove wider bound for texture mapping*/
  379 + srcPoints = removeBounds(srcPoints);
  380 + srcBound = getBounds(srcPoints, dst.file.get<int>("ProcrustesPadding"));
  381 + srcPoints = addBounds(srcPoints, srcBound);
  382 +
  383 + int dstWidth = dstBound.width() + dstBound.x();
  384 + int dstHeight = dstBound.height() + dstBound.y();
  385 + dst.m() = Mat::zeros(dstHeight, dstWidth, src.m().type());
  386 + for (int i = 0; i < triIndices.size(); i++) {
  387 + Point2f srcPoint1[3];
  388 + Point2f dstPoint1[3];
  389 + for (int j = 0; j < 3; j++) {
  390 + srcPoint1[j] = OpenCVUtils::toPoint(srcPoints[triIndices[i][j]]);
  391 + dstPoint1[j] = OpenCVUtils::toPoint(dstPoints[triIndices[i][j]]);
  392 + }
  393 +
  394 + Mat buffer(dstHeight, dstWidth, src.m().type());
  395 + warpAffine(src.m(), buffer, getAffineTransform(srcPoint1, dstPoint1), Size(dstBound.width(), dstBound.height()));
  396 +
  397 + Mat mask = Mat::zeros(dstHeight, dstWidth, CV_8UC1);
  398 + Point maskPoints[1][3];
  399 + maskPoints[0][0] = dstPoint1[0];
  400 + maskPoints[0][1] = dstPoint1[1];
  401 + maskPoints[0][2] = dstPoint1[2];
  402 + const Point* ppt = { maskPoints[0] };
  403 + fillConvexPoly(mask, ppt, 3, Scalar(255, 255, 255), 8);
  404 +
  405 + for (int i = 0; i < dstHeight; i++) {
  406 + for (int j = 0; j < dstWidth; j++) {
  407 + if (mask.at<uchar>(i,j) == 255) {
  408 + if (dst.m().type() == CV_32FC3 || dst.m().type() == CV_8UC3)
  409 + dst.m().at<cv::Vec3b>(i,j) = buffer.at<cv::Vec3b>(i,j);
  410 + else if (dst.m().type() == CV_32F)
  411 + dst.m().at<float>(i,j) = buffer.at<float>(i,j);
  412 + else if (dst.m().type() == CV_8U)
  413 + dst.m().at<uchar>(i,j) = buffer.at<uchar>(i,j);
  414 + else
  415 + qFatal("Unsupported pixel format.");
  416 + }
  417 + }
  418 + }
  419 +
  420 + }
  421 +
  422 + dst.file = src.file;
  423 + dst.file.clearPoints();
  424 + dst.file.clearRects();
  425 + dst.file.remove("ProcrustesPoints");
  426 + dst.file.remove("ProcrustesPadding");
  427 + dst.file.remove("ProcrustesBounds");
  428 + }
  429 +};
  430 +
  431 +BR_REGISTER(Transform, TextureMapTransform)
  432 +
  433 +// SynthesizePointsTransform helper class
  434 +struct TriangleIndicies
  435 +{
  436 + int indicies[3];
  437 +
  438 + TriangleIndicies()
  439 + {
  440 + indicies[0] = 0;
  441 + indicies[1] = 0;
  442 + indicies[2] = 0;
  443 + }
  444 +
  445 + TriangleIndicies(QList<int> indexList)
  446 + {
  447 + assert(indexList.size() == 3);
  448 + qSort(indexList);
  449 + indicies[0] = indexList[0];
  450 + indicies[1] = indexList[1];
  451 + indicies[2] = indexList[2];
  452 + }
  453 +};
  454 +
  455 +inline bool operator==(const TriangleIndicies &a, const TriangleIndicies &b)
  456 +{
  457 + return (a.indicies[0] == b.indicies[0]) && (a.indicies[1] == b.indicies[1]) && (a.indicies[2] == b.indicies[2]);
  458 +}
  459 +
  460 +inline uint qHash(const TriangleIndicies &key)
  461 +{
  462 + return ::qHash(key.indicies[0]) ^ ::qHash(key.indicies[1]) ^ ::qHash(key.indicies[2]);
  463 +}
  464 +
  465 +QDataStream &operator<<(QDataStream &stream, const TriangleIndicies &ti)
  466 +{
  467 + return stream << ti.indicies[0] << ti.indicies[1] << ti.indicies[2];
  468 +}
  469 +
  470 +QDataStream &operator>>(QDataStream &stream, TriangleIndicies &ti)
  471 +{
  472 + return stream >> ti.indicies[0] >> ti.indicies[1] >> ti.indicies[2];
  473 +}
  474 +
  475 +/*!
  476 + * \ingroup transforms
  477 + * \brief Synthesize additional points via triangulation.
  478 + * \author Josh Klontz \cite jklontz
  479 + */
  480 + class SynthesizePointsTransform : public MetadataTransform
  481 + {
  482 + Q_OBJECT
  483 + Q_PROPERTY(float minRelativeDistance READ get_minRelativeDistance WRITE set_minRelativeDistance RESET reset_minRelativeDistance STORED false)
  484 + BR_PROPERTY(float, minRelativeDistance, 0) // [0, 1] range controlling whether or not to nearby synthetic points.
  485 + // 0 = keep all points, 1 = keep only the most distance point.
  486 +
  487 + QList<TriangleIndicies> triangles;
  488 +
  489 + void train(const TemplateList &data)
  490 + {
  491 + // Because not all triangulations are the same, we have to decide on a canonical set of triangles at training time.
  492 + QHash<TriangleIndicies, int> counts;
  493 + foreach (const Template &datum, data) {
  494 +
  495 + const QList<QPointF> points = datum.file.points();
  496 + if (points.size() == 0)
  497 + continue;
  498 + const QList< QList<int> > triangulation = TextureMapTransform::getTriangulation(points, TextureMapTransform::getBounds(points, 10));
  499 + if (triangulation.empty())
  500 + continue;
  501 +
  502 + foreach (const QList<int> &indicies, triangulation)
  503 + counts[TriangleIndicies(indicies)]++;
  504 + }
  505 +
  506 + triangles.clear();
  507 + QHash<TriangleIndicies, int>::const_iterator i = counts.constBegin();
  508 + while (i != counts.constEnd()) {
  509 + if (3 * i.value() > data.size())
  510 + triangles.append(i.key()); // Keep triangles that occur in at least a third of the training instances
  511 + ++i;
  512 + }
  513 +
  514 + if (minRelativeDistance > 0) { // Discard relatively small triangles
  515 + QVector<float> averageMinDistances(triangles.size());
  516 + foreach (const Template &datum, data) {
  517 + File dst;
  518 + projectMetadata(datum.file, dst);
  519 + const QList<QPointF> points = dst.points();
  520 +
  521 + QVector<float> minDistances(triangles.size());
  522 + for (int i=0; i<triangles.size(); i++) {
  523 + // Work backwards so that we can also consider distances between synthetic points
  524 + const QPointF &point = points[points.size()-1-i];
  525 + float minDistance = std::numeric_limits<float>::max();
  526 + for (int j=0; j<points.size()-1-i; j++)
  527 + minDistance = min(minDistance, sqrtf(powf(point.x() - points[j].x(), 2.f) + powf(point.y() - points[j].y(), 2.f)));
  528 + minDistances[triangles.size()-1-i] = minDistance;
  529 + }
  530 +
  531 + const float maxMinDistance = Common::Max(minDistances);
  532 + for (int i=0; i<minDistances.size(); i++)
  533 + averageMinDistances[i] += (minDistances[i] / maxMinDistance);
  534 + }
  535 +
  536 + const float maxAverageMinDistance = Common::Max(averageMinDistances);
  537 + for (int i=averageMinDistances.size()-1; i>=0; i--)
  538 + if (averageMinDistances[i] / maxAverageMinDistance < minRelativeDistance)
  539 + triangles.removeAt(i);
  540 + }
  541 +
  542 + if (Globals->verbose)
  543 + qDebug() << "Kept" << triangles.size() << "of" << counts.size() << "triangles.";
  544 + }
  545 +
  546 + void projectMetadata(const File &src, File &dst) const
  547 + {
  548 + QList<QPointF> points = src.points();
  549 + if (points.size() == 0) {
  550 + dst.fte = true;
  551 + return;
  552 + }
  553 +
  554 + foreach (const TriangleIndicies &triangle, triangles) {
  555 + const QPointF &p0 = points[triangle.indicies[0]];
  556 + const QPointF &p1 = points[triangle.indicies[1]];
  557 + const QPointF &p2 = points[triangle.indicies[2]];
  558 + points.append((p0 + p1 + p2) / 3 /* append the centroid of the triangle */);
  559 + }
  560 + dst.setPoints(points);
  561 + }
  562 +
  563 + void store(QDataStream &stream) const
  564 + {
  565 + stream << triangles;
  566 + }
  567 +
  568 + void load(QDataStream &stream)
  569 + {
  570 + stream >> triangles;
  571 + }
  572 + };
  573 + BR_REGISTER(Transform, SynthesizePointsTransform)
  574 +
  575 +/*!
  576 + * \ingroup initializers
  577 + * \brief Initialize Procrustes croppings
  578 + * \author Brendan Klare \cite bklare
  579 + */
  580 +class ProcrustesInitializer : public Initializer
  581 +{
  582 + Q_OBJECT
  583 +
  584 + void initialize() const
  585 + {
  586 + Globals->abbreviations.insert("ProcrustesStasmFace","SelectPoints([17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76])+ProcrustesAlign(padding=6,width=120)+TextureMap+Resize(96,96)");
  587 + Globals->abbreviations.insert("ProcrustesStasmEyes","SelectPoints([28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47])+ProcrustesAlign(padding=8)+TextureMap+Resize(24,48)");
  588 + Globals->abbreviations.insert("ProcrustesStasmPeriocular","SelectPoints([28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,16,17,18,19,20,21,22,23,24,25,26,27])+ProcrustesAlign(padding=10)+TextureMap+Resize(36,48)");
  589 + Globals->abbreviations.insert("ProcrustesStasmBrow","SelectPoints([16,17,18,19,20,21,22,23,24,25,26,27])+ProcrustesAlign(padding=8)+TextureMap+Resize(24,48)");
  590 + Globals->abbreviations.insert("ProcrustesStasmNose","SelectPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58])+ProcrustesAlign(padding=12)+TextureMap+Resize(36,48)");
  591 + Globals->abbreviations.insert("ProcrustesStasmMouth","SelectPoints([59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76])+ProcrustesAlign(padding=10)+TextureMap+Resize(36,48)");
  592 + Globals->abbreviations.insert("ProcrustesStasmJaw", "SelectPoints([2,3,4,5,6,7,8,9,10])+ProcrustesAlign(padding=8)+TextureMap+Resize(36,48)");
  593 +
  594 + Globals->abbreviations.insert("ProcrustesEyes","SelectPoints([19,20,21,22,23,24,25,26,27,28,29,30])+ProcrustesAlign(padding=8)+TextureMap+Resize(24,48)");
  595 + Globals->abbreviations.insert("ProcrustesNose","SelectPoints([12,13,14,15,16,17,18])+ProcrustesAlign(padding=30)+TextureMap+Resize(36,48)");
  596 + Globals->abbreviations.insert("ProcrustesMouth","SelectPoints([31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50])+ProcrustesAlign(padding=6)+TextureMap+Resize(36,48)");
  597 + Globals->abbreviations.insert("ProcrustesBrow","SelectPoints([0,1,2,3,4,5,6,7,8,9])+ProcrustesAlign(padding=6)+TextureMap+Resize(24,48)");
  598 + Globals->abbreviations.insert("ProcrustesFace","ProcrustesAlign(padding=6,width=120)+TextureMap+Resize(96,96)");
  599 +
  600 + Globals->abbreviations.insert("ProcrustesLargeStasmFace","ProcrustesAlign(padding=18)+TextureMap+Resize(480,480)");
  601 + Globals->abbreviations.insert("ProcrustesLargeStasmEyes","SelectPoints([28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47])+ProcrustesAlign(padding=8)+TextureMap+Resize(240,480)");
  602 + Globals->abbreviations.insert("ProcrustesLargeStasmPeriocular","SelectPoints([28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,16,17,18,19,20,21,22,23,24,25,26,27])+ProcrustesAlign(padding=10)+TextureMap+Resize(360,480)");
  603 + Globals->abbreviations.insert("ProcrustesLargeStasmBrow","SelectPoints([16,17,18,19,20,21,22,23,24,25,26,27])+ProcrustesAlign(padding=8)+TextureMap+Resize(240,480)");
  604 + Globals->abbreviations.insert("ProcrustesLargeStasmNose","SelectPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58])+ProcrustesAlign(padding=12)+TextureMap+Resize(360,480)");
  605 + Globals->abbreviations.insert("ProcrustesLargeStasmMouth","SelectPoints([59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76])+ProcrustesAlign(padding=20)+TextureMap+Resize(360,480)");
  606 + Globals->abbreviations.insert("ProcrustesLargeStasmJaw", "SelectPoints([2,3,4,5,6,7,8,9,10])+ProcrustesAlign(padding=8)+TextureMap+Resize(360,480)");
  607 + }
  608 +};
  609 +BR_REGISTER(Initializer, ProcrustesInitializer)
  610 +
  611 +} // namespace br
  612 +
  613 +#include "align.moc"
... ...