--- /dev/null
+/*M///////////////////////////////////////////////////////////////////////////////////////
+//
+// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
+//
+// By downloading, copying, installing or using the software you agree to this license.
+// If you do not agree to this license, do not download, install,
+// copy or use the software.
+//
+//
+// License Agreement
+// For Open Source Computer Vision Library
+//
+// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
+// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
+// Third party copyrights are property of their respective owners.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// * Redistribution's of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistribution's in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// * The name of the copyright holders may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// This software is provided by the copyright holders and contributors "as is" and
+// any express or implied warranties, including, but not limited to, the implied
+// warranties of merchantability and fitness for a particular purpose are disclaimed.
+// In no event shall the Intel Corporation or contributors be liable for any direct,
+// indirect, incidental, special, exemplary, or consequential damages
+// (including, but not limited to, procurement of substitute goods or services;
+// loss of use, data, or profits; or business interruption) however caused
+// and on any theory of liability, whether in contract, strict liability,
+// or tort (including negligence or otherwise) arising in any way out of
+// the use of this software, even if advised of the possibility of such damage.
+//
+//M*/
+
+#include "_cvaux.h"
+
+namespace cv
+{
+
+/*
+ The code below implements keypoint detector, fern-based point classifier and a planar object detector.
+
+ References:
+ 1. Mustafa Özuysal, Michael Calonder, Vincent Lepetit, Pascal Fua,
+ "Fast KeyPoint Recognition Using Random Ferns,"
+ IEEE Transactions on Pattern Analysis and Machine Intelligence, 15 Jan. 2009.
+
+ 2. Vincent Lepetit, Pascal Fua,
+ “Towards Recognizing Feature Points Using Classification Trees,”
+ Technical Report IC/2004/74, EPFL, 2004.
+*/
+
+const int progressBarSize = 50;
+
+//////////////////////////// Patch Generator //////////////////////////////////
+
+static const double DEFAULT_BACKGROUND_MIN = 0;
+static const double DEFAULT_BACKGROUND_MAX = 256;
+static const double DEFAULT_NOISE_RANGE = 5;
+static const double DEFAULT_LAMBDA_MIN = 0.6;
+static const double DEFAULT_LAMBDA_MAX = 1.5;
+static const double DEFAULT_THETA_MIN = -CV_PI;
+static const double DEFAULT_THETA_MAX = CV_PI;
+static const double DEFAULT_PHI_MIN = -CV_PI;
+static const double DEFAULT_PHI_MAX = CV_PI;
+
+PatchGenerator::PatchGenerator()
+: backgroundMin(DEFAULT_BACKGROUND_MIN), backgroundMax(DEFAULT_BACKGROUND_MAX),
+noiseRange(DEFAULT_NOISE_RANGE), randomBlur(true), lambdaMin(DEFAULT_LAMBDA_MIN),
+lambdaMax(DEFAULT_LAMBDA_MAX), thetaMin(DEFAULT_THETA_MIN),
+thetaMax(DEFAULT_THETA_MAX), phiMin(DEFAULT_PHI_MIN),
+phiMax(DEFAULT_PHI_MAX)
+{
+}
+
+
+PatchGenerator::PatchGenerator(double _backgroundMin, double _backgroundMax,
+ double _noiseRange, bool _randomBlur,
+ double _lambdaMin, double _lambdaMax,
+ double _thetaMin, double _thetaMax,
+ double _phiMin, double _phiMax )
+: backgroundMin(_backgroundMin), backgroundMax(_backgroundMax),
+noiseRange(_noiseRange), randomBlur(_randomBlur),
+lambdaMin(_lambdaMin), lambdaMax(_lambdaMax),
+thetaMin(_thetaMin), thetaMax(_thetaMax),
+phiMin(_phiMin), phiMax(_phiMax)
+{
+}
+
+
+void PatchGenerator::generateRandomTransform(Point2f srcCenter, Point2f dstCenter,
+ Mat& transform, RNG& rng, bool inverse) const
+{
+ double lambda1 = rng.uniform(lambdaMin, lambdaMax);
+ double lambda2 = rng.uniform(lambdaMin, lambdaMax);
+ double theta = rng.uniform(thetaMin, thetaMax);
+ double phi = rng.uniform(phiMin, phiMax);
+
+ // Calculate random parameterized affine transformation A,
+ // A = T(patch center) * R(theta) * R(phi)' *
+ // S(lambda1, lambda2) * R(phi) * T(-pt)
+ double st = sin(theta);
+ double ct = cos(theta);
+ double sp = sin(phi);
+ double cp = cos(phi);
+ double c2p = cp*cp;
+ double s2p = sp*sp;
+
+ double A = lambda1*c2p + lambda2*s2p;
+ double B = (lambda2 - lambda1)*sp*cp;
+ double C = lambda1*s2p + lambda2*c2p;
+
+ double Ax_plus_By = A*srcCenter.x + B*srcCenter.y;
+ double Bx_plus_Cy = B*srcCenter.x + C*srcCenter.y;
+
+ transform.create(2, 3, CV_64F);
+ Mat_<double>& T = (Mat_<double>&)transform;
+ T(0,0) = A*ct - B*st;
+ T(0,1) = B*ct - C*st;
+ T(0,2) = -ct*Ax_plus_By + st*Bx_plus_Cy + dstCenter.x;
+ T(1,0) = A*st + B*ct;
+ T(1,1) = B*st + C*ct;
+ T(1,2) = -st*Ax_plus_By - ct*Bx_plus_Cy + dstCenter.y;
+
+ if( inverse )
+ invertAffineTransform(T, T);
+}
+
+
+void PatchGenerator::operator ()(const Mat& image, Point2f pt, Mat& patch, Size patchSize, RNG& rng) const
+{
+ double buffer[6];
+ Mat_<double> T(2, 3, buffer);
+
+ generateRandomTransform(pt, Point2f((patchSize.width-1)*0.5f, (patchSize.height-1)*0.5f), T, rng);
+ (*this)(image, T, patch, patchSize, rng);
+}
+
+
+void PatchGenerator::operator ()(const Mat& image, const Mat& T,
+ Mat& patch, Size patchSize, RNG& rng) const
+{
+ patch.create( patchSize, image.type() );
+ if( backgroundMin != backgroundMax )
+ {
+ rng.fill(patch, RNG::UNIFORM, Scalar::all(backgroundMin), Scalar::all(backgroundMax));
+ warpAffine(image, patch, T, patchSize, INTER_LINEAR, BORDER_TRANSPARENT);
+ }
+ else
+ warpAffine(image, patch, T, patchSize, INTER_LINEAR, BORDER_CONSTANT, Scalar::all(backgroundMin));
+
+ int ksize = randomBlur ? (unsigned)rng % 9 - 5 : 0;
+ if( ksize > 0 )
+ {
+ ksize = ksize*2 + 1;
+ GaussianBlur(patch, patch, Size(ksize, ksize), 0, 0);
+ }
+
+ if( noiseRange > 0 )
+ {
+ AutoBuffer<uchar> _noiseBuf( patchSize.width*patchSize.height*image.elemSize() );
+ Mat noise(patchSize, image.type(), (uchar*)_noiseBuf);
+ int delta = image.depth() == CV_8U ? 128 : image.depth() == CV_16U ? 32768 : 0;
+ rng.fill(noise, RNG::NORMAL, Scalar::all(delta), Scalar::all(noiseRange));
+ if( backgroundMin != backgroundMax )
+ addWeighted(patch, 1, noise, 1, -delta, patch);
+ else
+ {
+ for( int i = 0; i < patchSize.height; i++ )
+ {
+ uchar* prow = patch.ptr<uchar>(i);
+ const uchar* nrow = noise.ptr<uchar>(i);
+ for( int j = 0; j < patchSize.width; j++ )
+ if( prow[j] != backgroundMin )
+ prow[j] = saturate_cast<uchar>(prow[j] + nrow[j] - delta);
+ }
+ }
+ }
+}
+
+void PatchGenerator::warpWholeImage(const Mat& image, Mat& _T, Mat& buf,
+ Mat& warped, int border, RNG& rng) const
+{
+ Mat_<double> T = _T;
+ Rect roi(INT_MAX, INT_MAX, INT_MIN, INT_MIN);
+
+ for( int k = 0; k < 4; k++ )
+ {
+ Point2f pt0, pt1;
+ pt0.x = (float)(k == 0 || k == 3 ? 0 : image.cols);
+ pt0.y = (float)(k < 2 ? 0 : image.rows);
+ pt1.x = (float)(T(0,0)*pt0.x + T(0,1)*pt0.y + T(0,2));
+ pt1.y = (float)(T(1,0)*pt0.x + T(1,1)*pt0.y + T(1,2));
+
+ roi.x = std::min(roi.x, cvFloor(pt1.x));
+ roi.y = std::min(roi.y, cvFloor(pt1.y));
+ roi.width = std::max(roi.width, cvCeil(pt1.x));
+ roi.height = std::max(roi.height, cvCeil(pt1.y));
+ }
+
+ roi.width -= roi.x - 1;
+ roi.height -= roi.y - 1;
+ int dx = border - roi.x;
+ int dy = border - roi.y;
+
+ if( (roi.width+border*2)*(roi.height+border*2) > buf.cols )
+ buf.create(1, (roi.width+border*2)*(roi.height+border*2), image.type());
+
+ warped = Mat(roi.height + border*2, roi.width + border*2,
+ image.type(), buf.data);
+
+ T(0,2) += dx;
+ T(1,2) += dy;
+ (*this)(image, T, warped, warped.size(), rng);
+
+ if( T.data != _T.data )
+ T.convertTo(_T, _T.type());
+}
+
+
+/////////////////////////////////////// LDetector //////////////////////////////////////////////
+
+LDetector::LDetector() : radius(7), threshold(20), nOctaves(3), nViews(1000),
+ verbose(false), baseFeatureSize(32), clusteringDistance(2)
+{
+}
+
+LDetector::LDetector(int _radius, int _threshold, int _nOctaves, int _nViews,
+ double _baseFeatureSize, double _clusteringDistance)
+: radius(_radius), threshold(_threshold), nOctaves(_nOctaves), nViews(_nViews),
+ verbose(false), baseFeatureSize(_baseFeatureSize), clusteringDistance(_clusteringDistance)
+{
+}
+
+static void getDiscreteCircle(int R, vector<Point>& circle, vector<int>& filledHCircle)
+{
+ int x = R, y = 0;
+ for( ;;y++ )
+ {
+ x = cvRound(std::sqrt((double)R*R - y*y));
+ if( x < y )
+ break;
+ circle.push_back(Point(x,y));
+ if( x == y )
+ break;
+ }
+
+ int i, n8 = (int)circle.size() - (x == y), n8_ = n8 - (x != y), n4 = n8 + n8_, n = n4*4;
+ CV_Assert(n8 > 0);
+ circle.resize(n);
+
+ for( i = 0; i < n8; i++ )
+ {
+ Point p = circle[i];
+ circle[i+n4] = Point(-p.y, p.x);
+ circle[i+n4*2] = Point(-p.x, -p.y);
+ circle[i+n4*3] = Point(p.y, -p.x);
+ }
+
+ for( i = n8; i < n4; i++ )
+ {
+ Point p = circle[n4 - i], q = Point(p.y, p.x);
+ circle[i] = q;
+ circle[i+n4] = Point(-q.y, q.x);
+ circle[i+n4*2] = Point(-q.x, -q.y);
+ circle[i+n4*3] = Point(q.y, -q.x);
+ }
+
+ // the filled upper half of the circle is encoded as sequence of integers,
+ // i-th element is the coordinate of right-most circle point in each horizontal line y=i.
+ // the left-most point will be -filledHCircle[i].
+ for( i = 0, y = -1; i < n4; i++ )
+ {
+ Point p = circle[i];
+ if( p.y != y )
+ {
+ filledHCircle.push_back(p.x);
+ y = p.y;
+ if( y == R )
+ break;
+ }
+ }
+}
+
+
+struct CmpKeypointScores
+{
+ bool operator ()(const KeyPoint& a, const KeyPoint& b) const { return std::abs(a.response) > std::abs(b.response); }
+};
+
+
+void LDetector::getMostStable2D(const Mat& image, vector<KeyPoint>& keypoints,
+ int maxPoints, const PatchGenerator& _patchGenerator) const
+{
+ PatchGenerator patchGenerator = _patchGenerator;
+ patchGenerator.backgroundMin = patchGenerator.backgroundMax = 128;
+
+ Mat warpbuf, warped;
+ Mat _M(2, 3, CV_64F), _iM(2, 3, CV_64F);
+ double *M = (double*)_M.data, *iM = (double*)_iM.data;
+ RNG& rng = theRNG();
+ int i, k;
+ vector<KeyPoint> tempKeypoints;
+ double d2 = clusteringDistance*clusteringDistance;
+ keypoints.clear();
+
+ // TODO: this loop can be run in parallel, for that we need
+ // a separate accumulator keypoint lists for different threads.
+ for( i = 0; i < nViews; i++ )
+ {
+ // 1. generate random transform
+ // 2. map the source image corners and compute the ROI in canvas
+ // 3. select the ROI in canvas, adjust the transformation matrix
+ // 4. apply the transformation
+ // 5. run keypoint detector in pyramids
+ // 6. map each point back and update the lists of most stable points
+
+ if(verbose && (i+1)*progressBarSize/nViews != i*progressBarSize/nViews)
+ putchar('.');
+
+ if( i > 0 )
+ patchGenerator.generateRandomTransform(Point2f(), Point2f(), _M, rng);
+ else
+ {
+ // identity transformation
+ M[0] = M[4] = 1;
+ M[1] = M[3] = M[2] = M[5] = 0;
+ }
+
+ patchGenerator.warpWholeImage(image, _M, warpbuf, warped, cvCeil(baseFeatureSize*0.5+radius), rng);
+ (*this)(warped, tempKeypoints, maxPoints*3);
+ invertAffineTransform(_M, _iM);
+
+ int j, sz0 = (int)tempKeypoints.size(), sz1;
+ for( j = 0; j < sz0; j++ )
+ {
+ KeyPoint kpt1 = tempKeypoints[j];
+ KeyPoint kpt0((float)(iM[0]*kpt1.pt.x + iM[1]*kpt1.pt.y + iM[2]),
+ (float)(iM[3]*kpt1.pt.x + iM[4]*kpt1.pt.y + iM[5]),
+ kpt1.size, -1.f, 1.f, kpt1.octave);
+ float r = kpt1.size*0.5f;
+ if( kpt0.pt.x < r || kpt0.pt.x >= image.cols - r ||
+ kpt0.pt.y < r || kpt0.pt.y >= image.rows - r )
+ continue;
+
+ sz1 = (int)keypoints.size();
+ for( k = 0; k < sz1; k++ )
+ {
+ KeyPoint kpt = keypoints[k];
+ if( kpt.octave != kpt0.octave )
+ continue;
+ double dx = kpt.pt.x - kpt0.pt.x, dy = kpt.pt.y - kpt0.pt.y;
+ if( dx*dx + dy*dy <= d2*(1 << kpt.octave*2) )
+ {
+ keypoints[k] = KeyPoint((kpt.pt.x*kpt.response + kpt0.pt.x)/(kpt.response+1),
+ (kpt.pt.y*kpt.response + kpt0.pt.y)/(kpt.response+1),
+ kpt.size, -1.f, kpt.response + 1, kpt.octave);
+ break;
+ }
+ }
+ if( k == sz1 )
+ keypoints.push_back(kpt0);
+ }
+ }
+
+ if( verbose )
+ putchar('\n');
+
+ if( (int)keypoints.size() > maxPoints )
+ {
+ sort(keypoints, CmpKeypointScores());
+ keypoints.resize(maxPoints);
+ }
+}
+
+
+static inline int computeLResponse(const uchar* ptr, const int* cdata, int csize)
+{
+ int i, csize2 = csize/2, sum = -ptr[0]*csize;
+ for( i = 0; i < csize2; i++ )
+ {
+ int ofs = cdata[i];
+ sum += ptr[ofs] + ptr[-ofs];
+ }
+ return sum;
+}
+
+
+static Point2f adjustCorner(const float* fval, float& fvaln)
+{
+ double bx = (fval[3] - fval[5])*0.5;
+ double by = (fval[2] - fval[7])*0.5;
+ double Axx = fval[3] - fval[4]*2 + fval[5];
+ double Axy = (fval[0] - fval[2] - fval[6] + fval[8])*0.25;
+ double Ayy = fval[1] - fval[4]*2 + fval[7];
+ double D = Axx*Ayy - Axy*Axy;
+ D = D != 0 ? 1./D : 0;
+ double dx = (bx*Ayy - by*Axy)*D;
+ double dy = (by*Axx - bx*Axy)*D;
+ dx = std::min(std::max(dx, -1.), 1.);
+ dy = std::min(std::max(dy, -1.), 1.);
+ fvaln = (float)(fval[4] + (bx*dx + by*dy)*0.5);
+ if(fvaln*fval[4] < 0 || std::abs(fvaln) < std::abs(fval[4]))
+ fvaln = fval[4];
+
+ return Point2f((float)dx, (float)dy);
+}
+
+void LDetector::operator()(const Mat& image, vector<KeyPoint>& keypoints, int maxCount, bool scaleCoords) const
+{
+ vector<Mat> pyr;
+ buildPyramid(image, pyr, std::max(nOctaves-1, 0));
+ (*this)(pyr, keypoints, maxCount, scaleCoords);
+}
+
+void LDetector::operator()(const vector<Mat>& pyr, vector<KeyPoint>& keypoints, int maxCount, bool scaleCoords) const
+{
+ const int lthreshold = 3;
+ int L, x, y, i, j, k, tau = lthreshold;
+ Mat scoreBuf(pyr[0].size(), CV_16S), maskBuf(pyr[0].size(), CV_8U);
+ int scoreElSize = scoreBuf.elemSize();
+ vector<Point> circle0;
+ vector<int> fhcircle0, circle, fcircle_s, fcircle;
+ getDiscreteCircle(radius, circle0, fhcircle0);
+ CV_Assert(fhcircle0.size() == (size_t)(radius+1) && circle0.size() % 2 == 0);
+ keypoints.clear();
+
+ for( L = 0; L < nOctaves; L++ )
+ {
+ // Pyramidal keypoint detector body:
+ // 1. build next pyramid layer
+ // 2. scan points, check the circular neighborhood, compute the score
+ // 3. do non-maxima suppression
+ // 4. adjust the corners (sub-pix)
+ double cscale = scaleCoords ? 1 << L : 1;
+ Size layerSize = pyr[L].size();
+ if( layerSize.width < radius*2 + 3 || layerSize.height < radius*2 + 3 )
+ break;
+ Mat scoreLayer(layerSize, scoreBuf.type(), scoreBuf.data);
+ Mat maskLayer(layerSize, maskBuf.type(), maskBuf.data);
+ const Mat& pyrLayer = pyr[L];
+ int sstep = scoreLayer.step/sizeof(short);
+ int mstep = maskLayer.step;
+
+ int csize = (int)circle0.size(), csize2 = csize/2;
+ circle.resize(csize*3);
+ for( i = 0; i < csize; i++ )
+ circle[i] = circle[i+csize] = circle[i+csize*2] = (-circle0[i].y)*pyrLayer.step + circle0[i].x;
+ fcircle.clear();
+ fcircle_s.clear();
+ for( i = -radius; i <= radius; i++ )
+ {
+ x = fhcircle0[std::abs(i)];
+ for( j = -x; j <= x; j++ )
+ {
+ fcircle_s.push_back(i*sstep + j);
+ fcircle.push_back(i*pyrLayer.step + j);
+ }
+ }
+ int nsize = (int)fcircle.size();
+ const int* cdata = &circle[0];
+ const int* ndata = &fcircle[0];
+ const int* ndata_s = &fcircle_s[0];
+
+ for( y = 0; y < radius; y++ )
+ {
+ memset( scoreLayer.ptr<short>(y), 0, layerSize.width*scoreElSize );
+ memset( scoreLayer.ptr<short>(layerSize.height-y-1), 0, layerSize.width*scoreElSize );
+ memset( maskLayer.ptr<uchar>(y), 0, layerSize.width );
+ memset( maskLayer.ptr<uchar>(layerSize.height-y-1), 0, layerSize.width );
+ }
+
+ int vradius = radius*pyrLayer.step;
+
+ for( y = radius; y < layerSize.height - radius; y++ )
+ {
+ const uchar* img = pyrLayer.ptr<uchar>(y) + radius;
+ short* scores = scoreLayer.ptr<short>(y);
+ uchar* mask = maskLayer.ptr<uchar>(y);
+
+ for( x = 0; x < radius; x++ )
+ {
+ scores[x] = scores[layerSize.width - 1 - x] = 0;
+ mask[x] = mask[layerSize.width - 1 - x] = 0;
+ }
+
+ for( x = radius; x < layerSize.width - radius; x++, img++ )
+ {
+ int val0 = *img;
+ if( (std::abs(val0 - img[radius]) < tau && std::abs(val0 - img[-radius]) < tau) ||
+ (std::abs(val0 - img[vradius]) < tau && std::abs(val0 - img[-vradius]) < tau))
+ {
+ scores[x] = 0;
+ mask[x] = 0;
+ continue;
+ }
+
+ for( k = 0; k < csize; k++ )
+ {
+ if( std::abs(val0 - img[cdata[k]]) < tau &&
+ (std::abs(val0 - img[cdata[k + csize2]]) < tau ||
+ std::abs(val0 - img[cdata[k + csize2 - 1]]) < tau ||
+ std::abs(val0 - img[cdata[k + csize2 + 1]]) < tau ||
+ std::abs(val0 - img[cdata[k + csize2 - 2]]) < tau ||
+ std::abs(val0 - img[cdata[k + csize2 + 2]]) < tau/* ||
+ std::abs(val0 - img[cdata[k + csize2 - 3]]) < tau ||
+ std::abs(val0 - img[cdata[k + csize2 + 3]]) < tau*/) )
+ break;
+ }
+
+ if( k < csize )
+ {
+ scores[x] = 0;
+ mask[x] = 0;
+ }
+ else
+ {
+ scores[x] = (short)computeLResponse(img, cdata, csize);
+ mask[x] = 1;
+ }
+ }
+ }
+
+ for( y = radius+1; y < layerSize.height - radius-1; y++ )
+ {
+ const uchar* img = pyrLayer.ptr<uchar>(y) + radius+1;
+ short* scores = scoreLayer.ptr<short>(y) + radius+1;
+ const uchar* mask = maskLayer.ptr<uchar>(y) + radius+1;
+
+ for( x = radius+1; x < layerSize.width - radius-1; x++, img++, scores++, mask++ )
+ {
+ int val0 = *scores;
+ if( !*mask || std::abs(val0) < lthreshold ||
+ (mask[-1] + mask[1] + mask[-mstep-1] + mask[-mstep] + mask[-mstep+1]+
+ mask[mstep-1] + mask[mstep] + mask[mstep+1] < 3))
+ continue;
+ bool recomputeZeroScores = radius*2 < y && y < layerSize.height - radius*2 &&
+ radius*2 < x && x < layerSize.width - radius*2;
+
+ if( val0 > 0 )
+ {
+ for( k = 0; k < nsize; k++ )
+ {
+ int val = scores[ndata_s[k]];
+ if( val == 0 && recomputeZeroScores )
+ scores[ndata_s[k]] = (short)(val =
+ computeLResponse(img + ndata[k], cdata, csize));
+ if( val0 < val )
+ break;
+ }
+ }
+ else
+ {
+ for( k = 0; k < nsize; k++ )
+ {
+ int val = scores[ndata_s[k]];
+ if( val == 0 && recomputeZeroScores )
+ scores[ndata_s[k]] = (short)(val =
+ computeLResponse(img + ndata[k], cdata, csize));
+ if( val0 > val )
+ break;
+ }
+ }
+ if( k < nsize )
+ continue;
+ float fval[9], fvaln = 0;
+ for( int i1 = -1; i1 <= 1; i1++ )
+ for( int j1 = -1; j1 <= 1; j1++ )
+ {
+ fval[(i1+1)*3 + j1 + 1] = (float)(scores[sstep*i1+j1] ? scores[sstep*i1+j1] :
+ computeLResponse(img + pyrLayer.step*i1 + j1, cdata, csize));
+ }
+ Point2f pt = adjustCorner(fval, fvaln);
+ pt.x += x;
+ pt.y += y;
+ keypoints.push_back(KeyPoint((float)(pt.x*cscale), (float)(pt.y*cscale),
+ (float)(baseFeatureSize*cscale), -1, fvaln, L));
+ }
+ }
+ }
+
+ if( maxCount > 0 && keypoints.size() > (size_t)maxCount )
+ {
+ sort(keypoints, CmpKeypointScores());
+ keypoints.resize(maxCount);
+ }
+}
+
+void LDetector::read(const FileNode& objnode)
+{
+ radius = (int)objnode["radius"];
+ threshold = (int)objnode["threshold"];
+ nOctaves = (int)objnode["noctaves"];
+ nViews = (int)objnode["nviews"];
+ baseFeatureSize = (int)objnode["base-feature-size"];
+ clusteringDistance = (int)objnode["clustering-distance"];
+}
+
+void LDetector::write(FileStorage& fs, const String& name) const
+{
+ WriteStructContext ws(fs, name, CV_NODE_MAP);
+
+ fs << "radius" << radius
+ << "threshold" << threshold
+ << "noctaves" << nOctaves
+ << "nviews" << nViews
+ << "base-feature-size" << baseFeatureSize
+ << "clustering-distance" << clusteringDistance;
+}
+
+void LDetector::setVerbose(bool _verbose)
+{
+ verbose = _verbose;
+}
+
+/////////////////////////////////////// FernClassifier ////////////////////////////////////////////
+
+FernClassifier::FernClassifier()
+{
+ verbose = false;
+ clear();
+}
+
+
+FernClassifier::FernClassifier(const FileNode& node)
+{
+ verbose = false;
+ clear();
+ read(node);
+}
+
+FernClassifier::~FernClassifier()
+{
+}
+
+
+int FernClassifier::getClassCount() const
+{
+ return nclasses;
+}
+
+
+int FernClassifier::getStructCount() const
+{
+ return nstructs;
+}
+
+
+int FernClassifier::getStructSize() const
+{
+ return structSize;
+}
+
+
+int FernClassifier::getSignatureSize() const
+{
+ return signatureSize;
+}
+
+
+int FernClassifier::getCompressionMethod() const
+{
+ return compressionMethod;
+}
+
+
+Size FernClassifier::getPatchSize() const
+{
+ return patchSize;
+}
+
+
+FernClassifier::FernClassifier(const vector<Point2f>& points,
+ const vector<Ptr<Mat> >& refimgs,
+ const vector<int>& labels,
+ int _nclasses, int _patchSize,
+ int _signatureSize, int _nstructs,
+ int _structSize, int _nviews, int _compressionMethod,
+ const PatchGenerator& patchGenerator)
+{
+ verbose = false;
+ clear();
+ train(points, refimgs, labels, _nclasses, _patchSize,
+ _signatureSize, _nstructs, _structSize, _nviews,
+ _compressionMethod, patchGenerator);
+}
+
+
+void FernClassifier::write(FileStorage& fs, const String& objname) const
+{
+ WriteStructContext ws(fs, objname, CV_NODE_MAP);
+
+ cv::write(fs, "nstructs", nstructs);
+ cv::write(fs, "struct-size", structSize);
+ cv::write(fs, "nclasses", nclasses);
+ cv::write(fs, "signature-size", signatureSize);
+ cv::write(fs, "compression-method", compressionMethod);
+ cv::write(fs, "patch-size", patchSize.width);
+ {
+ WriteStructContext wsf(fs, "features", CV_NODE_SEQ + CV_NODE_FLOW);
+ int i, nfeatures = (int)features.size();
+ for( i = 0; i < nfeatures; i++ )
+ {
+ cv::write(fs, features[i].y1*patchSize.width + features[i].x1);
+ cv::write(fs, features[i].y2*patchSize.width + features[i].x2);
+ }
+ }
+ {
+ WriteStructContext wsp(fs, "posteriors", CV_NODE_SEQ + CV_NODE_FLOW);
+ cv::write(fs, posteriors);
+ }
+}
+
+
+void FernClassifier::read(const FileNode& objnode)
+{
+ clear();
+
+ nstructs = (int)objnode["nstructs"];
+ structSize = (int)objnode["struct-size"];
+ nclasses = (int)objnode["nclasses"];
+ signatureSize = (int)objnode["signature-size"];
+ compressionMethod = (int)objnode["compression-method"];
+ patchSize.width = patchSize.height = (int)objnode["patch-size"];
+ leavesPerStruct = 1 << structSize;
+
+ FileNode _nodes = objnode["features"];
+ int i, nfeatures = structSize*nstructs;
+ features.resize(nfeatures);
+ FileNodeIterator it = _nodes.begin(), it_end = _nodes.end();
+ for( i = 0; i < nfeatures && it != it_end; i++ )
+ {
+ int ofs1, ofs2;
+ it >> ofs1 >> ofs2;
+ features[i] = Feature(ofs1%patchSize.width, ofs1/patchSize.width,
+ ofs2%patchSize.width, ofs2/patchSize.width);
+ }
+
+ FileNode _posteriors = objnode["posteriors"];
+ int psz = leavesPerStruct*nstructs*signatureSize;
+ posteriors.reserve(psz);
+ _posteriors >> posteriors;
+}
+
+
+void FernClassifier::clear()
+{
+ signatureSize = nclasses = nstructs = structSize = compressionMethod = leavesPerStruct = 0;
+ vector<Feature>().swap(features);
+ vector<float>().swap(posteriors);
+}
+
+
+int FernClassifier::getLeaf(int fern, const Mat& _patch) const
+{
+ assert( 0 <= fern && fern < nstructs );
+ size_t fofs = fern*structSize, idx = 0;
+ const Mat_<uchar>& patch = (const Mat_<uchar>&)_patch;
+
+ for( int i = 0; i < structSize; i++ )
+ {
+ const Feature& f = features[fofs + i];
+ idx = (idx << 1) + f(patch);
+ }
+
+ return fern*leavesPerStruct + idx;
+}
+
+
+void FernClassifier::prepare(int _nclasses, int _patchSize, int _signatureSize,
+ int _nstructs, int _structSize,
+ int _nviews, int _compressionMethod)
+{
+ clear();
+
+ CV_Assert( _nclasses > 1 && _patchSize >= 5 && _nstructs > 0 &&
+ _nviews > 0 && _structSize > 0 &&
+ (_compressionMethod == COMPRESSION_NONE ||
+ _compressionMethod == COMPRESSION_RANDOM_PROJ ||
+ _compressionMethod == COMPRESSION_PCA) );
+
+ nclasses = _nclasses;
+ patchSize = Size(_patchSize, _patchSize);
+ nstructs = _nstructs;
+ structSize = _structSize;
+ signatureSize = std::min(_signatureSize, nclasses);
+ compressionMethod = signatureSize == nclasses ? COMPRESSION_NONE : _compressionMethod;
+
+ leavesPerStruct = 1 << structSize;
+
+ int i, nfeatures = structSize*nstructs;
+
+ features = vector<Feature>( nfeatures );
+ posteriors = vector<float>( leavesPerStruct*nstructs*nclasses, 1.f );
+ classCounters = vector<int>( nclasses, leavesPerStruct );
+
+ CV_Assert( patchSize.width <= 256 && patchSize.height <= 256 );
+ RNG& rng = theRNG();
+
+ for( i = 0; i < nfeatures; i++ )
+ {
+ int x1 = (unsigned)rng % patchSize.width;
+ int y1 = (unsigned)rng % patchSize.height;
+ int x2 = (unsigned)rng % patchSize.width;
+ int y2 = (unsigned)rng % patchSize.height;
+ features[i] = Feature(x1, y1, x2, y2);
+ }
+}
+
+
+void FernClassifier::train(const vector<Point2f>& points,
+ const vector<Ptr<Mat> >& refimgs,
+ const vector<int>& labels,
+ int _nclasses, int _patchSize,
+ int _signatureSize, int _nstructs,
+ int _structSize, int _nviews, int _compressionMethod,
+ const PatchGenerator& patchGenerator)
+{
+ _nclasses = _nclasses > 0 ? _nclasses : (int)points.size();
+ CV_Assert( labels.empty() || labels.size() == points.size() );
+
+ prepare(_nclasses, _patchSize, _signatureSize, _nstructs,
+ _structSize, _nviews, _compressionMethod);
+
+ // pass all the views of all the samples through the generated trees and accumulate
+ // the statistics (posterior probabilities) in leaves.
+ Mat patch;
+ int i, j, nsamples = (int)points.size();
+ RNG& rng = theRNG();
+
+ for( i = 0; i < nsamples; i++ )
+ {
+ Point2f pt = points[i];
+ const Mat& src = *refimgs[i];
+ int classId = labels.empty() ? i : labels[i];
+ if( verbose && (i+1)*progressBarSize/nsamples != i*progressBarSize/nsamples )
+ putchar('.');
+ CV_Assert( 0 <= classId && classId < nclasses );
+ classCounters[classId] += _nviews;
+ for( j = 0; j < _nviews; j++ )
+ {
+ patchGenerator(src, pt, patch, patchSize, rng);
+ for( int f = 0; f < nstructs; f++ )
+ posteriors[getLeaf(f, patch)*nclasses + classId]++;
+ }
+ }
+ if( verbose )
+ putchar('\n');
+
+ finalize(rng);
+}
+
+
+void FernClassifier::trainFromSingleView(const Mat& image,
+ const vector<KeyPoint>& keypoints,
+ int _patchSize, int _signatureSize,
+ int _nstructs, int _structSize,
+ int _nviews, int _compressionMethod,
+ const PatchGenerator& patchGenerator)
+{
+ prepare((int)keypoints.size(), _patchSize, _signatureSize, _nstructs,
+ _structSize, _nviews, _compressionMethod);
+ int i, j, k, nsamples = (int)keypoints.size(), maxOctave = 0;
+ for( i = 0; i < nsamples; i++ )
+ {
+ classCounters[i] = _nviews;
+ maxOctave = std::max(maxOctave, keypoints[i].octave);
+ }
+
+ double maxScale = patchGenerator.lambdaMax*2;
+ Mat canvas(cvRound(std::max(image.cols,image.rows)*maxScale + patchSize.width*2 + 10),
+ cvRound(std::max(image.cols,image.rows)*maxScale + patchSize.width*2 + 10), image.type());
+ Mat noisebuf;
+ vector<Mat> pyrbuf(maxOctave+1), pyr(maxOctave+1);
+ Point2f center0((image.cols-1)*0.5f, (image.rows-1)*0.5f),
+ center1((canvas.cols - 1)*0.5f, (canvas.rows - 1)*0.5f);
+ Mat _M(2, 3, CV_64F);
+ double *M = (double*)_M.data;
+ RNG& rng = theRNG();
+
+ Mat patch(patchSize, CV_8U);
+
+ for( i = 0; i < _nviews; i++ )
+ {
+ patchGenerator.generateRandomTransform(center0, center1, _M, rng);
+
+ CV_Assert(_M.type() == CV_64F);
+ Rect roi(INT_MAX, INT_MAX, INT_MIN, INT_MIN);
+
+ for( k = 0; k < 4; k++ )
+ {
+ Point2f pt0, pt1;
+ pt0.x = (float)(k == 0 || k == 3 ? 0 : image.cols);
+ pt0.y = (float)(k < 2 ? 0 : image.rows);
+ pt1.x = (float)(M[0]*pt0.x + M[1]*pt0.y + M[2]);
+ pt1.y = (float)(M[3]*pt0.x + M[4]*pt0.y + M[5]);
+
+ roi.x = std::min(roi.x, cvFloor(pt1.x));
+ roi.y = std::min(roi.y, cvFloor(pt1.y));
+ roi.width = std::max(roi.width, cvCeil(pt1.x));
+ roi.height = std::max(roi.height, cvCeil(pt1.y));
+ }
+
+ roi.width -= roi.x + 1;
+ roi.height -= roi.y + 1;
+
+ Mat canvas_roi(canvas, roi);
+ M[2] -= roi.x;
+ M[5] -= roi.y;
+
+ Size size = canvas_roi.size();
+ rng.fill(canvas_roi, RNG::UNIFORM, Scalar::all(0), Scalar::all(256));
+ warpAffine( image, canvas_roi, _M, size, INTER_LINEAR, BORDER_TRANSPARENT);
+
+ pyr[0] = canvas_roi;
+ for( j = 1; j <= maxOctave; j++ )
+ {
+ size = Size((size.width+1)/2, (size.height+1)/2);
+ if( pyrbuf[j].cols < size.width*size.height )
+ pyrbuf[j].create(1, size.width*size.height, image.type());
+ pyr[j] = Mat(size, image.type(), pyrbuf[j].data);
+ pyrDown(pyr[j-1], pyr[j]);
+ }
+
+ if( patchGenerator.noiseRange > 0 )
+ {
+ const int noiseDelta = 128;
+ if( noisebuf.cols < pyr[0].cols*pyr[0].rows )
+ noisebuf.create(1, pyr[0].cols*pyr[0].rows, image.type());
+ for( j = 0; j <= maxOctave; j++ )
+ {
+ Mat noise(pyr[j].size(), image.type(), noisebuf.data);
+ rng.fill(noise, RNG::UNIFORM, Scalar::all(-patchGenerator.noiseRange + noiseDelta),
+ Scalar::all(patchGenerator.noiseRange + noiseDelta));
+ addWeighted(pyr[j], 1, noise, 1, -noiseDelta, pyr[j]);
+ }
+ }
+
+ for( j = 0; j < nsamples; j++ )
+ {
+ KeyPoint kpt = keypoints[j];
+ float scale = 1.f/(1 << kpt.octave);
+ Point2f pt((float)((M[0]*kpt.pt.x + M[1]*kpt.pt.y + M[2])*scale),
+ (float)((M[3]*kpt.pt.x + M[4]*kpt.pt.y + M[5])*scale));
+ getRectSubPix(pyr[kpt.octave], patchSize, pt, patch, patch.type());
+ for( int f = 0; f < nstructs; f++ )
+ posteriors[getLeaf(f, patch)*nclasses + j]++;
+ }
+
+ if( verbose && (i+1)*progressBarSize/_nviews != i*progressBarSize/_nviews )
+ putchar('.');
+ }
+ if( verbose )
+ putchar('\n');
+
+ finalize(rng);
+}
+
+
+int FernClassifier::operator()(const Mat& img, Point2f pt, vector<float>& signature) const
+{
+ Mat patch;
+ getRectSubPix(img, patchSize, pt, patch, img.type());
+ return (*this)(patch, signature);
+}
+
+
+int FernClassifier::operator()(const Mat& patch, vector<float>& signature) const
+{
+ if( posteriors.empty() )
+ CV_Error(CV_StsNullPtr,
+ "The descriptor has not been trained or "
+ "the floating-point posteriors have been deleted");
+ CV_Assert(patch.size() == patchSize);
+
+ int i, j, sz = signatureSize;
+ signature.resize(sz);
+ float* s = &signature[0];
+
+ for( j = 0; j < sz; j++ )
+ s[j] = 0;
+
+ for( i = 0; i < nstructs; i++ )
+ {
+ int lf = getLeaf(i, patch);
+ const float* ldata = &posteriors[lf*signatureSize];
+ for( j = 0; j <= sz - 4; j += 4 )
+ {
+ float t0 = s[j] + ldata[j];
+ float t1 = s[j+1] + ldata[j+1];
+ s[j] = t0; s[j+1] = t1;
+ t0 = s[j+2] + ldata[j+2];
+ t1 = s[j+3] + ldata[j+3];
+ s[j+2] = t0; s[j+3] = t1;
+ }
+ for( ; j < sz; j++ )
+ s[j] += ldata[j];
+ }
+
+ j = 0;
+ if( signatureSize == nclasses && compressionMethod == COMPRESSION_NONE )
+ {
+ for( i = 1; i < nclasses; i++ )
+ if( s[j] < s[i] )
+ j = i;
+ }
+ return j;
+}
+
+
+void FernClassifier::finalize(RNG&)
+{
+ int i, j, k, n = nclasses;
+ vector<double> invClassCounters(n);
+ Mat_<double> _temp(1, n);
+ double* temp = &_temp(0,0);
+
+ for( i = 0; i < n; i++ )
+ invClassCounters[i] = 1./classCounters[i];
+
+ for( i = 0; i < nstructs; i++ )
+ {
+ for( j = 0; j < leavesPerStruct; j++ )
+ {
+ float* P = &posteriors[(i*leavesPerStruct + j)*nclasses];
+ double sum = 0;
+ for( k = 0; k < n; k++ )
+ sum += P[k]*invClassCounters[k];
+ sum = 1./sum;
+ for( k = 0; k < n; k++ )
+ temp[k] = P[k]*invClassCounters[k]*sum;
+ log(_temp, _temp);
+ for( k = 0; k < n; k++ )
+ P[k] = (float)temp[k];
+ }
+ }
+
+#if 0
+ // do the first pass over the data.
+ if( compressionMethod == COMPRESSION_RANDOM_PROJ )
+ {
+ // In case of random projection
+ // we generate a random m x n matrix consisting of -1's and 1's
+ // (called Bernoulli matrix) and multiply it by each vector
+ // of posterior probabilities.
+ // the product is stored back into the same input vector.
+
+ Mat_<uchar> csmatrix;
+ if( m < n )
+ {
+ // generate random Bernoulli matrix:
+ // -1's are replaced with 0's and 1's stay 1's.
+ csmatrix.create(m, n);
+ rng.fill(csmatrix, RNG::UNIFORM, Scalar::all(0), Scalar::all(2));
+ }
+ vector<float> dst(m);
+
+ for( i = 0; i < totalLeaves; i++ )
+ {
+ int S = sampleCounters[i];
+ if( S == 0 )
+ continue;
+
+ float scale = 1.f/(S*(m < n ? std::sqrt((float)m) : 1.f));
+ const int* leaf = (const int*)&posteriors[i*n];
+ float* out_leaf = (float*)&posteriors[i*m];
+
+ for( j = 0; j < m; j++ )
+ {
+ float val = 0;
+ if( m < n )
+ {
+ const uchar* csrow = csmatrix.ptr(j);
+ // Because -1's in the Bernoulli matrix are encoded as 0's,
+ // the real dot product value will be
+ // A - B, where A is the sum of leaf[j]'s for which csrow[j]==1 and
+ // B is the sum of leaf[j]'s for which csrow[j]==0.
+ // since A + B = S, then A - B = A - (S - A) = 2*A - S.
+ int A = 0;
+ for( k = 0; k < n; k++ )
+ A += leaf[k] & -(int)csrow[k];
+ val = (A*2 - S)*scale;
+ }
+ else
+ val = leaf[j]*scale;
+ dst[j] = val;
+ }
+
+ // put the vector back (since it's shorter than the original, we can do it in-place)
+ for( j = 0; j < m; j++ )
+ out_leaf[j] = dst[j];
+ }
+ }
+ else if( compressionMethod == COMPRESSION_PCA )
+ {
+ // In case of PCA we do 3 passes over the data:
+ // first, we compute the mean vector
+ // second, we compute the covariation matrix
+ // then we do eigen decomposition of the matrix and construct the PCA
+ // projection matrix
+ // and on the third pass we actually do PCA compression.
+
+ int nonEmptyLeaves = 0;
+ Mat_<double> _mean(1, n), _vec(1, n), _dvec(m, 1),
+ _cov(n, n), _evals(n, 1), _evects(n, n);
+ _mean = 0.;
+ double* mean = &_mean(0,0);
+ double* vec = &_vec(0,0);
+ double* dvec = &_dvec(0,0);
+
+ for( i = 0; i < totalLeaves; i++ )
+ {
+ int S = sampleCounters[i];
+ if( S == 0 )
+ continue;
+ float invS = 1.f/S;
+ const int* leaf = (const int*)&posteriors[0] + i*n;
+ float* out_leaf = (float*)&posteriors[0] + i*n;
+
+ for( j = 0; j < n; j++ )
+ {
+ float t = leaf[j]*invS;
+ out_leaf[j] = t;
+ mean[j] += t;
+ }
+ nonEmptyLeaves++;
+ }
+
+ CV_Assert( nonEmptyLeaves >= ntrees );
+ _mean *= 1./nonEmptyLeaves;
+
+ for( i = 0; i < totalLeaves; i++ )
+ {
+ int S = sampleCounters[i];
+ if( S == 0 )
+ continue;
+ const float* leaf = (const float*)&posteriors[0] + i*n;
+ for( j = 0; j < n; j++ )
+ vec[j] = leaf[j] - mean[j];
+ gemm(_vec, _vec, 1, _cov, 1, _cov, GEMM_1_T);
+ }
+
+ _cov *= 1./nonEmptyLeaves;
+ eigen(_cov, _evals, _evects);
+ // use the first m eigenvectors (stored as rows of the eigenvector matrix)
+ // as the projection matrix in PCA
+ _evects = _evects(Range(0, m), Range::all());
+
+ for( i = 0; i < totalLeaves; i++ )
+ {
+ int S = sampleCounters[i];
+ if( S == 0 )
+ continue;
+ const float* leaf = (const float*)&posteriors[0] + i*n;
+ float* out_leaf = (float*)&posteriors[0] + i*m;
+
+ for( j = 0; j < n; j++ )
+ vec[j] = leaf[j] - mean[j];
+ gemm(_evects, _vec, 1, Mat(), 0, _dvec, GEMM_2_T);
+
+ for( j = 0; j < m; j++ )
+ out_leaf[j] = (float)dvec[j];
+ }
+ }
+ else
+ CV_Error( CV_StsBadArg,
+ "Unknown compression method; use COMPRESSION_RANDOM_PROJ or COMPRESSION_PCA" );
+
+ // and shrink the vector
+ posteriors.resize(totalLeaves*m);
+#endif
+}
+
+void FernClassifier::setVerbose(bool _verbose)
+{
+ verbose = _verbose;
+}
+
+////////////////////////////////////// Planar Object Detector ////////////////////////////////////
+
+PlanarObjectDetector::PlanarObjectDetector()
+{
+}
+
+PlanarObjectDetector::PlanarObjectDetector(const FileNode& node)
+{
+ read(node);
+}
+
+PlanarObjectDetector::PlanarObjectDetector(const vector<Mat>& pyr, int npoints,
+ int patchSize, int nstructs, int structSize,
+ int nviews, const LDetector& detector,
+ const PatchGenerator& patchGenerator)
+{
+ train(pyr, npoints, patchSize, nstructs,
+ structSize, nviews, detector, patchGenerator);
+}
+
+PlanarObjectDetector::~PlanarObjectDetector()
+{
+}
+
+vector<KeyPoint> PlanarObjectDetector::getModelPoints() const
+{
+ return modelPoints;
+}
+
+void PlanarObjectDetector::train(const vector<Mat>& pyr, int npoints,
+ int patchSize, int nstructs, int structSize,
+ int nviews, const LDetector& detector,
+ const PatchGenerator& patchGenerator)
+{
+ modelROI = Rect(0, 0, pyr[0].cols, pyr[0].rows);
+ ldetector = detector;
+ ldetector.setVerbose(verbose);
+ ldetector.getMostStable2D(pyr[0], modelPoints, npoints, patchGenerator);
+
+ npoints = modelPoints.size();
+ fernClassifier.setVerbose(verbose);
+ fernClassifier.trainFromSingleView(pyr[0], modelPoints,
+ patchSize, (int)modelPoints.size(), nstructs, structSize, nviews,
+ FernClassifier::COMPRESSION_NONE, patchGenerator);
+}
+
+void PlanarObjectDetector::train(const vector<Mat>& pyr, const vector<KeyPoint>& keypoints,
+ int patchSize, int nstructs, int structSize,
+ int nviews, const LDetector& detector,
+ const PatchGenerator& patchGenerator)
+{
+ modelROI = Rect(0, 0, pyr[0].cols, pyr[0].rows);
+ ldetector = detector;
+ ldetector.setVerbose(verbose);
+ modelPoints.resize(keypoints.size());
+ std::copy(keypoints.begin(), keypoints.end(), modelPoints.begin());
+
+ fernClassifier.setVerbose(verbose);
+ fernClassifier.trainFromSingleView(pyr[0], modelPoints,
+ patchSize, (int)modelPoints.size(), nstructs, structSize, nviews,
+ FernClassifier::COMPRESSION_NONE, patchGenerator);
+}
+
+void PlanarObjectDetector::read(const FileNode& node)
+{
+ FileNodeIterator it = node["model-roi"].begin(), it_end;
+ it >> modelROI.x >> modelROI.y >> modelROI.width >> modelROI.height;
+ ldetector.read(node["detector"]);
+ fernClassifier.read(node["fern-classifier"]);
+ cv::read(node["model-points"], modelPoints);
+ CV_Assert(modelPoints.size() == (size_t)fernClassifier.getClassCount());
+}
+
+
+void PlanarObjectDetector::write(FileStorage& fs, const String& objname) const
+{
+ WriteStructContext ws(fs, objname, CV_NODE_MAP);
+
+ {
+ WriteStructContext wsroi(fs, "model-roi", CV_NODE_SEQ + CV_NODE_FLOW);
+ cv::write(fs, modelROI.x);
+ cv::write(fs, modelROI.y);
+ cv::write(fs, modelROI.width);
+ cv::write(fs, modelROI.height);
+ }
+ ldetector.write(fs, "detector");
+ cv::write(fs, "model-points", modelPoints);
+ fernClassifier.write(fs, "fern-classifier");
+}
+
+
+bool PlanarObjectDetector::operator()(const Mat& image, Mat& H, vector<Point2f>& corners) const
+{
+ vector<Mat> pyr;
+ buildPyramid(image, pyr, ldetector.nOctaves - 1);
+ vector<KeyPoint> keypoints;
+ ldetector(pyr, keypoints);
+
+ return (*this)(pyr, keypoints, H, corners);
+}
+
+bool PlanarObjectDetector::operator()(const vector<Mat>& pyr, const vector<KeyPoint>& keypoints,
+ Mat& _H, vector<Point2f>& corners, vector<int>* pairs) const
+{
+ int i, j, m = (int)modelPoints.size(), n = (int)keypoints.size();
+ vector<int> bestMatches(m, -1);
+ vector<float> maxLogProb(m, -FLT_MAX);
+ vector<float> signature;
+ vector<Point2f> fromPt, toPt;
+
+ for( i = 0; i < n; i++ )
+ {
+ KeyPoint kpt = keypoints[i];
+ CV_Assert(0 <= kpt.octave && kpt.octave < (int)pyr.size());
+ kpt.pt.x /= (float)(1 << kpt.octave);
+ kpt.pt.y /= (float)(1 << kpt.octave);
+ int k = fernClassifier(pyr[kpt.octave], kpt.pt, signature);
+ if( k >= 0 && (bestMatches[k] < 0 || signature[k] > maxLogProb[k]) )
+ {
+ maxLogProb[k] = signature[k];
+ bestMatches[k] = i;
+ }
+ }
+
+ if(pairs)
+ pairs->resize(0);
+
+ for( i = 0; i < m; i++ )
+ if( bestMatches[i] >= 0 )
+ {
+ fromPt.push_back(modelPoints[i].pt);
+ toPt.push_back(keypoints[bestMatches[i]].pt);
+ }
+
+ if( fromPt.size() < 4 )
+ return false;
+
+ vector<uchar> mask;
+ _H = findHomography(fromPt, toPt, mask, RANSAC, 10);
+ if( _H.data )
+ {
+ const Mat_<double>& H = _H;
+ corners.resize(4);
+ for( i = 0; i < 4; i++ )
+ {
+ Point2f pt((float)(modelROI.x + (i == 0 || i == 3 ? 0 : modelROI.width)),
+ (float)(modelROI.y + (i <= 1 ? 0 : modelROI.height)));
+ double w = 1./(H(2,0)*pt.x + H(2,1)*pt.y + H(2,2));
+ corners[i] = Point2f((float)((H(0,0)*pt.x + H(0,1)*pt.y + H(0,2))*w),
+ (float)((H(1,0)*pt.x + H(1,1)*pt.y + H(1,2))*w));
+ }
+ }
+
+ if( pairs )
+ {
+ for( i = j = 0; i < m; i++ )
+ if( bestMatches[i] >= 0 && mask[j++] )
+ {
+ pairs->push_back(i);
+ pairs->push_back(bestMatches[i]);
+ }
+ }
+
+ return _H.data != 0;
+}
+
+
+void PlanarObjectDetector::setVerbose(bool _verbose)
+{
+ verbose = _verbose;
+}
+
+}