Commit ffbadb9bf53d52e6a848c18f73594419819a5433

Authored by Brendan K
2 parents f98285aa 532103f3

Merge pull request #210 from biometrics/Procrustes

Procrustes
Showing 1 changed file with 185 additions and 0 deletions
openbr/plugins/landmarks.cpp
@@ -5,9 +5,11 @@ @@ -5,9 +5,11 @@
5 #include "openbr/core/eigenutils.h" 5 #include "openbr/core/eigenutils.h"
6 #include <QString> 6 #include <QString>
7 #include <Eigen/SVD> 7 #include <Eigen/SVD>
  8 +#include <Eigen/Dense>
8 9
9 using namespace std; 10 using namespace std;
10 using namespace cv; 11 using namespace cv;
  12 +using namespace Eigen;
11 13
12 namespace br 14 namespace br
13 { 15 {
@@ -139,6 +141,189 @@ BR_REGISTER(Transform, ProcrustesTransform) @@ -139,6 +141,189 @@ BR_REGISTER(Transform, ProcrustesTransform)
139 141
140 /*! 142 /*!
141 * \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 + */
  148 +class ProcrustesAlignTransform : public Transform
  149 +{
  150 + Q_OBJECT
  151 +
  152 + Q_PROPERTY(float width READ get_width WRITE set_width RESET reset_width STORED false)
  153 + Q_PROPERTY(float padding READ get_padding WRITE set_padding RESET reset_padding STORED false)
  154 + BR_PROPERTY(float, width, 80)
  155 + BR_PROPERTY(float, padding, 8)
  156 +
  157 + Eigen::MatrixXf referenceShape;
  158 + float minX;
  159 + float minY;
  160 + float maxX;
  161 + float maxY;
  162 + float aspectRatio;
  163 +
  164 + void init() {
  165 + minX = FLT_MAX,
  166 + minY = FLT_MAX,
  167 + maxX = -FLT_MAX,
  168 + maxY = -FLT_MAX;
  169 + aspectRatio = 0;
  170 + }
  171 +
  172 + static MatrixXf getRotation(MatrixXf ref, MatrixXf sample) {
  173 + MatrixXf R = ref.transpose() * sample;
  174 + JacobiSVD<MatrixXf> svd(R, ComputeFullU | ComputeFullV);
  175 + R = svd.matrixU() * svd.matrixV();
  176 + return R;
  177 + }
  178 +
  179 + //Converts x y points in a single vector to two column matrix
  180 + static MatrixXf vectorToMatrix(MatrixXf vector) {
  181 + int n = vector.rows();
  182 + MatrixXf matrix(n / 2, 2);
  183 + for (int i = 0; i < n / 2; i++) {
  184 + for (int j = 0; j < 2; j++) {
  185 + matrix(i, j) = vector(i * 2 + j);
  186 + }
  187 + }
  188 + return matrix;
  189 + }
  190 +
  191 + static MatrixXf matrixToVector(MatrixXf matrix) {
  192 + int n2 = matrix.rows();
  193 + MatrixXf vector(n2 * 2, 1);
  194 + for (int i = 0; i < n2; i++) {
  195 + for (int j = 0; j < 2; j++) {
  196 + vector(i * 2 + j) = matrix(i, j);
  197 + }
  198 + }
  199 + return vector;
  200 + }
  201 +
  202 + void train(const TemplateList &data)
  203 + {
  204 + MatrixXf points(data[0].file.points().size() * 2, data.size());
  205 +
  206 + // Normalize all sets of points
  207 + for (int j = 0; j < data.size(); j++) {
  208 + QList<QPointF> imagePoints = data[j].file.points();
  209 +
  210 + float meanX = 0,
  211 + meanY = 0;
  212 + for (int i = 0; i < imagePoints.size(); i++) {
  213 + points(i * 2, j) = imagePoints[i].x();
  214 + points(i * 2 + 1, j) = imagePoints[i].y();
  215 + meanX += imagePoints[i].x();
  216 + meanY += imagePoints[i].y();
  217 + }
  218 + meanX /= imagePoints.size();
  219 + meanY /= imagePoints.size();
  220 +
  221 + for (int i = 0; i < imagePoints.size(); i++) {
  222 + points(i * 2, j) -= meanX;
  223 + points(i * 2 + 1, j) -= meanY;
  224 + }
  225 + }
  226 +
  227 + //normalize scale
  228 + for (int i = 0; i < points.cols(); i++)
  229 + points.col(i) = points.col(i) / points.col(i).norm();
  230 +
  231 + //Normalize rotation
  232 + MatrixXf refPrev;
  233 + referenceShape = vectorToMatrix(points.rowwise().sum() / points.cols());
  234 + float diff = FLT_MAX;
  235 + while (diff > 1e-5) {//iterate until reference shape is stable
  236 + refPrev = referenceShape;
  237 +
  238 + for (int j = 0; j < points.cols(); j++) {
  239 + MatrixXf p = vectorToMatrix(points.col(j));
  240 + MatrixXf R = getRotation(referenceShape, p);
  241 + p = p * R.transpose();
  242 + points.col(j) = matrixToVector(p);
  243 + }
  244 + referenceShape = vectorToMatrix(points.rowwise().sum() / points.cols());
  245 + diff = (matrixToVector(referenceShape) - matrixToVector(refPrev)).norm();
  246 + }
  247 +
  248 + referenceShape = vectorToMatrix(points.rowwise().sum() / points.cols());
  249 +
  250 + //Choose crop boundaries and adjustments that captures all data
  251 + for (int i = 0; i < points.rows(); i++) {
  252 + for (int j = 0; j < points.cols(); j++) {
  253 + if (i % 2 == 0) {
  254 + if (points(i,j) > maxX)
  255 + maxX = points(i, j);
  256 + if (points(i,j) < minX)
  257 + minX = points(i, j);
  258 + } else {
  259 + if (points(i,j) > maxY)
  260 + maxY = points(i, j);
  261 + if (points(i,j) < minY)
  262 + minY = points(i, j);
  263 + }
  264 + }
  265 + }
  266 + aspectRatio = (maxX - minX) / (maxY - minY);
  267 + }
  268 +
  269 + void project(const Template &src, Template &dst) const
  270 + {
  271 + QList<QPointF> imagePoints = src.file.points();
  272 + MatrixXf p(imagePoints.size() * 2, 1);
  273 + for (int i = 0; i < imagePoints.size(); i++) {
  274 + p(i * 2) = imagePoints[i].x();
  275 + p(i * 2 + 1) = imagePoints[i].y();
  276 + }
  277 + p = vectorToMatrix(p);
  278 +
  279 + //Normalize translation
  280 + p.col(0) = p.col(0) - MatrixXf::Ones(p.rows(),1) * (p.col(0).sum() / p.rows());
  281 + p.col(1) = p.col(1) - MatrixXf::Ones(p.rows(),1) * (p.col(1).sum() / p.rows());
  282 +
  283 + //Normalize scale
  284 + p /= matrixToVector(p).norm();
  285 +
  286 + //Normalize rotation
  287 + MatrixXf R = getRotation(referenceShape, p);
  288 + p = p * R.transpose();
  289 +
  290 + //Translate and scale into output space and store in output list
  291 + QList<QPointF> procrustesPoints;
  292 + for (int i = 0; i < p.rows(); i++)
  293 + procrustesPoints.append( QPointF(
  294 + (p(i, 0) - minX) / (maxX - minX) * (width - 1) + padding,
  295 + (p(i, 1) - minY) / (maxY - minY) * (qRound( width / aspectRatio) - 1) + padding));
  296 +
  297 + dst = src;
  298 + dst.file.setList<QPointF>("ProcrustesPoints", procrustesPoints);
  299 + dst.file.set("ProcrustesBound", QRectF(0, 0, width + 2 * padding, (qRound(width / aspectRatio) + 2 * padding)));
  300 + }
  301 +
  302 + void store(QDataStream &stream) const
  303 + {
  304 + stream << referenceShape;
  305 + stream << minX;
  306 + stream << minY;
  307 + stream << maxX;
  308 + stream << maxY;
  309 + stream << aspectRatio;
  310 + }
  311 +
  312 + void load(QDataStream &stream)
  313 + {
  314 + stream >> referenceShape;
  315 + stream >> minX;
  316 + stream >> minY;
  317 + stream >> maxX;
  318 + stream >> maxY;
  319 + stream >> aspectRatio;
  320 + }
  321 +};
  322 +
  323 +BR_REGISTER(Transform, ProcrustesAlignTransform)
  324 +
  325 +/*!
  326 + * \ingroup transforms
142 * \brief Creates a Delaunay triangulation based on a set of points 327 * \brief Creates a Delaunay triangulation based on a set of points
143 * \author Scott Klum \cite sklum 328 * \author Scott Klum \cite sklum
144 */ 329 */