Commit d54b1db01e74c5b08c8cbcb0bd012399378e9a4b

Authored by Scott Klum
2 parents 6dd79bd3 5328152f

Merge branch 'master' of https://github.com/biometrics/openbr

openbr/plugins/core/align.cpp deleted
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"
openbr/plugins/imgproc/synthesizekeypoints.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 +// SynthesizePointsTransform helper class
  35 +struct TriangleIndicies
  36 +{
  37 + int indicies[3];
  38 +
  39 + TriangleIndicies()
  40 + {
  41 + indicies[0] = 0;
  42 + indicies[1] = 0;
  43 + indicies[2] = 0;
  44 + }
  45 +
  46 + TriangleIndicies(QList<int> indexList)
  47 + {
  48 + assert(indexList.size() == 3);
  49 + qSort(indexList);
  50 + indicies[0] = indexList[0];
  51 + indicies[1] = indexList[1];
  52 + indicies[2] = indexList[2];
  53 + }
  54 +};
  55 +
  56 +inline bool operator==(const TriangleIndicies &a, const TriangleIndicies &b)
  57 +{
  58 + return (a.indicies[0] == b.indicies[0]) && (a.indicies[1] == b.indicies[1]) && (a.indicies[2] == b.indicies[2]);
  59 +}
  60 +
  61 +inline uint qHash(const TriangleIndicies &key)
  62 +{
  63 + return ::qHash(key.indicies[0]) ^ ::qHash(key.indicies[1]) ^ ::qHash(key.indicies[2]);
  64 +}
  65 +
  66 +QDataStream &operator<<(QDataStream &stream, const TriangleIndicies &ti)
  67 +{
  68 + return stream << ti.indicies[0] << ti.indicies[1] << ti.indicies[2];
  69 +}
  70 +
  71 +QDataStream &operator>>(QDataStream &stream, TriangleIndicies &ti)
  72 +{
  73 + return stream >> ti.indicies[0] >> ti.indicies[1] >> ti.indicies[2];
  74 +}
  75 +
  76 +/*!
  77 + * \ingroup transforms
  78 + * \brief Synthesize additional points via triangulation.
  79 + * \author Josh Klontz \cite jklontz
  80 + */
  81 +class SynthesizePointsTransform : public MetadataTransform
  82 +{
  83 + Q_OBJECT
  84 + Q_PROPERTY(float minRelativeDistance READ get_minRelativeDistance WRITE set_minRelativeDistance RESET reset_minRelativeDistance STORED false)
  85 + BR_PROPERTY(float, minRelativeDistance, 0) // [0, 1] range controlling whether or not to nearby synthetic points.
  86 + // 0 = keep all points, 1 = keep only the most distance point.
  87 +
  88 + QList<TriangleIndicies> triangles;
  89 +
  90 + static QRectF getBounds(const QList<QPointF> &points, int dstPadding)
  91 + {
  92 + float srcMinX = FLT_MAX;
  93 + float srcMinY = FLT_MAX;
  94 + float srcMaxX = -FLT_MAX;
  95 + float srcMaxY = -FLT_MAX;
  96 + foreach (const QPointF &point, points) {
  97 + if (point.x() < srcMinX) srcMinX = point.x();
  98 + if (point.y() < srcMinY) srcMinY = point.y();
  99 + if (point.x() > srcMaxX) srcMaxX = point.x();
  100 + if (point.y() > srcMaxY) srcMaxY = point.y();
  101 + }
  102 +
  103 + const float padding = (srcMaxX - srcMinX) / 80 * dstPadding;
  104 + return QRectF(qRound(srcMinX - padding), qRound(srcMinY - padding), qRound(srcMaxX - srcMinX + 2 * padding), qRound(srcMaxY - srcMinY + 2 * padding));
  105 + }
  106 +
  107 + static int getVertexIndex(const QPointF &trianglePts, const QList<QPointF> &pts)
  108 + {
  109 + for (int i = 0; i < pts.size(); i++)
  110 + // Check points using single precision accuracy to avoid potential rounding error
  111 + if ((float(trianglePts.x()) == float(pts[i].x())) && (float(trianglePts.y()) == float(pts[i].y())))
  112 + return i;
  113 + qFatal("Couldn't identify index of requested point!");
  114 + return -1;
  115 + }
  116 +
  117 + static QList<QList<int> > getTriangulation(const QList<QPointF> &points, const QRectF &bound)
  118 + {
  119 + Subdiv2D subdiv(OpenCVUtils::toRect(bound));
  120 + foreach (const QPointF &point, points) {
  121 + if (!bound.contains(point))
  122 + return QList<QList<int> >();
  123 + subdiv.insert(OpenCVUtils::toPoint(point));
  124 + }
  125 +
  126 +
  127 + vector<Vec6f> triangleList;
  128 + subdiv.getTriangleList(triangleList);
  129 +
  130 + QList<QList<int> > triangleIndices;
  131 + foreach (const Vec6f &triangle, triangleList) {
  132 + bool valid = true;
  133 + const QPointF vertices[3] = { QPointF(triangle[0], triangle[1]),
  134 + QPointF(triangle[2], triangle[3]),
  135 + QPointF(triangle[4], triangle[5]) };
  136 + for (int j = 0; j < 3; j++)
  137 + if (vertices[j].x() > bound.right() || vertices[j].y() > bound.bottom() || vertices[j].x() < bound.left() || vertices[j].y() < bound.top()) {
  138 + valid = false;
  139 + break;
  140 + }
  141 +
  142 + if (valid) {
  143 + QList<int> tri;
  144 + for (int j = 0; j < 3; j++)
  145 + tri.append(getVertexIndex(vertices[j], points));
  146 + triangleIndices.append(tri);
  147 + }
  148 + }
  149 +
  150 + return triangleIndices;
  151 + }
  152 +
  153 + void train(const TemplateList &data)
  154 + {
  155 + // Because not all triangulations are the same, we have to decide on a canonical set of triangles at training time.
  156 + QHash<TriangleIndicies, int> counts;
  157 + foreach (const Template &datum, data) {
  158 +
  159 + const QList<QPointF> points = datum.file.points();
  160 + if (points.size() == 0)
  161 + continue;
  162 + const QList< QList<int> > triangulation = getTriangulation(points, getBounds(points, 10));
  163 + if (triangulation.empty())
  164 + continue;
  165 +
  166 + foreach (const QList<int> &indicies, triangulation)
  167 + counts[TriangleIndicies(indicies)]++;
  168 + }
  169 +
  170 + triangles.clear();
  171 + QHash<TriangleIndicies, int>::const_iterator i = counts.constBegin();
  172 + while (i != counts.constEnd()) {
  173 + if (3 * i.value() > data.size())
  174 + triangles.append(i.key()); // Keep triangles that occur in at least a third of the training instances
  175 + ++i;
  176 + }
  177 +
  178 + if (minRelativeDistance > 0) { // Discard relatively small triangles
  179 + QVector<float> averageMinDistances(triangles.size());
  180 + foreach (const Template &datum, data) {
  181 + File dst;
  182 + projectMetadata(datum.file, dst);
  183 + const QList<QPointF> points = dst.points();
  184 +
  185 + QVector<float> minDistances(triangles.size());
  186 + for (int i=0; i<triangles.size(); i++) {
  187 + // Work backwards so that we can also consider distances between synthetic points
  188 + const QPointF &point = points[points.size()-1-i];
  189 + float minDistance = std::numeric_limits<float>::max();
  190 + for (int j=0; j<points.size()-1-i; j++)
  191 + minDistance = min(minDistance, sqrtf(powf(point.x() - points[j].x(), 2.f) + powf(point.y() - points[j].y(), 2.f)));
  192 + minDistances[triangles.size()-1-i] = minDistance;
  193 + }
  194 +
  195 + const float maxMinDistance = Common::Max(minDistances);
  196 + for (int i=0; i<minDistances.size(); i++)
  197 + averageMinDistances[i] += (minDistances[i] / maxMinDistance);
  198 + }
  199 +
  200 + const float maxAverageMinDistance = Common::Max(averageMinDistances);
  201 + for (int i=averageMinDistances.size()-1; i>=0; i--)
  202 + if (averageMinDistances[i] / maxAverageMinDistance < minRelativeDistance)
  203 + triangles.removeAt(i);
  204 + }
  205 +
  206 + if (Globals->verbose)
  207 + qDebug() << "Kept" << triangles.size() << "of" << counts.size() << "triangles.";
  208 + }
  209 +
  210 + void projectMetadata(const File &src, File &dst) const
  211 + {
  212 + QList<QPointF> points = src.points();
  213 + if (points.size() == 0) {
  214 + dst.fte = true;
  215 + return;
  216 + }
  217 +
  218 + foreach (const TriangleIndicies &triangle, triangles) {
  219 + const QPointF &p0 = points[triangle.indicies[0]];
  220 + const QPointF &p1 = points[triangle.indicies[1]];
  221 + const QPointF &p2 = points[triangle.indicies[2]];
  222 + points.append((p0 + p1 + p2) / 3 /* append the centroid of the triangle */);
  223 + }
  224 + dst.setPoints(points);
  225 + }
  226 +
  227 + void store(QDataStream &stream) const
  228 + {
  229 + stream << triangles;
  230 + }
  231 +
  232 + void load(QDataStream &stream)
  233 + {
  234 + stream >> triangles;
  235 + }
  236 +};
  237 +
  238 +BR_REGISTER(Transform, SynthesizePointsTransform)
  239 +
  240 +} // namespace br
  241 +
  242 +#include "synthesizekeypoints.moc"
... ...