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"