From: Maria Dimashova Date: Mon, 26 Jul 2010 08:58:46 +0000 (+0000) Subject: modified FernClassifier::train(); remove old RTreeClassifier and added new implementa... X-Git-Tag: accepted/tizen/6.0/unified/20201030.111113~8899 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=b5a71db742a2754901c9bd8eb8ffe9cbad4067c6;p=platform%2Fupstream%2Fopencv.git modified FernClassifier::train(); remove old RTreeClassifier and added new implementation CalonderClassifier; removed old find_obj_calonder and added new one --- diff --git a/modules/features2d/include/opencv2/features2d/features2d.hpp b/modules/features2d/include/opencv2/features2d/features2d.hpp index 108ee6d..5c0174a 100644 --- a/modules/features2d/include/opencv2/features2d/features2d.hpp +++ b/modules/features2d/include/opencv2/features2d/features2d.hpp @@ -179,6 +179,17 @@ CVAPI(CvSeq*) cvGetStarKeypoints( const CvArr* img, CvMemStorage* storage, namespace cv { + struct CV_EXPORTS DefaultRngAuto + { + const static uint64 def_state = (uint64)-1; + const uint64 old_state; + + DefaultRngAuto() : old_state(theRNG().state) { theRNG().state = def_state; } + ~DefaultRngAuto() { theRNG().state = old_state; } + + DefaultRngAuto& operator=(const DefaultRngAuto&); + }; + // CvAffinePose: defines a parameterized affine transformation of an image patch. // An image patch is rotated on angle phi (in degrees), then scaled lambda1 times @@ -395,10 +406,7 @@ public: CV_EXPORTS void FAST( const Mat& image, vector& keypoints, int threshold, bool nonmaxSupression=true ); /*! - The Patch Generator class - - - + The Patch Generator class */ class CV_EXPORTS PatchGenerator { @@ -459,9 +467,9 @@ class CV_EXPORTS FernClassifier public: FernClassifier(); FernClassifier(const FileNode& node); - FernClassifier(const vector& points, - const vector >& refimgs, - const vector& labels=vector(), + FernClassifier(const vector >& points, + const vector& refimgs, + const vector >& labels=vector >(), int _nclasses=0, int _patchSize=PATCH_SIZE, int _signatureSize=DEFAULT_SIGNATURE_SIZE, int _nstructs=DEFAULT_STRUCTS, @@ -481,9 +489,9 @@ public: int _nviews=DEFAULT_VIEWS, int _compressionMethod=COMPRESSION_NONE, const PatchGenerator& patchGenerator=PatchGenerator()); - virtual void train(const vector& points, - const vector >& refimgs, - const vector& labels=vector(), + virtual void train(const vector >& points, + const vector& refimgs, + const vector >& labels=vector >(), int _nclasses=0, int _patchSize=PATCH_SIZE, int _signatureSize=DEFAULT_SIGNATURE_SIZE, int _nstructs=DEFAULT_STRUCTS, @@ -594,270 +602,127 @@ protected: FernClassifier fernClassifier; }; - - /****************************************************************************************\ -* Calonder Descriptor * +* Calonder Classifier * \****************************************************************************************/ - -struct CV_EXPORTS DefaultRngAuto -{ - const static uint64 def_state = (uint64)-1; - const uint64 old_state; - - DefaultRngAuto() : old_state(theRNG().state) { theRNG().state = def_state; } - ~DefaultRngAuto() { theRNG().state = old_state; } - - DefaultRngAuto& operator=(const DefaultRngAuto&); -}; - -/* -A pseudo-random number generator usable with std::random_shuffle. -*/ -typedef cv::RNG CalonderRng; -typedef unsigned int int_type; - -//---------------------------- -//randomized_tree.h - -//class RTTester; - -//namespace features { -static const size_t DEFAULT_REDUCED_NUM_DIM = 176; -static const float LOWER_QUANT_PERC = .03f; -static const float UPPER_QUANT_PERC = .92f; -static const int PATCH_SIZE = 32; -static const int DEFAULT_DEPTH = 9; -static const int DEFAULT_VIEWS = 5000; -struct RTreeNode; - -struct BaseKeypoint +class CV_EXPORTS CalonderClassifier { - int x; - int y; - IplImage* image; - - BaseKeypoint() - : x(0), y(0), image(NULL) - {} - - BaseKeypoint(int x, int y, IplImage* image) - : x(x), y(y), image(image) - {} -}; - -class CSMatrixGenerator { public: - typedef enum { PDT_GAUSS=1, PDT_BERNOULLI, PDT_DBFRIENDLY } PHI_DISTR_TYPE; - ~CSMatrixGenerator(); - static float* getCSMatrix(int m, int n, PHI_DISTR_TYPE dt); // do NOT free returned pointer - -private: - static float *cs_phi_; // matrix for compressive sensing - static int cs_phi_m_, cs_phi_n_; -}; - - -template< typename T > -struct AlignedMemBlock -{ - AlignedMemBlock() : raw(NULL), data(NULL) { }; - - // Alloc's an `a` bytes-aligned block good to hold `sz` elements of class T - AlignedMemBlock(const int n, const int a) - { - alloc(n, a); - } - - ~AlignedMemBlock() - { - free(raw); - } - - void alloc(const int n, const int a) - { - uchar* raw = (uchar*)malloc(n*sizeof(T) + a); - int delta = (a - uint64(raw)%a)%a; // # bytes required for padding s.t. we get `a`-aligned - data = reinterpret_cast(raw + delta); - } - - // Methods to access the aligned data. NEVER EVER FREE A RETURNED POINTER! - inline T* p() { return data; } - inline T* operator()() { return data; } - -private: - T *raw; // raw block, probably not aligned - T *data; // exposed data, aligned, DO NOT FREE -}; - -typedef AlignedMemBlock FloatSignature; -typedef AlignedMemBlock Signature; - -class CV_EXPORTS RandomizedTree -{ -public: - friend class RTreeClassifier; - //friend class ::RTTester; + CalonderClassifier(); + CalonderClassifier( const vector >& points, const vector& refimgs, + const vector >& labels=vector >(), int _numClasses=0, + int _pathSize=DEFAULT_PATCH_SIZE, + int _numTrees=DEFAULT_NUM_TREES, + int _treeDepth=DEFAULT_TREE_DEPTH, + int _numViews=DEFAULT_NUM_VIEWS, + int _compressedDim=DEFAULT_COMPRESSED_DIM, + int _compressType=DEFAULT_COMPRESS_TYPE, + int _numQuantBits=DEFAULT_NUM_QUANT_BITS, + const PatchGenerator& patchGenerator=PatchGenerator() ); + + virtual ~CalonderClassifier(); + virtual void clear(); - RandomizedTree(); - ~RandomizedTree(); + void train( const vector >& points, const vector& refimgs, + const vector >& labels=vector >(), int _nclasses=0, + int _pathSize=DEFAULT_PATCH_SIZE, + int _numTrees=DEFAULT_NUM_TREES, + int _treeDepth=DEFAULT_TREE_DEPTH, + int _numViews=DEFAULT_NUM_VIEWS, + int _compressedDim=DEFAULT_COMPRESSED_DIM, + int _compressType=DEFAULT_COMPRESS_TYPE, + int _numQuantBits=DEFAULT_NUM_QUANT_BITS, + const PatchGenerator& patchGenerator=PatchGenerator() ); - void train(std::vector const& base_set, cv::RNG &rng, - int depth, int views, size_t reduced_num_dim, int num_quant_bits); + virtual void operator()(const Mat& img, Point2f pt, vector& signature, float thresh=0.f) const; + virtual void operator()(const Mat& patch, vector& signature, float thresh=-1.f) const; +#define QUANTIZATION_AVAILABLE 1 +#if QUANTIZATION_AVAILABLE + void quantizePosteriors( int _numQuantBits, bool isClearFloatPosteriors=false ); + void clearFloatPosteriors(); + virtual void operator()(const Mat& img, Point2f pt, vector& signature, uchar thresh=-1.f) const; + virtual void operator()(const Mat& patch, vector& signature, uchar thresh=-1.f) const; +#endif - void train(std::vector const& base_set, cv::RNG &rng, - PatchGenerator &make_patch, int depth, int views, size_t reduced_num_dim, - int num_quant_bits); + void read( const FileNode& fn ); + void write( FileStorage& fs ) const; - // following two funcs are EXPERIMENTAL (do not use unless you know exactly what you do) - static void quantizeVector(float *vec, int dim, int N, float bnds[2], int clamp_mode=0); - static void quantizeVector(float *src, int dim, int N, float bnds[2], uchar *dst); + bool empty() const; - // patch_data must be a 32x32 array (no row padding) - float* getPosterior(uchar* patch_data); - const float* getPosterior(uchar* patch_data) const; - uchar* getPosterior2(uchar* patch_data); + void setVerbose( bool _verbose ); - void read(const char* file_name, int num_quant_bits); - void read(std::istream &is, int num_quant_bits); - void write(const char* file_name) const; - void write(std::ostream &os) const; + int getPatchSize() const; + int getNumTrees() const; + int getTreeDepth() const; + int getNumViews() const; + int getSignatureSize() const; + int getCompressType() const; + int getNumQuantBits() const; + int getOrigNumClasses() const; - inline int classes() { return classes_; } - inline int depth() { return depth_; } - inline void applyQuantization(int num_quant_bits) { makePosteriors2(num_quant_bits); } + enum + { + COMPRESS_NONE = -1, + COMPRESS_DISTR_GAUSS = 0, + COMPRESS_DISTR_BERNOULLI = 1, + COMPRESS_DISTR_DBFRIENDLY = 2, + }; - // debug - void savePosteriors(std::string url, bool append=false); - void savePosteriors2(std::string url, bool append=false); + static float GET_LOWER_QUANT_PERC() { return .03f; } + static float GET_UPPER_QUANT_PERC() { return .92f; } + enum + { + MAX_NUM_QUANT_BITS = 8, + DEFAULT_PATCH_SIZE = 32, + DEFAULT_NUM_TREES = 48, + DEFAULT_TREE_DEPTH = 9, + DEFAULT_NUM_VIEWS = 500, + DEFAULT_COMPRESSED_DIM = 176, + DEFAULT_COMPRESS_TYPE = COMPRESS_DISTR_BERNOULLI, + DEFAULT_NUM_QUANT_BITS = -1, + }; private: - int classes_; - int depth_; - int num_leaves_; - std::vector nodes_; - //float **posteriors_; // 16-bytes aligned posteriors - //uchar **posteriors2_; // 16-bytes aligned posteriors - FloatSignature *posteriors_; - Signature *posteriors2_; - std::vector leaf_counts_; - - void createNodes(int num_nodes, cv::RNG &rng); - void allocPosteriorsAligned(int num_leaves, int num_classes); - void freePosteriors(int which); // which: 1=posteriors_, 2=posteriors2_, 3=both - void init(int classes, int depth, cv::RNG &rng); - void addExample(int class_id, uchar* patch_data); - void finalize(size_t reduced_num_dim, int num_quant_bits); - int getIndex(uchar* patch_data) const; - inline float* getPosteriorByIndex(int index); - inline uchar* getPosteriorByIndex2(int index); - inline const float* getPosteriorByIndex(int index) const; - //void makeRandomMeasMatrix(float *cs_phi, PHI_DISTR_TYPE dt, size_t reduced_num_dim); - void convertPosteriorsToChar(); - void makePosteriors2(int num_quant_bits); - void compressLeaves(size_t reduced_num_dim); - void estimateQuantPercForPosteriors(float perc[2]); -}; + void prepare( int _patchSize, int _signatureSize, int _numTrees, int _treeDepth, int _numViews ); -struct RTreeNode -{ - short offset1, offset2; - - RTreeNode() {} - - RTreeNode(uchar x1, uchar y1, uchar x2, uchar y2) - : offset1(y1*PATCH_SIZE + x1), - offset2(y2*PATCH_SIZE + x2) - {} - - //! Left child on 0, right child on 1 - inline bool operator() (uchar* patch_data) const - { - return patch_data[offset1] > patch_data[offset2]; - } -}; + int getLeafIdx( int treeIdx, const Mat& patch ) const; + void finalize( int _compressedDim, int _compressType, int _numQuantBits, + const vector& leafSampleCounters); + void compressLeaves( int _compressedDim, int _compressType ); + bool verbose; -//} // namespace features -//---------------------------- -//rtree_classifier.h -//class RTTester; + int patchSize; + int signatureSize; + int numTrees; + int treeDepth; + int numViews; -//namespace features { + int origNumClasses; + int compressType; + int numQuantBits; -class CV_EXPORTS RTreeClassifier -{ -public: - //friend class ::RTTester; - static const int DEFAULT_TREES = 80; - static const size_t DEFAULT_NUM_QUANT_BITS = 4; - - //static const int SIG_LEN = 176; - - RTreeClassifier(); - - //modified - void train(std::vector const& base_set, - cv::RNG &rng, - int num_trees = RTreeClassifier::DEFAULT_TREES, - int depth = DEFAULT_DEPTH, - int views = DEFAULT_VIEWS, - size_t reduced_num_dim = DEFAULT_REDUCED_NUM_DIM, - int num_quant_bits = DEFAULT_NUM_QUANT_BITS, - bool print_status = true); - - void train(std::vector const& base_set, - cv::RNG &rng, - PatchGenerator &make_patch, - int num_trees = DEFAULT_TREES, - int depth = DEFAULT_DEPTH, - int views = DEFAULT_VIEWS, - size_t reduced_num_dim = DEFAULT_REDUCED_NUM_DIM, - int num_quant_bits = DEFAULT_NUM_QUANT_BITS, - bool print_status = true); - - // sig must point to a memory block of at least classes()*sizeof(float|uchar) bytes - void getSignature(IplImage *patch, uchar *sig); - void getSignature(IplImage *patch, float *sig); - void getSparseSignature(IplImage *patch, float *sig, float thresh); - // TODO: deprecated in favor of getSignature overload, remove - void getFloatSignature(IplImage *patch, float *sig) { getSignature(patch, sig); } - - static int countNonZeroElements(float *vec, int n, double tol=1e-10); - static inline void safeSignatureAlloc(uchar **sig, int num_sig=1, int sig_len=176); - static inline uchar* safeSignatureAlloc(int num_sig=1, int sig_len=176); - - inline int classes() const { return classes_; } - inline int original_num_classes() { return original_num_classes_; } - - void setQuantization(int num_quant_bits); - void discardFloatPosteriors(); - - void read(const char* file_name); - void read(std::istream &is); - void write(const char* file_name) const; - void write(std::ostream &os) const; - - // experimental and debug - void saveAllFloatPosteriors(std::string file_url); - void saveAllBytePosteriors(std::string file_url); - void setFloatPosteriorsFromTextfile_176(std::string url); - float countZeroElements(); - - std::vector trees_; + int numLeavesPerTree; + int numNodesPerTree; -private: - int classes_; - int num_quant_bits_; - uchar **posteriors_; - ushort *ptemp_; - int original_num_classes_; - bool keep_floats_; + struct Node + { + uchar x1, y1, x2, y2; + Node() : x1(0), y1(0), x2(0), y2(0) {} + Node( uchar _x1, uchar _y1, uchar _x2, uchar _y2 ) : x1(_x1), y1(_y1), x2(_x2), y2(_y2) + {} + int operator() (const Mat_& patch) const + { return patch(y1,x1) > patch(y2, x2) ? 1 : 0; } + }; + vector nodes; + vector posteriors; +#if QUANTIZATION_AVAILABLE + vector quantizedPosteriors; +#endif }; - /****************************************************************************************\ * One-Way Descriptor * \****************************************************************************************/ @@ -1004,7 +869,7 @@ protected: CvAffinePose* m_affine_poses; // an array of poses CvMat** m_transforms; // an array of affine transforms corresponding to poses - std::string m_feature_name; // the name of the feature associated with the descriptor + string m_feature_name; // the name of the feature associated with the descriptor CvPoint m_center; // the coordinates of the feature (the center of the input image ROI) int m_pca_dim_high; // the number of descriptor pca components to use for generating affine poses @@ -1275,7 +1140,8 @@ public: * * image The image. * keypoints The detected keypoints. - * mask Mask specifying where to look for keypoints (optional). Must be a char matrix with non-zero values in the region of interest. + * mask Mask specifying where to look for keypoints (optional). Must be a char + * matrix with non-zero values in the region of interest. */ void detect( const Mat& image, vector& keypoints, const Mat& mask=Mat() ) const { @@ -1430,8 +1296,8 @@ public: */ virtual void compute( const Mat& image, vector& keypoints, Mat& descriptors ) const = 0; - virtual void read (const FileNode&) {}; - virtual void write (FileStorage&) const {}; + virtual void read( const FileNode& ) {}; + virtual void write( FileStorage& ) const {}; protected: /* @@ -1451,9 +1317,9 @@ public: int firstOctave=SIFT::CommonParams::DEFAULT_FIRST_OCTAVE, int angleMode=SIFT::CommonParams::FIRST_ANGLE ); - virtual void compute( const Mat& image, vector& keypoints, Mat& descriptors) const; - virtual void read (const FileNode &fn); - virtual void write (FileStorage &fs) const; + virtual void compute( const Mat& image, vector& keypoints, Mat& descriptors ) const; + virtual void read( const FileNode &fn ); + virtual void write( FileStorage &fs ) const; protected: SIFT sift; @@ -1465,14 +1331,56 @@ public: SurfDescriptorExtractor( int nOctaves=4, int nOctaveLayers=2, bool extended=false ); - virtual void compute( const Mat& image, vector& keypoints, Mat& descriptors) const; - virtual void read (const FileNode &fn); - virtual void write (FileStorage &fs) const; + virtual void compute( const Mat& image, vector& keypoints, Mat& descriptors ) const; + virtual void read( const FileNode &fn ); + virtual void write( FileStorage &fs ) const; protected: SURF surf; }; +#if 0 +template +class CalonderDescriptorExtractor : public DescriptorExtractor +{ +public: + CalonderDescriptorExtractor( const string& classifierFile ); + + virtual void compute( const Mat& image, vector& keypoints, Mat& descriptors ) const; + virtual void read( const FileNode &fn ); + virtual void write( FileStorage &fs ) const; + +protected: + RTreeClassifier classifier_; + static const int BORDER_SIZE = 16; +}; + +template +CalonderDescriptorExtractor::CalonderDescriptorExtractor(const std::string& classifier_file) +{ + classifier_.read( classifier_file.c_str() ); +} + +template +void CalonderDescriptorExtractor::compute( const cv::Mat& image, + std::vector& keypoints, + cv::Mat& descriptors) const +{ + // Cannot compute descriptors for keypoints on the image border. + removeBorderKeypoints(keypoints, image.size(), BORDER_SIZE); + + /// @todo Check 16-byte aligned + descriptors.create(keypoints.size(), classifier_.classes(), cv::DataType::type); + IplImage ipl = (IplImage)image; + + for (size_t i = 0; i < keypoints.size(); ++i) { + cv::Point2f keypt = keypoints[i].pt; + cv::WImageView1_b patch = features::extractPatch(&ipl, keypt); + classifier_.getSignature(patch.Ipl(), descriptors.ptr(i)); + } +} +#endif + CV_EXPORTS Ptr createDescriptorExtractor( const string& descriptorExtractorType ); /****************************************************************************************\ @@ -1533,7 +1441,7 @@ struct CV_EXPORTS L1 /****************************************************************************************\ -* DMatch * +* DMatch * \****************************************************************************************/ /* * Struct for matching: match index and distance between descriptors @@ -1591,8 +1499,7 @@ public: * mask Mask specifying permissible matches. * matches Indices of the closest matches from the training set */ - void match( const Mat& query, const Mat& mask, - vector& matches ) const; + void match( const Mat& query, const Mat& mask, vector& matches ) const; /* * Find the best match for each descriptor from a query set @@ -1613,8 +1520,7 @@ public: * mask Mask specifying permissible matches. * matches DMatches of the closest matches from the training set */ - void match( const Mat& query, const Mat& mask, - vector& matches ) const; + void match( const Mat& query, const Mat& mask, vector& matches ) const; /* * Find many matches for each descriptor from a query set @@ -1638,7 +1544,7 @@ public: * threshold Distance threshold for descriptors matching */ void match( const Mat& query, const Mat& mask, - vector >& matches, float threshold ) const; + vector >& matches, float threshold ) const; @@ -1878,7 +1784,7 @@ void BruteForceMatcher::matchImpl( const Mat& descriptors_1, const Mat template<> void BruteForceMatcher >::matchImpl( const Mat& descriptors_1, const Mat& descriptors_2, - const Mat& mask, vector& matches ) const; + const Mat& mask, vector& matches ) const; CV_EXPORTS Ptr createDescriptorMatcher( const string& descriptorMatcherType ); @@ -2036,10 +1942,10 @@ public: virtual void clear (); // Reads match object from a file node - virtual void read (const FileNode &fn); + virtual void read( const FileNode &fn ); // Writes match object to a file storage - virtual void write (FileStorage& fs) const; + virtual void write( FileStorage& fs ) const; protected: Ptr base; @@ -2049,6 +1955,7 @@ protected: /* * CalonderDescriptorMatch */ +#if 0 class CV_EXPORTS CalonderDescriptorMatch : public GenericDescriptorMatch { public: @@ -2113,6 +2020,7 @@ protected: Ptr classifier; Params params; }; +#endif /* * FernDescriptorMatch @@ -2178,6 +2086,7 @@ protected: }; CV_EXPORTS Ptr createGenericDescriptorMatch( const string& genericDescritptorMatchType, const string ¶msFilename = string () ); + /****************************************************************************************\ * VectorDescriptorMatch * \****************************************************************************************/ @@ -2199,63 +2108,27 @@ public: void index(); // Calculates descriptors for a set of keypoints from a single image - virtual void add( const Mat& image, vector& keypoints ) - { - Mat descriptors; - extractor->compute( image, keypoints, descriptors ); - matcher->add( descriptors ); - - collection.add( Mat(), keypoints ); - }; + virtual void add( const Mat& image, vector& keypoints ); // Matches a set of keypoints with the training set - virtual void match( const Mat& image, vector& points, vector& keypointIndices ) - { - Mat descriptors; - extractor->compute( image, points, descriptors ); - - matcher->match( descriptors, keypointIndices ); - }; + virtual void match( const Mat& image, vector& points, vector& keypointIndices ); - virtual void match( const Mat& image, vector& points, vector& matches ) - { - Mat descriptors; - extractor->compute( image, points, descriptors ); - - matcher->match( descriptors, matches ); - } - - virtual void match( const Mat& image, vector& points, vector >& matches, float threshold ) - { - Mat descriptors; - extractor->compute( image, points, descriptors ); - - matcher->match( descriptors, matches, threshold ); - } + virtual void match( const Mat& image, vector& points, vector& matches ); - virtual void clear() - { - GenericDescriptorMatch::clear(); - matcher->clear(); - } + virtual void match( const Mat& image, vector& points, + vector >& matches, float threshold ); - virtual void read (const FileNode& fn) - { - GenericDescriptorMatch::read(fn); - extractor->read (fn); - } + virtual void clear(); + virtual void read( const FileNode& fn ); + virtual void write( FileStorage& fs ) const; - virtual void write (FileStorage& fs) const - { - GenericDescriptorMatch::write(fs); - extractor->write (fs); - } protected: Ptr extractor; Ptr matcher; //vector classIds; }; + struct CV_EXPORTS DrawMatchesFlags { enum{ DEFAULT = 0, // Output image matrix will be created (Mat::create), diff --git a/modules/features2d/src/calonder.cpp b/modules/features2d/src/calonder.cpp index 7c25d65..458f010 100644 --- a/modules/features2d/src/calonder.cpp +++ b/modules/features2d/src/calonder.cpp @@ -40,967 +40,525 @@ // //M*/ #include "precomp.hpp" - -#include -#include +#include #include -#include -#include -#include -#include - -using namespace cv; - -#if defined _MSC_VER && _MSC_VER >= 1400 -#pragma warning(disable: 4244 4267) -#endif -/****************************************************************************************\ -The code below is implementation of Calonder Descriptor and RTree Classifier -originally introduced by Michael Calonder. +using namespace std; -The code was integrated into OpenCV by Alexey Latyshev -\****************************************************************************************/ +const int progressBarSize = 50; +namespace cv +{ -namespace cv { - - - //---------------------------- - //randomized_tree.cpp - - inline uchar* getData(IplImage* image) - { - return reinterpret_cast(image->imageData); - } +CalonderClassifier::CalonderClassifier() +{ + verbose = false; + clear(); +} - inline float* RandomizedTree::getPosteriorByIndex(int index) - { - return const_cast(const_cast(this)->getPosteriorByIndex(index)); - } +CalonderClassifier::~CalonderClassifier() +{} + +CalonderClassifier::CalonderClassifier( const vector >& points, const vector& refimgs, + const vector >& labels, int _numClasses, + int _pathSize, int _numTrees, int _treeDepth, + int _numViews, int _compressedDim, int _compressType, int _numQuantBits, + const PatchGenerator &patchGenerator ) +{ + verbose = false; + train( points, refimgs, labels, _numClasses, _pathSize, _numTrees, _treeDepth, _numViews, + _compressedDim, _compressType, _numQuantBits, patchGenerator ); +} - inline const float* RandomizedTree::getPosteriorByIndex(int index) const - { - return posteriors_[index].p(); - } +int CalonderClassifier::getPatchSize() const +{ return patchSize; } - inline uchar* RandomizedTree::getPosteriorByIndex2(int index) - { - return posteriors2_[index].p(); - } +int CalonderClassifier::getNumTrees() const +{ return numTrees; } +int CalonderClassifier::getTreeDepth() const +{ return treeDepth; } - template < typename PointT > - cv::WImageView1_b extractPatch(cv::WImageView1_b const& image, PointT pt, int patch_sz = PATCH_SIZE) - { - const int offset = patch_sz / 2; +int CalonderClassifier::getNumViews() const +{ return numViews; } - // TODO: WImage{C}.View really should have const version - cv::WImageView1_b &img_ref = const_cast< cv::WImageView1_b& >(image); - return img_ref.View(pt.x - offset, pt.y - offset, patch_sz, patch_sz); - } +int CalonderClassifier::getSignatureSize() const +{ return signatureSize; } - template < typename PointT > - cv::WImageView3_b extractPatch3(cv::WImageView3_b const& image, PointT pt) - { - static const int offset = PATCH_SIZE / 2; +int CalonderClassifier::getCompressType() const +{ return compressType; } - // TODO: WImage{C}.View really should have const version - cv::WImageView3_b &img_ref = const_cast< cv::WImageView3_b& >(image); - return img_ref.View(pt.x - offset, pt.y - offset, - PATCH_SIZE, PATCH_SIZE); - } +int CalonderClassifier::getNumQuantBits() const +{ return numQuantBits; } - float *CSMatrixGenerator::cs_phi_ = NULL; - int CSMatrixGenerator::cs_phi_m_ = 0; - int CSMatrixGenerator::cs_phi_n_ = 0; +int CalonderClassifier::getOrigNumClasses() const +{ return origNumClasses; } - RandomizedTree::RandomizedTree() - : posteriors_(NULL), posteriors2_(NULL) - { - } +void CalonderClassifier::setVerbose( bool _verbose ) +{ + verbose = _verbose; +} - RandomizedTree::~RandomizedTree() - { - freePosteriors(3); - } +void CalonderClassifier::clear() +{ + patchSize = numTrees = origNumClasses = signatureSize = treeDepth = numViews = numQuantBits = 0; + compressType = COMPRESS_NONE; - void RandomizedTree::createNodes(int num_nodes, cv::RNG &rng) - { - nodes_.reserve(num_nodes); - for (int i = 0; i < num_nodes; ++i) { - nodes_.push_back( RTreeNode(rng(PATCH_SIZE), - rng(PATCH_SIZE), - rng(PATCH_SIZE), - rng(PATCH_SIZE)) ); - } - } + nodes.clear(); + posteriors.clear(); +#if QUANTIZATION_AVAILABLE + quantizedPosteriors.clear(); +#endif +} - int RandomizedTree::getIndex(uchar* patch_data) const - { - int index = 0; - for (int d = 0; d < depth_; ++d) { - int child_offset = nodes_[index](patch_data); - index = 2*index + 1 + child_offset; - } - return index - nodes_.size(); - } +bool CalonderClassifier::empty() const +{ + return posteriors.empty() && quantizedPosteriors.empty(); +} - void RandomizedTree::train(std::vector const& base_set, - cv::RNG &rng, int depth, int views, size_t reduced_num_dim, - int num_quant_bits) - { +void CalonderClassifier::prepare( int _patchSize, int _signatureSize, int _numTrees, int _treeDepth, int _numViews ) +{ + clear(); - //CalonderPatchGenerator make_patch(NULL, rng); - PatchGenerator make_patch = PatchGenerator(); - train(base_set, rng, make_patch, depth, views, reduced_num_dim, num_quant_bits); - } + patchSize = _patchSize; + signatureSize = _signatureSize; + numTrees = _numTrees; + treeDepth = _treeDepth; + numViews = _numViews; - void RandomizedTree::train(std::vector const& base_set, - cv::RNG &rng, PatchGenerator &make_patch, - int depth, int views, size_t reduced_num_dim, - int num_quant_bits) - { - init(base_set.size(), depth, rng); + numLeavesPerTree = 1 << treeDepth; // 2^d + numNodesPerTree = numLeavesPerTree - 1; // 2^d - 1 - Mat patch; + nodes = vector( numTrees*numNodesPerTree ); + posteriors = vector( numTrees*numLeavesPerTree*signatureSize, 0.f ); +} - // Estimate posterior probabilities using random affine views - std::vector::const_iterator keypt_it; - int class_id = 0; - for (keypt_it = base_set.begin(); keypt_it != base_set.end(); ++keypt_it, ++class_id) { - for (int i = 0; i < views; ++i) { +static int calcNumPoints( const vector >& points ) +{ + int count = 0; + for( size_t i = 0; i < points.size(); i++ ) + count += points[i].size(); + return count; +} +void CalonderClassifier::train( const vector >& points, const vector& refimgs, + const vector >& labels, int _numClasses, + int _patchSize, int _numTrees, int _treeDepth, int _numViews, + int _compressedDim, int _compressType, int _numQuantBits, + const PatchGenerator &patchGenerator ) +{ + if( points.empty() || refimgs.size() != points.size() ) + CV_Error( CV_StsBadSize, "points vector must be no empty and refimgs must have the same size as points" ); + if( _patchSize < 5 || _patchSize >= 256 ) + CV_Error( CV_StsBadArg, "patchSize must be in [5, 255]"); + if( _numTrees <= 0 || _treeDepth <= 0 ) + CV_Error( CV_StsBadArg, "numTrees, treeDepth, numViews must be positive"); + int numPoints = calcNumPoints( points ); + if( !labels.empty() && ( labels.size() != points.size() || _numClasses <=0 || _numClasses > numPoints ) ) + CV_Error( CV_StsBadArg, "labels has incorrect size or _numClasses is not in [1, numPoints]"); + _numViews = std::max( 1, _numViews ); + + int _origNumClasses = labels.empty() ? numPoints : _numClasses; + + if( verbose ) + { + cout << "Using train parameters:" << endl; + cout << " patchSize=" << _patchSize << endl; + cout << " numTrees=" << _numTrees << endl; + cout << " treeDepth=" << _treeDepth << endl; + cout << " numViews=" << _numViews << endl; + cout << " compressedDim=" << _compressedDim << endl; + cout << " compressType=" << _compressType << endl; + cout << " numQuantBits=" << _numQuantBits << endl; + cout << endl + << " numPoints=" << numPoints << endl; + cout << " origNumClasses=" << _origNumClasses << endl; + } + + prepare( _patchSize, _origNumClasses, _numTrees, _treeDepth, _numViews ); + + origNumClasses = _origNumClasses; + vector leafSampleCounters = vector( numTrees*numLeavesPerTree, 0 ); + // generate nodes + RNG rng = theRNG(); + for( int i = 0; i < numTrees*numNodesPerTree; i++ ) + { + uchar x1 = rng(_patchSize); + uchar y1 = rng(_patchSize); + uchar x2 = rng(_patchSize); + uchar y2 = rng(_patchSize); + nodes[i] = Node(x1, y1, x2, y2); + } + + Size size( patchSize, patchSize ); + Mat patch; + if( verbose ) cout << "START training..." << endl; + for( size_t treeIdx = 0; treeIdx < (size_t)numTrees; treeIdx++ ) + { + if( verbose ) cout << "< tree " << treeIdx << endl; + int globalPointIdx = 0; + int* treeLeafSampleCounters = &leafSampleCounters[treeIdx*numLeavesPerTree]; + float* treePosteriors = &posteriors[treeIdx*numLeavesPerTree*signatureSize]; + for( size_t imgIdx = 0; imgIdx < points.size(); imgIdx++ ) + { + const Point2f* imgPoints = &points[imgIdx][0]; + const int* imgLabels = labels.empty() ? 0 : &labels[imgIdx][0]; + int last = -1, cur; + for( size_t pointIdx = 0; pointIdx < points[imgIdx].size(); pointIdx++, globalPointIdx++ ) + { + int classID = imgLabels==0 ? globalPointIdx : imgLabels[pointIdx]; + Point2f pt = imgPoints[pointIdx]; + const Mat& src = refimgs[imgIdx]; - make_patch(keypt_it->image, Point2f(keypt_it->x,keypt_it->y) ,patch, Size(PATCH_SIZE,PATCH_SIZE),rng); + if( verbose && (cur = (int)((float)globalPointIdx/numPoints*progressBarSize)) != last ) + { + last = cur; + cout << "."; + cout.flush(); + } - IplImage _patch = patch; - addExample(class_id, getData(&_patch)); + CV_Assert( classID >= 0 && classID < signatureSize ); + for( int v = 0; v < numViews; v++ ) + { + patchGenerator( src, pt, patch, size, rng ); + // add sample + int leafIdx = getLeafIdx( treeIdx, patch ); + treeLeafSampleCounters[leafIdx]++; + treePosteriors[leafIdx*signatureSize + classID]++; + } } } - finalize(reduced_num_dim, num_quant_bits); - - } - - void RandomizedTree::allocPosteriorsAligned(int num_leaves, int num_classes) - { -printf("alloc posteriors aligned\n"); - freePosteriors(3); - - posteriors_ = new FloatSignature[num_leaves]; - for (int i=0; i" << endl; } - void RandomizedTree::init(int num_classes, int depth, cv::RNG &rng) - { - depth_ = depth; - num_leaves_ = 1 << depth; // 2**d - int num_nodes = num_leaves_ - 1; // 2**d - 1 - - // Initialize probabilities and counts to 0 - allocPosteriorsAligned(num_leaves_, num_classes); // will set classes_ correctly - for (int i = 0; i < num_leaves_; ++i) - memset((void*)posteriors_[i].p(), 0, num_classes*sizeof(float)); - leaf_counts_.resize(num_leaves_); - - for (int i = 0; i < num_leaves_; ++i) - memset((void*)posteriors2_[i].p(), 0, num_classes*sizeof(uchar)); + _compressedDim = std::max( 0, std::min(signatureSize, _compressedDim) ); + _numQuantBits = std::max( 0, std::min((int)MAX_NUM_QUANT_BITS, _numQuantBits) ); + finalize( _compressedDim, _compressType, _numQuantBits, leafSampleCounters ); - createNodes(num_nodes, rng); - } + if( verbose ) cout << "END training." << endl; +} - void RandomizedTree::addExample(int class_id, uchar* patch_data) +int CalonderClassifier::getLeafIdx( int treeIdx, const Mat& patch ) const +{ + const Node* treeNodes = &nodes[treeIdx*numNodesPerTree]; + int idx = 0; + for( int d = 0; d < treeDepth-1; d++ ) { - int index = getIndex(patch_data); - float* posterior = getPosteriorByIndex(index); - ++leaf_counts_[index]; - ++posterior[class_id]; + int offset = treeNodes[idx](patch); + idx = 2*idx + 1 + offset; } + return idx; +} - void RandomizedTree::finalize(size_t reduced_num_dim, int num_quant_bits) +void CalonderClassifier::finalize( int _compressedDim, int _compressType, int _numQuantBits, + const vector& leafSampleCounters ) +{ + for( int ti = 0; ti < numTrees; ti++ ) { + const int* treeLeafSampleCounters = &leafSampleCounters[ti*numLeavesPerTree]; + float* treePosteriors = &posteriors[ti*numLeavesPerTree*signatureSize]; // Normalize by number of patches to reach each leaf - for (int index = 0; index < num_leaves_; ++index) { - float* posterior = posteriors_[index].p(); - assert(posterior != NULL); - int count = leaf_counts_[index]; - if (count != 0) { - float normalizer = 1.0f / count; - for (int c = 0; c < classes_; ++c) { - *posterior *= normalizer; - ++posterior; - } - } - } - leaf_counts_.clear(); - - // apply compressive sensing - if ((int)reduced_num_dim != classes_) - compressLeaves(reduced_num_dim); - else { - static bool notified = false; - //if (!notified) - // printf("\n[OK] NO compression to leaves applied, dim=%i\n", reduced_num_dim); - notified = true; - } - - // convert float-posteriors to char-posteriors (quantization step) - makePosteriors2(num_quant_bits); - } - - void RandomizedTree::compressLeaves(size_t reduced_num_dim) - { - static bool warned = false; - if (!warned) { - printf("\n[OK] compressing leaves with phi %i x %i\n", (int)reduced_num_dim, classes_); - warned = true; - } - - static bool warned2 = false; - if ((int)reduced_num_dim == classes_) { - if (!warned2) - printf("[WARNING] RandomizedTree::compressLeaves: not compressing because reduced_dim == classes()\n"); - warned2 = true; - return; - } - - // DO NOT FREE RETURNED POINTER - float *cs_phi = CSMatrixGenerator::getCSMatrix(reduced_num_dim, classes_, CSMatrixGenerator::PDT_BERNOULLI); - - float *cs_posteriors = new float[num_leaves_ * reduced_num_dim]; // temp, num_leaves_ x reduced_num_dim - - for (int i=0; i 0 && _compressedDim < signatureSize ) + compressLeaves( _compressedDim, _compressType ); + else { - int N = (1<(y,x) = rng.gaussian( sigma ); } - - void RandomizedTree::quantizeVector(float *vec, int dim, int N, float bnds[2], uchar *dst) + else if( distrType == CalonderClassifier::COMPRESS_DISTR_BERNOULLI ) { - int map_bnd[2] = {0, N}; // bounds of quantized target interval we're mapping to - int tmp; - for (int k=0; kN) ? N : tmp)); - ++vec; - ++dst; - } + float par = (float)(1./sqrt(rows)); + for( int y = 0; y < rows; y++ ) + for( int x = 0; x < cols; x++ ) + mtr.at(y,x) = rng(2)==0 ? par : -par; } - - - void RandomizedTree::read(const char* file_name, int num_quant_bits) + else if( distrType == CalonderClassifier::COMPRESS_DISTR_DBFRIENDLY ) { - std::ifstream file(file_name, std::ifstream::binary); - read(file, num_quant_bits); - file.close(); + float par = (float)sqrt(3./rows); + for( int y = 0; y < rows; y++ ) + for( int x = 0; x < cols; x++ ) + { + int rng6 = rng(6); + mtr.at(y,x) = rng6==0 ? par : (rng6==1 ? -par : 0.f); + } } + else + CV_Assert( 0 ); - void RandomizedTree::read(std::istream &is, int num_quant_bits) - { - is.read((char*)(&classes_), sizeof(classes_)); - is.read((char*)(&depth_), sizeof(depth_)); - - num_leaves_ = 1 << depth_; - int num_nodes = num_leaves_ - 1; - - nodes_.resize(num_nodes); - is.read((char*)(&nodes_[0]), num_nodes * sizeof(nodes_[0])); + return mtr; +} - //posteriors_.resize(classes_ * num_leaves_); - //freePosteriors(3); - //printf("[DEBUG] reading: %i leaves, %i classes\n", num_leaves_, classes_); - allocPosteriorsAligned(num_leaves_, classes_); - for (int i=0; i comprPosteriors( numTrees*numLeavesPerTree*_compressedDim, 0); + Mat( numTrees*numLeavesPerTree, _compressedDim, CV_32FC1, &comprPosteriors[0] ) = + Mat( numTrees*numLeavesPerTree, signatureSize, CV_32FC1, &posteriors[0]) * compressionMtrT; - void RandomizedTree::write(std::ostream &os) const - { - if (!posteriors_) { - printf("WARNING: Cannot write float posteriors cause posteriors_ == NULL\n"); - return; - } + posteriors.resize( comprPosteriors.size() ); + copy( comprPosteriors.begin(), comprPosteriors.end(), posteriors.begin() ); - os.write((char*)(&classes_), sizeof(classes_)); - os.write((char*)(&depth_), sizeof(depth_)); + signatureSize = _compressedDim; + compressType = _compressType; +} - os.write((char*)(&nodes_[0]), nodes_.size() * sizeof(nodes_[0])); - for (int i=0; i0 ); + assert( p>=0 && p<=1 ); + vector vec( data, data+n ); + sort(vec.begin(), vec.end()); + int ix = (int)(p*(n-1)); + return vec[ix]; +} - void RandomizedTree::savePosteriors(std::string url, bool append) - { - std::ofstream file(url.c_str(), (append?std::ios::app:std::ios::out)); - for (int i=0; i0); - assert(p>=0 && p<=1); - std::vector vec(data, data+n); - sort(vec.begin(), vec.end()); - int ix = (int)(p*(n-1)); - return vec[ix]; - } +void CalonderClassifier::quantizePosteriors( int _numQuantBits, bool isClearFloatPosteriors ) +{ + uchar ubounds[] = { 0, (uchar)((1<<_numQuantBits)-1) }; + float fbounds[] = { 0.f, 0.f }; - void RandomizedTree::estimateQuantPercForPosteriors(float perc[2]) + int totalLeavesCount = numTrees*numLeavesPerTree; + for( int li = 0; li < totalLeavesCount; li++ ) // TODO for some random choosen leaves ! { - // _estimate_ percentiles for this tree - // TODO: do this more accurately - assert(posteriors_ != NULL); - perc[0] = perc[1] = .0f; - for (int i=0; i> cs_phi[i]; - } - ifs.close(); +void CalonderClassifier::clearFloatPosteriors() +{ + quantizedPosteriors.clear(); +} - static bool warned=false; - if (!warned) { - printf("[NOTE] RT: reading %ix%i PHI matrix from '%s'...\n", m, n, phi); - warned=true; - } +#endif +void CalonderClassifier::operator()( const Mat& img, Point2f pt, vector& signature, float thresh ) const +{ + if( img.empty() || img.type() != CV_8UC1 ) return; -#endif - float *cs_phi = cs_phi_; + Mat patch; + getRectSubPix(img, Size(patchSize,patchSize), pt, patch, img.type()); + (*this)( patch, signature, thresh ); +} - if (m == n) { - // special case - set to 0 for safety - memset(cs_phi, 0, m*n*sizeof(float)); - printf("[WARNING] %s:%i: square CS matrix (-> no reduction)\n", __FILE__, __LINE__); - } - else { - cv::RNG rng(23); - - // par is distr param, cf 'Favorable JL Distributions' (Baraniuk et al, 2006) - if (dt == PDT_GAUSS) { - float par = (float)(1./m); - //modified - cv::RNG _rng; - for (int i=0; i(0., par); - } - } - else if (dt == PDT_BERNOULLI) { - float par = (float)(1./sqrt((float)m)); - for (int i=0; i& signature, float thresh ) const +{ + if( posteriors.empty() || patch.empty() || patch.type() != CV_8UC1 || patch.cols < patchSize || patch.rows < patchSize ) + return; - return cs_phi_; - } + int treePostSize = numLeavesPerTree*signatureSize; - CSMatrixGenerator::~CSMatrixGenerator() + signature.resize( signatureSize, 0.f ); + float* sig = &signature[0]; + for( int ti = 0; ti < numTrees; ti++ ) { - if (cs_phi_) delete [] cs_phi_; - cs_phi_ = NULL; + int leafIdx = getLeafIdx( ti, patch ); + const float* post = &posteriors[ti*treePostSize + leafIdx*signatureSize]; + for( int ci = 0; ci < signatureSize; ci++ ) + sig[ci] += post[ci]; } - - - //} // namespace features - - //---------------------------- - //rtree_classifier.cpp - //namespace features { - - // Returns 16-byte aligned signatures that can be passed to getSignature(). - // Release by calling free() - NOT delete! - // - // note: 1) for num_sig>1 all signatures will still be 16-byte aligned, as - // long as sig_len%16 == 0 holds. - // 2) casting necessary, otherwise it breaks gcc's strict aliasing rules - inline void RTreeClassifier::safeSignatureAlloc(uchar **sig, int num_sig, int sig_len) + float coef = 1.f/numTrees; + for( int ci = 0; ci < signatureSize; ci++ ) { - assert(sig_len == 176); - void *p_sig; - //added - // posix_memalign(&p_sig, 16, num_sig*sig_len*sizeof(uchar)); - p_sig = malloc(num_sig*sig_len*sizeof(uchar)); - *sig = reinterpret_cast(p_sig); + sig[ci] *= coef; + if( sig[ci] < thresh ) + sig[ci] = 0; } +} - inline uchar* RTreeClassifier::safeSignatureAlloc(int num_sig, int sig_len) - { - uchar *sig; - safeSignatureAlloc(&sig, num_sig, sig_len); - return sig; - } +#if QUANTIZATION_AVAILABLE +void CalonderClassifier::operator()( const Mat& img, Point2f pt, vector& signature, uchar thresh ) const +{ + if( img.empty() || img.type() != CV_8UC1 ) + return; - inline void add(int size, const float* src1, const float* src2, float* dst) - { - while(--size >= 0) { - *dst = *src1 + *src2; - ++dst; ++src1; ++src2; - } - } + Mat patch; + getRectSubPix(img, Size(patchSize,patchSize), pt, patch, img.type()); + (*this)(patch, signature, thresh ); +} - inline void add(int size, const ushort* src1, const uchar* src2, ushort* dst) - { - while(--size >= 0) { - *dst = *src1 + *src2; - ++dst; ++src1; ++src2; - } - } +void CalonderClassifier::operator()( const Mat& patch, vector& signature, uchar thresh ) const +{ + if( quantizedPosteriors.empty() || patch.empty() || patch.type() != CV_8UC1 || patch.cols > patchSize || patch.rows > patchSize ) + return; + + int treePostSize = numLeavesPerTree*signatureSize; - RTreeClassifier::RTreeClassifier() - : classes_(0) + vector sum( signatureSize, 0.f ); + for( int ti = 0; ti < numTrees; ti++ ) { - posteriors_ = NULL; + int leafIdx = getLeafIdx( ti, patch ); + const uchar* post = &quantizedPosteriors[ti*treePostSize + leafIdx*signatureSize]; + for( int ci = 0; ci < signatureSize; ci++ ) + sum[ci] += post[ci]; } - - void RTreeClassifier::train(std::vector const& base_set, - cv::RNG &rng, int num_trees, int depth, - int views, size_t reduced_num_dim, - int num_quant_bits, bool print_status) + float coef = 1.f/numTrees; + signature.resize( signatureSize ); + uchar* sig = &signature[0]; + for( int ci = 0; ci < signatureSize; ci++ ) { - PatchGenerator make_patch = PatchGenerator(); - train(base_set, rng, make_patch, num_trees, depth, views, reduced_num_dim, num_quant_bits, print_status); + sig[ci] = (uchar)(sum[ci]*coef); + if( sig[ci] < thresh ) + sig[ci] = 0; } +} +#endif - // Single-threaded version of train(), with progress output - void RTreeClassifier::train(std::vector const& base_set, - cv::RNG &rng, PatchGenerator &make_patch, int num_trees, - int depth, int views, size_t reduced_num_dim, - int num_quant_bits, bool print_status) - { - if (reduced_num_dim > base_set.size()) { - if (print_status) - { - printf("INVALID PARAMS in RTreeClassifier::train: reduced_num_dim{%i} > base_set.size(){%i}\n", - (int)reduced_num_dim, (int)base_set.size()); - } - return; - } +void CalonderClassifier::read( const FileNode& fn ) +{ + prepare( fn["patchSize"], fn["signatureSize"], fn["numTrees"], fn["treeDepth"], fn["numViews"] ); + origNumClasses = fn["origNumClasses"]; + compressType = fn["compressType"]; + int _numQuantBits = fn["numQuantBits"]; - num_quant_bits_ = num_quant_bits; - classes_ = reduced_num_dim; // base_set.size(); - original_num_classes_ = base_set.size(); - trees_.resize(num_trees); - if (print_status) - { - printf("[OK] Training trees: base size=%i, reduced size=%i\n", (int)base_set.size(), (int)reduced_num_dim); - } + for( int ti = 0; ti < numTrees; ti++ ) + { + stringstream treeName; + treeName << "tree" << ti; + FileNode treeFN = fn["trees"][treeName.str()]; - int count = 1; - if (print_status) - { - printf("[OK] Trained 0 / %i trees", num_trees); fflush(stdout); - } - //added - //BOOST_FOREACH( RandomizedTree &tree, trees_ ) { - //tree.train(base_set, rng, make_patch, depth, views, reduced_num_dim, num_quant_bits_); - //printf("\r[OK] Trained %i / %i trees", count++, num_trees); - //fflush(stdout); - for (int i=0; i<(int)trees_.size(); i++) + Node* treeNodes = &nodes[ti*numNodesPerTree]; + FileNodeIterator nodesFNIter = treeFN["nodes"].begin(); + for( int ni = 0; ni < numNodesPerTree; ni++ ) { - trees_[i].train(base_set, rng, make_patch, depth, views, reduced_num_dim, num_quant_bits_); - if (print_status) - { - printf("\r[OK] Trained %i / %i trees", count++, num_trees); - fflush(stdout); - } + Node* node = treeNodes + ni; + nodesFNIter >> node->x1 >> node->y1 >> node->x2 >> node->y2; } - if (print_status) + FileNode posteriorsFN = treeFN["posteriors"]; + for( int li = 0; li < numLeavesPerTree; li++ ) { - printf("\n"); - countZeroElements(); - printf("\n\n"); + stringstream leafName; + leafName << "leaf" << li; + float* post = &posteriors[ti*numLeavesPerTree*signatureSize + li*signatureSize]; + FileNodeIterator leafFNIter = posteriorsFN[leafName.str()].begin(); + for( int ci = 0; ci < signatureSize; ci++ ) + leafFNIter >> post[ci]; } } - - void RTreeClassifier::getSignature(IplImage* patch, float *sig) - { - // Need pointer to 32x32 patch data - uchar buffer[PATCH_SIZE * PATCH_SIZE]; - uchar* patch_data; - if (patch->widthStep != PATCH_SIZE) { - //printf("[INFO] patch is padded, data will be copied (%i/%i).\n", - // patch->widthStep, PATCH_SIZE); - uchar* data = getData(patch); - patch_data = buffer; - for (int i = 0; i < PATCH_SIZE; ++i) { - memcpy((void*)patch_data, (void*)data, PATCH_SIZE); - data += patch->widthStep; - patch_data += PATCH_SIZE; - } - patch_data = buffer; - } - else { - patch_data = getData(patch); - } - - memset((void*)sig, 0, classes_ * sizeof(float)); - std::vector::iterator tree_it; - - // get posteriors - float **posteriors = new float*[trees_.size()]; // TODO: move alloc outside this func - float **pp = posteriors; - for (tree_it = trees_.begin(); tree_it != trees_.end(); ++tree_it, pp++) { - *pp = tree_it->getPosterior(patch_data); - assert(*pp != NULL); - } - - // sum them up - pp = posteriors; - for (tree_it = trees_.begin(); tree_it != trees_.end(); ++tree_it, pp++) - add(classes_, sig, *pp, sig); - - delete [] posteriors; - posteriors = NULL; - - // full quantization (experimental) -#if 0 - int n_max = 1<<8 - 1; - int sum_max = (1<<4 - 1)*trees_.size(); - int shift = 0; - while ((sum_max>>shift) > n_max) shift++; - - for (int i = 0; i < classes_; ++i) { - sig[i] = int(sig[i] + .5) >> shift; - if (sig[i]>n_max) sig[i] = n_max; - } - - static bool warned = false; - if (!warned) { - printf("[WARNING] Using full quantization (RTreeClassifier::getSignature)! shift=%i\n", shift); - warned = true; - } -#else - // TODO: get rid of this multiply (-> number of trees is known at train - // time, exploit it in RandomizedTree::finalize()) - float normalizer = 1.0f / trees_.size(); - for (int i = 0; i < classes_; ++i) - sig[i] *= normalizer; +#if QUANTIZATION_AVAILABLE + if( _numQuantBits ) + quantizePosteriors(_numQuantBits); #endif - } - - - // sum up 50 byte vectors of length 176 - // assume 5 bits max for input vector values - // final shift is 3 bits right - //void sum_50c_176c(uchar **pp, uchar *sig) - //{ - - //} - - void RTreeClassifier::getSignature(IplImage* patch, uchar *sig) - { - // Need pointer to 32x32 patch data - uchar buffer[PATCH_SIZE * PATCH_SIZE]; - uchar* patch_data; - if (patch->widthStep != PATCH_SIZE) { - //printf("[INFO] patch is padded, data will be copied (%i/%i).\n", - // patch->widthStep, PATCH_SIZE); - uchar* data = getData(patch); - patch_data = buffer; - for (int i = 0; i < PATCH_SIZE; ++i) { - memcpy((void*)patch_data, (void*)data, PATCH_SIZE); - data += patch->widthStep; - patch_data += PATCH_SIZE; - } - patch_data = buffer; - } else { - patch_data = getData(patch); - } - - std::vector::iterator tree_it; +} - // get posteriors - if (posteriors_ == NULL) +void CalonderClassifier::write( FileStorage& fs ) const +{ + if( !fs.isOpened() ) + return; + fs << "patchSize" << patchSize; + fs << "numTrees" << numTrees; + fs << "treeDepth" << treeDepth; + fs << "numViews" << numViews; + fs << "origNumClasses" << origNumClasses; + fs << "signatureSize" << signatureSize; + fs << "compressType" << compressType; + fs << "numQuantBits" << numQuantBits; + + fs << "trees" << "{"; + for( int ti = 0; ti < numTrees; ti++ ) + { + stringstream treeName; + treeName << "tree" << ti; + fs << treeName.str() << "{"; + + fs << "nodes" << "[:"; + const Node* treeNodes = &nodes[ti*numNodesPerTree]; + for( int ni = 0; ni < numNodesPerTree; ni++ ) { - posteriors_ = new uchar*[trees_.size()]; - //aadded - // posix_memalign((void **)&ptemp_, 16, classes_*sizeof(ushort)); - ptemp_ = (ushort*)malloc(classes_*sizeof(ushort)); - } - uchar **pp = posteriors_; - for (tree_it = trees_.begin(); tree_it != trees_.end(); ++tree_it, pp++) - *pp = tree_it->getPosterior2(patch_data); - pp = posteriors_; - -#if 0 // SSE2 optimized code - sum_50t_176c(pp, sig, ptemp_); // sum them up -#else - static bool warned = false; - - memset((void*)sig, 0, classes_ * sizeof(sig[0])); - ushort *sig16 = new ushort[classes_]; // TODO: make member, no alloc here - memset((void*)sig16, 0, classes_ * sizeof(sig16[0])); - for (tree_it = trees_.begin(); tree_it != trees_.end(); ++tree_it, pp++) - add(classes_, sig16, *pp, sig16); - - // squeeze signatures into an uchar - const bool full_shifting = true; - int shift; - if (full_shifting) { - float num_add_bits_f = log((float)trees_.size())/log(2.f); // # additional bits required due to summation - int num_add_bits = int(num_add_bits_f); - if (num_add_bits_f != float(num_add_bits)) ++num_add_bits; - shift = num_quant_bits_ + num_add_bits - 8*sizeof(uchar); - //shift = num_quant_bits_ + num_add_bits - 2; - //shift = 6; - if (shift>0) - for (int i = 0; i < classes_; ++i) - sig[i] = (sig16[i] >> shift); // &3 cut off all but lowest 2 bits, 3(dec) = 11(bin) - - if (!warned) - printf("[OK] RTC: quantizing by FULL RIGHT SHIFT, shift = %i\n", shift); - } - else { - printf("[ERROR] RTC: not implemented!\n"); - exit(0); - } - - if (!warned) - printf("[WARNING] RTC: unoptimized signature computation\n"); - warned = true; -#endif - } - - - void RTreeClassifier::getSparseSignature(IplImage *patch, float *sig, float thresh) - { - getFloatSignature(patch, sig); - for (int i=0; i 0) - res += (fabs(*vec++) > tol); - return res; - } - - void RTreeClassifier::read(const char* file_name) - { - std::ifstream file(file_name, std::ifstream::binary); - read(file); - file.close(); - } - - void RTreeClassifier::read(std::istream &is) - { - int num_trees = 0; - is.read((char*)(&num_trees), sizeof(num_trees)); - is.read((char*)(&classes_), sizeof(classes_)); - is.read((char*)(&original_num_classes_), sizeof(original_num_classes_)); - is.read((char*)(&num_quant_bits_), sizeof(num_quant_bits_)); - - if (num_quant_bits_<1 || num_quant_bits_>8) { - printf("[WARNING] RTC: suspicious value num_quant_bits_=%i found; setting to %i.\n", - num_quant_bits_, (int)DEFAULT_NUM_QUANT_BITS); - num_quant_bits_ = DEFAULT_NUM_QUANT_BITS; - } - - trees_.resize(num_trees); - std::vector::iterator tree_it; - - for (tree_it = trees_.begin(); tree_it != trees_.end(); ++tree_it) { - tree_it->read(is, num_quant_bits_); + const Node* node = treeNodes + ni; + fs << node->x1 << node->y1 << node->x2 << node->y2; } + fs << "]"; // nodes - printf("[OK] Loaded RTC, quantization=%i bits\n", num_quant_bits_); - - countZeroElements(); - } - - void RTreeClassifier::write(const char* file_name) const - { - std::ofstream file(file_name, std::ofstream::binary); - write(file); - file.close(); - } - - void RTreeClassifier::write(std::ostream &os) const - { - int num_trees = trees_.size(); - os.write((char*)(&num_trees), sizeof(num_trees)); - os.write((char*)(&classes_), sizeof(classes_)); - os.write((char*)(&original_num_classes_), sizeof(original_num_classes_)); - os.write((char*)(&num_quant_bits_), sizeof(num_quant_bits_)); - printf("RTreeClassifier::write: num_quant_bits_=%i\n", num_quant_bits_); - - std::vector::const_iterator tree_it; - for (tree_it = trees_.begin(); tree_it != trees_.end(); ++tree_it) - tree_it->write(os); - } - - void RTreeClassifier::saveAllFloatPosteriors(std::string url) - { - printf("[DEBUG] writing all float posteriors to %s...\n", url.c_str()); - for (int i=0; i<(int)trees_.size(); ++i) - trees_[i].savePosteriors(url, (i==0 ? false : true)); - printf("[DEBUG] done\n"); - } - - void RTreeClassifier::saveAllBytePosteriors(std::string url) - { - printf("[DEBUG] writing all byte posteriors to %s...\n", url.c_str()); - for (int i=0; i<(int)trees_.size(); ++i) - trees_[i].savePosteriors2(url, (i==0 ? false : true)); - printf("[DEBUG] done\n"); - } - - - void RTreeClassifier::setFloatPosteriorsFromTextfile_176(std::string url) - { - std::ifstream ifs(url.c_str()); - - for (int i=0; i<(int)trees_.size(); ++i) { - int num_classes = trees_[i].classes_; - assert(num_classes == 176); // TODO: remove this limitation (arose due to SSE2 optimizations) - for (int k=0; k> *post; - assert(ifs.good()); + fs << "posteriors" << "{"; + for( int li = 0; li < numLeavesPerTree; li++ ) + { + stringstream leafName; + leafName << "leaf" << li; + fs << leafName.str() << "[:"; + const float* post = &posteriors[ti*numLeavesPerTree*signatureSize + li*signatureSize]; + for( int ci = 0; ci < signatureSize; ci++ ) + { + fs << post[ci]; } + fs << "]"; // leaf } - classes_ = 176; - - //setQuantization(num_quant_bits_); - - ifs.close(); - printf("[EXPERIMENTAL] read entire tree from '%s'\n", url.c_str()); - } - - - float RTreeClassifier::countZeroElements() - { - int flt_zeros = 0; - int ui8_zeros = 0; - int num_elem = trees_[0].classes(); - for (int i=0; i<(int)trees_.size(); ++i) - for (int k=0; k<(int)trees_[i].num_leaves_; ++k) { - float *p = trees_[i].getPosteriorByIndex(k); - uchar *p2 = trees_[i].getPosteriorByIndex2(k); - assert(p); assert(p2); - for (int j=0; j& keypoints, } /****************************************************************************************\ -* SiftDescriptorExtractor * +* SiftDescriptorExtractor * \****************************************************************************************/ SiftDescriptorExtractor::SiftDescriptorExtractor( double magnification, bool isNormalize, bool recalculateAngles, int nOctaves, int nOctaveLayers, int firstOctave, int angleMode ) @@ -188,7 +188,7 @@ void SiftDescriptorExtractor::write (FileStorage &fs) const } /****************************************************************************************\ -* SurfDescriptorExtractor * +* SurfDescriptorExtractor * \****************************************************************************************/ SurfDescriptorExtractor::SurfDescriptorExtractor( int nOctaves, int nOctaveLayers, bool extended ) @@ -228,6 +228,10 @@ void SurfDescriptorExtractor::write( FileStorage &fs ) const fs << "extended" << surf.extended; } +/****************************************************************************************\ +* Factory functions for descriptor extractor and matcher creating * +\****************************************************************************************/ + Ptr createDescriptorExtractor( const string& descriptorExtractorType ) { DescriptorExtractor* de = 0; @@ -270,7 +274,9 @@ Ptr createDescriptorMatcher( const string& descriptorMatcherT return dm; } - +/****************************************************************************************\ +* BruteForceMatcher L2 specialization * +\****************************************************************************************/ template<> void BruteForceMatcher >::matchImpl( const Mat& descriptors_1, const Mat& descriptors_2, const Mat& /*mask*/, vector& matches ) const @@ -317,7 +323,6 @@ void BruteForceMatcher >::matchImpl( const Mat& descriptors_1, const M #endif } - /****************************************************************************************\ * GenericDescriptorMatch * \****************************************************************************************/ @@ -394,6 +399,9 @@ void GenericDescriptorMatch::clear() collection.clear(); } +/* + * Factory function for GenericDescriptorMatch creating + */ Ptr createGenericDescriptorMatch( const string& genericDescritptorMatchType, const string ¶msFilename ) { GenericDescriptorMatch *descriptorMatch = 0; @@ -409,7 +417,7 @@ Ptr createGenericDescriptorMatch( const string& genericD } else if( ! genericDescritptorMatchType.compare ("CALONDER") ) { - descriptorMatch = new CalonderDescriptorMatch (); + //descriptorMatch = new CalonderDescriptorMatch (); } if( !paramsFilename.empty() && descriptorMatch != 0 ) @@ -626,6 +634,7 @@ void OneWayDescriptorMatch::clear () /****************************************************************************************\ * CalonderDescriptorMatch * \****************************************************************************************/ +#if 0 CalonderDescriptorMatch::Params::Params( const RNG& _rng, const PatchGenerator& _patchGen, int _numTrees, int _depth, int _views, size_t _reducedNumDim, @@ -774,6 +783,7 @@ void CalonderDescriptorMatch::write( FileStorage& fs ) const fs << "numQuantBits" << params.numQuantBits; fs << "printStatus" << params.printStatus; } +#endif /****************************************************************************************\ * FernDescriptorMatch * @@ -827,22 +837,13 @@ void FernDescriptorMatch::trainFernClassifier() { assert( params.filename.empty() ); - vector points; - vector > refimgs; - vector labels; - for( size_t imageIdx = 0; imageIdx < collection.images.size(); imageIdx++ ) - { - for( size_t pointIdx = 0; pointIdx < collection.points[imageIdx].size(); pointIdx++ ) - { - refimgs.push_back(new Mat (collection.images[imageIdx])); - points.push_back(collection.points[imageIdx][pointIdx].pt); - labels.push_back((int)pointIdx); - } - } + vector > points; + for( size_t imgIdx = 0; imgIdx < collection.images.size(); imgIdx++ ) + KeyPoint::convert( collection.points[imgIdx], points[imgIdx] ); - classifier = new FernClassifier( points, refimgs, labels, params.nclasses, params.patchSize, - params.signatureSize, params.nstructs, params.structSize, params.nviews, - params.compressionMethod, params.patchGenerator ); + classifier = new FernClassifier( points, collection.images, vector >(), 0, // each points is a class + params.patchSize, params.signatureSize, params.nstructs, params.structSize, + params.nviews, params.compressionMethod, params.patchGenerator ); } } @@ -966,4 +967,59 @@ void FernDescriptorMatch::clear () classifier.release(); } +/****************************************************************************************\ +* VectorDescriptorMatch * +\****************************************************************************************/ +void VectorDescriptorMatch::add( const Mat& image, vector& keypoints ) +{ + Mat descriptors; + extractor->compute( image, keypoints, descriptors ); + matcher->add( descriptors ); + + collection.add( Mat(), keypoints ); +}; + +void VectorDescriptorMatch::match( const Mat& image, vector& points, vector& keypointIndices ) +{ + Mat descriptors; + extractor->compute( image, points, descriptors ); + + matcher->match( descriptors, keypointIndices ); +}; + +void VectorDescriptorMatch::match( const Mat& image, vector& points, vector& matches ) +{ + Mat descriptors; + extractor->compute( image, points, descriptors ); + + matcher->match( descriptors, matches ); +} + +void VectorDescriptorMatch::match( const Mat& image, vector& points, + vector >& matches, float threshold ) +{ + Mat descriptors; + extractor->compute( image, points, descriptors ); + + matcher->match( descriptors, matches, threshold ); +} + +void VectorDescriptorMatch::clear() +{ + GenericDescriptorMatch::clear(); + matcher->clear(); +} + +void VectorDescriptorMatch::read( const FileNode& fn ) +{ + GenericDescriptorMatch::read(fn); + extractor->read (fn); +} + +void VectorDescriptorMatch::write (FileStorage& fs) const +{ + GenericDescriptorMatch::write(fs); + extractor->write (fs); +} + } diff --git a/modules/features2d/src/planardetect.cpp b/modules/features2d/src/planardetect.cpp index 0ee4d30..e1bdafb 100644 --- a/modules/features2d/src/planardetect.cpp +++ b/modules/features2d/src/planardetect.cpp @@ -692,9 +692,9 @@ Size FernClassifier::getPatchSize() const } -FernClassifier::FernClassifier(const vector& points, - const vector >& refimgs, - const vector& labels, +FernClassifier::FernClassifier(const vector >& points, + const vector& refimgs, + const vector >& labels, int _nclasses, int _patchSize, int _signatureSize, int _nstructs, int _structSize, int _nviews, int _compressionMethod, @@ -829,41 +829,56 @@ void FernClassifier::prepare(int _nclasses, int _patchSize, int _signatureSize, } } +static int calcNumPoints( const vector >& points ) +{ + int count = 0; + for( size_t i = 0; i < points.size(); i++ ) + count += points[i].size(); + return count; +} -void FernClassifier::train(const vector& points, - const vector >& refimgs, - const vector& labels, +void FernClassifier::train(const vector >& points, + const vector& refimgs, + const vector >& 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( points.size() == refimgs.size() ); + int numPoints = calcNumPoints( points ); + _nclasses = (!labels.empty() && _nclasses>0) ? _nclasses : numPoints; 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++ ) + int globalPointIdx = 0; + for( size_t imgIdx = 0; imgIdx < points.size(); imgIdx++ ) { - 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++ ) + const Point2f* imgPoints = &points[imgIdx][0]; + const int* imgLabels = labels.empty() ? 0 : &labels[imgIdx][0]; + for( size_t pointIdx = 0; pointIdx < points[imgIdx].size(); pointIdx++, globalPointIdx++ ) { - patchGenerator(src, pt, patch, patchSize, rng); - for( int f = 0; f < nstructs; f++ ) - posteriors[getLeaf(f, patch)*nclasses + classId]++; + Point2f pt = imgPoints[pointIdx]; + const Mat& src = refimgs[imgIdx]; + int classId = imgLabels==0 ? globalPointIdx : imgLabels[pointIdx]; + if( verbose && (globalPointIdx+1)*progressBarSize/numPoints != globalPointIdx*progressBarSize/numPoints ) + putchar('.'); + CV_Assert( 0 <= classId && classId < nclasses ); + classCounters[classId] += _nviews; + for( int v = 0; v < _nviews; v++ ) + { + patchGenerator(src, pt, patch, patchSize, rng); + for( int f = 0; f < nstructs; f++ ) + posteriors[getLeaf(f, patch)*nclasses + classId]++; + } } } if( verbose ) diff --git a/samples/c/find_obj_calonder.cpp b/samples/c/find_obj_calonder.cpp index 6b138a2..dfc5690 100644 --- a/samples/c/find_obj_calonder.cpp +++ b/samples/c/find_obj_calonder.cpp @@ -1,309 +1,154 @@ -//Calonder descriptor sample -#include - -#if 0 -#include -#include -#include #include -#include -using namespace std; +#include +#include +#include +#include +#include -// Number of training points (set to -1 to use all points) -const int n_points = -1; - -//Draw the border of projection of train image calculed by averaging detected correspondences -const bool draw_border = true; +using namespace std; +using namespace cv; -void cvmSet6(CvMat* m, int row, int col, float val1, float val2, float val3, float val4, float val5, float val6) +void warpPerspectiveRand( const Mat& src, Mat& dst, Mat& H, RNG& rng ) { - cvmSet(m, row, col, val1); - cvmSet(m, row, col + 1, val2); - cvmSet(m, row, col + 2, val3); - cvmSet(m, row, col + 3, val4); - cvmSet(m, row, col + 4, val5); - cvmSet(m, row, col + 5, val6); + H.create(3, 3, CV_32FC1); + H.at(0,0) = rng.uniform( 0.8f, 1.2f); + H.at(0,1) = rng.uniform(-0.1f, 0.1f); + H.at(0,2) = rng.uniform(-0.1f, 0.1f)*src.cols; + H.at(1,0) = rng.uniform(-0.1f, 0.1f); + H.at(1,1) = rng.uniform( 0.8f, 1.2f); + H.at(1,2) = rng.uniform(-0.1f, 0.1f)*src.rows; + H.at(2,0) = rng.uniform( -1e-4f, 1e-4f); + H.at(2,1) = rng.uniform( -1e-4f, 1e-4f); + H.at(2,2) = rng.uniform( 0.8f, 1.2f); + + warpPerspective( src, dst, H, src.size() ); } -void FindAffineTransform(const vector& p1, const vector& p2, CvMat* affine) +int main( int argc, char **argv ) { - int eq_num = 2*(int)p1.size(); - CvMat* A = cvCreateMat(eq_num, 6, CV_32FC1); - CvMat* B = cvCreateMat(eq_num, 1, CV_32FC1); - CvMat* X = cvCreateMat(6, 1, CV_32FC1); - - for(int i = 0; i < (int)p1.size(); i++) + if( argc != 4 && argc != 3 ) { - cvmSet6(A, 2*i, 0, p1[i].x, p1[i].y, 1, 0, 0, 0); - cvmSet6(A, 2*i + 1, 0, 0, 0, 0, p1[i].x, p1[i].y, 1); - cvmSet(B, 2*i, 0, p2[i].x); - cvmSet(B, 2*i + 1, 0, p2[i].y); + cout << "Format:" << endl << + " classifier(xml to write) test_image file_with_train_images_filenames(txt)" << + " or" << endl << + " classifier(xml to read) test_image" << endl; + return -1; } - - cvSolve(A, B, X, CV_SVD); - - cvmSet(affine, 0, 0, cvmGet(X, 0, 0)); - cvmSet(affine, 0, 1, cvmGet(X, 1, 0)); - cvmSet(affine, 0, 2, cvmGet(X, 2, 0)); - cvmSet(affine, 1, 0, cvmGet(X, 3, 0)); - cvmSet(affine, 1, 1, cvmGet(X, 4, 0)); - cvmSet(affine, 1, 2, cvmGet(X, 5, 0)); - - cvReleaseMat(&A); - cvReleaseMat(&B); - cvReleaseMat(&X); -} -void MapVectorAffine(const vector& p1, vector& p2, CvMat* transform) -{ - float a = cvmGet(transform, 0, 0); - float b = cvmGet(transform, 0, 1); - float c = cvmGet(transform, 0, 2); - float d = cvmGet(transform, 1, 0); - float e = cvmGet(transform, 1, 1); - float f = cvmGet(transform, 1, 2); - - for(int i = 0; i < (int)p1.size(); i++) + CalonderClassifier classifier; + if( argc == 4 ) // Train { - float x = a*p1[i].x + b*p1[i].y + c; - float y = d*p1[i].x + e*p1[i].y + f; - p2.push_back(cvPoint(x, y)); + // Read train images and test image + ifstream fst( argv[3], ifstream::in ); + vector trainImgs; + while( !fst.eof() ) + { + string str; + getline( fst, str ); + if (str.empty()) break; + Mat img = imread( str, CV_LOAD_IMAGE_GRAYSCALE ); + if( !img.empty() ) + trainImgs.push_back( img ); + } + if( trainImgs.empty() ) + { + cout << "All train images can not be read." << endl; + return -1; + } + cout << trainImgs.size() << " train images were read." << endl; + + // Extract keypoints from train images + SurfFeatureDetector detector; + vector > trainPoints( trainImgs.size() ); + for( size_t i = 0; i < trainImgs.size(); i++ ) + { + vector kps; + detector.detect( trainImgs[i], kps ); + KeyPoint::convert( kps, trainPoints[i] ); + } + + // Train Calonder classifier on extracted points + classifier.setVerbose( true); + classifier.train( trainPoints, trainImgs ); + + // Write Calonder classifier + FileStorage fs( argv[1], FileStorage::WRITE ); + classifier.write( fs ); } -} - - -float CalcAffineReprojectionError(const vector& p1, const vector& p2, CvMat* transform) -{ - vector mapped_p1; - MapVectorAffine(p1, mapped_p1, transform); - float error = 0; - for(int i = 0; i < (int)p2.size(); i++) + else { - error += ((p2[i].x - mapped_p1[i].x)*(p2[i].x - mapped_p1[i].x)+(p2[i].y - mapped_p1[i].y)*(p2[i].y - mapped_p1[i].y)); + // Read Calonder classifier + FileStorage fs( argv[1], FileStorage::READ ); + classifier.read( fs.root() ); } - - error /= p2.size(); - - return error; -} -#endif - -int main( int, char** ) -{ - printf("calonder_sample is under construction\n"); - return 0; - -#if 0 - IplImage* test_image; - IplImage* train_image; - if (argc < 3) - { - - test_image = cvLoadImage("box_in_scene.png",0); - train_image = cvLoadImage("box.png ",0); - if (!test_image || !train_image) - { - printf("Usage: calonder_sample "); - return 0; - } - } - else - { - test_image = cvLoadImage(argv[2],0); - train_image = cvLoadImage(argv[1],0); - } - - - - - - if (!train_image) - { - printf("Unable to load train image\n"); - return 0; - } - - if (!test_image) - { - printf("Unable to load test image\n"); - return 0; - } - - - - CvMemStorage* storage = cvCreateMemStorage(0); - CvSeq *objectKeypoints = 0, *objectDescriptors = 0; - CvSeq *imageKeypoints = 0, *imageDescriptors = 0; - CvSURFParams params = cvSURFParams(500, 1); - cvExtractSURF( test_image, 0, &imageKeypoints, &imageDescriptors, storage, params ); - cvExtractSURF( train_image, 0, &objectKeypoints, &objectDescriptors, storage, params ); - - - cv::RTreeClassifier detector; - int patch_width = cv::PATCH_SIZE; - int patch_height = cv::PATCH_SIZE; - vector base_set; - int i=0; - CvSURFPoint* point; - - - for (i=0;i<(n_points > 0 ? n_points : objectKeypoints->total);i++) - { - point=(CvSURFPoint*)cvGetSeqElem(objectKeypoints,i); - base_set.push_back(cv::BaseKeypoint(point->pt.x,point->pt.y,train_image)); - } - //Detector training - cv::RNG rng( cvGetTickCount() ); - cv::PatchGenerator gen(0,255,2,false,0.7,1.3,-CV_PI/3,CV_PI/3,-CV_PI/3,CV_PI/3); - - printf("RTree Classifier training...\n"); - detector.train(base_set,rng,gen,24,cv::DEFAULT_DEPTH,2000,(int)base_set.size(),detector.DEFAULT_NUM_QUANT_BITS); - printf("Done\n"); - - float* signature = new float[detector.original_num_classes()]; - float* best_corr; - int* best_corr_idx; - if (imageKeypoints->total > 0) - { - best_corr = new float[imageKeypoints->total]; - best_corr_idx = new int[imageKeypoints->total]; - } - - for(i=0; i < imageKeypoints->total; i++) - { - point=(CvSURFPoint*)cvGetSeqElem(imageKeypoints,i); - int part_idx = -1; - float prob = 0.0f; - - - CvRect roi = cvRect((int)(point->pt.x) - patch_width/2,(int)(point->pt.y) - patch_height/2, patch_width, patch_height); - cvSetImageROI(test_image, roi); - roi = cvGetImageROI(test_image); - if(roi.width != patch_width || roi.height != patch_height) - { - best_corr_idx[i] = part_idx; - best_corr[i] = prob; - } - else - { - cvSetImageROI(test_image, roi); - IplImage* roi_image = cvCreateImage(cvSize(roi.width, roi.height), test_image->depth, test_image->nChannels); - cvCopy(test_image,roi_image); - - detector.getSignature(roi_image, signature); - - - for (int j = 0; j< detector.original_num_classes();j++) - { - if (prob < signature[j]) - { - part_idx = j; - prob = signature[j]; - } - } - - best_corr_idx[i] = part_idx; - best_corr[i] = prob; - - - if (roi_image) - cvReleaseImage(&roi_image); - } - cvResetImageROI(test_image); - } - - float min_prob = 0.0f; - vector object; - vector features; - - for (int j=0;jtotal;j++) - { - float prob = 0.0f; - int idx = -1; - for (i = 0; itotal;i++) - { - if ((best_corr_idx[i]!=j)||(best_corr[i] < min_prob)) - continue; - - if (best_corr[i] > prob) - { - prob = best_corr[i]; - idx = i; - } - } - if (idx >=0) - { - point=(CvSURFPoint*)cvGetSeqElem(objectKeypoints,j); - object.push_back(cvPoint((int)point->pt.x,(int)point->pt.y)); - point=(CvSURFPoint*)cvGetSeqElem(imageKeypoints,idx); - features.push_back(cvPoint((int)point->pt.x,(int)point->pt.y)); - } - } - if ((int)object.size() > 3) - { - CvMat* affine = cvCreateMat(2, 3, CV_32FC1); - FindAffineTransform(object,features,affine); - - vector corners; - vector mapped_corners; - corners.push_back(cvPoint(0,0)); - corners.push_back(cvPoint(0,train_image->height)); - corners.push_back(cvPoint(train_image->width,0)); - corners.push_back(cvPoint(train_image->width,train_image->height)); - MapVectorAffine(corners,mapped_corners,affine); - - //Drawing the result - IplImage* result = cvCreateImage(cvSize(test_image->width > train_image->width ? test_image->width : train_image->width, - train_image->height + test_image->height), - test_image->depth, test_image->nChannels); - cvSetImageROI(result,cvRect(0,0,train_image->width, train_image->height)); - cvCopy(train_image,result); - cvResetImageROI(result); - cvSetImageROI(result,cvRect(0,train_image->height,test_image->width, test_image->height)); - cvCopy(test_image,result); - cvResetImageROI(result); - - for (int i=0;i<(int)features.size();i++) - { - cvLine(result,object[i],cvPoint(features[i].x,features[i].y+train_image->height),cvScalar(255)); - } + if( classifier.empty() ) + { + cout << "Calonder classifier is empty" << endl; + return -1; + } - if (draw_border) - { - cvLine(result,cvPoint(mapped_corners[0].x, mapped_corners[0].y+train_image->height), - cvPoint(mapped_corners[1].x, mapped_corners[1].y+train_image->height),cvScalar(150),3); - cvLine(result,cvPoint(mapped_corners[0].x, mapped_corners[0].y+train_image->height), - cvPoint(mapped_corners[2].x, mapped_corners[2].y+train_image->height),cvScalar(150),3); - cvLine(result,cvPoint(mapped_corners[1].x, mapped_corners[1].y+train_image->height), - cvPoint(mapped_corners[3].x, mapped_corners[3].y+train_image->height),cvScalar(150),3); - cvLine(result,cvPoint(mapped_corners[2].x, mapped_corners[2].y+train_image->height), - cvPoint(mapped_corners[3].x, mapped_corners[3].y+train_image->height),cvScalar(150),3); - } + // Test Calonder classifier on test image and warped one + Mat testImg1 = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE ), testImg2, H12; + if( testImg1.empty() ) + { + cout << "Test image can not be read." << endl; + return -1; + } + warpPerspectiveRand( testImg1, testImg2, H12, theRNG() ); - cvSaveImage("Result.jpg",result); - cvNamedWindow("Result",0); - cvShowImage("Result",result); - cvWaitKey(); - cvReleaseMat(&affine); - cvReleaseImage(&result); - } - else - { - printf("Unable to find correspondence\n"); - } + // Exstract keypoints from test images + SurfFeatureDetector detector; + vector testKeypoints1; detector.detect( testImg1, testKeypoints1 ); + vector testKeypoints2; detector.detect( testImg2, testKeypoints2 ); + vector testPoints1; KeyPoint::convert( testKeypoints1, testPoints1 ); + vector testPoints2; KeyPoint::convert( testKeypoints2, testPoints2 ); - - - if (signature) - delete[] signature; - if (best_corr) - delete[] best_corr; - cvReleaseMemStorage(&storage); - cvReleaseImage(&train_image); - cvReleaseImage(&test_image); + // Calculate Calonder descriptors + int signatureSize = classifier.getSignatureSize(); + vector r1(testPoints1.size()*signatureSize), r2(testPoints2.size()*signatureSize); + vector::iterator rit = r1.begin(); + for( size_t i = 0; i < testPoints1.size(); i++ ) + { + vector s; + classifier( testImg1, testPoints1[i], s ); + copy( s.begin(), s.end(), rit ); + rit += s.size(); + } + rit = r2.begin(); + for( size_t i = 0; i < testPoints2.size(); i++ ) + { + vector s; + classifier( testImg2, testPoints2[i], s ); + copy( s.begin(), s.end(), rit ); + rit += s.size(); + } - return 0; -#endif + Mat descriptors1(testPoints1.size(), classifier.getSignatureSize(), CV_32FC1, &r1[0] ), + descriptors2(testPoints2.size(), classifier.getSignatureSize(), CV_32FC1, &r2[0] ); + + // Match descriptors + BruteForceMatcher > matcher; + matcher.add( descriptors2 ); + vector matches; + matcher.match( descriptors1, matches ); + + // Draw results + // Prepare inlier mask + vector matchesMask( matches.size(), 0 ); + Mat points1t; perspectiveTransform(Mat(testPoints1), points1t, H12); + vector::const_iterator mit = matches.begin(); + for( size_t mi = 0; mi < matches.size(); mi++ ) + { + if( norm(testPoints2[matches[mi]] - points1t.at(mi,0)) < 4 ) // inlier + matchesMask[mi] = 1; + } + // Draw + Mat drawImg; + drawMatches( testImg1, testKeypoints1, testImg2, testKeypoints2, matches, drawImg, CV_RGB(0, 255, 0), CV_RGB(0, 0, 255), matchesMask ); + string winName = "Matches"; + namedWindow( winName, WINDOW_AUTOSIZE ); + imshow( winName, drawImg ); + waitKey(); }