Commit f95bb8b78dcdb0fba746cb3fcfc48b8a8c56ac67

Authored by Brendan Klare
2 parents 7c90515d e4a62a38

Merge branch 'RegionConsolidate'

A new transform, ConsolidateRegions, is added. This transform should
be used after BuildScales and/or SlidingWindow to take potentially
overlapping detections, and consolidate them into a single detection.
This is a very numerically correct approach, in that a Laplacian matrix
is built from overlapping rectangles, and spectral clustering is then
performed to find the clusters/groupings of detected regions. However,
it is perhaps not the most computationally efficient solution, especially
when the number of detected regions is large (e.g., over 200). This is b/c
the eigendecomposition step will be a bit of a bottleneck.

Future solutions could move in one of (at least) two directions. The first is
the use of numerical analysis methods to efficiently find the smallest eigenvalue/vectors.
This is not readily support by Eigen.

The second would be more logic-based approaches. Presumably the method used by opencv
to consolidate regions would be applicable here.

In the meantime this solutions very much works, and should be used until a more
effecient method is made available.
openbr/core/eigenutils.cpp 0 → 100644
  1 +#include "eigenutils.h"
  2 +#include <openbr/openbr_plugin.h>
  3 +
  4 +using namespace Eigen;
  5 +using namespace cv;
  6 +
  7 +//Helper function to quickly write eigen matrix to disk. Not efficient.
  8 +void writeEigen(MatrixXf X, QString filename) {
  9 + Mat m(X.rows(),X.cols(),CV_32FC1);
  10 + for (int i = 0; i < X.rows(); i++) {
  11 + for (int j = 0; j < X.cols(); j++) {
  12 + m.at<float>(i,j) = X(i,j);
  13 + }
  14 + }
  15 + QScopedPointer<br::Format> format(br::Factory<br::Format>::make(filename));
  16 + format->write(br::Template(m));
  17 +}
  18 +
  19 +
... ...
openbr/core/eigenutils.h
... ... @@ -14,49 +14,51 @@
14 14 * limitations under the License. *
15 15 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16 16  
17   -#ifndef EIGENUTILS_H
18   -#define EIGENUTILS_H
19   -
20   -#include <QDataStream>
21   -#include <Eigen/Core>
22   -#include <assert.h>
23   -
24   -template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols>
25   -inline QDataStream &operator<<(QDataStream &stream, const Eigen::Matrix< _Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols > &mat)
26   -{
27   - int r = mat.rows();
28   - int c = mat.cols();
29   - stream << r << c;
30   -
31   - _Scalar *data = new _Scalar[r*c];
32   - for (int i=0; i<r; i++)
33   - for (int j=0; j<c; j++)
34   - data[i*c+j] = mat(i, j);
35   - int bytes = r*c*sizeof(_Scalar);
36   - int bytes_written = stream.writeRawData((const char*)data, bytes);
37   - if (bytes != bytes_written) qFatal("EigenUtils.h operator<< failure.");
38   -
39   - delete[] data;
40   - return stream;
41   -}
42   -
43   -template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols>
44   -inline QDataStream &operator>>(QDataStream &stream, Eigen::Matrix< _Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols > &mat)
45   -{
46   - int r, c;
47   - stream >> r >> c;
48   - mat.resize(r, c);
49   -
50   - _Scalar *data = new _Scalar[r*c];
51   - int bytes = r*c*sizeof(_Scalar);
52   - int bytes_read = stream.readRawData((char*)data, bytes);
53   - if (bytes != bytes_read) qFatal("EigenUtils.h operator>> failure.");
54   - for (int i=0; i<r; i++)
55   - for (int j=0; j<c; j++)
56   - mat(i, j) = data[i*c+j];
57   -
58   - delete[] data;
59   - return stream;
60   -}
61   -
62   -#endif // EIGENUTILS_H
  17 +#ifndef EIGENUTILS_H
  18 +#define EIGENUTILS_H
  19 +
  20 +#include <QDataStream>
  21 +#include <Eigen/Core>
  22 +#include <assert.h>
  23 +
  24 +void writeEigen(Eigen::MatrixXf X, QString filename);
  25 +
  26 +template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols>
  27 +inline QDataStream &operator<<(QDataStream &stream, const Eigen::Matrix< _Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols > &mat)
  28 +{
  29 + int r = mat.rows();
  30 + int c = mat.cols();
  31 + stream << r << c;
  32 +
  33 + _Scalar *data = new _Scalar[r*c];
  34 + for (int i=0; i<r; i++)
  35 + for (int j=0; j<c; j++)
  36 + data[i*c+j] = mat(i, j);
  37 + int bytes = r*c*sizeof(_Scalar);
  38 + int bytes_written = stream.writeRawData((const char*)data, bytes);
  39 + if (bytes != bytes_written) qFatal("EigenUtils.h operator<< failure.");
  40 +
  41 + delete[] data;
  42 + return stream;
  43 +}
  44 +
  45 +template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols>
  46 +inline QDataStream &operator>>(QDataStream &stream, Eigen::Matrix< _Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols > &mat)
  47 +{
  48 + int r, c;
  49 + stream >> r >> c;
  50 + mat.resize(r, c);
  51 +
  52 + _Scalar *data = new _Scalar[r*c];
  53 + int bytes = r*c*sizeof(_Scalar);
  54 + int bytes_read = stream.readRawData((char*)data, bytes);
  55 + if (bytes != bytes_read) qFatal("EigenUtils.h operator>> failure.");
  56 + for (int i=0; i<r; i++)
  57 + for (int j=0; j<c; j++)
  58 + mat(i, j) = data[i*c+j];
  59 +
  60 + delete[] data;
  61 + return stream;
  62 +}
  63 +
  64 +#endif // EIGENUTILS_H
... ...
openbr/plugins/slidingwindow.cpp
... ... @@ -5,8 +5,10 @@
5 5 #include <opencv2/objdetect/objdetect.hpp>
6 6 #include <opencv2/imgproc/imgproc.hpp>
7 7 #include <opencv2/highgui/highgui.hpp>
  8 +#include <Eigen/Dense>
8 9  
9 10 using namespace cv;
  11 +using namespace Eigen;
10 12  
11 13 namespace br
12 14 {
... ... @@ -130,6 +132,7 @@ public:
130 132 BuildScalesTransform() : Transform(false, true) {}
131 133 private:
132 134 int windowHeight;
  135 + float aspectRatio;
133 136  
134 137 void train(const TemplateList &_data)
135 138 {
... ... @@ -232,8 +235,6 @@ private:
232 235 return;
233 236 }
234 237 }
235   -
236   - float aspectRatio;
237 238 };
238 239  
239 240 BR_REGISTER(Transform, BuildScalesTransform)
... ... @@ -268,6 +269,130 @@ class HOGDetectTransform : public UntrainableTransform
268 269  
269 270 BR_REGISTER(Transform, HOGDetectTransform)
270 271  
  272 +/*!
  273 + * \ingroup transforms
  274 + * \brief Consolidate redundant/overlapping detections.
  275 + * \author Brendan Klare \cite bklare
  276 + */
  277 +class ConsolidateDetectionsTransform : public Transform
  278 +{
  279 + Q_OBJECT
  280 +
  281 +public:
  282 + ConsolidateDetectionsTransform() : Transform(false, false) {}
  283 +private:
  284 +
  285 + void project(const Template &src, Template &dst) const
  286 + {
  287 + dst = src;
  288 + if (!dst.file.contains("Confidences"))
  289 + return;
  290 +
  291 + //Compute overlap between rectangles and create discrete Laplacian matrix
  292 + QList<Rect> rects = OpenCVUtils::toRects(src.file.rects());
  293 + int n = rects.size();
  294 + MatrixXf laplace(n,n);
  295 + for (int i = 0; i < n; i++) {
  296 + laplace(i,i) = 0;
  297 + }
  298 + for (int i = 0; i < n; i++){
  299 + for (int j = i + 1; j < n; j++) {
  300 + float overlap = (float)((rects[i] & rects[j]).area()) / (float)max(rects[i].area(), rects[j].area());
  301 + if (overlap > 0.5) {
  302 + laplace(i,j) = -1.0;
  303 + laplace(j,i) = -1.0;
  304 + laplace(i,i) = laplace(i,i) + 1.0;
  305 + laplace(j,j) = laplace(j,j) + 1.0;
  306 + } else {
  307 + laplace(i,j) = 0;
  308 + laplace(j,i) = 0;
  309 + }
  310 + }
  311 + }
  312 +
  313 + // Compute eigendecomposition
  314 + SelfAdjointEigenSolver<Eigen::MatrixXf> eSolver(laplace);
  315 + MatrixXf allEVals = eSolver.eigenvalues();
  316 + MatrixXf allEVecs = eSolver.eigenvectors();
  317 +
  318 + //Keep eigenvectors with zero eigenvalues
  319 + int nRegions = 0;
  320 + for (int i = 0; i < n; i++) {
  321 + if (fabs(allEVals(i)) < 1e-4) {
  322 + nRegions++;
  323 + }
  324 + }
  325 + MatrixXf regionVecs(n, nRegions);
  326 + for (int i = 0, cnt = 0; i < n; i++) {
  327 + if (fabs(allEVals(i)) < 1e-4)
  328 + regionVecs.col(cnt++) = allEVecs.col(i);
  329 + }
  330 +
  331 + //Determine membership for each consolidated location
  332 + // and compute average of regions. This is determined by
  333 + // finding which eigenvector has the highest magnitude for
  334 + // each input dimension. Each input dimension corresponds to
  335 + // one of the input rect region. Thus, each eigenvector represents
  336 + // a set of overlaping regions.
  337 + float midX[nRegions];
  338 + float midY[nRegions];
  339 + float avgWidth[nRegions];
  340 + float avgHeight[nRegions];
  341 + float confs[nRegions];
  342 + int cnts[nRegions];
  343 + int mx;
  344 + int mxIdx;
  345 + for (int i = 0 ; i < nRegions; i++) {
  346 + midX[i] = 0;
  347 + midY[i] = 0;
  348 + avgWidth[i] = 0;
  349 + avgHeight[i] = 0;
  350 + confs[i] = 0;
  351 + cnts[i] = 0;
  352 + }
  353 +
  354 + QList<float> confidences = dst.file.getList<float>("Confidences");
  355 + for (int i = 0; i < n; i++) {
  356 + mx = 0.0;
  357 + mxIdx = -1;
  358 +
  359 + for (int j = 0; j < nRegions; j++) {
  360 + if (fabs(regionVecs(i,j)) > mx) {
  361 + mx = fabs(regionVecs(i,j));
  362 + mxIdx = j;
  363 + }
  364 + }
  365 +
  366 + Rect curRect = rects[i];
  367 + midX[mxIdx] += ((float)curRect.x + (float)curRect.width / 2.0);
  368 + midY[mxIdx] += ((float)curRect.y + (float)curRect.height / 2.0);
  369 + avgWidth[mxIdx] += (float) curRect.width;
  370 + avgHeight[mxIdx] += (float) curRect.height;
  371 + confs[mxIdx] += confidences[i];
  372 + cnts[mxIdx]++;
  373 + }
  374 +
  375 + QList<Rect> consolidatedRects;
  376 + QList<float> consolidatedConfidences;
  377 + for (int i = 0; i < nRegions; i++) {
  378 + float cntF = (float) cnts[i];
  379 + if (cntF > 0) {
  380 + int x = qRound((midX[i] / cntF) - (avgWidth[i] / cntF) / 2.0);
  381 + int y = qRound((midY[i] / cntF) - (avgHeight[i] / cntF) / 2.0);
  382 + int w = qRound(avgWidth[i] / cntF);
  383 + int h = qRound(avgHeight[i] / cntF);
  384 + consolidatedRects.append(Rect(x,y,w,h));
  385 + consolidatedConfidences.append(confs[i] / cntF);
  386 + }
  387 + }
  388 +
  389 + dst.file.setRects(consolidatedRects);
  390 + dst.file.setList<float>("Confidences", consolidatedConfidences);
  391 + }
  392 +};
  393 +
  394 +BR_REGISTER(Transform, ConsolidateDetectionsTransform)
  395 +
271 396 } // namespace br
272 397  
273 398 #include "slidingwindow.moc"
... ...