Commit 3c6b8c3c45410062f1da4ae2a77931bdf83794f3

Authored by Charles Otto
2 parents d6b19edd 69d0258d

Merge branch 'master' of https://github.com/biometrics/openbr into specificity

3rdparty/stasm4.0.0/CMakeLists.txt
... ... @@ -32,8 +32,12 @@ if(MSVC)
32 32 endif()
33 33  
34 34 add_subdirectory(${PROJECT_SOURCE_DIR}/stasm)
  35 +if(MSVC)
  36 + add_library(stasm STATIC ${SOURCE})
  37 +else()
  38 + add_library(stasm SHARED ${SOURCE})
  39 +endif()
35 40  
36   -add_library(stasm SHARED ${SOURCE})
37 41 qt5_use_modules(stasm ${QT_DEPENDENCIES})
38 42 set(SOURCE ${SOURCE} PARENT_SCOPE)
39 43 target_link_libraries(stasm ${OpenCV_LIBS} ${Qt5Core_QTMAIN_LIBRARIES})
... ...
3rdparty/stasm4.0.0/stasm/stasm_lib.cpp
... ... @@ -157,7 +157,6 @@ int stasm_search_auto_ext( // extended version of stasm_search_auto
157 157 {
158 158 int returnval = 1; // assume success
159 159 *foundface = 0; // but assume no face found
160   - CatchOpenCvErrs();
161 160 try
162 161 {
163 162 CheckStasmInit();
... ... @@ -204,7 +203,6 @@ int stasm_search_auto_ext( // extended version of stasm_search_auto
204 203 {
205 204 returnval = 0; // a call was made to Err or a CV_Assert failed
206 205 }
207   - UncatchOpenCvErrs();
208 206 return returnval;
209 207 }
210 208  
... ...
CMakeLists.txt
... ... @@ -17,7 +17,10 @@ set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
17 17 set(CMAKE_MODULE_PATH "${BR_SHARE_DIR}/cmake" ${CMAKE_MODULE_PATH})
18 18 set(PACKAGE_YEAR 2013)
19 19  
20   -if("${CMAKE_VERSION}" VERSION_GREATER 2.8.10)
  20 +if(${CMAKE_VERSION} VERSION_EQUAL 2.8.11)
  21 + cmake_policy(SET CMP0020 OLD)
  22 +endif()
  23 +if(${CMAKE_VERSION} VERSION_GREATER 2.8.11)
21 24 cmake_policy(SET CMP0020 OLD)
22 25 endif()
23 26  
... ...
openbr/core/bee.cpp
... ... @@ -43,9 +43,15 @@ FileList BEE::readSigset(const File &sigset, bool ignoreMetadata)
43 43 QFile file(sigset.resolved());
44 44 bool success;
45 45 success = file.open(QIODevice::ReadOnly); if (!success) qFatal("Unable to open %s for reading.", qPrintable(sigset));
46   - success = doc.setContent(&file); if (!success) qFatal("Unable to parse %s.", qPrintable(sigset));
  46 + success = doc.setContent(&file);
  47 +
47 48 file.close();
48 49  
  50 + if (!success) {
  51 + qWarning("Unable to parse %s.", qPrintable(sigset));
  52 + return fileList;
  53 + }
  54 +
49 55 QDomElement docElem = doc.documentElement();
50 56 if (docElem.nodeName() != "biometric-signature-set")
51 57 return fileList;
... ...
openbr/core/core.cpp
... ... @@ -167,7 +167,6 @@ struct AlgorithmCore
167 167 }
168 168  
169 169 const float speed = 1000 * Globals->totalSteps / Globals->startTime.elapsed() / std::max(1, abs(Globals->parallelism));
170   - qDebug() << Globals->startTime.elapsed();
171 170 if (!Globals->quiet && (Globals->totalSteps > 1))
172 171 fprintf(stderr, "\rSPEED=%.1e SIZE=%.4g FAILURES=%d/%d \n",
173 172 speed, totalBytes/totalCount, failureCount, totalCount);
... ...
openbr/core/plot.cpp
... ... @@ -247,8 +247,8 @@ float Evaluate(const Mat &amp;simmat, const Mat &amp;mask, const QString &amp;csv)
247 247 }
248 248  
249 249 // Write FAR/TAR Bar Chart (BC)
250   - lines.append(qPrintable(QString("BC,0.001,%1").arg(QString::number(result = getTAR(operatingPoints, 0.001), 'f', 3))));
251   - lines.append(qPrintable(QString("BC,0.01,%1").arg(QString::number(getTAR(operatingPoints, 0.01), 'f', 3))));
  250 + lines.append(qPrintable(QString("BC,0.001,%1").arg(QString::number(getTAR(operatingPoints, 0.001), 'f', 3))));
  251 + lines.append(qPrintable(QString("BC,0.01,%1").arg(QString::number(result = getTAR(operatingPoints, 0.01), 'f', 3))));
252 252  
253 253 // Write SD & KDE
254 254 points = qMin(qMin(Max_Points, genuines.size()), impostors.size());
... ... @@ -269,7 +269,7 @@ float Evaluate(const Mat &amp;simmat, const Mat &amp;mask, const QString &amp;csv)
269 269 }
270 270  
271 271 // Write Cumulative Match Characteristic (CMC) curve
272   - const int Max_Retrieval = 100;
  272 + const int Max_Retrieval = 200;
273 273 const int Report_Retrieval = 5;
274 274  
275 275 float reportRetrievalRate = -1;
... ... @@ -287,7 +287,7 @@ float Evaluate(const Mat &amp;simmat, const Mat &amp;mask, const QString &amp;csv)
287 287 }
288 288  
289 289 if (!csv.isEmpty()) QtUtils::writeFile(csv, lines);
290   - qDebug("TAR @ FAR = 0.001: %.3f\nRetrieval Rate @ Rank = %d: %.3f", result, Report_Retrieval, reportRetrievalRate);
  290 + qDebug("TAR @ FAR = 0.01: %.3f\nRetrieval Rate @ Rank = %d: %.3f", result, Report_Retrieval, reportRetrievalRate);
291 291 return result;
292 292 }
293 293  
... ...
openbr/openbr_plugin.cpp
... ... @@ -368,7 +368,7 @@ TemplateList TemplateList::fromGallery(const br::File &amp;gallery)
368 368 QScopedPointer<Gallery> i(Gallery::make(file));
369 369 TemplateList newTemplates = i->read();
370 370  
371   - // If file is a Format not a Gallery
  371 + // If file is a Format not a Gallery (e.g. XML Format vs. XML Gallery)
372 372 if (newTemplates.isEmpty())
373 373 newTemplates.append(file);
374 374  
... ... @@ -1193,6 +1193,33 @@ void Transform::backProject(const TemplateList &amp;dst, TemplateList &amp;src) const
1193 1193 futures.waitForFinished();
1194 1194 }
1195 1195  
  1196 +QList<Transform *> Transform::getChildren() const
  1197 +{
  1198 + QList<Transform *> output;
  1199 + for (int i=0; i < metaObject()->propertyCount(); i++) {
  1200 + const char * prop_name = metaObject()->property(i).name();
  1201 + const QVariant & variant = this->property(prop_name);
  1202 +
  1203 + if (variant.canConvert<Transform *>())
  1204 + output.append(variant.value<Transform *>());
  1205 + if (variant.canConvert<QList<Transform *> >())
  1206 + output.append(variant.value<QList<Transform *> >());
  1207 + }
  1208 + return output;
  1209 +}
  1210 +
  1211 +TemplateEvent * Transform::getEvent(const QString & name)
  1212 +{
  1213 + foreach(Transform * child, getChildren())
  1214 + {
  1215 + TemplateEvent * probe = child->getEvent(name);
  1216 + if (probe)
  1217 + return probe;
  1218 + }
  1219 +
  1220 + return NULL;
  1221 +}
  1222 +
1196 1223 /* Distance - public methods */
1197 1224 Distance *Distance::make(QString str, QObject *parent)
1198 1225 {
... ...
openbr/openbr_plugin.h
... ... @@ -1040,6 +1040,21 @@ private:
1040 1040 * @{
1041 1041 */
1042 1042  
  1043 +class TemplateEvent : public QObject
  1044 +{
  1045 + Q_OBJECT
  1046 +
  1047 +public:
  1048 + void pulseSignal(const Template & output) const
  1049 + {
  1050 + emit theSignal(output);
  1051 + }
  1052 +
  1053 +signals:
  1054 + void theSignal(const Template & output) const;
  1055 +};
  1056 +
  1057 +
1043 1058 /*!
1044 1059 * \brief Plugin base class for processing a template.
1045 1060 *
... ... @@ -1144,6 +1159,17 @@ public:
1144 1159 */
1145 1160 virtual Transform * smartCopy() { return this;}
1146 1161  
  1162 + /*!
  1163 + * \brief Recursively retrieve a named event, returns NULL if an event is not found.
  1164 + */
  1165 + virtual TemplateEvent * getEvent(const QString & name);
  1166 +
  1167 + /*!
  1168 + * \brief Get a list of child transforms of this transform, child transforms are considered to be
  1169 + * any transforms stored as properties of this transform.
  1170 + */
  1171 + QList<Transform *> getChildren() const;
  1172 +
1147 1173 protected:
1148 1174 Transform(bool independent = true, bool trainable = true); /*!< \brief Construct a transform. */
1149 1175 inline Transform *make(const QString &description) { return make(description, this); } /*!< \brief Make a subtransform. */
... ...
openbr/plugins/crop.cpp
... ... @@ -71,18 +71,32 @@ BR_REGISTER(Transform, ROITransform)
71 71 * \ingroup transforms
72 72 * \brief Resize the template
73 73 * \author Josh Klontz \cite jklontz
  74 + * \note Method: Area should be used for shrinking an image, Cubic for slow but accurate enlargment, Bilin for fast enlargement.
74 75 */
75 76 class ResizeTransform : public UntrainableTransform
76 77 {
77 78 Q_OBJECT
  79 + Q_ENUMS(Method)
  80 +
  81 +public:
  82 + /*!< */
  83 + enum Method { Near = INTER_NEAREST,
  84 + Area = INTER_AREA,
  85 + Bilin = INTER_LINEAR,
  86 + Cubic = INTER_CUBIC,
  87 + Lanczo = INTER_LANCZOS4};
  88 +
  89 +private:
78 90 Q_PROPERTY(int rows READ get_rows WRITE set_rows RESET reset_rows STORED false)
79 91 Q_PROPERTY(int columns READ get_columns WRITE set_columns RESET reset_columns STORED false)
  92 + Q_PROPERTY(Method method READ get_method WRITE set_method RESET reset_method STORED false)
80 93 BR_PROPERTY(int, rows, -1)
81 94 BR_PROPERTY(int, columns, -1)
  95 + BR_PROPERTY(Method, method, Bilin)
82 96  
83 97 void project(const Template &src, Template &dst) const
84 98 {
85   - resize(src, dst, Size((columns == -1) ? src.m().cols*rows/src.m().rows : columns, rows));
  99 + resize(src, dst, Size((columns == -1) ? src.m().cols*rows/src.m().rows : columns, rows), 0, 0, method);
86 100 }
87 101 };
88 102  
... ...
openbr/plugins/distance.cpp
... ... @@ -298,6 +298,33 @@ class IdenticalDistance : public Distance
298 298  
299 299 BR_REGISTER(Distance, IdenticalDistance)
300 300  
301   -} // namespace br
302 301  
  302 +/*!
  303 + * \ingroup distances
  304 + * \brief Online distance metric to attenuate match scores across multiple frames
  305 + * \author Brendan klare \cite bklare
  306 + */
  307 +class OnlineDistance : public Distance
  308 +{
  309 + Q_OBJECT
  310 + Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false)
  311 + Q_PROPERTY(float alpha READ get_alpha WRITE set_alpha RESET reset_alpha STORED false)
  312 + BR_PROPERTY(br::Distance*, distance, NULL)
  313 + BR_PROPERTY(float, alpha, 0.1f);
  314 +
  315 + mutable QHash<QString,float> scoreHash;
  316 + mutable QMutex mutex;
  317 +
  318 + float compare(const Template &target, const Template &query) const
  319 + {
  320 + float currentScore = distance->compare(target, query);
  321 +
  322 + QMutexLocker mutexLocker(&mutex);
  323 + return scoreHash[target.file.name] = (1.0- alpha) * scoreHash[target.file.name] + alpha * currentScore;
  324 + }
  325 +};
  326 +
  327 +BR_REGISTER(Distance, OnlineDistance)
  328 +
  329 +} // namespace br
303 330 #include "distance.moc"
... ...
openbr/plugins/format.cpp
... ... @@ -660,7 +660,7 @@ class xmlFormat : public Format
660 660 QFile f(fileName);
661 661  
662 662 if (!f.open(QIODevice::ReadOnly)) qFatal("Unable to open %s for reading.", qPrintable(file.flat()));
663   - if (!doc.setContent(&f)) qFatal("Unable to parse %s.", qPrintable(file.flat()));
  663 + if (!doc.setContent(&f)) qWarning("Unable to parse %s.", qPrintable(file.flat()));
664 664 f.close();
665 665  
666 666 QDomElement docElem = doc.documentElement();
... ...
openbr/plugins/gui.cpp
... ... @@ -220,16 +220,16 @@ signals:
220 220  
221 221 BR_REGISTER(Transform, ShowTransform)
222 222  
223   -class FPSSynch : public TimeVaryingTransform
  223 +class FPSLimit : public TimeVaryingTransform
224 224 {
225 225 Q_OBJECT
226 226 Q_PROPERTY(int targetFPS READ get_targetFPS WRITE set_targetFPS RESET reset_targetFPS STORED false)
227 227 BR_PROPERTY(int, targetFPS, 30)
228 228  
229 229 public:
230   - FPSSynch() : TimeVaryingTransform(false, false) {}
  230 + FPSLimit() : TimeVaryingTransform(false, false) {}
231 231  
232   - ~FPSSynch() {}
  232 + ~FPSLimit() {}
233 233  
234 234 void train(const TemplateList &data) { (void) data; }
235 235  
... ... @@ -237,16 +237,18 @@ public:
237 237 void projectUpdate(const TemplateList &src, TemplateList &dst)
238 238 {
239 239 dst = src;
240   - qint64 time_delta = timer.elapsed();
  240 + qint64 current_time = timer.elapsed();
  241 + qint64 target_time = last_time + target_wait;
  242 + qint64 wait_time = target_time - current_time;
241 243  
242   - qint64 wait_time = target_wait - time_delta;
243   - timer.start();
  244 + last_time = current_time;
244 245  
245 246 if (wait_time < 0) {
246 247 return;
247 248 }
248 249  
249 250 QThread::msleep(wait_time);
  251 + last_time = timer.elapsed();
250 252 }
251 253  
252 254 void finalize(TemplateList & output)
... ... @@ -256,15 +258,17 @@ public:
256 258  
257 259 void init()
258 260 {
259   - target_wait = 1000 / targetFPS;
  261 + target_wait = 1000.0 / targetFPS;
260 262 timer.start();
  263 + last_time = timer.elapsed();
261 264 }
262 265  
263 266 protected:
264 267 QElapsedTimer timer;
265 268 qint64 target_wait;
  269 + qint64 last_time;
266 270 };
267   -BR_REGISTER(Transform, FPSSynch)
  271 +BR_REGISTER(Transform, FPSLimit)
268 272  
269 273 class FPSCalc : public TimeVaryingTransform
270 274 {
... ...
openbr/plugins/landmarks.cpp
... ... @@ -21,6 +21,9 @@ class ProcrustesTransform : public Transform
21 21 {
22 22 Q_OBJECT
23 23  
  24 + Q_PROPERTY(bool warp READ get_warp WRITE set_warp RESET reset_warp STORED false)
  25 + BR_PROPERTY(bool, warp, true)
  26 +
24 27 Eigen::MatrixXf meanShape;
25 28  
26 29 void train(const TemplateList &data)
... ... @@ -30,23 +33,33 @@ class ProcrustesTransform : public Transform
30 33 // Normalize all sets of points
31 34 foreach (br::Template datum, data) {
32 35 QList<QPointF> points = datum.file.points();
  36 + QList<QRectF> rects = datum.file.rects();
  37 +
  38 + if (points.empty() || rects.empty()) continue;
33 39  
34   - if (points.empty()) continue;
  40 + // Assume rect appended last was bounding box
  41 + points.append(rects.last().topLeft());
  42 + points.append(rects.last().topRight());
  43 + points.append(rects.last().bottomLeft());
  44 + points.append(rects.last().bottomRight());
35 45  
36   - cv::Scalar mean = cv::mean(OpenCVUtils::toPoints(points).toVector().toStdVector());
  46 + // Center shape at origin
  47 + Scalar mean = cv::mean(OpenCVUtils::toPoints(points).toVector().toStdVector());
37 48 for (int i = 0; i < points.size(); i++) points[i] -= QPointF(mean[0],mean[1]);
38 49  
  50 + // Remove scale component
39 51 float norm = cv::norm(OpenCVUtils::toPoints(points).toVector().toStdVector());
40 52 for (int i = 0; i < points.size(); i++) points[i] /= norm;
41 53  
42 54 normalizedPoints.append(points);
43 55 }
44 56  
45   - // Determine mean shape
46   - Eigen::MatrixXf shapeBuffer(normalizedPoints[0].size(), 2);
  57 + if (normalizedPoints.empty()) qFatal("Unable to calculate normalized points");
47 58  
48   - for (int i = 0; i < normalizedPoints[0].size(); i++) {
  59 + // Determine mean shape, assuming all shapes contain the same number of points
  60 + meanShape = Eigen::MatrixXf(normalizedPoints[0].size(), 2);
49 61  
  62 + for (int i = 0; i < normalizedPoints[0].size(); i++) {
50 63 double x = 0;
51 64 double y = 0;
52 65  
... ... @@ -58,39 +71,58 @@ class ProcrustesTransform : public Transform
58 71 x /= (double)normalizedPoints.size();
59 72 y /= (double)normalizedPoints.size();
60 73  
61   - shapeBuffer(i,0) = x;
62   - shapeBuffer(i,1) = y;
  74 + meanShape(i,0) = x;
  75 + meanShape(i,1) = y;
63 76 }
64   -
65   - meanShape = shapeBuffer;
66 77 }
67 78  
68 79 void project(const Template &src, Template &dst) const
69 80 {
70 81 QList<QPointF> points = src.file.points();
  82 + QList<QRectF> rects = src.file.rects();
71 83  
72   - cv::Scalar mean = cv::mean(OpenCVUtils::toPoints(points).toVector().toStdVector());
  84 + if (points.empty() || rects.empty()) {
  85 + dst = src;
  86 + qWarning("Procrustes alignment failed because points or rects are empty.");
  87 + return;
  88 + }
  89 +
  90 + // Assume rect appended last was bounding box
  91 + points.append(rects.last().topLeft());
  92 + points.append(rects.last().topRight());
  93 + points.append(rects.last().bottomLeft());
  94 + points.append(rects.last().bottomRight());
  95 +
  96 + Scalar mean = cv::mean(OpenCVUtils::toPoints(points).toVector().toStdVector());
73 97 for (int i = 0; i < points.size(); i++) points[i] -= QPointF(mean[0],mean[1]);
74 98  
  99 + Eigen::MatrixXf srcMat(points.size(), 2);
75 100 float norm = cv::norm(OpenCVUtils::toPoints(points).toVector().toStdVector());
76   - Eigen::MatrixXf srcPoints(points.size(), 2);
77   -
78 101 for (int i = 0; i < points.size(); i++) {
79   - srcPoints(i,0) = points[i].x()/(norm/150.)+50;
80   - srcPoints(i,1) = points[i].y()/(norm/150.)+50;
  102 + points[i] /= norm;
  103 + srcMat(i,0) = points[i].x();
  104 + srcMat(i,1) = points[i].y();
81 105 }
82 106  
83   - Eigen::JacobiSVD<Eigen::MatrixXf> svd(srcPoints.transpose()*meanShape, Eigen::ComputeThinU | Eigen::ComputeThinV);
84   -
  107 + Eigen::JacobiSVD<Eigen::MatrixXf> svd(srcMat.transpose()*meanShape, Eigen::ComputeThinU | Eigen::ComputeThinV);
85 108 Eigen::MatrixXf R = svd.matrixU()*svd.matrixV().transpose();
86 109  
87   - Eigen::MatrixXf dstPoints = srcPoints*R;
88   -
89   - points.clear();
  110 + dst = src;
90 111  
91   - for (int i = 0; i < dstPoints.rows(); i++) points.append(QPointF(dstPoints(i,0),dstPoints(i,1)));
  112 + if (warp) {
  113 + Eigen::MatrixXf dstMat = srcMat*R;
  114 + for (int i = 0; i < dstMat.rows(); i++) {
  115 + dst.file.appendPoint(QPointF(dstMat(i,0),dstMat(i,1)));
  116 + }
  117 + }
92 118  
93   - dst.file.appendPoints(points);
  119 + dst.file.set("Procrustes_0_0", R(0,0));
  120 + dst.file.set("Procrustes_1_0", R(1,0));
  121 + dst.file.set("Procrustes_1_1", R(1,1));
  122 + dst.file.set("Procrustes_0_1", R(0,1));
  123 + dst.file.set("Procrustes_mean_0", mean[0]);
  124 + dst.file.set("Procrustes_mean_1", mean[1]);
  125 + dst.file.set("Procrustes_norm", norm);
94 126 }
95 127  
96 128 void store(QDataStream &stream) const
... ... @@ -109,60 +141,202 @@ BR_REGISTER(Transform, ProcrustesTransform)
109 141  
110 142 /*!
111 143 * \ingroup transforms
112   - * \brief Wraps STASM key point detector
  144 + * \brief Creates a Delaunay triangulation based on a set of points
113 145 * \author Scott Klum \cite sklum
114 146 */
115   -class DelauneyTransform : public UntrainableTransform
  147 +class DelaunayTransform : public UntrainableTransform
116 148 {
117 149 Q_OBJECT
118 150  
  151 + Q_PROPERTY(float scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false)
  152 + Q_PROPERTY(QString widthCrop READ get_widthCrop WRITE set_widthCrop RESET reset_widthCrop STORED false)
  153 + Q_PROPERTY(QString heightCrop READ get_heightCrop WRITE set_heightCrop RESET reset_heightCrop STORED false)
  154 + Q_PROPERTY(bool warp READ get_warp WRITE set_warp RESET reset_warp STORED false)
119 155 Q_PROPERTY(bool draw READ get_draw WRITE set_draw RESET reset_draw STORED false)
  156 + BR_PROPERTY(float, scaleFactor, 1)
  157 + BR_PROPERTY(QString, widthCrop, QString())
  158 + BR_PROPERTY(QString, heightCrop, QString())
  159 + BR_PROPERTY(bool, warp, true)
120 160 BR_PROPERTY(bool, draw, false)
121 161  
122 162 void project(const Template &src, Template &dst) const
123 163 {
124   - dst = src;
125   -
126 164 Subdiv2D subdiv(Rect(0,0,src.m().cols,src.m().rows));
127 165  
128   - foreach(const cv::Point2f& point, OpenCVUtils::toPoints(src.file.points())) subdiv.insert(point);
  166 + QList<QPointF> points = src.file.points();
  167 + QList<QRectF> rects = src.file.rects();
  168 +
  169 + if (points.empty() || rects.empty()) {
  170 + dst = src;
  171 + qWarning("Delauney triangulation failed because points or rects are empty.");
  172 + return;
  173 + }
  174 +
  175 + // Assume rect appended last was bounding box
  176 + points.append(rects.last().topLeft());
  177 + points.append(rects.last().topRight());
  178 + points.append(rects.last().bottomLeft());
  179 + points.append(rects.last().bottomRight());
  180 +
  181 + for (int i = 0; i < points.size(); i++) {
  182 + if (points[i].x() < 0 || points[i].y() < 0 || points[i].y() >= src.m().rows || points[i].x() >= src.m().cols) {
  183 + dst = src;
  184 + qWarning("Delauney triangulation failed because points lie on boundary.");
  185 + return;
  186 + }
  187 + subdiv.insert(OpenCVUtils::toPoint(points[i]));
  188 + }
129 189  
130 190 vector<Vec6f> triangleList;
131 191 subdiv.getTriangleList(triangleList);
132   - vector<Point> pt(3);
133 192  
134   - Scalar delaunay_color(0, 0, 0);
  193 + QList< QList<Point> > validTriangles;
  194 +
  195 + for (size_t i = 0; i < triangleList.size(); ++i) {
  196 +
  197 + vector<Point> pt(3);
  198 + pt[0] = Point(cvRound(triangleList[i][0]), cvRound(triangleList[i][1]));
  199 + pt[1] = Point(cvRound(triangleList[i][2]), cvRound(triangleList[i][3]));
  200 + pt[2] = Point(cvRound(triangleList[i][4]), cvRound(triangleList[i][5]));
  201 +
  202 + bool inside = true;
  203 + for (int j = 0; j < 3; j++) {
  204 + if (pt[j].x > src.m().cols || pt[j].y > src.m().rows || pt[j].x < 0 || pt[j].y < 0) inside = false;
  205 + }
  206 +
  207 + if (inside) validTriangles.append(QList<Point>()<< pt[0] << pt[1] << pt[2]);
  208 + }
  209 +
  210 + dst.m() = src.m().clone();
135 211  
136 212 if (draw) {
137   - int count = 0;
138   - for(size_t i = 0; i < triangleList.size(); ++i) {
139   - Vec6f t = triangleList[i];
140   -
141   - pt[0] = Point(cvRound(t[0]), cvRound(t[1]));
142   - pt[1] = Point(cvRound(t[2]), cvRound(t[3]));
143   - pt[2] = Point(cvRound(t[4]), cvRound(t[5]));
144   -
145   - bool inside = true;
146   - for (int i = 0; i < 3; i++) {
147   - if(pt[i].x > dst.m().cols || pt[i].y > dst.m().rows || pt[i].x <= 0 || pt[i].y <= 0) {
148   - inside = false;
149   - }
  213 + foreach(const QList<Point>& triangle, validTriangles) {
  214 + line(dst.m(), triangle[0], triangle[1], Scalar(0,0,0), 1);
  215 + line(dst.m(), triangle[1], triangle[2], Scalar(0,0,0), 1);
  216 + line(dst.m(), triangle[2], triangle[0], Scalar(0,0,0), 1);
  217 + }
  218 + }
  219 +
  220 + if (warp) {
  221 + Eigen::MatrixXf R(2,2);
  222 + R(0,0) = src.file.get<float>("Procrustes_0_0");
  223 + R(1,0) = src.file.get<float>("Procrustes_1_0");
  224 + R(1,1) = src.file.get<float>("Procrustes_1_1");
  225 + R(0,1) = src.file.get<float>("Procrustes_0_1");
  226 +
  227 + cv::Scalar mean(2);
  228 + mean[0] = src.file.get<float>("Procrustes_mean_0");
  229 + mean[1] = src.file.get<float>("Procrustes_mean_1");
  230 +
  231 + float norm = src.file.get<float>("Procrustes_norm");
  232 +
  233 + dst.m() = Mat::zeros(src.m().rows,src.m().cols,src.m().type());
  234 +
  235 + QList<Point2f> mappedPoints;
  236 +
  237 + for (int i = 0; i < validTriangles.size(); i++) {
  238 + Eigen::MatrixXf srcMat(validTriangles[i].size(), 2);
150 239  
  240 + for (int j = 0; j < 3; j++) {
  241 + srcMat(j,0) = (validTriangles[i][j].x-mean[0])/norm;
  242 + srcMat(j,1) = (validTriangles[i][j].y-mean[1])/norm;
151 243 }
152   - if (inside) {
153   - count++;
154   - //qDebug() << count << pt[0] << pt[1] << pt[2] << "Area" << contourArea(pt);
155   - line(dst.m(), pt[0], pt[1], delaunay_color, 1);
156   - line(dst.m(), pt[1], pt[2], delaunay_color, 1);
157   - line(dst.m(), pt[2], pt[0], delaunay_color, 1);
  244 +
  245 + Eigen::MatrixXf dstMat = srcMat*R;
  246 +
  247 + Point2f srcPoints[3];
  248 + for (int j = 0; j < 3; j++) srcPoints[j] = validTriangles[i][j];
  249 +
  250 + Point2f dstPoints[3];
  251 + for (int j = 0; j < 3; j++) {
  252 + // Scale and shift destination points
  253 + Point2f warpedPoint = Point2f(dstMat(j,0)*scaleFactor+src.m().cols/2,dstMat(j,1)*scaleFactor+src.m().rows/2);
  254 + dstPoints[j] = warpedPoint;
  255 + mappedPoints.append(warpedPoint);
  256 + }
  257 +
  258 + Mat buffer(src.m().rows,src.m().cols,src.m().type());
  259 +
  260 + warpAffine(src.m(), buffer, getAffineTransform(srcPoints, dstPoints), Size(src.m().cols,src.m().rows));
  261 +
  262 + Mat mask = Mat::zeros(src.m().rows, src.m().cols, CV_8UC1);
  263 + Point maskPoints[1][3];
  264 + maskPoints[0][0] = dstPoints[0];
  265 + maskPoints[0][1] = dstPoints[1];
  266 + maskPoints[0][2] = dstPoints[2];
  267 + const Point* ppt = { maskPoints[0] };
  268 +
  269 + fillConvexPoly(mask, ppt, 3, Scalar(255,255,255), 8);
  270 +
  271 + Mat output(src.m().rows,src.m().cols,src.m().type());
  272 +
  273 + // Optimization needed
  274 + if (i > 0) {
  275 + Mat overlap;
  276 + bitwise_and(dst.m(),mask,overlap);
  277 + for (int j = 0; j < overlap.rows; j++) {
  278 + for (int k = 0; k < overlap.cols; k++) {
  279 + if (overlap.at<uchar>(k,j) != 0) {
  280 + mask.at<uchar>(k,j) = 0;
  281 + }
  282 + }
  283 + }
158 284 }
  285 +
  286 + bitwise_and(buffer,mask,output);
  287 +
  288 + dst.m() += output;
159 289 }
  290 +
  291 + Rect boundingBox = boundingRect(mappedPoints.toVector().toStdVector());
  292 +
  293 + boundingBox.x += boundingBox.width * QtUtils::toPoint(widthCrop).x();
  294 + boundingBox.y += boundingBox.height * QtUtils::toPoint(heightCrop).x();
  295 + boundingBox.width *= 1-QtUtils::toPoint(widthCrop).y();
  296 + boundingBox.height *= 1-QtUtils::toPoint(heightCrop).y();
  297 +
  298 + dst.m() = Mat(dst.m(), boundingBox);
160 299 }
161 300 }
162 301  
163 302 };
164 303  
165   -BR_REGISTER(Transform, DelauneyTransform)
  304 +BR_REGISTER(Transform, DelaunayTransform)
  305 +
  306 +/*!
  307 + * \ingroup transforms
  308 + * \brief Computes the mean of a set of templates.
  309 + * \note Suitable for visualization only as it sets every projected template to the mean template.
  310 + * \author Scott Klum \cite sklum
  311 + */
  312 +class MeanTransform : public Transform
  313 +{
  314 + Q_OBJECT
  315 +
  316 + Mat mean;
  317 +
  318 + void train(const TemplateList &data)
  319 + {
  320 + mean = Mat::zeros(data[0].m().rows,data[0].m().cols,CV_32F);
  321 +
  322 + for (int i = 0; i < data.size(); i++) {
  323 + Mat converted;
  324 + data[i].m().convertTo(converted, CV_32F);
  325 + mean += converted;
  326 + }
  327 +
  328 + mean /= data.size();
  329 + }
  330 +
  331 + void project(const Template &src, Template &dst) const
  332 + {
  333 + dst = src;
  334 + dst.m() = mean;
  335 + }
  336 +
  337 +};
  338 +
  339 +BR_REGISTER(Transform, MeanTransform)
166 340  
167 341 } // namespace br
168 342  
... ...
openbr/plugins/misc.cpp
... ... @@ -35,6 +35,7 @@ class OpenTransform : public UntrainableMetaTransform
35 35  
36 36 void project(const Template &src, Template &dst) const
37 37 {
  38 + if (!src.isEmpty()) { dst = src; return; }
38 39 if (Globals->verbose) qDebug("Opening %s", qPrintable(src.file.flat()));
39 40 dst.file = src.file;
40 41 foreach (const File &file, src.file.split()) {
... ... @@ -561,6 +562,28 @@ class ExpandRectTransform : public UntrainableTransform
561 562  
562 563 BR_REGISTER(Transform, ExpandRectTransform)
563 564  
  565 +
  566 +class EventTransform : public UntrainableMetaTransform
  567 +{
  568 + Q_OBJECT
  569 + Q_PROPERTY(QString eventName READ get_eventName WRITE set_eventName RESET reset_eventName STORED false)
  570 + BR_PROPERTY(QString, eventName, "")
  571 +
  572 + TemplateEvent event;
  573 +
  574 + void project(const Template &src, Template &dst) const
  575 + {
  576 + dst = src;
  577 + event.pulseSignal(dst);
  578 + }
  579 +
  580 + TemplateEvent * getEvent(const QString & name)
  581 + {
  582 + return name == eventName ? &event : NULL;
  583 + }
  584 +};
  585 +BR_REGISTER(Transform, EventTransform)
  586 +
564 587 }
565 588  
566 589 #include "misc.moc"
... ...
openbr/plugins/pp5.cpp
... ... @@ -97,8 +97,8 @@ struct PP5Context
97 97  
98 98 static void createRawImage(const cv::Mat &src, ppr_raw_image_type &dst)
99 99 {
100   - if (!src.isContinuous()) qFatal("PP5Context::createRawImage requires continuous data.");
101   - if (src.channels() == 3) ppr_raw_image_create(&dst, src.cols, src.rows, PPR_RAW_IMAGE_BGR24);
  100 + if (!src.isContinuous()) qFatal("PP5Context::createRawImage requires continuous data.");
  101 + else if (src.channels() == 3) ppr_raw_image_create(&dst, src.cols, src.rows, PPR_RAW_IMAGE_BGR24);
102 102 else if (src.channels() == 1) ppr_raw_image_create(&dst, src.cols, src.rows, PPR_RAW_IMAGE_GRAY8);
103 103 else qFatal("PP5Context::createRawImage invalid channel count.");
104 104 memcpy(dst.data, src.data, src.channels()*src.rows*src.cols);
... ... @@ -241,41 +241,43 @@ class PP5EnrollTransform : public UntrainableMetaTransform
241 241 PP5Context *context = contexts.acquire();
242 242  
243 243 foreach(const Template & src, srcList) {
244   - ppr_raw_image_type raw_image;
245   - PP5Context::createRawImage(src, raw_image);
246   - ppr_image_type image;
247   - TRY(ppr_create_image(raw_image, &image))
248   - ppr_face_list_type face_list;
249   - TRY(ppr_detect_faces(context->context, image, &face_list))
250   -
251   - for (int i=0; i<face_list.length; i++) {
252   - ppr_face_type face = face_list.faces[i];
253   - int extractable;
254   - TRY(ppr_is_template_extractable(context->context, face, &extractable))
255   - if (!extractable && !detectOnly) continue;
256   -
257   - cv::Mat m;
258   - if (detectOnly) {
259   - m = src;
260   - } else {
261   - TRY(ppr_extract_face_template(context->context, image, &face))
262   - context->createMat(face, m);
263   - }
264   - Template dst;
265   - dst.file = src.file;
266   -
267   - dst.file.append(PP5Context::toMetadata(face));
268   - dst += m;
269   - dstList.append(dst);
270   -
271   - // Found a face, nothing else to do (if we aren't trying to find multiple faces).
272   - if (!Globals->enrollAll)
273   - break;
  244 + if (!src.isEmpty()) {
  245 + ppr_raw_image_type raw_image;
  246 + PP5Context::createRawImage(src, raw_image);
  247 + ppr_image_type image;
  248 + TRY(ppr_create_image(raw_image, &image))
  249 + ppr_face_list_type face_list;
  250 + TRY(ppr_detect_faces(context->context, image, &face_list))
  251 +
  252 + for (int i=0; i<face_list.length; i++) {
  253 + ppr_face_type face = face_list.faces[i];
  254 + int extractable;
  255 + TRY(ppr_is_template_extractable(context->context, face, &extractable))
  256 + if (!extractable && !detectOnly) continue;
  257 +
  258 + cv::Mat m;
  259 + if (detectOnly) {
  260 + m = src;
  261 + } else {
  262 + TRY(ppr_extract_face_template(context->context, image, &face))
  263 + context->createMat(face, m);
  264 + }
  265 + Template dst;
  266 + dst.file = src.file;
  267 +
  268 + dst.file.append(PP5Context::toMetadata(face));
  269 + dst += m;
  270 + dstList.append(dst);
  271 +
  272 + // Found a face, nothing else to do (if we aren't trying to find multiple faces).
  273 + if (!Globals->enrollAll)
  274 + break;
  275 + }
  276 +
  277 + ppr_free_face_list(face_list);
  278 + ppr_free_image(image);
  279 + ppr_raw_image_free(raw_image);
274 280 }
275   -
276   - ppr_free_face_list(face_list);
277   - ppr_free_image(image);
278   - ppr_raw_image_free(raw_image);
279 281 }
280 282  
281 283 // No faces were detected, output something with FTE set.
... ... @@ -305,10 +307,14 @@ class PP5CompareDistance : public Distance
305 307  
306 308 float compare(const Template &target, const Template &query) const
307 309 {
308   - (void) target;
309   - (void) query;
310   - qFatal("Compare single templates should never be called!");
311   - return 0;
  310 + TemplateList targetList;
  311 + targetList.append(target);
  312 + TemplateList queryList;
  313 + queryList.append(query);
  314 + MatrixOutput *score = MatrixOutput::make(targetList.files(), queryList.files());
  315 + compare(targetList, queryList, score);
  316 + return score->data.at<float>(0);
  317 +
312 318 }
313 319  
314 320 void compare(const TemplateList &target, const TemplateList &query, Output *output) const
... ...
openbr/plugins/regions.cpp
... ... @@ -95,8 +95,9 @@ class CatTransform : public UntrainableMetaTransform
95 95 for (int i=0; i<src.size(); i++)
96 96 sizes[i%partitions] += src[i].total();
97 97  
98   - foreach (int size, sizes)
99   - dst.append(Mat(1, size, src.m().type()));
  98 + if (!src.empty())
  99 + foreach (int size, sizes)
  100 + dst.append(Mat(1, size, src.m().type()));
100 101  
101 102 QVector<int> offsets(partitions, 0);
102 103 for (int i=0; i<src.size(); i++) {
... ... @@ -230,6 +231,7 @@ class RectFromPointsTransform : public UntrainableTransform
230 231 if (src.file.points()[index].y() > maxY) maxY = src.file.points()[index].y();
231 232 points.append(src.file.points()[index]);
232 233 }
  234 + else qFatal("Incorrect indices");
233 235 }
234 236  
235 237 double width = maxX-minX;
... ... @@ -238,12 +240,15 @@ class RectFromPointsTransform : public UntrainableTransform
238 240  
239 241 double height = maxY-minY;
240 242 double deltaHeight = width/aspectRatio - height;
241   - height += deltaHeight;
  243 + height += deltaHeight;
242 244  
243 245 dst.file.setPoints(points);
244 246  
245 247 if (crop) dst.m() = src.m()(Rect(std::max(0.0, minX - deltaWidth/2.0), std::max(0.0, minY - deltaHeight/2.0), std::min((double)src.m().cols, width), std::min((double)src.m().rows, height)));
246   - else dst.m() = src.m();
  248 + else {
  249 + dst.file.appendRect(QRectF(std::max(0.0, minX - deltaWidth/2.0), std::max(0.0, minY - deltaHeight/2.0), std::min((double)src.m().cols, width), std::min((double)src.m().rows, height)));
  250 + dst.m() = src.m();
  251 + }
247 252 }
248 253 };
249 254  
... ...
openbr/plugins/register.cpp
... ... @@ -28,10 +28,22 @@ namespace br
28 28 * \ingroup transforms
29 29 * \brief Performs a two or three point registration.
30 30 * \author Josh Klontz \cite jklontz
  31 + * \note Method: Area should be used for shrinking an image, Cubic for slow but accurate enlargment, Bilin for fast enlargement.
31 32 */
32 33 class AffineTransform : public UntrainableTransform
33 34 {
34 35 Q_OBJECT
  36 + Q_ENUMS(Method)
  37 +
  38 +public:
  39 + /*!< */
  40 + enum Method { Near = INTER_NEAREST,
  41 + Area = INTER_AREA,
  42 + Bilin = INTER_LINEAR,
  43 + Cubic = INTER_CUBIC,
  44 + Lanczo = INTER_LANCZOS4};
  45 +
  46 +private:
35 47 Q_PROPERTY(int width READ get_width WRITE set_width RESET reset_width STORED false)
36 48 Q_PROPERTY(int height READ get_height WRITE set_height RESET reset_height STORED false)
37 49 Q_PROPERTY(float x1 READ get_x1 WRITE set_x1 RESET reset_x1 STORED false)
... ... @@ -40,6 +52,7 @@ class AffineTransform : public UntrainableTransform
40 52 Q_PROPERTY(float y2 READ get_y2 WRITE set_y2 RESET reset_y2 STORED false)
41 53 Q_PROPERTY(float x3 READ get_x3 WRITE set_x3 RESET reset_x3 STORED false)
42 54 Q_PROPERTY(float y3 READ get_y3 WRITE set_y3 RESET reset_y3 STORED false)
  55 + Q_PROPERTY(Method method READ get_method WRITE set_method RESET reset_method STORED false)
43 56 BR_PROPERTY(int, width, 64)
44 57 BR_PROPERTY(int, height, 64)
45 58 BR_PROPERTY(float, x1, 0)
... ... @@ -48,6 +61,7 @@ class AffineTransform : public UntrainableTransform
48 61 BR_PROPERTY(float, y2, -1)
49 62 BR_PROPERTY(float, x3, -1)
50 63 BR_PROPERTY(float, y3, -1)
  64 + BR_PROPERTY(Method, method, Bilin)
51 65  
52 66 static Point2f getThirdAffinePoint(const Point2f &a, const Point2f &b)
53 67 {
... ... @@ -72,9 +86,6 @@ class AffineTransform : public UntrainableTransform
72 86 (src.file.contains("Affine_2") || twoPoints)) {
73 87 srcPoints[0] = OpenCVUtils::toPoint(src.file.get<QPointF>("Affine_0"));
74 88 srcPoints[1] = OpenCVUtils::toPoint(src.file.get<QPointF>("Affine_1"));
75   -
76   - dst.file.set("Affine_0", OpenCVUtils::fromPoint(dstPoints[0]));
77   - dst.file.set("Affine_1", OpenCVUtils::fromPoint(dstPoints[1]));
78 89 if (!twoPoints) srcPoints[2] = OpenCVUtils::toPoint(src.file.get<QPointF>("Affine_2"));
79 90 } else {
80 91 const QList<Point2f> landmarks = OpenCVUtils::toPoints(src.file.points());
... ... @@ -89,13 +100,12 @@ class AffineTransform : public UntrainableTransform
89 100  
90 101 dst.file.set("Affine_0", OpenCVUtils::fromPoint(landmarks[0]));
91 102 dst.file.set("Affine_1", OpenCVUtils::fromPoint(landmarks[1]));
92   -
93 103 if (!twoPoints) dst.file.set("Affine_2", OpenCVUtils::fromPoint(landmarks[2]));
94 104 }
95 105 }
96 106 if (twoPoints) srcPoints[2] = getThirdAffinePoint(srcPoints[0], srcPoints[1]);
97 107  
98   - warpAffine(src, dst, getAffineTransform(srcPoints, dstPoints), Size(width, height));
  108 + warpAffine(src, dst, getAffineTransform(srcPoints, dstPoints), Size(width, height), method);
99 109 }
100 110 };
101 111  
... ...
openbr/plugins/stasm4.cpp
... ... @@ -38,8 +38,8 @@ class StasmInitializer : public Initializer
38 38 {
39 39 Globals->abbreviations.insert("RectFromStasmEyes","RectFromPoints([29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47],0.125,6.0)+Resize(44,164)");
40 40 Globals->abbreviations.insert("RectFromStasmBrow","RectFromPoints([17, 18, 19, 20, 21, 22, 23, 24],0.15,6)+Resize(28,132)");
41   - Globals->abbreviations.insert("RectFromStasmNose","RectFromPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],0.15,1.25)+Resize(60,60)");
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,3.0)+Resize(28,68)");
  41 + Globals->abbreviations.insert("RectFromStasmNose","RectFromPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],0.15,1.25)");
  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.5)");
43 43 }
44 44 };
45 45  
... ...
openbr/plugins/stream.cpp
... ... @@ -241,7 +241,7 @@ public:
241 241 }
242 242  
243 243 virtual void close() = 0;
244   - virtual bool open(Template & output) = 0;
  244 + virtual bool open(Template & output, int start_index=0) = 0;
245 245 virtual bool isOpen() = 0;
246 246  
247 247 virtual bool getNext(FrameData & input) = 0;
... ... @@ -262,13 +262,13 @@ class VideoDataSource : public DataSource
262 262 public:
263 263 VideoDataSource(int maxFrames) : DataSource(maxFrames) {}
264 264  
265   - bool open(Template &input)
  265 + bool open(Template &input, int start_index=0)
266 266 {
267 267 final_frame = -1;
268 268 last_issued = -2;
269 269 last_received = -3;
270 270  
271   - next_idx = 0;
  271 + next_idx = start_index;
272 272 basis = input;
273 273 bool is_int = false;
274 274 int anInt = input.file.name.toInt(&is_int);
... ... @@ -336,11 +336,11 @@ public:
336 336 }
337 337 bool data_ok;
338 338  
339   - bool open(Template &input)
  339 + bool open(Template &input, int start_index=0)
340 340 {
341 341 basis = input;
342 342 current_idx = 0;
343   - next_sequence = 0;
  343 + next_sequence = start_index;
344 344 final_frame = -1;
345 345 last_issued = -2;
346 346 last_received = -3;
... ... @@ -406,22 +406,31 @@ public:
406 406 }
407 407 }
408 408  
409   - bool open(Template & input)
  409 + bool open(TemplateList & input)
  410 + {
  411 + currentIdx = 0;
  412 + templates = input;
  413 +
  414 + return open(templates[currentIdx]);
  415 + }
  416 +
  417 + bool open(Template & input, int start_index=0)
410 418 {
411 419 close();
412 420 final_frame = -1;
413 421 last_issued = -2;
414 422 last_received = -3;
  423 + next_frame = start_index;
415 424  
416 425 // Input has no matrices? Its probably a video that hasn't been loaded yet
417 426 if (input.empty()) {
418 427 actualSource = new VideoDataSource(0);
419   - actualSource->open(input);
  428 + actualSource->open(input, next_frame);
420 429 }
421 430 else {
422 431 // create frame dealer
423 432 actualSource = new TemplateDataSource(0);
424   - actualSource->open(input);
  433 + actualSource->open(input, next_frame);
425 434 }
426 435 if (!isOpen()) {
427 436 delete actualSource;
... ... @@ -434,10 +443,31 @@ public:
434 443 bool isOpen() { return !actualSource ? false : actualSource->isOpen(); }
435 444  
436 445 protected:
  446 + int currentIdx;
  447 + int next_frame;
  448 + TemplateList templates;
437 449 DataSource * actualSource;
438 450 bool getNext(FrameData & output)
439 451 {
440   - return actualSource->getNext(output);
  452 + bool res = actualSource->getNext(output);
  453 + if (res) {
  454 + next_frame = output.sequenceNumber+1;
  455 + return true;
  456 + }
  457 +
  458 + while(!res) {
  459 + currentIdx++;
  460 +
  461 + if (currentIdx >= templates.size())
  462 + return false;
  463 + bool open_res = open(templates[currentIdx], next_frame);
  464 + if (!open_res)
  465 + return false;
  466 + res = actualSource->getNext(output);
  467 + }
  468 +
  469 + next_frame = output.sequenceNumber+1;
  470 + return res;
441 471 }
442 472  
443 473 };
... ... @@ -796,11 +826,9 @@ public:
796 826 // start processing
797 827 void projectUpdate(const TemplateList & src, TemplateList & dst)
798 828 {
799   - if (src.size() != 1)
800   - qFatal("Expected single template input to stream");
801   -
802 829 dst = src;
803   - bool res = readStage->dataSource.open(dst[0]);
  830 +
  831 + bool res = readStage->dataSource.open(dst);
804 832 if (!res) return;
805 833  
806 834 QThreadPool::globalInstance()->releaseThread();
... ...
share/openbr/cmake/InstallDependencies.cmake
... ... @@ -10,7 +10,8 @@ function(install_opencv_library lib)
10 10 if(NOT MSVC)
11 11 set(BR_INSTALL_DEPENDENCIES_PREFIX "lib")
12 12 endif()
13   - install(FILES ${OpenCV_DIR}/bin/${BR_INSTALL_DEPENDENCIES_PREFIX}${lib}${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}${BR_INSTALL_DEPENDENCIES_SUFFIX}.dll DESTINATION bin)
  13 + list(GET OpenCV_LIB_DIR 0 cv_lib_stripped)
  14 + install(FILES ${cv_lib_stripped}/../bin/${BR_INSTALL_DEPENDENCIES_PREFIX}${lib}${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}${BR_INSTALL_DEPENDENCIES_SUFFIX}.dll DESTINATION bin)
14 15 else()
15 16 set(OpenCV_LIB_DIR "/usr/local/lib")
16 17 install(FILES ${OpenCV_LIB_DIR}/lib${lib}.${OpenCV_VERSION_MAJOR}.${OpenCV_VERSION_MINOR}.${OpenCV_VERSION_PATCH}${CMAKE_SHARED_LIBRARY_SUFFIX} DESTINATION lib)
... ...