Commit 385abcbd7afdf656b8b72f9d7897acb93748ebef
Merge pull request #224 from biometrics/TextureMap
Texture map
Showing
5 changed files
with
120 additions
and
189 deletions
openbr/core/eigenutils.cpp
| ... | ... | @@ -95,3 +95,12 @@ MatrixXf removeRowCol(MatrixXf X, int row, int col) { |
| 95 | 95 | } |
| 96 | 96 | return Y; |
| 97 | 97 | } |
| 98 | + | |
| 99 | +MatrixXf pointsToMatrix(QList<QPointF> points) { | |
| 100 | + MatrixXf P(points.size(), 2); | |
| 101 | + for (int i = 0; i < points.size(); i++) { | |
| 102 | + P(i, 0) = points[i].x(); | |
| 103 | + P(i, 1) = points[i].y(); | |
| 104 | + } | |
| 105 | + return P; | |
| 106 | +} | ... | ... |
openbr/core/eigenutils.h
| ... | ... | @@ -29,9 +29,13 @@ void printEigen(Eigen::MatrixXd X); |
| 29 | 29 | void printEigen(Eigen::MatrixXf X); |
| 30 | 30 | void printSize(Eigen::MatrixXf X); |
| 31 | 31 | |
| 32 | -//Remove row and column from the matrix | |
| 32 | +//Remove row and column from the matrix: | |
| 33 | 33 | Eigen::MatrixXf removeRowCol(Eigen::MatrixXf X, int row, int col); |
| 34 | 34 | |
| 35 | +//Convert a point list into a matrix: | |
| 36 | +Eigen::MatrixXf pointsToMatrix(QList<QPointF> points); | |
| 37 | + | |
| 38 | + | |
| 35 | 39 | template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols> |
| 36 | 40 | inline QDataStream &operator<<(QDataStream &stream, const Eigen::Matrix< _Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols > &mat) |
| 37 | 41 | { |
| ... | ... | @@ -121,4 +125,5 @@ Eigen::MatrixBase<T> eigStd(const Eigen::MatrixBase<T>& x,int dim) |
| 121 | 125 | } |
| 122 | 126 | qFatal("A matrix can only have two dimensions"); |
| 123 | 127 | } |
| 128 | + | |
| 124 | 129 | #endif // EIGENUTILS_H | ... | ... |
openbr/plugins/draw.cpp
| ... | ... | @@ -527,6 +527,101 @@ class DrawSegmentation : public UntrainableTransform |
| 527 | 527 | }; |
| 528 | 528 | BR_REGISTER(Transform, DrawSegmentation) |
| 529 | 529 | |
| 530 | +/*! | |
| 531 | + * \ingroup transforms | |
| 532 | + * \brief Write all mats to disk as images. | |
| 533 | + * \author Brendan Klare \bklare | |
| 534 | + */ | |
| 535 | +class WriteImageTransform : public TimeVaryingTransform | |
| 536 | +{ | |
| 537 | + Q_OBJECT | |
| 538 | + Q_PROPERTY(QString outputDirectory READ get_outputDirectory WRITE set_outputDirectory RESET reset_outputDirectory STORED false) | |
| 539 | + Q_PROPERTY(QString imageName READ get_imageName WRITE set_imageName RESET reset_imageName STORED false) | |
| 540 | + Q_PROPERTY(QString imgExtension READ get_imgExtension WRITE set_imgExtension RESET reset_imgExtension STORED false) | |
| 541 | + BR_PROPERTY(QString, outputDirectory, "Temp") | |
| 542 | + BR_PROPERTY(QString, imageName, "image") | |
| 543 | + BR_PROPERTY(QString, imgExtension, "jpg") | |
| 544 | + | |
| 545 | + int cnt; | |
| 546 | + | |
| 547 | + void init() { | |
| 548 | + cnt = 0; | |
| 549 | + if (! QDir(outputDirectory).exists()) | |
| 550 | + QDir().mkdir(outputDirectory); | |
| 551 | + } | |
| 552 | + | |
| 553 | + void projectUpdate(const Template &src, Template &dst) | |
| 554 | + { | |
| 555 | + dst = src; | |
| 556 | + OpenCVUtils::saveImage(dst.m(), QString("%1/%2_%3.%4").arg(outputDirectory).arg(imageName).arg(cnt++, 5, QChar('0')).arg(imgExtension)); | |
| 557 | + } | |
| 558 | + | |
| 559 | +}; | |
| 560 | +BR_REGISTER(Transform, WriteImageTransform) | |
| 561 | + | |
| 562 | + | |
| 563 | +/** | |
| 564 | + * @brief The MeanImageTransform class computes the average template/image | |
| 565 | + * and save the result as an encoded image. | |
| 566 | + */ | |
| 567 | +class MeanImageTransform : public TimeVaryingTransform | |
| 568 | +{ | |
| 569 | + Q_OBJECT | |
| 570 | + | |
| 571 | + Q_PROPERTY(QString imgname READ get_imgname WRITE set_imgname RESET reset_imgname STORED false) | |
| 572 | + Q_PROPERTY(QString ext READ get_ext WRITE set_ext RESET reset_ext STORED false) | |
| 573 | + | |
| 574 | + BR_PROPERTY(QString, imgname, "average") | |
| 575 | + BR_PROPERTY(QString, ext, "jpg") | |
| 576 | + | |
| 577 | + Mat average; | |
| 578 | + int cnt; | |
| 579 | + | |
| 580 | + void init() | |
| 581 | + { | |
| 582 | + cnt = 0; | |
| 583 | + } | |
| 584 | + | |
| 585 | + void projectUpdate(const Template &src, Template &dst) | |
| 586 | + { | |
| 587 | + dst = src; | |
| 588 | + if (cnt == 0) { | |
| 589 | + if (src.m().channels() == 1) | |
| 590 | + average = Mat::zeros(dst.m().size(),CV_64FC1); | |
| 591 | + else if (src.m().channels() == 3) | |
| 592 | + average = Mat::zeros(dst.m().size(),CV_64FC3); | |
| 593 | + else | |
| 594 | + qFatal("Unsupported number of channels"); | |
| 595 | + } | |
| 596 | + | |
| 597 | + Mat temp; | |
| 598 | + if (src.m().channels() == 1) { | |
| 599 | + src.m().convertTo(temp, CV_64FC1); | |
| 600 | + average += temp; | |
| 601 | + } else if (src.m().channels() == 3) { | |
| 602 | + src.m().convertTo(temp, CV_64FC3); | |
| 603 | + average += temp; | |
| 604 | + } else | |
| 605 | + qFatal("Unsupported number of channels"); | |
| 606 | + | |
| 607 | + cnt++; | |
| 608 | + } | |
| 609 | + | |
| 610 | + virtual void finalize(TemplateList & output) | |
| 611 | + { | |
| 612 | + average /= float(cnt); | |
| 613 | + imwrite(QString("%1.%2").arg(imgname).arg(ext).toStdString(), average); | |
| 614 | + output = TemplateList(); | |
| 615 | + } | |
| 616 | + | |
| 617 | + | |
| 618 | +public: | |
| 619 | + MeanImageTransform() : TimeVaryingTransform(false, false) {} | |
| 620 | +}; | |
| 621 | + | |
| 622 | +BR_REGISTER(Transform, MeanImageTransform) | |
| 623 | + | |
| 624 | + | |
| 530 | 625 | // TODO: re-implement EditTransform using Qt |
| 531 | 626 | #if 0 |
| 532 | 627 | /*! | ... | ... |
openbr/plugins/landmarks.cpp
| ... | ... | @@ -141,193 +141,10 @@ BR_REGISTER(Transform, ProcrustesTransform) |
| 141 | 141 | |
| 142 | 142 | /*! |
| 143 | 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 | |
| 327 | 144 | * \brief Creates a Delaunay triangulation based on a set of points |
| 328 | 145 | * \author Scott Klum \cite sklum |
| 329 | 146 | */ |
| 330 | -class DelaunayTransform : public UntrainableTransform | |
| 147 | +class DelaunayTransform : public Transform | |
| 331 | 148 | { |
| 332 | 149 | Q_OBJECT |
| 333 | 150 | ... | ... |
openbr/plugins/stasm4.cpp
| ... | ... | @@ -37,6 +37,8 @@ class StasmInitializer : public Initializer |
| 37 | 37 | { |
| 38 | 38 | Globals->abbreviations.insert("RectFromStasmEyes","RectFromPoints([28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47],0.3,5.3)"); |
| 39 | 39 | Globals->abbreviations.insert("RectFromStasmBrow","RectFromPoints([16,17,18,19,20,21,22,23,24,25,26,27],0.15,5)"); |
| 40 | + Globals->abbreviations.insert("RectFromStasmPeriocular","RectFromPoints([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]],0.3,5.3)"); | |
| 41 | + Globals->abbreviations.insert("RectFromStasmBrow","RectFromPoints([16,17,18,19,20,21,22,23,24,25,26,27],0.15,5)"); | |
| 40 | 42 | Globals->abbreviations.insert("RectFromStasmNose","RectFromPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],0.15,1.15)"); |
| 41 | 43 | Globals->abbreviations.insert("RectFromStasmNoseWithBridge", "RectFromPoints([21, 22, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],0.15,.6)"); |
| 42 | 44 | Globals->abbreviations.insert("RectFromStasmMouth","RectFromPoints([59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76],0.3,2)"); |
| ... | ... | @@ -73,8 +75,11 @@ class StasmTransform : public UntrainableTransform |
| 73 | 75 | |
| 74 | 76 | void project(const Template &src, Template &dst) const |
| 75 | 77 | { |
| 76 | - if (src.m().channels() != 1) qFatal("Stasm expects single channel matrices."); | |
| 77 | - | |
| 78 | + Mat stasmSrc(src); | |
| 79 | + if (src.m().channels() == 3) | |
| 80 | + cvtColor(src, stasmSrc, CV_BGR2GRAY); | |
| 81 | + else if (src.m().channels() != 1) | |
| 82 | + qFatal("Stasm expects single channel matrices."); | |
| 78 | 83 | dst = src; |
| 79 | 84 | |
| 80 | 85 | StasmCascadeClassifier *stasmCascade = stasmCascadeResource.acquire(); |
| ... | ... | @@ -115,14 +120,14 @@ class StasmTransform : public UntrainableTransform |
| 115 | 120 | else if (i == 39) /*Stasm Left Eye*/ { eyes[2*i] = leftEye.x(); eyes[2*i+1] = leftEye.y(); } |
| 116 | 121 | else { eyes[2*i] = 0; eyes[2*i+1] = 0; } |
| 117 | 122 | } |
| 118 | - stasm_search_pinned(landmarks, eyes, reinterpret_cast<const char*>(src.m().data), src.m().cols, src.m().rows, NULL); | |
| 123 | + stasm_search_pinned(landmarks, eyes, reinterpret_cast<const char*>(stasmSrc.data), stasmSrc.cols, stasmSrc.rows, NULL); | |
| 119 | 124 | |
| 120 | 125 | // The ASM in Stasm is guaranteed to converge in this case |
| 121 | 126 | foundFace = 1; |
| 122 | 127 | } |
| 123 | 128 | } |
| 124 | 129 | |
| 125 | - if (!foundFace) stasm_search_single(&foundFace, landmarks, reinterpret_cast<const char*>(src.m().data), src.m().cols, src.m().rows, *stasmCascade, NULL, NULL); | |
| 130 | + if (!foundFace) stasm_search_single(&foundFace, landmarks, reinterpret_cast<const char*>(stasmSrc.data), stasmSrc.cols, stasmSrc.rows, *stasmCascade, NULL, NULL); | |
| 126 | 131 | |
| 127 | 132 | if (stasm3Format) { |
| 128 | 133 | nLandmarks = 76; | ... | ... |