1 // Copyright (c) 2018, ETH Zurich and UNC Chapel Hill.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 //     * Redistributions of source code must retain the above copyright
8 //       notice, this list of conditions and the following disclaimer.
9 //
10 //     * Redistributions in binary form must reproduce the above copyright
11 //       notice, this list of conditions and the following disclaimer in the
12 //       documentation and/or other materials provided with the distribution.
13 //
14 //     * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of
15 //       its contributors may be used to endorse or promote products derived
16 //       from this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 // POSSIBILITY OF SUCH DAMAGE.
29 //
30 // Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de)
31 
32 #include "ui/feature_matching_widget.h"
33 
34 #include "feature/matching.h"
35 #include "ui/options_widget.h"
36 #include "ui/thread_control_widget.h"
37 
38 namespace colmap {
39 
40 class FeatureMatchingTab : public QWidget {
41  public:
42   FeatureMatchingTab(QWidget* parent, OptionManager* options);
43 
44   virtual void Run() = 0;
45 
46  protected:
47   void CreateGeneralOptions();
48 
49   OptionManager* options_;
50   OptionsWidget* options_widget_;
51   QGridLayout* grid_layout_;
52   ThreadControlWidget* thread_control_widget_;
53 };
54 
55 class ExhaustiveMatchingTab : public FeatureMatchingTab {
56  public:
57   ExhaustiveMatchingTab(QWidget* parent, OptionManager* options);
58   void Run() override;
59 };
60 
61 class SequentialMatchingTab : public FeatureMatchingTab {
62  public:
63   SequentialMatchingTab(QWidget* parent, OptionManager* options);
64   void Run() override;
65 };
66 
67 class VocabTreeMatchingTab : public FeatureMatchingTab {
68  public:
69   VocabTreeMatchingTab(QWidget* parent, OptionManager* options);
70   void Run() override;
71 };
72 
73 class SpatialMatchingTab : public FeatureMatchingTab {
74  public:
75   SpatialMatchingTab(QWidget* parent, OptionManager* options);
76   void Run() override;
77 };
78 
79 class TransitiveMatchingTab : public FeatureMatchingTab {
80  public:
81   TransitiveMatchingTab(QWidget* parent, OptionManager* options);
82   void Run() override;
83 };
84 
85 class CustomMatchingTab : public FeatureMatchingTab {
86  public:
87   CustomMatchingTab(QWidget* parent, OptionManager* options);
88   void Run() override;
89 
90  private:
91   std::string match_list_path_;
92   QComboBox* match_type_cb_;
93 };
94 
FeatureMatchingTab(QWidget * parent,OptionManager * options)95 FeatureMatchingTab::FeatureMatchingTab(QWidget* parent, OptionManager* options)
96     : QWidget(parent),
97       options_(options),
98       options_widget_(new OptionsWidget(this)),
99       grid_layout_(new QGridLayout(this)),
100       thread_control_widget_(new ThreadControlWidget(this)) {}
101 
CreateGeneralOptions()102 void FeatureMatchingTab::CreateGeneralOptions() {
103   options_widget_->AddSpacer();
104   options_widget_->AddSpacer();
105   options_widget_->AddSection("General Options");
106   options_widget_->AddSpacer();
107 
108   options_widget_->AddOptionInt(&options_->sift_matching->num_threads,
109                                 "num_threads", -1);
110   options_widget_->AddOptionBool(&options_->sift_matching->use_gpu, "use_gpu");
111   options_widget_->AddOptionText(&options_->sift_matching->gpu_index,
112                                  "gpu_index");
113   options_widget_->AddOptionDouble(&options_->sift_matching->max_ratio,
114                                    "max_ratio");
115   options_widget_->AddOptionDouble(&options_->sift_matching->max_distance,
116                                    "max_distance");
117   options_widget_->AddOptionBool(&options_->sift_matching->cross_check,
118                                  "cross_check");
119   options_widget_->AddOptionInt(&options_->sift_matching->max_num_matches,
120                                 "max_num_matches");
121   options_widget_->AddOptionDouble(&options_->sift_matching->max_error,
122                                    "max_error");
123   options_widget_->AddOptionDouble(&options_->sift_matching->confidence,
124                                    "confidence", 0, 1, 0.00001, 5);
125   options_widget_->AddOptionInt(&options_->sift_matching->max_num_trials,
126                                 "max_num_trials");
127   options_widget_->AddOptionDouble(&options_->sift_matching->min_inlier_ratio,
128                                    "min_inlier_ratio", 0, 1, 0.001, 3);
129   options_widget_->AddOptionInt(&options_->sift_matching->min_num_inliers,
130                                 "min_num_inliers");
131   options_widget_->AddOptionBool(&options_->sift_matching->multiple_models,
132                                  "multiple_models");
133   options_widget_->AddOptionBool(&options_->sift_matching->guided_matching,
134                                  "guided_matching");
135 
136   options_widget_->AddSpacer();
137 
138   QScrollArea* options_scroll_area = new QScrollArea(this);
139   options_scroll_area->setAlignment(Qt::AlignHCenter);
140   options_scroll_area->setWidget(options_widget_);
141   grid_layout_->addWidget(options_scroll_area, grid_layout_->rowCount(), 0);
142 
143   QPushButton* run_button = new QPushButton(tr("Run"), this);
144   grid_layout_->addWidget(run_button, grid_layout_->rowCount(), 0);
145   connect(run_button, &QPushButton::released, this, &FeatureMatchingTab::Run);
146 }
147 
ExhaustiveMatchingTab(QWidget * parent,OptionManager * options)148 ExhaustiveMatchingTab::ExhaustiveMatchingTab(QWidget* parent,
149                                              OptionManager* options)
150     : FeatureMatchingTab(parent, options) {
151   options_widget_->AddOptionInt(&options_->exhaustive_matching->block_size,
152                                 "block_size", 2);
153 
154   CreateGeneralOptions();
155 }
156 
Run()157 void ExhaustiveMatchingTab::Run() {
158   options_widget_->WriteOptions();
159 
160   Thread* matcher = new ExhaustiveFeatureMatcher(*options_->exhaustive_matching,
161                                                  *options_->sift_matching,
162                                                  *options_->database_path);
163   thread_control_widget_->StartThread("Matching...", true, matcher);
164 }
165 
SequentialMatchingTab(QWidget * parent,OptionManager * options)166 SequentialMatchingTab::SequentialMatchingTab(QWidget* parent,
167                                              OptionManager* options)
168     : FeatureMatchingTab(parent, options) {
169   options_widget_->AddOptionInt(&options_->sequential_matching->overlap,
170                                 "overlap");
171   options_widget_->AddOptionBool(
172       &options_->sequential_matching->quadratic_overlap, "quadratic_overlap");
173   options_widget_->AddOptionBool(&options_->sequential_matching->loop_detection,
174                                  "loop_detection");
175   options_widget_->AddOptionInt(
176       &options_->sequential_matching->loop_detection_period,
177       "loop_detection_period");
178   options_widget_->AddOptionInt(
179       &options_->sequential_matching->loop_detection_num_images,
180       "loop_detection_num_images");
181   options_widget_->AddOptionInt(
182       &options_->sequential_matching->loop_detection_num_nearest_neighbors,
183       "loop_detection_num_nearest_neighbors");
184   options_widget_->AddOptionInt(
185       &options_->sequential_matching->loop_detection_num_checks,
186       "loop_detection_num_checks", 1);
187   options_widget_->AddOptionInt(
188       &options_->sequential_matching
189            ->loop_detection_num_images_after_verification,
190       "loop_detection_num_images_after_verification", 0);
191   options_widget_->AddOptionInt(
192       &options_->sequential_matching->loop_detection_max_num_features,
193       "loop_detection_max_num_features", -1);
194   options_widget_->AddOptionFilePath(
195       &options_->sequential_matching->vocab_tree_path, "vocab_tree_path");
196 
197   CreateGeneralOptions();
198 }
199 
Run()200 void SequentialMatchingTab::Run() {
201   options_widget_->WriteOptions();
202 
203   if (options_->sequential_matching->loop_detection &&
204       !ExistsFile(options_->sequential_matching->vocab_tree_path)) {
205     QMessageBox::critical(this, "", tr("Invalid vocabulary tree path."));
206     return;
207   }
208 
209   Thread* matcher = new SequentialFeatureMatcher(*options_->sequential_matching,
210                                                  *options_->sift_matching,
211                                                  *options_->database_path);
212   thread_control_widget_->StartThread("Matching...", true, matcher);
213 }
214 
VocabTreeMatchingTab(QWidget * parent,OptionManager * options)215 VocabTreeMatchingTab::VocabTreeMatchingTab(QWidget* parent,
216                                            OptionManager* options)
217     : FeatureMatchingTab(parent, options) {
218   options_widget_->AddOptionInt(&options_->vocab_tree_matching->num_images,
219                                 "num_images");
220   options_widget_->AddOptionInt(
221       &options_->vocab_tree_matching->num_nearest_neighbors,
222       "num_nearest_neighbors");
223   options_widget_->AddOptionInt(&options_->vocab_tree_matching->num_checks,
224                                 "num_checks", 1);
225   options_widget_->AddOptionInt(
226       &options_->vocab_tree_matching->num_images_after_verification,
227       "num_images_after_verification", 0);
228   options_widget_->AddOptionInt(
229       &options_->vocab_tree_matching->max_num_features, "max_num_features", -1);
230   options_widget_->AddOptionFilePath(
231       &options_->vocab_tree_matching->vocab_tree_path, "vocab_tree_path");
232 
233   CreateGeneralOptions();
234 }
235 
Run()236 void VocabTreeMatchingTab::Run() {
237   options_widget_->WriteOptions();
238 
239   if (!ExistsFile(options_->vocab_tree_matching->vocab_tree_path)) {
240     QMessageBox::critical(this, "", tr("Invalid vocabulary tree path."));
241     return;
242   }
243 
244   Thread* matcher = new VocabTreeFeatureMatcher(*options_->vocab_tree_matching,
245                                                 *options_->sift_matching,
246                                                 *options_->database_path);
247   thread_control_widget_->StartThread("Matching...", true, matcher);
248 }
249 
SpatialMatchingTab(QWidget * parent,OptionManager * options)250 SpatialMatchingTab::SpatialMatchingTab(QWidget* parent, OptionManager* options)
251     : FeatureMatchingTab(parent, options) {
252   options_widget_->AddOptionBool(&options_->spatial_matching->is_gps, "is_gps");
253   options_widget_->AddOptionBool(&options_->spatial_matching->ignore_z,
254                                  "ignore_z");
255   options_widget_->AddOptionInt(&options_->spatial_matching->max_num_neighbors,
256                                 "max_num_neighbors");
257   options_widget_->AddOptionDouble(&options_->spatial_matching->max_distance,
258                                    "max_distance");
259 
260   CreateGeneralOptions();
261 }
262 
Run()263 void SpatialMatchingTab::Run() {
264   options_widget_->WriteOptions();
265 
266   Thread* matcher = new SpatialFeatureMatcher(*options_->spatial_matching,
267                                               *options_->sift_matching,
268                                               *options_->database_path);
269   thread_control_widget_->StartThread("Matching...", true, matcher);
270 }
271 
TransitiveMatchingTab(QWidget * parent,OptionManager * options)272 TransitiveMatchingTab::TransitiveMatchingTab(QWidget* parent,
273                                              OptionManager* options)
274     : FeatureMatchingTab(parent, options) {
275   options_widget_->AddOptionInt(&options->transitive_matching->batch_size,
276                                 "batch_size");
277   options_widget_->AddOptionInt(&options->transitive_matching->num_iterations,
278                                 "num_iterations");
279 
280   CreateGeneralOptions();
281 }
282 
Run()283 void TransitiveMatchingTab::Run() {
284   options_widget_->WriteOptions();
285 
286   Thread* matcher = new TransitiveFeatureMatcher(*options_->transitive_matching,
287                                                  *options_->sift_matching,
288                                                  *options_->database_path);
289   thread_control_widget_->StartThread("Matching...", true, matcher);
290 }
291 
CustomMatchingTab(QWidget * parent,OptionManager * options)292 CustomMatchingTab::CustomMatchingTab(QWidget* parent, OptionManager* options)
293     : FeatureMatchingTab(parent, options) {
294   match_type_cb_ = new QComboBox(this);
295   match_type_cb_->addItem(QString("Image pairs"));
296   match_type_cb_->addItem(QString("Raw feature matches"));
297   match_type_cb_->addItem(QString("Inlier feature matches"));
298   options_widget_->AddOptionRow("type", match_type_cb_, nullptr);
299 
300   options_widget_->AddOptionFilePath(&match_list_path_, "match_list_path");
301   options_widget_->AddOptionInt(&options_->image_pairs_matching->block_size,
302                                 "block_size", 2);
303 
304   CreateGeneralOptions();
305 }
306 
Run()307 void CustomMatchingTab::Run() {
308   options_widget_->WriteOptions();
309 
310   if (!ExistsFile(match_list_path_)) {
311     QMessageBox::critical(this, "", tr("Path does not exist!"));
312     return;
313   }
314 
315   Thread* matcher = nullptr;
316   if (match_type_cb_->currentIndex() == 0) {
317     ImagePairsMatchingOptions matcher_options;
318     matcher_options.match_list_path = match_list_path_;
319     matcher = new ImagePairsFeatureMatcher(
320         matcher_options, *options_->sift_matching, *options_->database_path);
321   } else {
322     FeaturePairsMatchingOptions matcher_options;
323     matcher_options.match_list_path = match_list_path_;
324     if (match_type_cb_->currentIndex() == 1) {
325       matcher_options.verify_matches = true;
326     } else if (match_type_cb_->currentIndex() == 2) {
327       matcher_options.verify_matches = false;
328     }
329 
330     matcher = new FeaturePairsFeatureMatcher(
331         matcher_options, *options_->sift_matching, *options_->database_path);
332   }
333 
334   thread_control_widget_->StartThread("Matching...", true, matcher);
335 }
336 
FeatureMatchingWidget(QWidget * parent,OptionManager * options)337 FeatureMatchingWidget::FeatureMatchingWidget(QWidget* parent,
338                                              OptionManager* options)
339     : parent_(parent) {
340   // Do not change flag, to make sure feature database is not accessed from
341   // multiple threads
342   setWindowFlags(Qt::Window);
343   setWindowTitle("Feature matching");
344 
345   QGridLayout* grid = new QGridLayout(this);
346 
347   tab_widget_ = new QTabWidget(this);
348   tab_widget_->addTab(new ExhaustiveMatchingTab(this, options),
349                       tr("Exhaustive"));
350   tab_widget_->addTab(new SequentialMatchingTab(this, options),
351                       tr("Sequential"));
352   tab_widget_->addTab(new VocabTreeMatchingTab(this, options), tr("VocabTree"));
353   tab_widget_->addTab(new SpatialMatchingTab(this, options), tr("Spatial"));
354   tab_widget_->addTab(new TransitiveMatchingTab(this, options),
355                       tr("Transitive"));
356   tab_widget_->addTab(new CustomMatchingTab(this, options), tr("Custom"));
357 
358   grid->addWidget(tab_widget_, 0, 0);
359 }
360 
showEvent(QShowEvent * event)361 void FeatureMatchingWidget::showEvent(QShowEvent* event) {
362   parent_->setDisabled(true);
363 }
364 
hideEvent(QHideEvent * event)365 void FeatureMatchingWidget::hideEvent(QHideEvent* event) {
366   parent_->setEnabled(true);
367 }
368 
369 }  // namespace colmap
370