Commit 48d184af11d199e4c9079d49d3c910abfcdababc
1 parent
260843b5
Killed
Showing
1 changed file
with
0 additions
and
369 deletions
openbr/plugins/landmarks.cpp
| @@ -141,204 +141,6 @@ BR_REGISTER(Transform, ProcrustesTransform) | @@ -141,204 +141,6 @@ BR_REGISTER(Transform, ProcrustesTransform) | ||
| 141 | 141 | ||
| 142 | /*! | 142 | /*! |
| 143 | * \ingroup transforms | 143 | * \ingroup transforms |
| 144 | - * \brief Improved procrustes alignment of points, to include a post processing scaling of points | ||
| 145 | - * to faciliate subsequent texture mapping. | ||
| 146 | - * \author Brendan Klare \cite bklare | ||
| 147 | - * \param width Width of output coordinate space (before padding) | ||
| 148 | - * \param padding Amount of padding around the coordinate space | ||
| 149 | - * \param useFirst whether or not to use the first instance as the reference object | ||
| 150 | - */ | ||
| 151 | -class ProcrustesAlignTransform : public Transform | ||
| 152 | -{ | ||
| 153 | - Q_OBJECT | ||
| 154 | - | ||
| 155 | - Q_PROPERTY(float width READ get_width WRITE set_width RESET reset_width STORED false) | ||
| 156 | - Q_PROPERTY(float padding READ get_padding WRITE set_padding RESET reset_padding STORED false) | ||
| 157 | - Q_PROPERTY(bool useFirst READ get_useFirst WRITE set_useFirst RESET reset_useFirst STORED false) | ||
| 158 | - BR_PROPERTY(float, width, 80) | ||
| 159 | - BR_PROPERTY(float, padding, 8) | ||
| 160 | - BR_PROPERTY(bool, useFirst, false) | ||
| 161 | - | ||
| 162 | - | ||
| 163 | - Eigen::MatrixXf referenceShape; | ||
| 164 | - float minX; | ||
| 165 | - float minY; | ||
| 166 | - float maxX; | ||
| 167 | - float maxY; | ||
| 168 | - float aspectRatio; | ||
| 169 | - | ||
| 170 | - void init() { | ||
| 171 | - aspectRatio = 0; | ||
| 172 | - } | ||
| 173 | - | ||
| 174 | - static MatrixXf getRotation(MatrixXf ref, MatrixXf sample) { | ||
| 175 | - MatrixXf R = sample.transpose() * ref; | ||
| 176 | - JacobiSVD<MatrixXf> svd(R, ComputeFullU | ComputeFullV); | ||
| 177 | - R = svd.matrixU() * svd.matrixV().transpose(); | ||
| 178 | - return R; | ||
| 179 | - } | ||
| 180 | - | ||
| 181 | - //Converts x y points in a single vector to two column matrix | ||
| 182 | - static MatrixXf vectorToMatrix(MatrixXf vector) { | ||
| 183 | - int n = vector.rows(); | ||
| 184 | - MatrixXf matrix(n / 2, 2); | ||
| 185 | - for (int i = 0; i < n / 2; i++) { | ||
| 186 | - for (int j = 0; j < 2; j++) { | ||
| 187 | - matrix(i, j) = vector(i * 2 + j); | ||
| 188 | - } | ||
| 189 | - } | ||
| 190 | - return matrix; | ||
| 191 | - } | ||
| 192 | - | ||
| 193 | - static MatrixXf matrixToVector(MatrixXf matrix) { | ||
| 194 | - int n2 = matrix.rows(); | ||
| 195 | - MatrixXf vector(n2 * 2, 1); | ||
| 196 | - for (int i = 0; i < n2; i++) { | ||
| 197 | - for (int j = 0; j < 2; j++) { | ||
| 198 | - vector(i * 2 + j) = matrix(i, j); | ||
| 199 | - } | ||
| 200 | - } | ||
| 201 | - return vector; | ||
| 202 | - } | ||
| 203 | - | ||
| 204 | - void train(const TemplateList &data) | ||
| 205 | - { | ||
| 206 | - MatrixXf points(data[0].file.points().size() * 2, data.size()); | ||
| 207 | - | ||
| 208 | - // Normalize all sets of points | ||
| 209 | - for (int j = 0; j < data.size(); j++) { | ||
| 210 | - QList<QPointF> imagePoints = data[j].file.points(); | ||
| 211 | - | ||
| 212 | - float meanX = 0, | ||
| 213 | - meanY = 0; | ||
| 214 | - for (int i = 0; i < imagePoints.size(); i++) { | ||
| 215 | - points(i * 2, j) = imagePoints[i].x(); | ||
| 216 | - points(i * 2 + 1, j) = imagePoints[i].y(); | ||
| 217 | - meanX += imagePoints[i].x(); | ||
| 218 | - meanY += imagePoints[i].y(); | ||
| 219 | - } | ||
| 220 | - meanX /= imagePoints.size(); | ||
| 221 | - meanY /= imagePoints.size(); | ||
| 222 | - | ||
| 223 | - for (int i = 0; i < imagePoints.size(); i++) { | ||
| 224 | - points(i * 2, j) -= meanX; | ||
| 225 | - points(i * 2 + 1, j) -= meanY; | ||
| 226 | - } | ||
| 227 | - } | ||
| 228 | - | ||
| 229 | - //normalize scale | ||
| 230 | - for (int i = 0; i < points.cols(); i++) | ||
| 231 | - points.col(i) = points.col(i) / points.col(i).norm(); | ||
| 232 | - | ||
| 233 | - //Normalize rotation | ||
| 234 | - if (!useFirst) { | ||
| 235 | - referenceShape = vectorToMatrix(points.rowwise().sum() / points.cols()); | ||
| 236 | - } else { | ||
| 237 | - referenceShape = vectorToMatrix(points.col(0)); | ||
| 238 | - } | ||
| 239 | - | ||
| 240 | - for (int i = 0; i < points.cols(); i++) { | ||
| 241 | - MatrixXf p = vectorToMatrix(points.col(i)); | ||
| 242 | - MatrixXf R = getRotation(referenceShape, p); | ||
| 243 | - points.col(i) = matrixToVector(p * R); | ||
| 244 | - } | ||
| 245 | - | ||
| 246 | - //Choose crop boundaries and adjustments that captures most data | ||
| 247 | - MatrixXf minXs(points.cols(),1); | ||
| 248 | - MatrixXf minYs(points.cols(),1); | ||
| 249 | - MatrixXf maxXs(points.cols(),1); | ||
| 250 | - MatrixXf maxYs(points.cols(),1); | ||
| 251 | - for (int j = 0; j < points.cols(); j++) { | ||
| 252 | - minX = FLT_MAX, | ||
| 253 | - minY = FLT_MAX, | ||
| 254 | - maxX = -FLT_MAX, | ||
| 255 | - maxY = -FLT_MAX; | ||
| 256 | - for (int i = 0; i < points.rows(); i++) { | ||
| 257 | - if (i % 2 == 0) { | ||
| 258 | - if (points(i,j) > maxX) | ||
| 259 | - maxX = points(i, j); | ||
| 260 | - if (points(i,j) < minX) | ||
| 261 | - minX = points(i, j); | ||
| 262 | - } else { | ||
| 263 | - if (points(i,j) > maxY) | ||
| 264 | - maxY = points(i, j); | ||
| 265 | - if (points(i,j) < minY) | ||
| 266 | - minY = points(i, j); | ||
| 267 | - } | ||
| 268 | - } | ||
| 269 | - | ||
| 270 | - minXs(j) = minX; | ||
| 271 | - maxXs(j) = maxX; | ||
| 272 | - minYs(j) = minY; | ||
| 273 | - maxYs(j) = maxY; | ||
| 274 | - } | ||
| 275 | - | ||
| 276 | - minX = eigMean(minXs) - 0 * eigStd(minXs); | ||
| 277 | - minY = eigMean(minYs) - 0 * eigStd(minYs); | ||
| 278 | - maxX = eigMean(maxXs) + 0 * eigStd(maxXs); | ||
| 279 | - maxY = eigMean(maxYs) + 0 * eigStd(maxYs); | ||
| 280 | - aspectRatio = (maxX - minX) / (maxY - minY); | ||
| 281 | - } | ||
| 282 | - | ||
| 283 | - void project(const Template &src, Template &dst) const | ||
| 284 | - { | ||
| 285 | - QList<QPointF> imagePoints = src.file.points(); | ||
| 286 | - MatrixXf p(imagePoints.size() * 2, 1); | ||
| 287 | - for (int i = 0; i < imagePoints.size(); i++) { | ||
| 288 | - p(i * 2) = imagePoints[i].x(); | ||
| 289 | - p(i * 2 + 1) = imagePoints[i].y(); | ||
| 290 | - } | ||
| 291 | - p = vectorToMatrix(p); | ||
| 292 | - | ||
| 293 | - //Normalize translation | ||
| 294 | - p.col(0) = p.col(0) - MatrixXf::Ones(p.rows(),1) * (p.col(0).sum() / p.rows()); | ||
| 295 | - p.col(1) = p.col(1) - MatrixXf::Ones(p.rows(),1) * (p.col(1).sum() / p.rows()); | ||
| 296 | - | ||
| 297 | - //Normalize scale | ||
| 298 | - p /= matrixToVector(p).norm(); | ||
| 299 | - | ||
| 300 | - //Normalize rotation | ||
| 301 | - MatrixXf R = getRotation(referenceShape, p); | ||
| 302 | - p = p * R; | ||
| 303 | - | ||
| 304 | - //Translate and scale into output space and store in output list | ||
| 305 | - QList<QPointF> procrustesPoints; | ||
| 306 | - for (int i = 0; i < p.rows(); i++) | ||
| 307 | - procrustesPoints.append( QPointF( | ||
| 308 | - (p(i, 0) - minX) / (maxX - minX) * (width - 1) + padding, | ||
| 309 | - (p(i, 1) - minY) / (maxY - minY) * (qRound( width / aspectRatio) - 1) + padding)); | ||
| 310 | - | ||
| 311 | - dst = src; | ||
| 312 | - dst.file.setList<QPointF>("ProcrustesPoints", procrustesPoints); | ||
| 313 | - dst.file.set("ProcrustesBound", QRectF(0, 0, width + 2 * padding, (qRound(width / aspectRatio) + 2 * padding))); | ||
| 314 | - dst.file.set("ProcrustesPadding", padding); | ||
| 315 | - } | ||
| 316 | - | ||
| 317 | - void store(QDataStream &stream) const | ||
| 318 | - { | ||
| 319 | - stream << referenceShape; | ||
| 320 | - stream << minX; | ||
| 321 | - stream << minY; | ||
| 322 | - stream << maxX; | ||
| 323 | - stream << maxY; | ||
| 324 | - stream << aspectRatio; | ||
| 325 | - } | ||
| 326 | - | ||
| 327 | - void load(QDataStream &stream) | ||
| 328 | - { | ||
| 329 | - stream >> referenceShape; | ||
| 330 | - stream >> minX; | ||
| 331 | - stream >> minY; | ||
| 332 | - stream >> maxX; | ||
| 333 | - stream >> maxY; | ||
| 334 | - stream >> aspectRatio; | ||
| 335 | - } | ||
| 336 | -}; | ||
| 337 | - | ||
| 338 | -BR_REGISTER(Transform, ProcrustesAlignTransform) | ||
| 339 | - | ||
| 340 | -/*! | ||
| 341 | - * \ingroup transforms | ||
| 342 | * \brief Creates a Delaunay triangulation based on a set of points | 144 | * \brief Creates a Delaunay triangulation based on a set of points |
| 343 | * \author Scott Klum \cite sklum | 145 | * \author Scott Klum \cite sklum |
| 344 | */ | 146 | */ |
| @@ -481,177 +283,6 @@ BR_REGISTER(Transform, DelaunayTransform) | @@ -481,177 +283,6 @@ BR_REGISTER(Transform, DelaunayTransform) | ||
| 481 | 283 | ||
| 482 | /*! | 284 | /*! |
| 483 | * \ingroup transforms | 285 | * \ingroup transforms |
| 484 | - * \brief Maps texture from one set of points to another. Assumes that points are rigidly transformed | ||
| 485 | - * \author Brendan Klare \cite bklare | ||
| 486 | - * \author Scott Klum \cite sklum | ||
| 487 | - */ | ||
| 488 | -class TextureMapTransform : public UntrainableTransform | ||
| 489 | -{ | ||
| 490 | - Q_OBJECT | ||
| 491 | - | ||
| 492 | - static QRectF getBounds(QList<QPointF> points, int dstPadding) { | ||
| 493 | - float srcMinX = FLT_MAX; | ||
| 494 | - float srcMinY = FLT_MAX; | ||
| 495 | - float srcMaxX = -FLT_MAX; | ||
| 496 | - float srcMaxY = -FLT_MAX; | ||
| 497 | - for (int i = 0; i < points.size(); i++) { | ||
| 498 | - if (points[i].x() < srcMinX) srcMinX = points[i].x(); | ||
| 499 | - if (points[i].y() < srcMinY) srcMinY = points[i].y(); | ||
| 500 | - if (points[i].x() > srcMaxX) srcMaxX = points[i].x(); | ||
| 501 | - if (points[i].y() > srcMaxY) srcMaxY = points[i].y(); | ||
| 502 | - } | ||
| 503 | - | ||
| 504 | - float padding = (srcMaxX - srcMinX) / 80 * dstPadding; | ||
| 505 | - return QRectF(qRound(srcMinX - padding), qRound(srcMinY - padding), qRound(srcMaxX - srcMinX + 2 * padding), qRound(srcMaxY - srcMinY + 2 * padding)); | ||
| 506 | - } | ||
| 507 | - | ||
| 508 | - static int getVertexIndex(QPointF trianglePts, QList<QPointF> pts) | ||
| 509 | - { | ||
| 510 | - for (int i = 0; i < pts.size(); i++) | ||
| 511 | - if (trianglePts.x() == pts[i].x() && trianglePts.y() == pts[i].y()) | ||
| 512 | - return i; | ||
| 513 | - return -1; | ||
| 514 | - } | ||
| 515 | - | ||
| 516 | - static QList<QPointF> addBounds(const QList<QPointF> _points, const QRectF bound) | ||
| 517 | - { | ||
| 518 | - QList<QPointF> points(_points); | ||
| 519 | - points.append(bound.topLeft()); | ||
| 520 | - points.append(QPointF(bound.right() - 1, bound.top())); | ||
| 521 | - points.append(QPointF(bound.left(), bound.bottom() - 1)); | ||
| 522 | - points.append(QPointF(bound.right() - 1, bound.bottom() - 1)); | ||
| 523 | - return points; | ||
| 524 | - } | ||
| 525 | - | ||
| 526 | - static QList<QList<int> > getTriangulation(const QList<QPointF> _points, const QRectF bound) | ||
| 527 | - { | ||
| 528 | - QList<QPointF> points(_points); | ||
| 529 | - | ||
| 530 | - Subdiv2D subdiv(OpenCVUtils::toRect(bound)); | ||
| 531 | - for (int i = 0; i < points.size(); i++) | ||
| 532 | - subdiv.insert(OpenCVUtils::toPoint(points[i])); | ||
| 533 | - | ||
| 534 | - vector<Vec6f> triangleList; | ||
| 535 | - subdiv.getTriangleList(triangleList); | ||
| 536 | - | ||
| 537 | - QList<QList<int> > triangleIndices; | ||
| 538 | - for (size_t i = 0; i < triangleList.size(); i++) { | ||
| 539 | - bool valid = true; | ||
| 540 | - QList<QPointF> vertices; | ||
| 541 | - vertices.append(QPointF(triangleList[i][0],triangleList[i][1])); | ||
| 542 | - vertices.append(QPointF(triangleList[i][2],triangleList[i][3])); | ||
| 543 | - vertices.append(QPointF(triangleList[i][4],triangleList[i][5])); | ||
| 544 | - for (int j = 0; j < 3; j++) | ||
| 545 | - if (vertices[j].x() > bound.right() || vertices[j].y() > bound.bottom() || vertices[j].x() < bound.left() || vertices[j].y() < bound.top()) | ||
| 546 | - valid = false; | ||
| 547 | - | ||
| 548 | - if (valid) { | ||
| 549 | - QList<int> tri; | ||
| 550 | - for (int j = 0; j < 3; j++) | ||
| 551 | - tri.append(getVertexIndex(vertices[j], points)); | ||
| 552 | - triangleIndices.append(tri); | ||
| 553 | - } | ||
| 554 | - } | ||
| 555 | - | ||
| 556 | - return triangleIndices; | ||
| 557 | - } | ||
| 558 | - | ||
| 559 | - void project(const Template &src, Template &dst) const | ||
| 560 | - { | ||
| 561 | - QList<QPointF> dstPoints = dst.file.getList<QPointF>("ProcrustesPoints"); | ||
| 562 | - QList<QPointF> srcPoints = dst.file.points(); | ||
| 563 | - QRectF dstBound = dst.file.get<QRectF>("ProcrustesBound"); | ||
| 564 | - QRectF srcBound = getBounds(srcPoints, dst.file.get<int>("ProcrustesPadding")); | ||
| 565 | - if (dstPoints.empty() || srcPoints.empty()) { | ||
| 566 | - dst = src; | ||
| 567 | - if (Globals->verbose) qWarning("Delauney triangulation failed because points or rects are empty."); | ||
| 568 | - return; | ||
| 569 | - } | ||
| 570 | - | ||
| 571 | - dstPoints = addBounds(dstPoints, dstBound); | ||
| 572 | - srcPoints = addBounds(srcPoints, srcBound); | ||
| 573 | - | ||
| 574 | - QList<QList<int> > triIndices = getTriangulation(srcPoints, srcBound); | ||
| 575 | - | ||
| 576 | - int dstWidth = dstBound.width() + dstBound.x(); | ||
| 577 | - int dstHeight = dstBound.height() + dstBound.y(); | ||
| 578 | - dst.m() = Mat::zeros(dstHeight, dstWidth, src.m().type()); | ||
| 579 | - for (int i = 0; i < triIndices.size(); i++) { | ||
| 580 | - Point2f srcPoint1[3]; | ||
| 581 | - Point2f dstPoint1[3]; | ||
| 582 | - for (int j = 0; j < 3; j++) { | ||
| 583 | - srcPoint1[j] = OpenCVUtils::toPoint(srcPoints[triIndices[i][j]]); | ||
| 584 | - dstPoint1[j] = OpenCVUtils::toPoint(dstPoints[triIndices[i][j]]); | ||
| 585 | - } | ||
| 586 | - | ||
| 587 | - Mat buffer(dstHeight, dstWidth, src.m().type()); | ||
| 588 | - warpAffine(src.m(), buffer, getAffineTransform(srcPoint1, dstPoint1), Size(dstBound.width(), dstBound.height())); | ||
| 589 | - | ||
| 590 | - Mat mask = Mat::zeros(dstHeight, dstWidth, CV_8UC1); | ||
| 591 | - Point maskPoints[1][3]; | ||
| 592 | - maskPoints[0][0] = dstPoint1[0]; | ||
| 593 | - maskPoints[0][1] = dstPoint1[1]; | ||
| 594 | - maskPoints[0][2] = dstPoint1[2]; | ||
| 595 | - const Point* ppt = { maskPoints[0] }; | ||
| 596 | - fillConvexPoly(mask, ppt, 3, Scalar(255, 255, 255), 8); | ||
| 597 | - | ||
| 598 | - for (int i = 0; i < dstHeight; i++) { | ||
| 599 | - for (int j = 0; j < dstWidth; j++) { | ||
| 600 | - if (mask.at<uchar>(i,j) == 255) { | ||
| 601 | - if (dst.m().type() == CV_32FC3 || dst.m().type() == CV_8UC3) | ||
| 602 | - dst.m().at<cv::Vec3b>(i,j) = buffer.at<cv::Vec3b>(i,j); | ||
| 603 | - else if (dst.m().type() == CV_32F) | ||
| 604 | - dst.m().at<float>(i,j) = buffer.at<float>(i,j); | ||
| 605 | - else if (dst.m().type() == CV_8U) | ||
| 606 | - dst.m().at<uchar>(i,j) = buffer.at<uchar>(i,j); | ||
| 607 | - else | ||
| 608 | - qFatal("Unsupported pixel format."); | ||
| 609 | - } | ||
| 610 | - } | ||
| 611 | - } | ||
| 612 | - | ||
| 613 | - } | ||
| 614 | - | ||
| 615 | - dst.file.clearPoints(); | ||
| 616 | - dst.file.clearRects(); | ||
| 617 | - dst.file.setPoints(dstPoints); | ||
| 618 | - } | ||
| 619 | -}; | ||
| 620 | - | ||
| 621 | -BR_REGISTER(Transform, TextureMapTransform) | ||
| 622 | - | ||
| 623 | -/*! | ||
| 624 | - * \ingroup initializers | ||
| 625 | - * \brief Initialize Procrustes croppings | ||
| 626 | - * \author Brendan Klare \cite bklare | ||
| 627 | - */ | ||
| 628 | -class ProcrustesInitializer : public Initializer | ||
| 629 | -{ | ||
| 630 | - Q_OBJECT | ||
| 631 | - | ||
| 632 | - void initialize() const | ||
| 633 | - { | ||
| 634 | - Globals->abbreviations.insert("ProcrustesStasmFace","ProcrustesAlign(padding=16)+TextureMap+Resize(48,48)"); | ||
| 635 | - 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)"); | ||
| 636 | - 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=8)+TextureMap+Resize(36,48)"); | ||
| 637 | - Globals->abbreviations.insert("ProcrustesStasmBrow","SelectPoints([16,17,18,19,20,21,22,23,24,25,26,27])+ProcrustesAlign(padding=8)+TextureMap+Resize(24,48)"); | ||
| 638 | - Globals->abbreviations.insert("ProcrustesStasmNose","SelectPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58])+ProcrustesAlign(padding=8)+TextureMap+Resize(36,48)"); | ||
| 639 | - 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)"); | ||
| 640 | - Globals->abbreviations.insert("ProcrustesStasmJaw", "SelectPoints([2,3,4,5,6,7,8,9,10])+ProcrustesAlign(padding=8)+TextureMap+Resize(36,48)"); | ||
| 641 | - | ||
| 642 | - Globals->abbreviations.insert("ProcrustesLargeStasmFace","ProcrustesAlign(padding=16)+TextureMap+Resize(480,480)"); | ||
| 643 | - 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)"); | ||
| 644 | - 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=8)+TextureMap+Resize(360,480)"); | ||
| 645 | - Globals->abbreviations.insert("ProcrustesLargeStasmBrow","SelectPoints([16,17,18,19,20,21,22,23,24,25,26,27])+ProcrustesAlign(padding=8)+TextureMap+Resize(240,480)"); | ||
| 646 | - Globals->abbreviations.insert("ProcrustesLargeStasmNose","SelectPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58])+ProcrustesAlign(padding=8)+TextureMap+Resize(360,480)"); | ||
| 647 | - 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)"); | ||
| 648 | - Globals->abbreviations.insert("ProcrustesLargeStasmJaw", "SelectPoints([2,3,4,5,6,7,8,9,10])+ProcrustesAlign(padding=8)+TextureMap+Resize(360,480)"); | ||
| 649 | - } | ||
| 650 | -}; | ||
| 651 | -BR_REGISTER(Initializer, ProcrustesInitializer) | ||
| 652 | - | ||
| 653 | -/*! | ||
| 654 | - * \ingroup transforms | ||
| 655 | * \brief Creates a Delaunay triangulation based on a set of points | 286 | * \brief Creates a Delaunay triangulation based on a set of points |
| 656 | * \author Scott Klum \cite sklum | 287 | * \author Scott Klum \cite sklum |
| 657 | */ | 288 | */ |