/**************************************************************************** * VCGLib o o * * Visual and Computer Graphics Library o o * * _ O _ * * Copyright(C) 2004-2016 \/)\/ * * Visual Computing Lab /\/| * * ISTI - Italian National Research Council | * * \ * * All rights reserved. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * * for more details. * * * ****************************************************************************/ #ifndef RANSAC_MATCHING_H #define RANSAC_MATCHING_H #include #include #include #include #include namespace vcg { /** BaseFeature a no-feature feature * * Basically it serve the purpose of evaluating the ransac framework factoring out the goodness of the feature. * */ template class BaseFeature { public: BaseFeature():_v(0) {} typename MeshType::VertexType *_v; typename MeshType::CoordType P() {return _v->cP();} }; template class BaseFeatureSet { public: typedef BaseFeature FeatureType; typedef typename MeshType::VertexType VertexType; typedef typename MeshType::ScalarType ScalarType; class Param { public: Param() { featureSampleRatio = 0.5; // the number of feature that we choose on the total number of samples. } ScalarType featureSampleRatio; }; std::vector fixFeatureVec; std::vector movFeatureVec; FeatureType &ff(int i) { return fixFeatureVec[i]; } FeatureType &mf(int i) { return movFeatureVec[i]; } int ffNum() const { return fixFeatureVec.size(); } int mfNum() const { return movFeatureVec.size(); } void Init(MeshType &fix, MeshType &mov, std::vector &fixSampleVec, std::vector &movSampleVec, Param &fpp) { this->fixFeatureVec.resize(fixSampleVec.size()*fpp.featureSampleRatio); for(int i=0;ifixFeatureVec[i]._v = fixSampleVec[i]; this->movFeatureVec.resize(movSampleVec.size()*fpp.featureSampleRatio); for(int i=0;imovFeatureVec[i]._v = movSampleVec[i]; printf("Generated %i Features on Fix\n",this->fixFeatureVec.size()); printf("Generated %i Features on Mov\n",this->movFeatureVec.size()); } // Returns the indexes of all the fix features matching a given one (from mov usually) // remember that the idea is that // we are aliging mov (that could be a single map) to fix (that could be a set of already aligned maps) void getMatchingFixFeatureVec(FeatureType &q, vector &ffiVec, size_t maxMatchingFeature) { ffiVec.resize(std::min(fixFeatureVec.size(),maxMatchingFeature)); for(int i=0;i class NDFeature { public: NDFeature():_v(0) {} typename MeshType::VertexType *_v; typename MeshType::CoordType nd; // typename MeshType::CoordType P() {return _v->cP();} }; template class NDFeatureSet { public: typedef NDFeature FeatureType; typedef typename MeshType::VertexType VertexType; typedef typename MeshType::CoordType CoordType; typedef typename MeshType::ScalarType ScalarType; class Param { public: Param() { levAbs=CoordType(0,0,0); levPerc[0] = 0.01; levPerc[1] = levPerc[0]*2.0; levPerc[2] = levPerc[1]*2.0; } CoordType levPerc; CoordType levAbs; }; std::vector fixFeatureVec; std::vector movFeatureVec; KdTree *fixFeatureTree; FeatureType &ff(int i) { return fixFeatureVec[i]; } FeatureType &mf(int i) { return movFeatureVec[i]; } int ffNum() const { return fixFeatureVec.size(); } int mfNum() const { return movFeatureVec.size(); } void Init(MeshType &fix, MeshType &mov, std::vector &fixSampleVec, std::vector &movSampleVec, Param &pp) { ScalarType dd = std::max(fix.bbox.Diag(),mov.bbox.Diag()); if(pp.levAbs == CoordType(0,0,0)) pp.levAbs= pp.levPerc * dd; BuildNDFeatureVector(fix,fixSampleVec,pp.levAbs,fixFeatureVec); BuildNDFeatureVector(mov,movSampleVec,pp.levAbs,movFeatureVec); ConstDataWrapper cdw( &(fixFeatureVec[0].nd), fixFeatureVec.size(), sizeof(FeatureType)); fixFeatureTree = new KdTree(cdw); printf("Generated %i ND Features on Fix\n",this->fixFeatureVec.size()); printf("Generated %i ND Features on Mov\n",this->movFeatureVec.size()); } static void BuildNDFeatureVector(MeshType &m, std::vector &sampleVec, Point3f &distLev, std::vector &featureVec ) { tri::UpdateNormal::PerVertexNormalized(m); tri::Smooth::VertexNormalLaplacian(m,10); VertexConstDataWrapper ww(m); KdTree tree(ww); featureVec.resize(sampleVec.size()); const Point3f sqDistLev(distLev[0]*distLev[0], distLev[1]*distLev[1], distLev[2]*distLev[2]); for(int i=0;i ptIndVec; std::vector sqDistVec; tree.doQueryDist(sampleVec[i]->P(), distLev[2], ptIndVec, sqDistVec); Point3f varSum(0,0,0); Point3i varCnt(0,0,0); for(int j=0;j &ffiVec, int maxNum) { ffiVec.clear(); typename KdTree::PriorityQueue pq; this->fixFeatureTree->doQueryK(q.nd,maxNum,pq); for(int i=0;i features matching a given feature * * The framework, given two meshes (fix and mov), will search for a triplet of * matching features that brings mov onto fix. * * Validity of a transformation is checked by mean of two poisson disk sampling of the input meshes. */ template class RansacFramework { typedef typename FeatureSetType::FeatureType FeatureType; typedef typename FeatureSetType::Param FeatureParam; typedef typename MeshType::CoordType CoordType; typedef typename MeshType::BoxType BoxType; typedef typename MeshType::ScalarType ScalarType; typedef typename MeshType::VertexType VertexType; typedef typename MeshType::VertexPointer VertexPointer; typedef typename MeshType::VertexIterator VertexIterator; typedef typename MeshType::EdgeType EdgeType; typedef typename MeshType::EdgeIterator EdgeIterator; typedef typename MeshType::FaceType FaceType; typedef typename MeshType::FacePointer FacePointer; typedef typename MeshType::FaceIterator FaceIterator; typedef typename MeshType::FaceContainer FaceContainer; typedef Matrix44 Matrix44Type; public: class Param { public: Param() { iterMax=100; samplingRadiusPerc=0.005; samplingRadiusAbs=0; evalSize=1000; inlierRatioThr=0.3; inlierDistanceThrPerc = 1.5; // the distance between a transformed mov sample and the corresponding on fix should be 1.5 * sampling dist. congruenceThrPerc = 2.0; // the distance between two matching features must be within 2.0 * sampling distance minFeatureDistancePerc = 4.0; // the distance between two chosen features must be at least 4.0 * sampling distance maxMatchingFeatureNum = 100; areaThrPerc = 20.0; // Triplets that make small triangles are discarded } ScalarType inlierRatioThr; ScalarType inlierDistanceThrPerc; ScalarType congruenceThrPerc; ScalarType minFeatureDistancePerc; ScalarType samplingRadiusPerc; ScalarType samplingRadiusAbs; ScalarType areaThrPerc; int iterMax; int evalSize; int maxMatchingFeatureNum; ScalarType inlierSquareThr() const { return pow(samplingRadiusAbs* inlierDistanceThrPerc,2); } }; class Candidate { public: int fixInd[3]; int movInd[3]; int inlierNum; int evalSize; Matrix44Type Tr; ScalarType err() const {return float(inlierNum)/float(evalSize);} bool operator <(const Candidate &cc) const { return this->err() > cc.err(); } }; FeatureSetType FS; std::vector fixConsensusVec, movConsensusVec; KdTree *consensusTree; // Given three pairs of sufficiently different distances (e.g. the edges of a scalene triangle) // it finds the permutation that brings the vertexes so that the distances match. // The meaning of the permutation vector nm0,nm1,nm2 is that the (N)ew index of (M)ov vertx i is the value of nmi bool FindPermutation(int d01, int d02, int d12, int m01, int m02, int m12, int nm[], Param &pp) { ScalarType eps = pp.samplingRadiusAbs*2.0; if(fabs(d01-m01) ww(fixM); // KdTree(ww) mTree; MeshType tmpM; int neededSizeSum=0; int foundCnt=0; printf("Testing Feature size %i\n",testSize); for(int i=0;i closeFeatureVec; FS.getMatchingFixFeatureVec(FS.mf(i), closeFeatureVec, j); for(int k=0; k::AddVertex(tmpM, FS.mf(i).P()); tmpM.vert.back().Q() = neededSize; neededSizeSum+=neededSize; if(neededSize::PerVertexQualityRamp(tmpM); tri::io::ExporterPLY::Save(tmpM,filename, tri::io::Mask::IOM_VERTCOLOR + tri::io::Mask::IOM_VERTQUALITY); printf("Found %i / %i Average Needed Size %5.2f on %i\n",foundCnt,FS.mfNum(), float(neededSizeSum)/FS.mfNum(),testSize); } // The main loop. // Choose three points on mov that make a scalene triangle // and search on fix three other points with matchng distances void Process_SearchEvaluateTriple (vector &cVec, Param &pp) { math::MarsenneTwisterRNG rnd; // ScalarType congruenceEps = pow(pp.samplingRadiusAbs * pp.congruenceThrPerc,2.0f); ScalarType congruenceEps = pp.samplingRadiusAbs * pp.congruenceThrPerc; ScalarType minFeatureDistEps = pp.samplingRadiusAbs * pp.minFeatureDistancePerc; ScalarType minAreaThr = pp.samplingRadiusAbs * pp.samplingRadiusAbs *pp.areaThrPerc; printf("Starting search congruenceEps = samplingRadiusAbs * 3.0 = %6.2f \n",congruenceEps); int iterCnt=0; while ( (iterCnt < pp.iterMax) && (cVec.size()<100) ) { Candidate c; // Choose a random pair of features from mov c.movInd[0] = rnd.generate(FS.mfNum()); c.movInd[1] = rnd.generate(FS.mfNum()); ScalarType d01 = Distance(FS.mf(c.movInd[0]).P(),FS.mf(c.movInd[1]).P()); if( d01 > minFeatureDistEps ) { c.movInd[2] = rnd.generate(FS.mfNum()); ScalarType d02=Distance(FS.mf(c.movInd[0]).P(),FS.mf(c.movInd[2]).P()); ScalarType d12=Distance(FS.mf(c.movInd[1]).P(),FS.mf(c.movInd[2]).P()); ScalarType areaTri = DoubleArea(Triangle3(FS.mf(c.movInd[0]).P(), FS.mf(c.movInd[1]).P(), FS.mf(c.movInd[2]).P() )); if( ( d02 > minFeatureDistEps ) && // Sample are sufficiently distant ( d12 > minFeatureDistEps ) && ( areaTri > minAreaThr) && ( fabs(d01-d02) > congruenceEps ) && // and they make a scalene triangle ( fabs(d01-d12) > congruenceEps ) && ( fabs(d12-d02) > congruenceEps ) ) { // Find a congruent triple on mov printf("Starting search of a [%i] congruent triple for %4i %4i %4i - %6.2f %6.2f %6.2f\n", iterCnt,c.movInd[0],c.movInd[1],c.movInd[2],d01,d02,d12); // As a first Step we ask for three vectors of matching features; std::vector fixFeatureVec0; FS.getMatchingFixFeatureVec(FS.mf(c.movInd[0]), fixFeatureVec0,pp.maxMatchingFeatureNum); std::vector fixFeatureVec1; FS.getMatchingFixFeatureVec(FS.mf(c.movInd[1]), fixFeatureVec1,pp.maxMatchingFeatureNum); std::vector fixFeatureVec2; FS.getMatchingFixFeatureVec(FS.mf(c.movInd[2]), fixFeatureVec2,pp.maxMatchingFeatureNum); int congrNum=0; int congrGoodNum=0; for(int i=0;i100) break; c.fixInd[0]=fixFeatureVec0[i]; for(int j=0;j100) break; c.fixInd[1]=fixFeatureVec1[j]; ScalarType m01 = Distance(FS.ff(c.fixInd[0]).P(),FS.ff(c.fixInd[1]).P()); if( (fabs(m01-d01)100) break; c.fixInd[2]=fixFeatureVec2[k]; ScalarType m02=Distance(FS.ff(c.fixInd[0]).P(),FS.ff(c.fixInd[2]).P()); ScalarType m12=Distance(FS.ff(c.fixInd[1]).P(),FS.ff(c.fixInd[2]).P()); if( (fabs(m02-d02) pp.inlierRatioThr ){ printf("- - Found %lu th good congruent triple %i %i %i -- %f / %i \n", cVec.size(), c.movInd[0],c.movInd[1],c.movInd[2],c.err(),pp.evalSize); // printf(" - %4.3f %4.3f %4.3f - %4.3f %4.3f %4.3f \n", // FS.ff(c.fixInd[0]).nd[0], FS.ff(c.fixInd[0]).nd[1], FS.ff(c.fixInd[0]).nd[2], // FS.mf(c.movInd[0]).nd[0], FS.mf(c.movInd[0]).nd[1],FS.mf(c.movInd[0]).nd[2]); ++congrGoodNum; cVec.push_back(c); } } } } } } printf("Completed Search of congruent triple (found %i / %i good/congruent)\n",congrGoodNum,congrNum); } } ++iterCnt; } // end While printf("Found %lu candidates \n",cVec.size()); sort(cVec.begin(),cVec.end()); printf("best candidate %f \n",cVec[0].err()); pp.evalSize = FS.mfNum(); for(int i=0;i::iterator pi=movConsensusVec.begin(); for(int j=0;j<2;++j) { for(int i=0;idoQueryClosest(qp,ind,squareDist); if(squareDist < sqThr) ++c.inlierNum; ++pi; } // Early bailout if after 1/2 of the test we have a very low consensus reject if((j==0) && (c.inlierNum < mid/10)) { c.inlierNum *=2; return; } } } void DumpInlier(MeshType &m, Candidate &c, Param &pp) { ScalarType sqThr = pp.inlierSquareThr(); for(int i=0;idoQueryClosest(qp,ind,squareDist); if(squareDist < sqThr) tri::Allocator::AddVertex(m,qp); } } // Find the transformation that matches the mov onto the fix // eg M * piMov = piFix Matrix44f GenerateMatchingMatrix(Candidate &c, Param pp) { std::vector pFix(3); pFix[0]= FS.ff(c.fixInd[0]).P(); pFix[1]= FS.ff(c.fixInd[1]).P(); pFix[2]= FS.ff(c.fixInd[2]).P(); std::vector pMov(3); pMov[0]= FS.mf(c.movInd[0]).P(); pMov[1]= FS.mf(c.movInd[1]).P(); pMov[2]= FS.mf(c.movInd[2]).P(); Point3f upFix = vcg::Normal(pFix[0],pFix[1],pFix[2]); Point3f upMov = vcg::Normal(pMov[0],pMov[1],pMov[2]); upFix.Normalize(); upMov.Normalize(); upFix *= Distance(pFix[0],pFix[1]); upMov *= Distance(pMov[0],pMov[1]); for(int i=0;i<3;++i) pFix.push_back(pFix[i]+upFix); for(int i=0;i<3;++i) pMov.push_back(pMov[i]+upMov); Matrix44f res; ComputeRigidMatchMatrix(pFix,pMov,res); return res; } void Init(MeshType &fixM, MeshType &movM, Param &pp, FeatureParam &fpp) { tri::UpdateNormal::PerVertexNormalizedPerFaceNormalized(fixM); tri::UpdateNormal::PerVertexNormalizedPerFaceNormalized(movM); // First a bit of Sampling typedef tri::TrivialPointerSampler BaseSampler; typename tri::SurfaceSampling::PoissonDiskParam pdp; pdp.randomSeed = 0; pdp.bestSampleChoiceFlag = true; pdp.bestSamplePoolSize = 20; int t0=clock(); pp.samplingRadiusAbs = pp.samplingRadiusPerc *fixM.bbox.Diag(); BaseSampler pdSampler; std::vector fixSampleVec; tri::SurfaceSampling::PoissonDiskPruning(pdSampler, fixM, pp.samplingRadiusAbs,pdp); std::swap(pdSampler.sampleVec,fixSampleVec); std::vector movSampleVec; tri::SurfaceSampling::PoissonDiskPruning(pdSampler, movM, pp.samplingRadiusAbs,pdp); std::swap(pdSampler.sampleVec,movSampleVec); int t1=clock(); printf("Poisson Sampling of surfaces %5.2f ( %iv and %iv) \n",float(t1-t0)/CLOCKS_PER_SEC,fixSampleVec.size(),movSampleVec.size()); printf("Sampling Radius %f \n",pp.samplingRadiusAbs); for(int i=0;ifixConsensusVec.push_back(fixSampleVec[i]->P()); for(int i=0;imovConsensusVec.push_back(movSampleVec[i]->P()); FS.Init(fixM, movM, fixSampleVec, movSampleVec, fpp); std::random_shuffle(movConsensusVec.begin(),movConsensusVec.end()); VectorConstDataWrapper > ww(fixConsensusVec); consensusTree = new KdTree(ww); } }; } //end namespace vcg #endif // RANSAC_MATCHING_H