Commit 385abcbd7afdf656b8b72f9d7897acb93748ebef

Authored by Brendan K
2 parents a3093cc6 e6e03bfd

Merge pull request #224 from biometrics/TextureMap

Texture map
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&lt;T&gt; eigStd(const Eigen::MatrixBase&lt;T&gt;&amp; 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;
... ...