ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
extraction.cc
Go to the documentation of this file.
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 "feature/extraction.h"
33 
34 #include <numeric>
35 
36 #include "SiftGPU/SiftGPU.h"
37 #include "feature/sift.h"
38 #include "util/cuda.h"
39 #include "util/misc.h"
40 
41 namespace colmap {
42 namespace {
43 
44 void ScaleKeypoints(const Bitmap& bitmap, const Camera& camera,
45  FeatureKeypoints* keypoints) {
46  if (static_cast<size_t>(bitmap.Width()) != camera.Width() ||
47  static_cast<size_t>(bitmap.Height()) != camera.Height()) {
48  const float scale_x = static_cast<float>(camera.Width()) / bitmap.Width();
49  const float scale_y = static_cast<float>(camera.Height()) / bitmap.Height();
50  for (auto& keypoint : *keypoints) {
51  keypoint.Rescale(scale_x, scale_y);
52  }
53  }
54 }
55 
56 void MaskKeypoints(const Bitmap& mask, FeatureKeypoints* keypoints,
58  size_t out_index = 0;
59  BitmapColor<uint8_t> color;
60  for (size_t i = 0; i < keypoints->size(); ++i) {
61  if (!mask.GetPixel(static_cast<int>(keypoints->at(i).x),
62  static_cast<int>(keypoints->at(i).y), &color) ||
63  color.r == 0) {
64  // Delete this keypoint by not copying it to the output.
65  } else {
66  // Retain this keypoint by copying it to the output index (in case this
67  // index differs from its current position).
68  if (out_index != i) {
69  keypoints->at(out_index) = keypoints->at(i);
70  for (int col = 0; col < descriptors->cols(); ++col) {
71  (*descriptors)(out_index, col) = (*descriptors)(i, col);
72  }
73  }
74  out_index += 1;
75  }
76  }
77 
78  keypoints->resize(out_index);
79  descriptors->conservativeResize(out_index, descriptors->cols());
80 }
81 
82 } // namespace
83 
85  const ImageReaderOptions& reader_options,
86  const SiftExtractionOptions& sift_options)
87  : reader_options_(reader_options),
88  sift_options_(sift_options),
89  database_(reader_options_.database_path),
90  image_reader_(reader_options_, &database_) {
91  CHECK(reader_options_.Check());
92  CHECK(sift_options_.Check());
93 
94  std::shared_ptr<Bitmap> camera_mask;
95  if (!reader_options_.camera_mask_path.empty()) {
96  camera_mask = std::shared_ptr<Bitmap>(new Bitmap());
97  if (!camera_mask->Read(reader_options_.camera_mask_path,
98  /*as_rgb*/ false)) {
99  std::cerr << " ERROR: Cannot read camera mask file: "
100  << reader_options_.camera_mask_path
101  << ". No mask is going to be used." << std::endl;
102  camera_mask.reset();
103  }
104  }
105 
106  const int num_threads = GetEffectiveNumThreads(sift_options_.num_threads);
107  CHECK_GT(num_threads, 0);
108 
109  // Make sure that we only have limited number of objects in the queue to avoid
110  // excess in memory usage since images and features take lots of memory.
111  const int kQueueSize = 1;
112  resizer_queue_.reset(new JobQueue<internal::ImageData>(kQueueSize));
113  extractor_queue_.reset(new JobQueue<internal::ImageData>(kQueueSize));
114  writer_queue_.reset(new JobQueue<internal::ImageData>(kQueueSize));
115 
116  if (sift_options_.max_image_size > 0) {
117  for (int i = 0; i < num_threads; ++i) {
118  resizers_.emplace_back(new internal::ImageResizerThread(
119  sift_options_.max_image_size, resizer_queue_.get(),
120  extractor_queue_.get()));
121  }
122  }
123 
124  if (!sift_options_.domain_size_pooling &&
125  !sift_options_.estimate_affine_shape && sift_options_.use_gpu) {
126  std::vector<int> gpu_indices = CSVToVector<int>(sift_options_.gpu_index);
127  CHECK_GT(gpu_indices.size(), 0);
128 
129 #ifdef CUDA_ENABLED
130  if (gpu_indices.size() == 1 && gpu_indices[0] == -1) {
131  const int num_cuda_devices = GetNumCudaDevices();
132  CHECK_GT(num_cuda_devices, 0);
133  gpu_indices.resize(num_cuda_devices);
134  std::iota(gpu_indices.begin(), gpu_indices.end(), 0);
135  }
136 #endif // CUDA_ENABLED
137 
138  auto sift_gpu_options = sift_options_;
139  for (const auto& gpu_index : gpu_indices) {
140  sift_gpu_options.gpu_index = std::to_string(gpu_index);
141  extractors_.emplace_back(new internal::SiftFeatureExtractorThread(
142  sift_gpu_options, camera_mask, extractor_queue_.get(),
143  writer_queue_.get()));
144  }
145  } else {
146  if (sift_options_.num_threads == -1 &&
147  sift_options_.max_image_size ==
148  SiftExtractionOptions().max_image_size &&
149  sift_options_.first_octave == SiftExtractionOptions().first_octave) {
150  std::cout
151  << "WARNING: Your current options use the maximum number of "
152  "threads on the machine to extract features. Exracting SIFT "
153  "features on the CPU can consume a lot of RAM per thread for "
154  "large images. Consider reducing the maximum image size and/or "
155  "the first octave or manually limit the number of extraction "
156  "threads. Ignore this warning, if your machine has sufficient "
157  "memory for the current settings."
158  << std::endl;
159  }
160 
161  auto custom_sift_options = sift_options_;
162  custom_sift_options.use_gpu = false;
163  for (int i = 0; i < num_threads; ++i) {
164  extractors_.emplace_back(new internal::SiftFeatureExtractorThread(
165  custom_sift_options, camera_mask, extractor_queue_.get(),
166  writer_queue_.get()));
167  }
168  }
169 
170  writer_.reset(new internal::FeatureWriterThread(
171  image_reader_.NumImages(), &database_, writer_queue_.get()));
172 }
173 
174 void SiftFeatureExtractor::Run() {
175  PrintHeading1("Feature extraction");
176 
177  for (auto& resizer : resizers_) {
178  resizer->Start();
179  }
180 
181  for (auto& extractor : extractors_) {
182  extractor->Start();
183  }
184 
185  writer_->Start();
186 
187  for (auto& extractor : extractors_) {
188  if (!extractor->CheckValidSetup()) {
189  return;
190  }
191  }
192 
193  while (image_reader_.NextIndex() < image_reader_.NumImages()) {
194  if (IsStopped()) {
195  resizer_queue_->Stop();
196  extractor_queue_->Stop();
197  resizer_queue_->Clear();
198  extractor_queue_->Clear();
199  break;
200  }
201 
202  internal::ImageData image_data;
203  image_data.status =
204  image_reader_.Next(&image_data.camera, &image_data.image,
205  &image_data.bitmap, &image_data.mask);
206 
207  if (image_data.status != ImageReader::Status::SUCCESS) {
208  image_data.bitmap.Deallocate();
209  }
210 
211  if (sift_options_.max_image_size > 0) {
212  CHECK(resizer_queue_->Push(image_data));
213  } else {
214  CHECK(extractor_queue_->Push(image_data));
215  }
216  }
217 
218  resizer_queue_->Wait();
219  resizer_queue_->Stop();
220  for (auto& resizer : resizers_) {
221  resizer->Wait();
222  }
223 
224  extractor_queue_->Wait();
225  extractor_queue_->Stop();
226  for (auto& extractor : extractors_) {
227  extractor->Wait();
228  }
229 
230  writer_queue_->Wait();
231  writer_queue_->Stop();
232  writer_->Wait();
233 
235 }
236 
238  const std::string& import_path)
239  : reader_options_(reader_options), import_path_(import_path) {}
240 
241 void FeatureImporter::Run() {
242  PrintHeading1("Feature import");
243 
244  if (!ExistsDir(import_path_)) {
245  std::cerr << " ERROR: Import directory does not exist." << std::endl;
246  return;
247  }
248 
249  Database database(reader_options_.database_path);
250  ImageReader image_reader(reader_options_, &database);
251 
252  while (image_reader.NextIndex() < image_reader.NumImages()) {
253  if (IsStopped()) {
254  break;
255  }
256 
257  std::cout << StringPrintf("Processing file [%d/%d]",
258  image_reader.NextIndex() + 1,
259  image_reader.NumImages())
260  << std::endl;
261 
262  // Load image data and possibly save camera to database.
263  Camera camera;
264  Image image;
265  Bitmap bitmap;
266  if (image_reader.Next(&camera, &image, &bitmap, nullptr) !=
268  continue;
269  }
270 
271  const std::string path = JoinPaths(import_path_, image.Name() + ".txt");
272 
273  if (ExistsFile(path)) {
274  FeatureKeypoints keypoints;
277 
278  std::cout << " Features: " << keypoints.size() << std::endl;
279 
280  DatabaseTransaction database_transaction(&database);
281 
282  if (image.ImageId() == kInvalidImageId) {
283  image.SetImageId(database.WriteImage(image));
284  }
285 
286  if (!database.ExistsKeypoints(image.ImageId())) {
287  database.WriteKeypoints(image.ImageId(), keypoints);
288  }
289 
290  if (!database.ExistsDescriptors(image.ImageId())) {
291  database.WriteDescriptors(image.ImageId(), descriptors);
292  }
293  } else {
294  std::cout << " SKIP: No features found at " << path << std::endl;
295  }
296  }
297 
299 }
300 
301 namespace internal {
302 
303 ImageResizerThread::ImageResizerThread(const int max_image_size,
304  JobQueue<ImageData>* input_queue,
305  JobQueue<ImageData>* output_queue)
306  : max_image_size_(max_image_size),
307  input_queue_(input_queue),
308  output_queue_(output_queue) {}
309 
310 void ImageResizerThread::Run() {
311  while (true) {
312  if (IsStopped()) {
313  break;
314  }
315 
316  const auto input_job = input_queue_->Pop();
317  if (input_job.IsValid()) {
318  auto image_data = input_job.Data();
319 
320  if (image_data.status == ImageReader::Status::SUCCESS) {
321  if (static_cast<int>(image_data.bitmap.Width()) > max_image_size_ ||
322  static_cast<int>(image_data.bitmap.Height()) > max_image_size_) {
323  // Fit the down-sampled version exactly into the max dimensions.
324  const double scale =
325  static_cast<double>(max_image_size_) /
326  std::max(image_data.bitmap.Width(), image_data.bitmap.Height());
327  const int new_width =
328  static_cast<int>(image_data.bitmap.Width() * scale);
329  const int new_height =
330  static_cast<int>(image_data.bitmap.Height() * scale);
331 
332  image_data.bitmap.Rescale(new_width, new_height);
333  }
334  }
335 
336  output_queue_->Push(image_data);
337  } else {
338  break;
339  }
340  }
341 }
342 
344  const SiftExtractionOptions& sift_options,
345  const std::shared_ptr<Bitmap>& camera_mask,
346  JobQueue<ImageData>* input_queue, JobQueue<ImageData>* output_queue)
347  : sift_options_(sift_options),
348  camera_mask_(camera_mask),
349  input_queue_(input_queue),
350  output_queue_(output_queue) {
351  CHECK(sift_options_.Check());
352 
353 #ifndef CUDA_ENABLED
354  if (sift_options_.use_gpu) {
355  opengl_context_.reset(new OpenGLContextManager());
356  }
357 #endif
358 }
359 
360 void SiftFeatureExtractorThread::Run() {
361  std::unique_ptr<SiftGPU> sift_gpu;
362  if (sift_options_.use_gpu) {
363 #ifndef CUDA_ENABLED
364  CHECK(opengl_context_);
365  opengl_context_->MakeCurrent();
366 #endif
367 
368  sift_gpu.reset(new SiftGPU);
369  if (!CreateSiftGPUExtractor(sift_options_, sift_gpu.get())) {
370  std::cerr << "ERROR: SiftGPU not fully supported." << std::endl;
372  return;
373  }
374  }
375 
377 
378  while (true) {
379  if (IsStopped()) {
380  break;
381  }
382 
383  const auto input_job = input_queue_->Pop();
384  if (input_job.IsValid()) {
385  auto image_data = input_job.Data();
386 
387  if (image_data.status == ImageReader::Status::SUCCESS) {
388  bool success = false;
389  if (sift_options_.estimate_affine_shape ||
390  sift_options_.domain_size_pooling) {
392  sift_options_, image_data.bitmap, &image_data.keypoints,
393  &image_data.descriptors);
394  } else if (sift_options_.use_gpu) {
395  success = ExtractSiftFeaturesGPU(
396  sift_options_, image_data.bitmap, sift_gpu.get(),
397  &image_data.keypoints, &image_data.descriptors);
398  } else {
399  success = ExtractSiftFeaturesCPU(sift_options_, image_data.bitmap,
400  &image_data.keypoints,
401  &image_data.descriptors);
402  }
403  if (success) {
404  ScaleKeypoints(image_data.bitmap, image_data.camera,
405  &image_data.keypoints);
406  if (camera_mask_) {
407  MaskKeypoints(*camera_mask_, &image_data.keypoints,
408  &image_data.descriptors);
409  }
410  if (image_data.mask.Data()) {
411  MaskKeypoints(image_data.mask, &image_data.keypoints,
412  &image_data.descriptors);
413  }
414  } else {
415  image_data.status = ImageReader::Status::FAILURE;
416  }
417  }
418 
419  image_data.bitmap.Deallocate();
420 
421  output_queue_->Push(image_data);
422  } else {
423  break;
424  }
425  }
426 }
427 
429  Database* database,
430  JobQueue<ImageData>* input_queue)
431  : num_images_(num_images), database_(database), input_queue_(input_queue) {}
432 
433 void FeatureWriterThread::Run() {
434  size_t image_index = 0;
435  while (true) {
436  if (IsStopped()) {
437  break;
438  }
439 
440  auto input_job = input_queue_->Pop();
441  if (input_job.IsValid()) {
442  auto& image_data = input_job.Data();
443 
444  image_index += 1;
445 
446  std::cout << StringPrintf("Processed file [%d/%d]", image_index,
447  num_images_)
448  << std::endl;
449 
450  std::cout << StringPrintf(" Name: %s",
451  image_data.image.Name().c_str())
452  << std::endl;
453 
454  if (image_data.status == ImageReader::Status::IMAGE_EXISTS) {
455  std::cout << " SKIP: Features for image already extracted."
456  << std::endl;
457  } else if (image_data.status == ImageReader::Status::BITMAP_ERROR) {
458  std::cout << " ERROR: Failed to read image file format." << std::endl;
459  } else if (image_data.status ==
461  std::cout << " ERROR: Single camera specified, "
462  "but images have different dimensions."
463  << std::endl;
464  } else if (image_data.status ==
466  std::cout << " ERROR: Image previously processed, but current image "
467  "has different dimensions."
468  << std::endl;
469  } else if (image_data.status == ImageReader::Status::CAMERA_PARAM_ERROR) {
470  std::cout << " ERROR: Camera has invalid parameters." << std::endl;
471  } else if (image_data.status == ImageReader::Status::FAILURE) {
472  std::cout << " ERROR: Failed to extract features." << std::endl;
473  }
474 
475  if (image_data.status != ImageReader::Status::SUCCESS) {
476  continue;
477  }
478 
479  std::cout << StringPrintf(" Dimensions: %d x %d",
480  image_data.camera.Width(),
481  image_data.camera.Height())
482  << std::endl;
483  std::cout << StringPrintf(" Camera: #%d - %s",
484  image_data.camera.CameraId(),
485  image_data.camera.ModelName().c_str())
486  << std::endl;
487  std::cout << StringPrintf(" Focal Length: %.2fpx",
488  image_data.camera.MeanFocalLength());
489  if (image_data.camera.HasPriorFocalLength()) {
490  std::cout << " (Prior)" << std::endl;
491  } else {
492  std::cout << std::endl;
493  }
494  if (image_data.image.HasTvecPrior()) {
495  std::cout << StringPrintf(
496  " GPS: LAT=%.3f, LON=%.3f, ALT=%.3f",
497  image_data.image.TvecPrior(0),
498  image_data.image.TvecPrior(1),
499  image_data.image.TvecPrior(2))
500  << std::endl;
501  }
502  std::cout << StringPrintf(" Features: %d",
503  image_data.keypoints.size())
504  << std::endl;
505 
506  DatabaseTransaction database_transaction(database_);
507 
508  if (image_data.image.ImageId() == kInvalidImageId) {
509  image_data.image.SetImageId(database_->WriteImage(image_data.image));
510  }
511 
512  if (!database_->ExistsKeypoints(image_data.image.ImageId())) {
513  database_->WriteKeypoints(image_data.image.ImageId(),
514  image_data.keypoints);
515  }
516 
517  if (!database_->ExistsDescriptors(image_data.image.ImageId())) {
518  database_->WriteDescriptors(image_data.image.ImageId(),
519  image_data.descriptors);
520  }
521  } else {
522  break;
523  }
524  }
525 }
526 
527 } // namespace internal
528 } // namespace colmap
std::shared_ptr< core::Tensor > image
math::float4 color
void WriteDescriptors(const image_t image_id, const FeatureDescriptors &descriptors) const
Definition: database.cc:676
void WriteKeypoints(const image_t image_id, const FeatureKeypoints &keypoints) const
Definition: database.cc:665
bool ExistsKeypoints(const image_t image_id) const
Definition: database.cc:307
bool ExistsDescriptors(const image_t image_id) const
Definition: database.cc:311
image_t WriteImage(const Image &image, const bool use_image_id=false) const
Definition: database.cc:633
FeatureImporter(const ImageReaderOptions &reader_options, const std::string &import_path)
Definition: extraction.cc:237
size_t NextIndex() const
Status Next(Camera *camera, Image *image, Bitmap *bitmap, Bitmap *mask)
Definition: image_reader.cc:86
size_t NumImages() const
SiftFeatureExtractor(const ImageReaderOptions &reader_options, const SiftExtractionOptions &sift_options)
Definition: extraction.cc:84
const Timer & GetTimer() const
Definition: threading.cc:154
void SignalInvalidSetup()
Definition: threading.cc:146
void SignalValidSetup()
Definition: threading.cc:138
bool IsStopped()
Definition: threading.cc:97
void PrintMinutes() const
Definition: timer.cc:93
FeatureWriterThread(const size_t num_images, Database *database, JobQueue< ImageData > *input_queue)
Definition: extraction.cc:428
ImageResizerThread(const int max_image_size, JobQueue< ImageData > *input_queue, JobQueue< ImageData > *output_queue)
Definition: extraction.cc:303
SiftFeatureExtractorThread(const SiftExtractionOptions &sift_options, const std::shared_ptr< Bitmap > &camera_mask, JobQueue< ImageData > *input_queue, JobQueue< ImageData > *output_queue)
Definition: extraction.cc:343
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
static const std::string path
Definition: PointCloud.cpp:59
bool ExtractCovariantSiftFeaturesCPU(const SiftExtractionOptions &options, const Bitmap &bitmap, FeatureKeypoints *keypoints, FeatureDescriptors *descriptors)
Definition: sift.cc:599
bool CreateSiftGPUExtractor(const SiftExtractionOptions &options, SiftGPU *sift_gpu)
Definition: sift.cc:776
Eigen::Matrix< uint8_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > FeatureDescriptors
Definition: types.h:79
int GetNumCudaDevices()
Definition: cuda.cc:57
bool ExistsDir(const std::string &path)
Definition: misc.cc:104
bool ExistsFile(const std::string &path)
Definition: misc.cc:100
std::string JoinPaths(T const &... paths)
Definition: misc.h:128
bool ExtractSiftFeaturesCPU(const SiftExtractionOptions &options, const Bitmap &bitmap, FeatureKeypoints *keypoints, FeatureDescriptors *descriptors)
Definition: sift.cc:419
void PrintHeading1(const std::string &heading)
Definition: misc.cc:225
std::vector< FeatureKeypoint > FeatureKeypoints
Definition: types.h:77
std::string StringPrintf(const char *format,...)
Definition: string.cc:131
const image_t kInvalidImageId
Definition: types.h:76
bool ExtractSiftFeaturesGPU(const SiftExtractionOptions &options, const Bitmap &bitmap, SiftGPU *sift_gpu, FeatureKeypoints *keypoints, FeatureDescriptors *descriptors)
Definition: sift.cc:875
void LoadSiftFeaturesFromTextFile(const std::string &path, FeatureKeypoints *keypoints, FeatureDescriptors *descriptors)
Definition: sift.cc:943
int GetEffectiveNumThreads(const int num_threads)
Definition: threading.cc:269
std::string to_string(const T &n)
Definition: Common.h:20
CorePointDescSet * descriptors
std::string gpu_index
Definition: sift.h:32