Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions 4 src/colmap/retrieval/visual_index.cc
Original file line number Diff line number Diff line change
Expand Up @@ -634,8 +634,8 @@ std::unique_ptr<VisualIndex> VisualIndex::Create(int desc_dim,
int embedding_dim) {
if (desc_dim == 128 && embedding_dim == 64) {
return std::make_unique<FaissVisualIndex<128, 64>>();
} else if (desc_dim == 32 && embedding_dim == 8) {
return std::make_unique<FaissVisualIndex<32, 8>>();
} else if (desc_dim == 32 && embedding_dim == 16) {
return std::make_unique<FaissVisualIndex<32, 16>>();
} else {
std::ostringstream error;
error << "Visual index with descriptor dimension " << desc_dim
Expand Down
3 changes: 2 additions & 1 deletion 3 src/colmap/retrieval/visual_index.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ class VisualIndex {
// is assigned to.
int num_neighbors = 5;

// Whether to perform spatial verification after image retrieval.
// Perform spatial verification after image retrieval, if > 0.
// Defines the number of neighbors to re-rank using spatial verification.
int num_images_after_verification = 0;

// The number of checks in the nearest neighbor search.
Expand Down
101 changes: 99 additions & 2 deletions 101 src/colmap/retrieval/visual_index_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,107 @@ TEST_P(ParameterizedVisualIndexTests, ReadWrite) {
EXPECT_FALSE(visual_index->IsImageIndexed(3));
}

TEST_P(ParameterizedVisualIndexTests, SpatialVerification) {
const auto [desc_dim, embedding_dim] = GetParam();

SetPRNGSeed(0);

VisualIndex::BuildOptions build_options;
// Keep test runtimes low.
build_options.num_iterations = 10;
build_options.num_rounds = 1;
build_options.num_visual_words = 100;

VisualIndex::Descriptors train_descriptors =
VisualIndex::Descriptors::Random(1000, desc_dim);
auto visual_index = VisualIndex::Create(desc_dim, embedding_dim);
visual_index->Build(build_options, train_descriptors);
EXPECT_EQ(visual_index->NumVisualWords(), 100);

// Add multiple images with geometries and descriptors
VisualIndex::IndexOptions index_options;
constexpr int kNumImages = 5;
constexpr int kNumFeatures = 50;

VisualIndex::Geometries query_keypoints(kNumFeatures);
for (size_t j = 0; j < query_keypoints.size(); ++j) {
// Create a grid of keypoints (non-collinear)
query_keypoints[j].x = static_cast<float>((j % 10) * kNumFeatures);
query_keypoints[j].y = static_cast<float>((j / 10.f) * kNumFeatures);
}

// Use the same descriptors for all images.
const VisualIndex::Descriptors descriptors =
VisualIndex::Descriptors::Random(kNumFeatures, desc_dim);

for (int i = 0; i < kNumImages; ++i) {
VisualIndex::Geometries keypoints(kNumFeatures);
// For every other image, create keypoints with different but consistent
// translational transformations to the query keypoints. The other outlier
// images have inconsistent spatial transformations with random keypoints.
if (i % 2 == 0) {
// Create keypoints with different spatial transformations.
const float x_offset = static_cast<float>(i * 20);
const float y_offset = static_cast<float>(i * 30);
for (size_t j = 0; j < keypoints.size(); ++j) {
keypoints[j].x = query_keypoints[j].x + x_offset;
keypoints[j].y = query_keypoints[j].y + y_offset;
}
} else {
for (size_t j = 0; j < keypoints.size(); ++j) {
const Eigen::Vector2f offset = Eigen::Vector2f::Random();
keypoints[j].x = offset.x();
keypoints[j].y = offset.y();
}
}

visual_index->Add(index_options, /*image_id=*/i, keypoints, descriptors);
}

EXPECT_EQ(visual_index->NumImages(), kNumImages);
visual_index->Prepare();

VisualIndex::QueryOptions query_options;

std::vector<ImageScore> image_scores_without_verification;
visual_index->Query(query_options,
query_keypoints,
descriptors,
&image_scores_without_verification);
EXPECT_EQ(image_scores_without_verification.size(), kNumImages);
for (const auto& score : image_scores_without_verification) {
EXPECT_LT(score.score, 1.0f);
}

query_options.num_images_after_verification = kNumImages;
std::vector<ImageScore> image_scores_with_verification;
visual_index->Query(query_options,
query_keypoints,
descriptors,
&image_scores_with_verification);
EXPECT_EQ(image_scores_with_verification.size(), kNumImages);
// Make sure inlier images have more keypoints as inliers.
for (const auto& score : image_scores_with_verification) {
if (score.image_id % 2 == 0) {
EXPECT_GT(score.score, kNumFeatures / 2);
} else {
EXPECT_LT(score.score, kNumFeatures / 4);
}
}

// Test with max_num_images constraint, should respect max_num_images.
query_options.max_num_images = 2;
query_options.num_images_after_verification = 4;
std::vector<ImageScore> image_scores_limited;
visual_index->Query(
query_options, query_keypoints, descriptors, &image_scores_limited);
EXPECT_LE(image_scores_limited.size(), query_options.max_num_images);
}

INSTANTIATE_TEST_SUITE_P(VisualIndexTests,
ParameterizedVisualIndexTests,
::testing::Values(std::make_pair(128, 64),
std::make_pair(32, 8)));
std::make_pair(32, 16)));

#ifdef COLMAP_DOWNLOAD_ENABLED

Expand All @@ -185,7 +282,7 @@ TEST(VisualIndex, Download) {

VisualIndex::Descriptors descriptors =
VisualIndex::Descriptors::Random(50, 32);
auto visual_index = VisualIndex::Create(32, 8);
auto visual_index = VisualIndex::Create(32, 16);
VisualIndex::BuildOptions build_options;
build_options.num_visual_words = 5;
// NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall)
Expand Down
46 changes: 24 additions & 22 deletions 46 src/colmap/retrieval/vote_and_verify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -407,28 +407,30 @@ int VoteAndVerify(const VoteAndVerifyOptions& options,
std::vector<Eigen::Matrix2x3d> models;
AffineTransformEstimator::Estimate(
best_inlier_points1, best_inlier_points2, &models);
THROW_CHECK_EQ(models.size(), 1);
const Eigen::Matrix2x3d& A = models[0];
Eigen::Matrix3d A_homogeneous = Eigen::Matrix3d::Identity();
A_homogeneous.topRows<2>() = A;
const Eigen::Matrix2x3d inv_A = A_homogeneous.inverse().topRows<2>();

TwoWayTransform local_tform;
local_tform.A12 = A.leftCols<2>().cast<float>();
local_tform.t12 = A.rightCols<1>().cast<float>();
local_tform.A21 = inv_A.leftCols<2>().cast<float>();
local_tform.t21 = inv_A.rightCols<1>().cast<float>();

ComputeInliers(local_tform,
matches,
options.max_transfer_error,
options.max_scale_error,
best_num_inliers,
&inlier_idxs);

if (inlier_idxs.size() > best_num_inliers) {
best_num_inliers = inlier_idxs.size();
best_tform = local_tform;
if (!models.empty()) {
THROW_CHECK_EQ(models.size(), 1);
const Eigen::Matrix2x3d& A12 = models[0];
Eigen::Matrix3d A_homogeneous = Eigen::Matrix3d::Identity();
A_homogeneous.topRows<2>() = A12;
const Eigen::Matrix2x3d A21 = A_homogeneous.inverse().topRows<2>();

TwoWayTransform local_tform;
local_tform.A12 = A12.leftCols<2>().cast<float>();
local_tform.t12 = A12.rightCols<1>().cast<float>();
local_tform.A21 = A21.leftCols<2>().cast<float>();
local_tform.t21 = A21.rightCols<1>().cast<float>();

ComputeInliers(local_tform,
matches,
options.max_transfer_error,
options.max_scale_error,
best_num_inliers,
&inlier_idxs);

if (inlier_idxs.size() > best_num_inliers) {
best_num_inliers = inlier_idxs.size();
best_tform = local_tform;
}
}
}

Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.