Commit ffbadb9bf53d52e6a848c18f73594419819a5433
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 | */ |