00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include <iostream>
00022 #include <fstream>
00023 #include <vector>
00024 #include <string>
00025 #include <boost/foreach.hpp>
00026 #include <boost/shared_ptr.hpp>
00027
00028 #include <tclap/CmdLine.h>
00029
00030 #include <localfeatures/KeyPointDetector.h>
00031 #include <localfeatures/CircularKeyPointDescriptor.h>
00032 #include <localfeatures/Sieve.h>
00033 #include <localfeatures/KeyPointIO.h>
00034
00035 #include <vigra/impex.hxx>
00036 #include <vigra/stdimage.hxx>
00037 #include <vigra/stdimagefunctions.hxx>
00038 #include <vigra/rgbvalue.hxx>
00039
00040 using namespace std;
00041 using namespace TCLAP;
00042 using namespace lfeat;
00043
00044 const char* kVersion="0.9.5";
00045
00046 #define TRACE_IMG(A) cerr << A << std::endl
00047 #define TRACE_INFO(A) cerr << A << std::endl
00048
00049
00050
00051
00052
00053
00054
00055
00056 class KeyPointVectInsertor : public lfeat::KeyPointInsertor
00057 {
00058 public:
00059 KeyPointVectInsertor ( KeyPointVect_t& iVect ) : _v ( iVect ) {};
00060 inline virtual void operator() ( const lfeat::KeyPoint& k )
00061 {
00062 _v.push_back ( KeyPointPtr ( new lfeat::KeyPoint ( k ) ) );
00063 }
00064
00065 private:
00066 KeyPointVect_t& _v;
00067 };
00068
00069
00070 class SieveExtractorKP : public lfeat::SieveExtractor<KeyPointPtr>
00071 {
00072 public:
00073 SieveExtractorKP ( KeyPointVect_t& iV ) : _v ( iV ) {};
00074 inline virtual void operator() ( const KeyPointPtr& k )
00075 {
00076 _v.push_back ( k );
00077 }
00078 private:
00079 KeyPointVect_t& _v;
00080 };
00081
00082 bool DetectKeypoints ( const std::string& imgfile, bool downscale,
00083 double surfScoreThreshold,
00084 KeyPointPtr preKeypoint,
00085 bool onlyInterestPoints, int sieveWidth,
00086 int sieveHeight, int sieveSize, KeypointWriter& writer )
00087 {
00088 TRACE_IMG ( "Analyze image..." );
00089 try
00090
00091 {
00092 vigra::ImageImportInfo aImageInfo ( imgfile.c_str() );
00093
00094 int aNewImgWidth = aImageInfo.width();
00095 int aNewImgHeight = aImageInfo.height();
00096
00097 int aOrigImgWidth = aNewImgWidth;
00098 int aOrigImgHeight = aNewImgHeight;
00099
00100 int scale = 1;
00101 if ( downscale )
00102 {
00103 aNewImgWidth >>= 1;
00104 aNewImgHeight >>= 1;
00105 scale = 2;
00106 }
00107
00108 vigra::DImage aImageDouble ( aNewImgWidth, aNewImgHeight );
00109
00110 if ( aImageInfo.isGrayscale() )
00111 {
00112 if ( downscale )
00113 {
00114 TRACE_IMG ( "Load greyscale..." );
00115 vigra::DImage aImageG ( aImageInfo.width(), aImageInfo.height() );
00116 importImage ( aImageInfo, destImage ( aImageG ) );
00117 vigra::resizeImageNoInterpolation (
00118 aImageG.upperLeft(),
00119 aImageG.upperLeft() + vigra::Diff2D ( aNewImgWidth * 2, aNewImgHeight * 2 ),
00120 vigra::DImage::Accessor(),
00121 aImageDouble.upperLeft(),
00122 aImageDouble.lowerRight(),
00123 vigra::DImage::Accessor() );
00124 }
00125 else
00126 {
00127 TRACE_IMG ( "Load greyscale..." );
00128 importImage ( aImageInfo, destImage ( aImageDouble ) );
00129 }
00130 }
00131 else
00132 {
00133 TRACE_IMG ( "Load RGB..." );
00134
00135 vigra::DRGBImage aImageRGB ( aImageInfo.width(), aImageInfo.height() );
00136
00137 if ( aImageInfo.numExtraBands() == 1 )
00138
00139 {
00140
00141 TRACE_INFO ( "Image with alpha channels are not supported" );
00142 return false;
00143
00144 }
00145
00146 else if ( aImageInfo.numExtraBands() == 0 )
00147
00148 {
00149
00150 vigra::importImage ( aImageInfo, destImage ( aImageRGB ) );
00151 }
00152 else
00153 {
00154 TRACE_INFO ( "Image with multiple alpha channels are not supported" );
00155 return false;
00156 }
00157
00158 if ( downscale )
00159 {
00160 TRACE_IMG ( "Resize to greyscale double..." );
00161 vigra::resizeImageNoInterpolation (
00162 aImageRGB.upperLeft(),
00163 aImageRGB.upperLeft() + vigra::Diff2D ( aNewImgWidth * 2, aNewImgHeight * 2 ),
00164 vigra::RGBToGrayAccessor<vigra::RGBValue<double> >(),
00165 aImageDouble.upperLeft(),
00166 aImageDouble.lowerRight(),
00167 vigra::DImage::Accessor() );
00168
00169 }
00170 else
00171 {
00172
00173 TRACE_IMG ( "Convert to greyscale double..." );
00174 vigra::copyImage ( aImageRGB.upperLeft(),
00175 aImageRGB.lowerRight(),
00176 vigra::RGBToGrayAccessor<vigra::RGBValue<double> >(),
00177 aImageDouble.upperLeft(),
00178 vigra::DImage::Accessor() );
00179 }
00180 }
00181
00182
00183 ImageInfo imginfo(imgfile, aOrigImgWidth, aOrigImgHeight);
00184
00185 TRACE_IMG ( "Build integral image..." );
00186
00187 lfeat::Image img;
00188 img.init ( aImageDouble );
00189
00190 KeyPointVect_t kp;
00191 KeyPointVectInsertor aInsertor = KeyPointVectInsertor ( kp );
00192 if ( ! preKeypoint)
00193 {
00194
00195 lfeat::KeyPointDetector aKP;
00196 aKP.setScoreThreshold ( surfScoreThreshold );
00197
00198
00199 aKP.detectKeypoints ( img, aInsertor );
00200
00201 TRACE_IMG ( "Found "<< kp.size() << " interest points." );
00202
00203 TRACE_IMG ( "Filtering keypoints..." );
00204
00205 lfeat::Sieve<lfeat::KeyPointPtr, lfeat::KeyPointPtrSort > aSieve ( sieveWidth, sieveHeight, sieveSize );
00206
00207 double aXF = ( double ) sieveWidth / ( double ) aImageDouble.width();
00208 double aYF = ( double ) sieveHeight / ( double ) aImageDouble.height();
00209 BOOST_FOREACH ( KeyPointPtr& aK, kp )
00210 aSieve.insert ( aK, ( int ) ( aK->_x * aXF ), ( int ) ( aK->_y * aYF ) );
00211
00212
00213 kp.clear();
00214
00215
00216 SieveExtractorKP aSieveExt ( kp );
00217 aSieve.extract ( aSieveExt );
00218
00219 TRACE_IMG ( "Kept " << kp.size() << " interest points." );
00220 }
00221 else
00222 {
00223 kp.push_back(boost::shared_ptr<KeyPoint>(preKeypoint));
00224 }
00225
00226 lfeat::KeyPointDescriptor* aKPD;
00227 aKPD = new lfeat::CircularKeyPointDescriptor( img );
00228 TRACE_IMG ( "Generating descriptors and writing output..." );
00229
00230
00231 int dims = aKPD->getDescriptorLength();
00232 if ( onlyInterestPoints )
00233 {
00234 dims = 0;
00235 }
00236
00237
00238
00239 KeyPointVect_t kp_new_ori;
00240
00241 BOOST_FOREACH ( KeyPointPtr& aK, kp )
00242 {
00243 if (!( preKeypoint && preKeypoint->_ori > -10000))
00244 {
00245 double angles[4];
00246 int nAngles = aKPD->assignOrientation ( *aK, angles );
00247 std::cerr << "Orientations:" << aK->_ori;
00248 for (int i=0; i < nAngles; i++)
00249 {
00250
00251 KeyPointPtr aKn = KeyPointPtr ( new lfeat::KeyPoint ( *aK ) );
00252 aKn->_ori = angles[i];
00253 std::cerr << " " << aKn->_ori;
00254 kp_new_ori.push_back(aKn);
00255 }
00256 std::cerr << std::endl;
00257 }
00258 }
00259
00260
00261 kp.insert(kp.end(), kp_new_ori.begin(), kp_new_ori.end());
00262
00263 writer.writeHeader ( imginfo, kp.size(), aKPD->getDescriptorLength() );
00264
00265 BOOST_FOREACH ( KeyPointPtr& aK, kp )
00266 {
00267 if ( !onlyInterestPoints )
00268 {
00269 aKPD->makeDescriptor ( *aK );
00270 }
00271 writer.writeKeypoint ( aK->_x * scale, aK->_y * scale, aK->_scale * scale, aK->_ori,
00272 aK->_score, dims, aK->_vec );
00273 }
00274 writer.writeFooter();
00275 delete aKPD;
00276 }
00277
00278 catch ( std::exception& e )
00279
00280 {
00281
00282 TRACE_INFO ( "An error happened while computing keypoints : caught exception: " << e.what() << endl );
00283
00284 return false;
00285
00286 }
00287
00288 return true;
00289 }
00290
00291
00292 class MyOutput : public StdOutput
00293 {
00294 public:
00295
00296 virtual void failure ( CmdLineInterface& c, ArgException& e )
00297 {
00298 std::cerr << "Parse error: " << e.argId() << std::endl << " " << e.error() << std::endl << std::endl << endl;
00299 usage ( c );
00300 }
00301
00302 virtual void usage ( CmdLineInterface& c )
00303 {
00304 int iML = 30;
00305 cout << "Basic usage : " << endl;
00306 cout << " "<< c.getProgramName() << " [options ] IMG" << endl;
00307
00308 cout << endl <<"All options : " << endl;
00309 list<Arg*> args = c.getArgList();
00310 for ( ArgListIterator it = args.begin(); it != args.end(); it++ )
00311 {
00312 string aL = ( *it )->longID();
00313 string aD = ( *it )->getDescription();
00314
00315 size_t p = aD.find_first_of ( "\t" );
00316 while ( p != string::npos )
00317 {
00318 string aD1 = aD.substr ( 0, p );
00319 string aD2 = aD.substr ( p+1, aD.size() - p + 1 );
00320
00321 aD = aD1 + "\n" + string ( iML, ' ' ) + aD2;
00322 p = aD.find_first_of ( "\t" );
00323 }
00324
00325
00326 if ( (int)aL.size() > iML )
00327 {
00328 cout << aL << endl << string ( iML, ' ' ) << aD << endl;
00329 }
00330 else
00331 {
00332 cout << aL << string ( iML - aL.size(), ' ' ) << aD << endl;
00333 }
00334 }
00335 }
00336
00337 virtual void version ( CmdLineInterface& c )
00338 {
00339 cout << "my version message: 0.1" << endl;
00340 }
00341 };
00342
00343
00344
00345 void parseOptions ( int argc, char** argv )
00346 {
00347 try
00348 {
00349
00350 CmdLine cmd ( "keypoints", ' ', kVersion );
00351
00352 MyOutput my;
00353 cmd.setOutput ( &my );
00354
00355 SwitchArg aArgFullScale ( "","fullscale", "Uses full scale image to detect keypoints (default:false)\n", false );
00356
00357 ValueArg<int> aArgSurfScoreThreshold ( "","surfscore", "Detection score threshold (default : 1000)\n", false, 1000, "int" );
00358 ValueArg<int> aArgSieve1Width ( "","sievewidth", "Interest point sieve: Number of buckets on width (default : 10)", false, 10, "int" );
00359 ValueArg<int> aArgSieve1Height ( "","sieveheight", "Interest point sieve : Number of buckets on height (default : 10)", false, 10, "int" );
00360 ValueArg<int> aArgSieve1Size ( "","sievesize", "Interest point sieve : Max points per bucket (default : 10)\n", false, 10, "int" );
00361 ValueArg<std::string> aArgOutputFormat ( "","format", "Output format (text, autopano-xml, descperf), default text\n", false, "text", "string" );
00362 ValueArg<std::string> aArgOutputFile ( "o","output", "Output file. If not specified, print to standard out\n", false, "", "string" );
00363 SwitchArg aArgInterestPoints ( "","interestpoints", "output only the interest points and the scale (default:false)\n", false );
00364 ValueArg<std::string> aArgFixedInterestPoint ( "","ip", "Compute descriptor at x,y,scale,ori \n", false, "", "string" );
00365
00366 cmd.add ( aArgSurfScoreThreshold );
00367 cmd.add ( aArgFullScale );
00368 cmd.add ( aArgSieve1Width );
00369 cmd.add ( aArgSieve1Height );
00370 cmd.add ( aArgSieve1Size );
00371 cmd.add ( aArgOutputFormat );
00372 cmd.add ( aArgOutputFile );
00373 cmd.add ( aArgInterestPoints );
00374 cmd.add ( aArgFixedInterestPoint );
00375
00376
00377
00378
00379
00380
00381 UnlabeledMultiArg<string> aArgFiles ( "fileName", "Image files", true, "string" );
00382 cmd.add ( aArgFiles );
00383
00384 cmd.parse ( argc,argv );
00385
00386
00387
00388
00389 vector<string> aFiles = aArgFiles.getValue();
00390 if ( aFiles.size() != 1 )
00391 {
00392 exit ( 1 );
00393 }
00394
00395 double surfScoreThreshold=1000;
00396 if ( aArgSurfScoreThreshold.isSet() )
00397 {
00398 surfScoreThreshold = ( aArgSurfScoreThreshold.getValue() );
00399 }
00400
00401 bool downscale = true;
00402 if ( aArgFullScale.isSet() )
00403 {
00404 downscale = false;
00405 }
00406
00407 int sieveWidth = 10;
00408 if ( aArgSieve1Width.isSet() )
00409 {
00410 sieveWidth = aArgSieve1Width.getValue();
00411 }
00412 int sieveHeight = 10;
00413 if ( aArgSieve1Height.isSet() )
00414 {
00415 sieveHeight = aArgSieve1Height.getValue();
00416 }
00417 int sieveSize = 10;
00418 if ( aArgSieve1Size.isSet() )
00419 {
00420 sieveSize = aArgSieve1Size.getValue();
00421 }
00422
00423 bool onlyInterestPoints = false;
00424 if ( aArgInterestPoints.isSet() )
00425 {
00426 onlyInterestPoints = true;
00427 }
00428
00429 std::ostream* outstream;
00430 if ( aArgOutputFile.isSet() )
00431 {
00432 outstream = new std::ofstream(aArgOutputFile.getValue().c_str());
00433 }
00434 else
00435 {
00436 outstream = & std::cout;
00437 }
00438
00439 KeypointWriter* writer = 0;
00440 std::string outputformat = "text";
00441 if ( aArgOutputFormat.isSet() )
00442 {
00443 outputformat = aArgOutputFormat.getValue();
00444 }
00445 if (outputformat == "text")
00446 {
00447 writer = new SIFTFormatWriter(*outstream);
00448 }
00449 else if (outputformat == "autopano-sift-xml")
00450 {
00451 writer = new AutopanoSIFTWriter(*outstream);
00452 }
00453 else if (outputformat == "descperf")
00454 {
00455 writer = new DescPerfFormatWriter(*outstream);
00456 }
00457 else
00458 {
00459 std::cerr << "Unknown output format, valid values are text, autopano-sift-xml, descperf" << std::endl;
00460 exit(1);
00461 }
00462
00463
00464 KeyPointPtr preKPPtr;
00465 if ( aArgFixedInterestPoint.isSet() )
00466 {
00467 preKPPtr = KeyPointPtr(new KeyPoint());
00468 preKPPtr->_x = -10001;
00469 preKPPtr->_ori = -10001;
00470 int nf = sscanf(aArgFixedInterestPoint.getValue().c_str(), "%lf:%lf:%lf:%lf",
00471 &(preKPPtr->_x), &(preKPPtr->_y), &(preKPPtr->_scale), &(preKPPtr->_ori));
00472 std::cerr << "passed orientation: " << preKPPtr->_ori << std::endl;
00473 if (nf < 3)
00474 {
00475 std::cerr << "Invalid value for --ip option, expected --ip x:y:scale:ori" << std::endl;
00476 exit(1);
00477 }
00478 }
00479
00480 DetectKeypoints ( aFiles[0], downscale, surfScoreThreshold, preKPPtr, onlyInterestPoints, sieveWidth, sieveHeight, sieveSize, *writer );
00481
00482 if ( aArgOutputFile.isSet() )
00483 {
00484 delete outstream;
00485 }
00486
00487 }
00488 catch ( ArgException& e )
00489 {
00490 cout << "ERROR: " << e.error() << " " << e.argId() << endl;
00491 }
00492 }
00493
00494 int main ( int argc, char** argv )
00495 {
00496 std::cerr << "keypoints " << kVersion << " by Anael Orlinski - naouel@naouel.org" << endl << endl;
00497
00498
00499 parseOptions ( argc, argv );
00500
00501 return 0;
00502
00503 }