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,3 +95,12 @@ MatrixXf removeRowCol(MatrixXf X, int row, int col) {
95 } 95 }
96 return Y; 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,9 +29,13 @@ void printEigen(Eigen::MatrixXd X);
29 void printEigen(Eigen::MatrixXf X); 29 void printEigen(Eigen::MatrixXf X);
30 void printSize(Eigen::MatrixXf X); 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 Eigen::MatrixXf removeRowCol(Eigen::MatrixXf X, int row, int col); 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 template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols> 39 template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols>
36 inline QDataStream &operator<<(QDataStream &stream, const Eigen::Matrix< _Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols > &mat) 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,4 +125,5 @@ Eigen::MatrixBase&lt;T&gt; eigStd(const Eigen::MatrixBase&lt;T&gt;&amp; x,int dim)
121 } 125 }
122 qFatal("A matrix can only have two dimensions"); 126 qFatal("A matrix can only have two dimensions");
123 } 127 }
  128 +
124 #endif // EIGENUTILS_H 129 #endif // EIGENUTILS_H
openbr/plugins/draw.cpp
@@ -527,6 +527,101 @@ class DrawSegmentation : public UntrainableTransform @@ -527,6 +527,101 @@ class DrawSegmentation : public UntrainableTransform
527 }; 527 };
528 BR_REGISTER(Transform, DrawSegmentation) 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 // TODO: re-implement EditTransform using Qt 625 // TODO: re-implement EditTransform using Qt
531 #if 0 626 #if 0
532 /*! 627 /*!
openbr/plugins/landmarks.cpp
@@ -141,193 +141,10 @@ BR_REGISTER(Transform, ProcrustesTransform) @@ -141,193 +141,10 @@ 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 - */  
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 * \brief Creates a Delaunay triangulation based on a set of points 144 * \brief Creates a Delaunay triangulation based on a set of points
328 * \author Scott Klum \cite sklum 145 * \author Scott Klum \cite sklum
329 */ 146 */
330 -class DelaunayTransform : public UntrainableTransform 147 +class DelaunayTransform : public Transform
331 { 148 {
332 Q_OBJECT 149 Q_OBJECT
333 150
openbr/plugins/stasm4.cpp
@@ -37,6 +37,8 @@ class StasmInitializer : public Initializer @@ -37,6 +37,8 @@ class StasmInitializer : public Initializer
37 { 37 {
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)"); 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 Globals->abbreviations.insert("RectFromStasmBrow","RectFromPoints([16,17,18,19,20,21,22,23,24,25,26,27],0.15,5)"); 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 Globals->abbreviations.insert("RectFromStasmNose","RectFromPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],0.15,1.15)"); 42 Globals->abbreviations.insert("RectFromStasmNose","RectFromPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],0.15,1.15)");
41 Globals->abbreviations.insert("RectFromStasmNoseWithBridge", "RectFromPoints([21, 22, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],0.15,.6)"); 43 Globals->abbreviations.insert("RectFromStasmNoseWithBridge", "RectFromPoints([21, 22, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],0.15,.6)");
42 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)"); 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,8 +75,11 @@ class StasmTransform : public UntrainableTransform
73 75
74 void project(const Template &src, Template &dst) const 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 dst = src; 83 dst = src;
79 84
80 StasmCascadeClassifier *stasmCascade = stasmCascadeResource.acquire(); 85 StasmCascadeClassifier *stasmCascade = stasmCascadeResource.acquire();
@@ -115,14 +120,14 @@ class StasmTransform : public UntrainableTransform @@ -115,14 +120,14 @@ class StasmTransform : public UntrainableTransform
115 else if (i == 39) /*Stasm Left Eye*/ { eyes[2*i] = leftEye.x(); eyes[2*i+1] = leftEye.y(); } 120 else if (i == 39) /*Stasm Left Eye*/ { eyes[2*i] = leftEye.x(); eyes[2*i+1] = leftEye.y(); }
116 else { eyes[2*i] = 0; eyes[2*i+1] = 0; } 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 // The ASM in Stasm is guaranteed to converge in this case 125 // The ASM in Stasm is guaranteed to converge in this case
121 foundFace = 1; 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 if (stasm3Format) { 132 if (stasm3Format) {
128 nLandmarks = 76; 133 nLandmarks = 76;