1 // -*- c-basic-offset: 4 ; tab-width: 4 -*-
2 /*
3 * Copyright (C) 2007-2008 Anael Orlinski
4 *
5 * This file is part of Panomatic.
6 *
7 * Panomatic is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * Panomatic is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Panomatic; if not, write to the Free Software
19 * <http://www.gnu.org/licenses/>.
20 */
21 
22 #include "PanoDetector.h"
23 #include <iostream>
24 #include <fstream>
25 #include <sstream>
26 
27 #include <time.h>
28 
29 #include "Utils.h"
30 #include "Tracer.h"
31 #include "hugin_base/hugin_utils/platform.h"
32 
33 #include <algorithms/nona/ComputeImageROI.h>
34 #include <algorithms/basic/CalculateOptimalScale.h>
35 #include <algorithms/basic/LayerStacks.h>
36 #include <nona/RemappedPanoImage.h>
37 #include <nona/ImageRemapper.h>
38 
39 //for multi row strategy
40 #include <panodata/StandardImageVariableGroups.h>
41 #include <panodata/Panorama.h>
42 #include <algorithms/optimizer/ImageGraph.h>
43 #include <algorithms/optimizer/PTOptimizer.h>
44 #include <algorithms/basic/CalculateOverlap.h>
45 
46 #include "ImageImport.h"
47 
48 #ifdef _WIN32
49 #include <direct.h>
50 #else
51 #include <unistd.h>
52 #endif
53 #include <hugin_config.h>
54 
55 #ifndef srandom
56 #define srandom srand
57 #endif
58 
59 #ifdef HAVE_OPENMP
60 #include <omp.h>
61 #endif
62 
63 #define TRACE_IMG(X) {if (_panoDetector.getVerbose() == 1) {TRACE_INFO("i" << _imgData._number << " : " << X << std::endl);}}
64 #define TRACE_PAIR(X) {if (_panoDetector.getVerbose() == 1){ TRACE_INFO("i" << _matchData._i1->_number << " <> " \
65                 "i" << _matchData._i2->_number << " : " << X << std::endl);}}
66 
includeTrailingPathSep(std::string path)67 std::string includeTrailingPathSep(std::string path)
68 {
69     std::string pathWithSep(path);
70 #ifdef _WIN32
71     if(pathWithSep[pathWithSep.length()-1]!='\\' || pathWithSep[pathWithSep.length()-1]!='/')
72     {
73         pathWithSep.append("\\");
74     }
75 #else
76     if(pathWithSep[pathWithSep.length()-1]!='/')
77     {
78         pathWithSep.append("/");
79     }
80 #endif
81     return pathWithSep;
82 };
83 
getKeyfilenameFor(std::string keyfilesPath,std::string filename)84 std::string getKeyfilenameFor(std::string keyfilesPath, std::string filename)
85 {
86     std::string newfilename;
87     if(keyfilesPath.empty())
88     {
89         //if no path for keyfiles is given we are saving into the same directory as image file
90         newfilename=hugin_utils::stripExtension(filename);
91     }
92     else
93     {
94         newfilename = includeTrailingPathSep(keyfilesPath);
95         newfilename.append(hugin_utils::stripPath(hugin_utils::stripExtension(filename)));
96     };
97     newfilename.append(".key");
98     return newfilename;
99 };
100 
PanoDetector()101 PanoDetector::PanoDetector() :
102     _writeAllKeyPoints(false), _verbose(1),
103     _sieve1Width(10), _sieve1Height(10), _sieve1Size(100),
104     _kdTreeSearchSteps(200), _kdTreeSecondDistance(0.25),
105     _minimumMatches(6), _ransacMode(HuginBase::RANSACOptimizer::AUTO), _ransacIters(1000), _ransacDistanceThres(50),
106     _sieve2Width(5), _sieve2Height(5), _sieve2Size(1),
107     _matchingStrategy(ALLPAIRS), _linearMatchLen(1),
108     _test(false), _cores(0), _downscale(true), _cache(false), _cleanup(false),
109     _celeste(false), _celesteThreshold(0.5), _celesteRadius(20),
110     _keypath(""), _outputFile("default.pto"), _outputGiven(false), svmModel(NULL)
111 {
112     _panoramaInfo = new HuginBase::Panorama();
113 }
114 
~PanoDetector()115 PanoDetector::~PanoDetector()
116 {
117     delete _panoramaInfo;
118 }
119 
checkData()120 bool PanoDetector::checkData()
121 {
122     // test linear match data
123     if (_linearMatchLen < 1)
124     {
125         std::cout << "Linear match length must be at least 1." << std::endl;
126         return false;
127     }
128 
129     // check the test mode
130     if (_test)
131     {
132         if (_filesData.size() != 2)
133         {
134             std::cout << "In test mode you must provide exactly 2 images." << std::endl;
135             return false;
136         }
137     }
138 
139     return true;
140 }
141 
printDetails()142 void PanoDetector::printDetails()
143 {
144     std::cout << "Input file           : " << _inputFile << std::endl;
145     if (_keyPointsIdx.size() != 0)
146     {
147         std::cout << "Output file(s)       : keyfile(s) for images";
148         for (unsigned int i = 0; i < _keyPointsIdx.size(); ++i)
149         {
150             std::cout << " " << _keyPointsIdx[i] << std::endl;
151         }
152     }
153     else
154     {
155         if(_writeAllKeyPoints)
156         {
157             std::cout << "Output file(s)       : keyfiles for all images in project" << std::endl;
158         }
159         else
160         {
161             std::cout << "Output file          : " << _outputFile << std::endl;
162         };
163     }
164     if(!_keypath.empty())
165     {
166         std::cout <<     "Path to keyfiles     : " << _keypath << std::endl;
167     };
168     if(_cleanup)
169     {
170         std::cout << "Cleanup temporary files." << std::endl;
171     };
172     if(_cache)
173     {
174         std::cout << "Automatically cache keypoints files to disc." << std::endl;
175     };
176 #ifdef HAVE_OPENMP
177     std::cout << "Number of threads  : " << (_cores>0 ? _cores : omp_get_max_threads()) << std::endl << std::endl;
178 #endif
179     std::cout << "Input image options" << std::endl;
180     std::cout << "  Downscale to half-size : " << (_downscale?"yes":"no") << std::endl;
181     if(_celeste)
182     {
183         std::cout << "Celeste options" << std::endl;
184         std::cout << "  Threshold : " << _celesteThreshold << std::endl;
185         std::cout << "  Radius : " << _celesteRadius << std::endl;
186     }
187     std::cout << "Sieve 1 Options" << std::endl;
188     std::cout << "  Width : " << _sieve1Width << std::endl;
189     std::cout << "  Height : " << _sieve1Height << std::endl;
190     std::cout << "  Size : " << _sieve1Size << std::endl;
191     std::cout << "  ==> Maximum keypoints per image : " << _sieve1Size* _sieve1Height* _sieve1Width << std::endl;
192     std::cout << "KDTree Options" << std::endl;
193     std::cout << "  Search steps : " << _kdTreeSearchSteps << std::endl;
194     std::cout << "  Second match distance : " << _kdTreeSecondDistance << std::endl;
195     std::cout << "Matching Options" << std::endl;
196     switch(_matchingStrategy)
197     {
198         case ALLPAIRS:
199             std::cout << "  Mode : All pairs" << std::endl;
200             break;
201         case LINEAR:
202             std::cout << "  Mode : Linear match with length of " << _linearMatchLen << " image" << std::endl;
203             break;
204         case MULTIROW:
205             std::cout << "  Mode : Multi row" << std::endl;
206             break;
207         case PREALIGNED:
208             std::cout << "  Mode : Prealigned positions" << std::endl;
209             break;
210     };
211     std::cout << "  Distance threshold : " << _ransacDistanceThres << std::endl;
212     std::cout << "RANSAC Options" << std::endl;
213     std::cout << "  Mode : ";
214     switch (_ransacMode)
215     {
216         case HuginBase::RANSACOptimizer::AUTO:
217             std::cout << "auto" << std::endl;
218             break;
219         case HuginBase::RANSACOptimizer::HOMOGRAPHY:
220             std::cout << "homography" << std::endl;
221             break;
222         case HuginBase::RANSACOptimizer::RPY:
223             std::cout << "roll, pitch, yaw" << std::endl;
224             break;
225         case HuginBase::RANSACOptimizer::RPYV:
226             std::cout << "roll, pitch, yaw, fov" << std::endl;
227             break;
228         case HuginBase::RANSACOptimizer::RPYVB:
229             std::cout << "roll, pitch, yaw, fov, distortion" << std::endl;
230             break;
231     }
232     std::cout << "  Iterations : " << _ransacIters << std::endl;
233     std::cout << "  Distance threshold : " << _ransacDistanceThres << std::endl;
234     std::cout << "Minimum matches per image pair: " << _minimumMatches << std::endl;
235     std::cout << "Sieve 2 Options" << std::endl;
236     std::cout << "  Width : " << _sieve2Width << std::endl;
237     std::cout << "  Height : " << _sieve2Height << std::endl;
238     std::cout << "  Size : " << _sieve2Size << std::endl;
239     std::cout << "  ==> Maximum matches per image pair : " << _sieve2Size* _sieve2Height* _sieve2Width << std::endl;
240 }
241 
printFilenames()242 void PanoDetector::printFilenames()
243 {
244     std::cout << std::endl << "Project contains the following images:" << std::endl;
245     for(unsigned int i=0; i<_panoramaInfo->getNrOfImages(); i++)
246     {
247         std::string name(_panoramaInfo->getImage(i).getFilename());
248         if(name.compare(0,_prefix.length(),_prefix)==0)
249         {
250             name=name.substr(_prefix.length(),name.length()-_prefix.length());
251         }
252         std::cout << "Image " << i << std::endl << "  Imagefile: " << name << std::endl;
253         bool writeKeyfileForImage=false;
254         if(!_keyPointsIdx.empty())
255         {
256             for(unsigned j=0; j<_keyPointsIdx.size() && !writeKeyfileForImage; j++)
257             {
258                 writeKeyfileForImage=_keyPointsIdx[j]==i;
259             };
260         };
261         if(_cache || _filesData[i]._hasakeyfile || writeKeyfileForImage)
262         {
263             name=_filesData[i]._keyfilename;
264             if(name.compare(0,_prefix.length(),_prefix)==0)
265             {
266                 name=name.substr(_prefix.length(),name.length()-_prefix.length());
267             }
268             std::cout << "  Keyfile  : " << name;
269             if(writeKeyfileForImage)
270             {
271                 std::cout << " (will be generated)" << std::endl;
272             }
273             else
274             {
275                 std::cout << (_filesData[i]._hasakeyfile?" (will be loaded)":" (will be generated)") << std::endl;
276             };
277         };
278         std::cout << "  Remapped : " << (_filesData[i].NeedsRemapping()?"yes":"no") << std::endl;
279     };
280 };
281 
282 class Runnable
283 {
284 public:
Runnable()285     Runnable() {};
286     virtual void run() = 0;
~Runnable()287     virtual ~Runnable() {};
288 };
289 
290 // definition of a runnable class for image data
291 class ImgDataRunnable : public Runnable
292 {
293 public:
ImgDataRunnable(PanoDetector::ImgData & iImageData,const PanoDetector & iPanoDetector)294     ImgDataRunnable(PanoDetector::ImgData& iImageData, const PanoDetector& iPanoDetector) : Runnable(),
295         _panoDetector(iPanoDetector), _imgData(iImageData) {};
296 
run()297     virtual void run()
298     {
299         TRACE_IMG("Analyzing image...");
300         if (!PanoDetector::AnalyzeImage(_imgData, _panoDetector))
301         {
302             return;
303         }
304         PanoDetector::FindKeyPointsInImage(_imgData, _panoDetector);
305         PanoDetector::FilterKeyPointsInImage(_imgData, _panoDetector);
306         PanoDetector::MakeKeyPointDescriptorsInImage(_imgData, _panoDetector);
307         PanoDetector::RemapBackKeypoints(_imgData, _panoDetector);
308         PanoDetector::BuildKDTreesInImage(_imgData, _panoDetector);
309         PanoDetector::FreeMemoryInImage(_imgData, _panoDetector);
310     }
311 private:
312     const PanoDetector&			_panoDetector;
313     PanoDetector::ImgData&		_imgData;
314 };
315 
316 // definition of a runnable class for writeKeyPoints
317 class WriteKeyPointsRunnable : public Runnable
318 {
319 public:
WriteKeyPointsRunnable(PanoDetector::ImgData & iImageData,const PanoDetector & iPanoDetector)320     WriteKeyPointsRunnable(PanoDetector::ImgData& iImageData, const PanoDetector& iPanoDetector) :
321         _panoDetector(iPanoDetector), _imgData(iImageData) {};
322 
run()323     virtual void run()
324     {
325         TRACE_IMG("Analyzing image...");
326         if (!PanoDetector::AnalyzeImage(_imgData, _panoDetector))
327         {
328             return;
329         }
330         PanoDetector::FindKeyPointsInImage(_imgData, _panoDetector);
331         PanoDetector::FilterKeyPointsInImage(_imgData, _panoDetector);
332         PanoDetector::MakeKeyPointDescriptorsInImage(_imgData, _panoDetector);
333         PanoDetector::RemapBackKeypoints(_imgData, _panoDetector);
334         PanoDetector::FreeMemoryInImage(_imgData, _panoDetector);
335     }
336 private:
337     const PanoDetector&			_panoDetector;
338     PanoDetector::ImgData&		_imgData;
339 };
340 
341 // definition of a runnable class for keypoints data
342 class LoadKeypointsDataRunnable : public Runnable
343 {
344 public:
LoadKeypointsDataRunnable(PanoDetector::ImgData & iImageData,const PanoDetector & iPanoDetector)345     LoadKeypointsDataRunnable(PanoDetector::ImgData& iImageData, const PanoDetector& iPanoDetector) :
346         _panoDetector(iPanoDetector), _imgData(iImageData) {};
347 
run()348     virtual void run()
349     {
350         TRACE_IMG("Loading keypoints...");
351         PanoDetector::LoadKeypoints(_imgData, _panoDetector);
352         PanoDetector::BuildKDTreesInImage(_imgData, _panoDetector);
353     }
354 
355 private:
356     const PanoDetector&			_panoDetector;
357     PanoDetector::ImgData&		_imgData;
358 };
359 
360 // definition of a runnable class for MatchData
361 class MatchDataRunnable : public Runnable
362 {
363 public:
MatchDataRunnable(PanoDetector::MatchData & iMatchData,const PanoDetector & iPanoDetector)364     MatchDataRunnable(PanoDetector::MatchData& iMatchData, const PanoDetector& iPanoDetector) :
365         _panoDetector(iPanoDetector), _matchData(iMatchData) {};
366 
run()367     virtual void run()
368     {
369         //TRACE_PAIR("Matching...");
370         if(!_matchData._i1->_kp.empty() && !_matchData._i2->_kp.empty())
371         {
372             PanoDetector::FindMatchesInPair(_matchData, _panoDetector);
373             PanoDetector::RansacMatchesInPair(_matchData, _panoDetector);
374             PanoDetector::FilterMatchesInPair(_matchData, _panoDetector);
375             TRACE_PAIR("Found " << _matchData._matches.size() << " matches");
376         };
377     }
378 private:
379     const PanoDetector&			_panoDetector;
380     PanoDetector::MatchData&	_matchData;
381 };
382 
LoadSVMModel()383 bool PanoDetector::LoadSVMModel()
384 {
385     std::string model_file = ("celeste.model");
386     std::ifstream test(model_file.c_str());
387     if (!test.good())
388     {
389         std::string install_path_model=hugin_utils::GetDataDir();
390         install_path_model.append(model_file);
391         std::ifstream test2(install_path_model.c_str());
392         if (!test2.good())
393         {
394             std::cout << std::endl << "Couldn't open SVM model file " << model_file << std::endl;
395             std::cout << "Also tried " << install_path_model << std::endl << std::endl;
396             return false;
397         };
398         model_file = install_path_model;
399     }
400     if(!celeste::loadSVMmodel(svmModel,model_file))
401     {
402         svmModel=NULL;
403         return false;
404     };
405     return true;
406 };
407 
408 typedef std::vector<Runnable*> RunnableVector;
409 
RunQueue(std::vector<Runnable * > & queue)410 void RunQueue(std::vector<Runnable*>& queue)
411 {
412 #pragma omp parallel for schedule(dynamic)
413     for (int i = 0; i < queue.size(); ++i)
414     {
415         queue[i]->run();
416     };
417     // now clear queue
418     while (!queue.empty())
419     {
420         delete queue.back();
421         queue.pop_back();
422     };
423 };
424 
run()425 void PanoDetector::run()
426 {
427     // init the random time generator
428     srandom((unsigned int)time(NULL));
429 
430     // Load the input project file
431     if(!loadProject())
432     {
433         return;
434     };
435     if(_writeAllKeyPoints)
436     {
437         for(unsigned int i=0; i<_panoramaInfo->getNrOfImages(); i++)
438         {
439             _keyPointsIdx.push_back(i);
440         };
441     };
442 
443     if(_cleanup)
444     {
445         CleanupKeyfiles();
446         return;
447     };
448 
449     //checking, if memory allows running desired number of threads
450     unsigned long maxImageSize=0;
451     bool withRemap=false;
452     for (ImgDataIt_t aB = _filesData.begin(); aB != _filesData.end(); ++aB)
453     {
454         if(!aB->second._hasakeyfile)
455         {
456             maxImageSize=std::max<unsigned long>(aB->second._detectWidth*aB->second._detectHeight,maxImageSize);
457             if(aB->second.NeedsRemapping())
458             {
459                 withRemap=true;
460             };
461         };
462     };
463 #ifdef HAVE_OPENMP
464     if (_cores == 0)
465     {
466         setCores(omp_get_max_threads());
467     };
468     if (maxImageSize != 0)
469     {
470         unsigned long long maxCores;
471         //determinded factors by testing of some projects
472         //the memory usage seems to be very high
473         //if the memory usage could be decreased these numbers can be decreased
474         if(withRemap)
475         {
476             maxCores=utils::getTotalMemory()/(maxImageSize*75);
477         }
478         else
479         {
480             maxCores=utils::getTotalMemory()/(maxImageSize*50);
481         };
482         if(maxCores<1)
483         {
484             maxCores=1;
485         }
486         if(maxCores<_cores)
487         {
488             if(getVerbose()>0)
489             {
490                 std::cout << "\nThe available memory does not allow running " << _cores << " threads parallel.\n"
491                             << "Running cpfind with " << maxCores << " threads.\n";
492             };
493             setCores(maxCores);
494         };
495     };
496     omp_set_num_threads(_cores);
497 #endif
498     RunnableVector queue;
499     svmModel = NULL;
500     if(_celeste)
501     {
502         TRACE_INFO("\nLoading Celeste model file...\n");
503         if(!LoadSVMModel())
504         {
505             setCeleste(false);
506         };
507     };
508 
509     //print some more information about the images
510     if (_verbose > 0)
511     {
512         printFilenames();
513     }
514 
515     // 2. run analysis of images or keypoints
516 #if _WIN32
517     //multi threading of image loading results sometime in a race condition
518     //try to prevent this by initialisation of codecManager before
519     //running multi threading part
520     std::string s=vigra::impexListExtensions();
521 #endif
522     if (_keyPointsIdx.size() != 0)
523     {
524         if (_verbose > 0)
525         {
526             TRACE_INFO(std::endl << "--- Analyze Images ---" << std::endl);
527         }
528         for (unsigned int i = 0; i < _keyPointsIdx.size(); ++i)
529         {
530             queue.push_back(new WriteKeyPointsRunnable(_filesData[_keyPointsIdx[i]], *this));
531         };
532     }
533     else
534     {
535         TRACE_INFO(std::endl << "--- Analyze Images ---" << std::endl);
536         if (getMatchingStrategy() == MULTIROW)
537         {
538             // when using multirow, don't analyse stacks with linked positions
539             buildMultiRowImageSets();
540             HuginBase::UIntSet imagesToAnalyse;
541             imagesToAnalyse.insert(_image_layer.begin(), _image_layer.end());
542             for (size_t i = 0; i < _image_stacks.size(); i++)
543             {
544                 imagesToAnalyse.insert(_image_stacks[i].begin(), _image_stacks[i].end());
545             }
546             for (HuginBase::UIntSet::const_iterator it = imagesToAnalyse.begin(); it != imagesToAnalyse.end(); ++it)
547             {
548                 if (_filesData[*it]._hasakeyfile)
549                 {
550                     queue.push_back(new LoadKeypointsDataRunnable(_filesData[*it], *this));
551                 }
552                 else
553                 {
554                     queue.push_back(new ImgDataRunnable(_filesData[*it], *this));
555                 };
556             };
557         }
558         else
559         {
560             for (ImgDataIt_t aB = _filesData.begin(); aB != _filesData.end(); ++aB)
561             {
562                 if (aB->second._hasakeyfile)
563                 {
564                     queue.push_back(new LoadKeypointsDataRunnable(aB->second, *this));
565                 }
566                 else
567                 {
568                     queue.push_back(new ImgDataRunnable(aB->second, *this));
569                 }
570             }
571         };
572     }
573     RunQueue(queue);
574 
575     if(svmModel!=NULL)
576     {
577         celeste::destroySVMmodel(svmModel);
578     };
579 
580     // check if the load of images succeed.
581     if (!checkLoadSuccess())
582     {
583         TRACE_INFO("One or more images failed to load. Exiting.");
584         return;
585     }
586 
587     if(_cache)
588     {
589         TRACE_INFO(std::endl << "--- Cache keyfiles to disc ---" << std::endl);
590         for (ImgDataIt_t aB = _filesData.begin(); aB != _filesData.end(); ++aB)
591         {
592             if (!aB->second._hasakeyfile)
593             {
594                 TRACE_INFO("i" << aB->second._number << " : Caching keypoints..." << std::endl);
595                 writeKeyfile(aB->second);
596             };
597         };
598     };
599 
600     // Detect matches if writeKeyPoints wasn't set
601     if(_keyPointsIdx.empty())
602     {
603         switch (getMatchingStrategy())
604         {
605             case ALLPAIRS:
606             case LINEAR:
607                 {
608                     std::vector<HuginBase::UIntSet> imgPairs(_panoramaInfo->getNrOfImages());
609                     if(!match(imgPairs))
610                     {
611                         return;
612                     };
613                 };
614                 break;
615             case MULTIROW:
616                 if(!matchMultiRow())
617                 {
618                     return;
619                 };
620                 break;
621             case PREALIGNED:
622                 {
623                     //check, which image pairs are already connected by control points
624                     std::vector<HuginBase::UIntSet> connectedImages(_panoramaInfo->getNrOfImages());
625                     HuginBase::CPVector cps=_panoramaInfo->getCtrlPoints();
626                     for(HuginBase::CPVector::const_iterator it=cps.begin();it!=cps.end(); ++it)
627                     {
628                         if((*it).mode==HuginBase::ControlPoint::X_Y)
629                         {
630                             connectedImages[(*it).image1Nr].insert((*it).image2Nr);
631                             connectedImages[(*it).image2Nr].insert((*it).image1Nr);
632                         };
633                     };
634                     //build dummy map
635                     std::vector<size_t> imgMap(_panoramaInfo->getNrOfImages());
636                     for(size_t i=0; i<_panoramaInfo->getNrOfImages(); i++)
637                     {
638                         imgMap[i]=i;
639                     };
640                     //and the final matching step
641                     if(!matchPrealigned(_panoramaInfo, connectedImages, imgMap))
642                     {
643                         return;
644                     };
645                 }
646                 break;
647         };
648     }
649 
650     // 5. write output
651     if (_keyPointsIdx.size() != 0)
652     {
653         //Write all keyfiles
654         TRACE_INFO(std::endl<< "--- Write Keyfiles output ---" << std::endl << std::endl);
655         for (unsigned int i = 0; i < _keyPointsIdx.size(); ++i)
656         {
657             writeKeyfile(_filesData[_keyPointsIdx[i]]);
658         };
659         if(_outputGiven)
660         {
661             std::cout << std::endl << "Warning: You have given the --output switch." << std::endl
662                  << "This switch is not compatible with the --writekeyfile or --kall switch." << std::endl
663                  << "If you want to generate the keyfiles and" << std::endl
664                  << "do the matching in the same run use the --cache switch instead." << std::endl << std::endl;
665         };
666     }
667     else
668     {
669         /// Write output project
670         TRACE_INFO(std::endl<< "--- Write Project output ---" << std::endl);
671         writeOutput();
672         TRACE_INFO("Written output to " << _outputFile << std::endl << std::endl);
673     };
674 }
675 
match(std::vector<HuginBase::UIntSet> & checkedPairs)676 bool PanoDetector::match(std::vector<HuginBase::UIntSet> &checkedPairs)
677 {
678     // 3. prepare matches
679     RunnableVector queue;
680     MatchData_t matchesData;
681     unsigned int aLen = _filesData.size();
682     if (getMatchingStrategy()==LINEAR)
683     {
684         aLen = _linearMatchLen;
685     }
686 
687     if (aLen >= _filesData.size())
688     {
689         aLen = _filesData.size() - 1;
690     }
691 
692     for (unsigned int i1 = 0; i1 < _filesData.size(); ++i1)
693     {
694         unsigned int aEnd = i1 + 1 + aLen;
695         if (_filesData.size() < aEnd)
696         {
697             aEnd = _filesData.size();
698         }
699 
700         for (unsigned int i2 = (i1+1); i2 < aEnd; ++i2)
701         {
702             if(set_contains(checkedPairs[i1], i2))
703             {
704                 continue;
705             };
706             // create a new entry in the matches map
707             matchesData.push_back(MatchData());
708 
709             MatchData& aM = matchesData.back();
710             aM._i1 = &(_filesData[i1]);
711             aM._i2 = &(_filesData[i2]);
712 
713             checkedPairs[i1].insert(i2);
714             checkedPairs[i2].insert(i1);
715         }
716     }
717     // 4. find matches
718     TRACE_INFO(std::endl<< "--- Find pair-wise matches ---" << std::endl);
719     for (size_t i = 0; i < matchesData.size(); ++i)
720     {
721         queue.push_back(new MatchDataRunnable(matchesData[i], *this));
722     };
723     RunQueue(queue);
724 
725     // Add detected matches to _panoramaInfo
726     for (size_t i = 0; i < matchesData.size(); ++i)
727     {
728         const MatchData& aM = matchesData[i];
729         for (size_t j = 0; j < aM._matches.size(); ++j)
730         {
731             const lfeat::PointMatchPtr& aPM = aM._matches[j];
732             _panoramaInfo->addCtrlPoint(HuginBase::ControlPoint(aM._i1->_number, aPM->_img1_x, aPM->_img1_y,
733                 aM._i2->_number, aPM->_img2_x, aPM->_img2_y));
734         };
735     };
736     return true;
737 };
738 
loadProject()739 bool PanoDetector::loadProject()
740 {
741     std::ifstream ptoFile(_inputFile.c_str());
742     if (ptoFile.bad())
743     {
744         std::cerr << "ERROR: could not open file: '" << _inputFile << "'!" << std::endl;
745         return false;
746     }
747     _prefix=hugin_utils::getPathPrefix(_inputFile);
748     if(_prefix.empty())
749     {
750         // Get the current working directory:
751         char* buffer;
752 #ifdef _WIN32
753 #define getcwd _getcwd
754 #endif
755         if((buffer=getcwd(NULL,0))!=NULL)
756         {
757             _prefix.append(buffer);
758             free(buffer);
759             _prefix=includeTrailingPathSep(_prefix);
760         }
761     };
762     _panoramaInfo->setFilePrefix(_prefix);
763     AppBase::DocumentData::ReadWriteError err = _panoramaInfo->readData(ptoFile);
764     if (err != AppBase::DocumentData::SUCCESSFUL)
765     {
766         std::cerr << "ERROR: couldn't parse panos tool script: '" << _inputFile << "'!" << std::endl;
767         return false;
768     }
769 
770     // Create a copy of panoramaInfo that will be used to define
771     // image options
772     _panoramaInfoCopy=_panoramaInfo->duplicate();
773 
774     // Add images found in the project file to _filesData
775     const unsigned int nImg = _panoramaInfo->getNrOfImages();
776     unsigned int imgWithKeyfile=0;
777     for (unsigned int imgNr = 0; imgNr < nImg; ++imgNr)
778     {
779         // insert the image in the map
780         _filesData.insert(std::make_pair(imgNr, ImgData()));
781 
782         // get the data
783         ImgData& aImgData = _filesData[imgNr];
784 
785         // get a copy of image info
786         HuginBase::SrcPanoImage img = _panoramaInfoCopy.getSrcImage(imgNr);
787 
788         // set the name
789         aImgData._name = img.getFilename();
790 
791         // modify image position in the copy
792         img.setYaw(0);
793         img.setRoll(0);
794         img.setPitch(0);
795         img.setX(0);
796         img.setY(0);
797         img.setZ(0);
798         img.setActive(true);
799         _panoramaInfoCopy.setImage(imgNr,img);
800 
801         // Number pointing to image info in _panoramaInfo
802         aImgData._number = imgNr;
803 
804         bool needsremap=(img.getHFOV()>=65 && img.getProjection() != HuginBase::SrcPanoImage::FISHEYE_STEREOGRAPHIC);
805         // set image detection size
806         if(needsremap)
807         {
808             _filesData[imgNr]._detectWidth = std::max(img.getSize().width(),img.getSize().height());
809             _filesData[imgNr]._detectHeight = std::max(img.getSize().width(),img.getSize().height());
810             _filesData[imgNr].SetSizeMode(ImgData::REMAPPED);
811         }
812         else
813         {
814             _filesData[imgNr]._detectWidth = img.getSize().width();
815             _filesData[imgNr]._detectHeight = img.getSize().height();
816         };
817 
818         // don't downscale if downscale image is smaller than 1000 pixel
819         if (_downscale && std::min(img.getSize().width(), img.getSize().height()) > 2000)
820         {
821             _filesData[imgNr]._detectWidth >>= 1;
822             _filesData[imgNr]._detectHeight >>= 1;
823             if (!_filesData[imgNr].NeedsRemapping())
824             {
825                 _filesData[imgNr].SetSizeMode(ImgData::DOWNSCALED);
826             };
827         }
828 
829         // set image remapping options
830         if (aImgData.GetSizeMode() == ImgData::REMAPPED)
831         {
832             aImgData._projOpts.setProjection(HuginBase::PanoramaOptions::STEREOGRAPHIC);
833             aImgData._projOpts.setHFOV(250);
834             aImgData._projOpts.setVFOV(250);
835             aImgData._projOpts.setWidth(250);
836             aImgData._projOpts.setHeight(250);
837 
838             // determine size of output image.
839             // The old code did not work with images with images with a FOV
840             // approaching 180 degrees
841             vigra::Rect2D roi=estimateOutputROI(_panoramaInfoCopy,aImgData._projOpts,imgNr);
842             double scalefactor = std::max((double)_filesData[imgNr]._detectWidth / roi.width(),
843                                      (double)_filesData[imgNr]._detectHeight / roi.height() );
844 
845             // resize output canvas
846             vigra::Size2D canvasSize((int)aImgData._projOpts.getWidth() * scalefactor,
847                                      (int)aImgData._projOpts.getHeight() * scalefactor);
848             aImgData._projOpts.setWidth(canvasSize.width(), false);
849             aImgData._projOpts.setHeight(canvasSize.height());
850 
851             // set roi to cover the remapped input image
852             roi *= scalefactor;
853             _filesData[imgNr]._detectWidth = roi.width();
854             _filesData[imgNr]._detectHeight = roi.height();
855             aImgData._projOpts.setROI(roi);
856         }
857 
858         // Specify if the image has an associated keypoint file
859 
860         aImgData._keyfilename = getKeyfilenameFor(_keypath,aImgData._name);
861         aImgData._hasakeyfile = hugin_utils::FileExists(aImgData._keyfilename);
862         if(aImgData._hasakeyfile)
863         {
864             imgWithKeyfile++;
865         };
866     }
867     //update masks, convert positive masks into negative masks
868     //because positive masks works only if the images are on the final positions
869     _panoramaInfoCopy.updateMasks(true);
870 
871     //if all images has keyfile, we don't need to load celeste model file
872     if(nImg==imgWithKeyfile)
873     {
874         _celeste=false;
875     };
876     return true;
877 }
878 
checkLoadSuccess()879 bool PanoDetector::checkLoadSuccess()
880 {
881     if(!_keyPointsIdx.empty())
882     {
883         for (unsigned int i = 0; i < _keyPointsIdx.size(); ++i)
884         {
885             if(_filesData[_keyPointsIdx[i]]._loadFail)
886             {
887                 return false;
888             };
889         };
890     }
891     else
892     {
893         for (unsigned int aFileN = 0; aFileN < _filesData.size(); ++aFileN)
894         {
895             if(_filesData[aFileN]._loadFail)
896             {
897                 return false;
898             };
899         };
900     };
901     return true;
902 }
903 
CleanupKeyfiles()904 void PanoDetector::CleanupKeyfiles()
905 {
906     for (ImgDataIt_t aB = _filesData.begin(); aB != _filesData.end(); ++aB)
907     {
908         if (aB->second._hasakeyfile)
909         {
910             remove(aB->second._keyfilename.c_str());
911         };
912     };
913 };
914 
buildMultiRowImageSets()915 void PanoDetector::buildMultiRowImageSets()
916 {
917     std::vector<HuginBase::UIntVector> stacks = HuginBase::getSortedStacks(_panoramaInfo);
918     //get image with median exposure for search with cp generator
919     for (size_t i = 0; i < stacks.size(); ++i)
920     {
921         size_t index = 0;
922         if (_panoramaInfo->getImage(*(stacks[i].begin())).getExposureValue() != _panoramaInfo->getImage(*(stacks[i].rbegin())).getExposureValue())
923         {
924             index = stacks[i].size() / 2;
925         };
926         _image_layer.insert(stacks[i][index]);
927         if (stacks[i].size()>1)
928         {
929             //build list for stacks, consider only unlinked stacks
930             if (!_panoramaInfo->getImage(*(stacks[i].begin())).YawisLinked())
931             {
932                 _image_stacks.push_back(stacks[i]);
933             };
934         };
935     };
936 };
937 
matchMultiRow()938 bool PanoDetector::matchMultiRow()
939 {
940     RunnableVector queue;
941     MatchData_t matchesData;
942     //step 1
943     std::vector<HuginBase::UIntSet> checkedImagePairs(_panoramaInfo->getNrOfImages());
944     for (size_t i = 0; i < _image_stacks.size();i++)
945     {
946         //build match list for stacks
947         for(unsigned int j=0; j<_image_stacks[i].size()-1; j++)
948         {
949             const size_t img1 = _image_stacks[i][j];
950             const size_t img2 = _image_stacks[i][j + 1];
951             matchesData.push_back(MatchData());
952             MatchData& aM=matchesData.back();
953             if (img1 < img2)
954             {
955                 aM._i1 = &(_filesData[img1]);
956                 aM._i2 = &(_filesData[img2]);
957             }
958             else
959             {
960                 aM._i1 = &(_filesData[img2]);
961                 aM._i2 = &(_filesData[img1]);
962             };
963             checkedImagePairs[img1].insert(img2);
964             checkedImagePairs[img2].insert(img1);
965         };
966     };
967     //build match data list for image pairs
968     if(_image_layer.size()>1)
969     {
970         for (HuginBase::UIntSet::const_iterator it = _image_layer.begin(); it != _image_layer.end(); ++it)
971         {
972             const size_t img1 = *it;
973             HuginBase::UIntSet::const_iterator it2 = it;
974             ++it2;
975             if (it2 != _image_layer.end())
976             {
977                 const size_t img2 = *it2;
978                 matchesData.push_back(MatchData());
979                 MatchData& aM = matchesData.back();
980                 if (img1 < img2)
981                 {
982                     aM._i1 = &(_filesData[img1]);
983                     aM._i2 = &(_filesData[img2]);
984                 }
985                 else
986                 {
987                     aM._i1 = &(_filesData[img2]);
988                     aM._i2 = &(_filesData[img1]);
989                 };
990                 checkedImagePairs[img1].insert(img2);
991                 checkedImagePairs[img2].insert(img1);
992             };
993         };
994     };
995     TRACE_INFO(std::endl<< "--- Find matches ---" << std::endl);
996     for (size_t i = 0; i < matchesData.size(); ++i)
997     {
998         queue.push_back(new MatchDataRunnable(matchesData[i], *this));
999     };
1000     RunQueue(queue);
1001 
1002     // Add detected matches to _panoramaInfo
1003     for (size_t i = 0; i < matchesData.size(); ++i)
1004     {
1005         const MatchData& aM = matchesData[i];
1006         for (size_t j = 0; j < aM._matches.size(); ++j)
1007         {
1008             const lfeat::PointMatchPtr& aPM = aM._matches[j];
1009             _panoramaInfo->addCtrlPoint(HuginBase::ControlPoint(aM._i1->_number, aPM->_img1_x, aPM->_img1_y,
1010                 aM._i2->_number, aPM->_img2_x, aPM->_img2_y));
1011         };
1012     };
1013 
1014     // step 2: connect all image groups
1015     queue.clear();
1016     matchesData.clear();
1017     HuginBase::Panorama mediumPano = _panoramaInfo->getSubset(_image_layer);
1018     HuginGraph::ImageGraph graph(mediumPano);
1019     const HuginGraph::ImageGraph::Components comps = graph.GetComponents();
1020     const size_t n = comps.size();
1021     if(n>1)
1022     {
1023         std::vector<unsigned int> ImagesGroups;
1024         for(size_t i=0; i<n; i++)
1025         {
1026             HuginBase::UIntSet::iterator imgIt = _image_layer.begin();
1027             std::advance(imgIt, *(comps[i].begin()));
1028             ImagesGroups.push_back(*imgIt);
1029             if(comps[i].size()>1)
1030             {
1031                 imgIt = _image_layer.begin();
1032                 std::advance(imgIt, *(comps[i].rbegin()));
1033                 ImagesGroups.push_back(*imgIt);
1034             }
1035         };
1036         for(unsigned int i=0; i<ImagesGroups.size()-1; i++)
1037         {
1038             for(unsigned int j=i+1; j<ImagesGroups.size(); j++)
1039             {
1040                 size_t img1=ImagesGroups[i];
1041                 size_t img2=ImagesGroups[j];
1042                 //skip already checked image pairs
1043                 if(set_contains(checkedImagePairs[img1],img2))
1044                 {
1045                     continue;
1046                 };
1047                 matchesData.push_back(MatchData());
1048                 MatchData& aM = matchesData.back();
1049                 if (img1 < img2)
1050                 {
1051                     aM._i1 = &(_filesData[img1]);
1052                     aM._i2 = &(_filesData[img2]);
1053                 }
1054                 else
1055                 {
1056                     aM._i1 = &(_filesData[img2]);
1057                     aM._i2 = &(_filesData[img1]);
1058                 };
1059                 checkedImagePairs[img1].insert(img2);
1060                 checkedImagePairs[img2].insert(img1);
1061             };
1062         };
1063         TRACE_INFO(std::endl<< "--- Find matches in images groups ---" << std::endl);
1064         for (size_t i = 0; i < matchesData.size(); ++i)
1065         {
1066             queue.push_back(new MatchDataRunnable(matchesData[i], *this));
1067         };
1068         RunQueue(queue);
1069 
1070         for (size_t i = 0; i < matchesData.size(); ++i)
1071         {
1072             const MatchData& aM = matchesData[i];
1073             for (size_t j = 0; j < aM._matches.size(); ++j)
1074             {
1075                 const lfeat::PointMatchPtr& aPM = aM._matches[j];
1076                 _panoramaInfo->addCtrlPoint(HuginBase::ControlPoint(aM._i1->_number, aPM->_img1_x, aPM->_img1_y,
1077                     aM._i2->_number, aPM->_img2_x, aPM->_img2_y));
1078             };
1079         };
1080     };
1081     // step 3: now connect all overlapping images
1082     queue.clear();
1083     matchesData.clear();
1084     HuginBase::Panorama optPano=_panoramaInfo->getSubset(_image_layer);
1085     HuginGraph::ImageGraph graph2(optPano);
1086     if(graph2.IsConnected())
1087     {
1088         if(_image_layer.size()>2)
1089         {
1090             //reset translation parameters
1091             HuginBase::VariableMapVector varMapVec = optPano.getVariables();
1092             for(size_t i=0; i<varMapVec.size(); i++)
1093             {
1094                 map_get(varMapVec[i], "TrX").setValue(0);
1095                 map_get(varMapVec[i], "TrY").setValue(0);
1096                 map_get(varMapVec[i], "TrZ").setValue(0);
1097             };
1098             optPano.updateVariables(varMapVec);
1099             //next steps happens only when all images are connected;
1100             //now optimize panorama
1101             HuginBase::PanoramaOptions opts = optPano.getOptions();
1102             opts.setProjection(HuginBase::PanoramaOptions::EQUIRECTANGULAR);
1103             opts.optimizeReferenceImage=0;
1104             // calculate proper scaling, 1:1 resolution.
1105             // Otherwise optimizer distances are meaningless.
1106             opts.setWidth(30000, false);
1107             opts.setHeight(15000);
1108 
1109             optPano.setOptions(opts);
1110             int w = hugin_utils::roundi(HuginBase::CalculateOptimalScale::calcOptimalScale(optPano) * opts.getWidth());
1111             opts.setWidth(w);
1112             opts.setHeight(w/2);
1113             optPano.setOptions(opts);
1114 
1115             //generate optimize vector, optimize only yaw and pitch
1116             HuginBase::OptimizeVector optvars;
1117             for (unsigned i=0; i < optPano.getNrOfImages(); i++)
1118             {
1119                 std::set<std::string> imgopt;
1120                 if(i==opts.optimizeReferenceImage)
1121                 {
1122                     //optimize only anchors pitch, not yaw
1123                     imgopt.insert("p");
1124                 }
1125                 else
1126                 {
1127                     imgopt.insert("p");
1128                     imgopt.insert("y");
1129                 };
1130                 optvars.push_back(imgopt);
1131             }
1132             optPano.setOptimizeVector(optvars);
1133 
1134             // remove vertical and horizontal control points
1135             HuginBase::CPVector cps = optPano.getCtrlPoints();
1136             HuginBase::CPVector newCP;
1137             for (HuginBase::CPVector::const_iterator it = cps.begin(); it != cps.end(); ++it)
1138             {
1139                 if (it->mode == HuginBase::ControlPoint::X_Y)
1140                 {
1141                     newCP.push_back(*it);
1142                 }
1143             }
1144             optPano.setCtrlPoints(newCP);
1145 
1146             if (getVerbose() < 2)
1147             {
1148                 PT_setProgressFcn(ptProgress);
1149                 PT_setInfoDlgFcn(ptinfoDlg);
1150             };
1151             //in a first step do a pairwise optimisation
1152             HuginBase::AutoOptimise::autoOptimise(optPano, false);
1153             //now the final optimisation
1154             HuginBase::PTools::optimize(optPano);
1155             if (getVerbose() < 2)
1156             {
1157                 PT_setProgressFcn(NULL);
1158                 PT_setInfoDlgFcn(NULL);
1159             };
1160 
1161             //now match overlapping images
1162             std::vector<size_t> imgMap(_image_layer.begin(), _image_layer.end());
1163             if(!matchPrealigned(&optPano, checkedImagePairs, imgMap, false))
1164             {
1165                 return false;
1166             };
1167         };
1168     }
1169     else
1170     {
1171         if(!match(checkedImagePairs))
1172         {
1173             return false;
1174         };
1175     };
1176     return true;
1177 };
1178 
matchPrealigned(HuginBase::Panorama * pano,std::vector<HuginBase::UIntSet> & connectedImages,std::vector<size_t> imgMap,bool exactOverlap)1179 bool PanoDetector::matchPrealigned(HuginBase::Panorama* pano, std::vector<HuginBase::UIntSet> &connectedImages, std::vector<size_t> imgMap, bool exactOverlap)
1180 {
1181     RunnableVector queue;
1182     MatchData_t matchesData;
1183     HuginBase::Panorama tempPano = pano->duplicate();
1184     if(!exactOverlap)
1185     {
1186         // increase hfov by 25 % to handle narrow overlaps (or even no overlap) better
1187         HuginBase::VariableMapVector varMapVec = tempPano.getVariables();
1188         for(size_t i=0; i<tempPano.getNrOfImages(); i++)
1189         {
1190             HuginBase::Variable& hfovVar = map_get(varMapVec[i], "v");
1191             hfovVar.setValue(std::min(360.0, 1.25 * hfovVar.getValue()));
1192         };
1193         tempPano.updateVariables(varMapVec);
1194     };
1195     HuginBase::CalculateImageOverlap overlap(&tempPano);
1196     overlap.calculate(10);
1197     for(size_t i=0; i<tempPano.getNrOfImages()-1; i++)
1198     {
1199         for(size_t j=i+1; j<tempPano.getNrOfImages(); j++)
1200         {
1201             if(set_contains(connectedImages[imgMap[i]],imgMap[j]))
1202             {
1203                 continue;
1204             };
1205             if(overlap.getOverlap(i,j)>0)
1206             {
1207                 matchesData.push_back(MatchData());
1208                 MatchData& aM = matchesData.back();
1209                 aM._i1 = &(_filesData[imgMap[i]]);
1210                 aM._i2 = &(_filesData[imgMap[j]]);
1211                 connectedImages[imgMap[i]].insert(imgMap[j]);
1212                 connectedImages[imgMap[j]].insert(imgMap[i]);
1213             };
1214         };
1215     };
1216 
1217     TRACE_INFO(std::endl<< "--- Find matches for overlapping images ---" << std::endl);
1218     for (size_t i = 0; i < matchesData.size(); ++i)
1219     {
1220         queue.push_back(new MatchDataRunnable(matchesData[i], *this));
1221     };
1222     RunQueue(queue);
1223 
1224     // Add detected matches to _panoramaInfo
1225     for (size_t i = 0; i < matchesData.size(); ++i)
1226     {
1227         const MatchData& aM = matchesData[i];
1228         for (size_t j = 0; j < aM._matches.size(); ++j)
1229         {
1230             const lfeat::PointMatchPtr& aPM = aM._matches[j];
1231             _panoramaInfo->addCtrlPoint(HuginBase::ControlPoint(aM._i1->_number, aPM->_img1_x, aPM->_img1_y,
1232                 aM._i2->_number, aPM->_img2_x, aPM->_img2_y));
1233         };
1234     };
1235 
1236     return true;
1237 };
1238