8000 Synthesize full two-view geometry and raw matches by ahojnnes · Pull Request #2595 · colmap/colmap · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Synthesize full two-view geometry and raw matches #2595

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 18, 2024
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: 4 additions & 0 deletions pycolmap/ci/test-colmap-windows.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
& "./scripts/shell/enter_vs_dev_shell.ps1"
& "${env:VCPKG_INSTALLATION_ROOT}/vcpkg.exe" integrate install

& python -c "import pycolmap; print(pycolmap.__version__)"
4 changes: 3 additions & 1 deletion pycolmap/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,19 @@ wheel.expand-macos-universal-tags = true
[tool.cibuildwheel]
build = "cp3{8,9,10,11,12}-{macosx,manylinux,win}*"
archs = ["auto64"]
test-command = "python -c \"import pycolmap; print(pycolmap.__version__)\""

[tool.cibuildwheel.environment]
VCPKG_COMMIT_ID = "13bde2ff13192e1b2fdd37bd9b475c7665ae6ae5"

[tool.cibuildwheel.linux]
before-all = "{package}/ci/install-colmap-centos.sh"
test-command = "python -c \"import pycolmap; print(pycolmap.__version__)\""

[tool.cibuildwheel.macos]
before-all = "{package}/ci/install-colmap-macos.sh"
test-command = "python -c \"import pycolmap; print(pycolmap.__version__)\""

[tool.cibuildwheel.windows]
before-all = "powershell -File {package}/ci/install-colmap-windows.ps1"
before-build = "pip install delvewheel"
test-command = "powershell -File {package}/ci/test-colmap-windows.ps1"
2 changes: 1 addition & 1 deletion src/colmap/geometry/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ COLMAP_ADD_LIBRARY(
)

COLMAP_ADD_TEST(
NAME essential_matrix_utils_test
NAME essential_matrix_test
SRCS essential_matrix_test.cc
LINK_LIBS colmap_geometry
)
Expand Down
6 changes: 6 additions & 0 deletions src/colmap/geometry/essential_matrix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,10 @@ Eigen::Matrix3d InvertEssentialMatrix(const Eigen::Matrix3d& E) {
return E.transpose();
}

Eigen::Matrix3d FundamentalFromEssentialMatrix(const Eigen::Matrix3d& K2,
const Eigen::Matrix3d& E,
const Eigen::Matrix3d& K1) {
return K2.transpose().inverse() * E * K1.inverse();
}

} // namespace colmap
6 changes: 6 additions & 0 deletions src/colmap/geometry/essential_matrix.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,10 @@ Eigen::Vector3d EpipoleFromEssentialMatrix(const Eigen::Matrix3d& E,
// @return Inverted essential matrix.
Eigen::Matrix3d InvertEssentialMatrix(const Eigen::Matrix3d& matrix);

// Composes the fundamental matrix from image 1 to 2 from the essential matrix
// and two camera's calibrations.
Eigen::Matrix3d FundamentalFromEssentialMatrix(const Eigen::Matrix3d& K2,
const Eigen::Matrix3d& E,
const Eigen::Matrix3d& K1);

} // namespace colmap
13 changes: 13 additions & 0 deletions src/colmap/geometry/essential_matrix_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,18 @@ TEST(InvertEssentialMatrix, Nominal) {
}
}

TEST(FundamentalFromEssentialMatrix, Nominal) {
const Eigen::Matrix3d E = EssentialMatrixFromPose(
Rigid3d(Eigen::Quaterniond::UnitRandom(), Eigen::Vector3d::Random()));
const Eigen::Matrix3d K1 =
(Eigen::Matrix3d() << 2, 0, 1, 0, 3, 2, 0, 0, 1).finished();
const Eigen::Matrix3d K2 =
(Eigen::Matrix3d() << 3, 0, 2, 0, 4, 1, 0, 0, 1).finished();
const Eigen::Matrix3d F = FundamentalFromEssentialMatrix(K2, E, K1);
const Eigen::Vector3d x(3, 2, 1);
EXPECT_TRUE((K2.transpose().inverse() * E * x).isApprox(F * K1 * x));
EXPECT_TRUE((E * K1.inverse() * x).isApprox(K2.transpose() * F * x));
}

} // namespace
} // namespace colmap
3 changes: 2 additions & 1 deletion src/colmap/scene/database.cc
Original file line number Diff line number Diff line change
Expand Up @@ -739,8 +739,9 @@ void Database::WriteMatches(const image_t image_id1,
SQLITE3_CALL(sqlite3_bind_int64(sql_stmt_write_matches_, 1, pair_id));

// Important: the swapped data must live until the query is executed.
FeatureMatchesBlob swapped_blob;
if (SwapImagePair(image_id1, image_id2)) {
FeatureMatchesBlob swapped_blob = blob;
swapped_blob = blob;
SwapFeatureMatchesBlob(&swapped_blob);
WriteDynamicMatrixBlob(sql_stmt_write_matches_, swapped_blob, 2);
} else {
Expand Down
49 changes: 38 additions & 11 deletions src/colmap/scene/database_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -269,23 +269,50 @@ TEST(Database, Matches) {
Database database(Database::kInMemoryDatabasePath);
const image_t image_id1 = 1;
const image_t image_id2 = 2;
const FeatureMatches matches = FeatureMatches(1000);
database.WriteMatches(image_id1, image_id2, matches);
const FeatureMatches matches_read =
database.ReadMatches(image_id1, image_id2);
EXPECT_EQ(matches.size(), matches_read.size());
for (size_t i = 0; i < matches.size(); ++i) {
EXPECT_EQ(matches[i].point2D_idx1, matches_read[i].point2D_idx1);
EXPECT_EQ(matches[i].point2D_idx2, matches_read[i].point2D_idx2);
constexpr int kNumMatches = 1000;
FeatureMatches matches12(kNumMatches);
FeatureMatches matches21(kNumMatches);
for (size_t i = 0; i < matches12.size(); ++i) {
matches12[i].point2D_idx1 = i;
matches12[i].point2D_idx2 = 10000 + i;
matches21[i].point2D_idx1 = 10000 + i;
matches21[i].point2D_idx2 = i;
}

auto expectValidMatches = [&]() {
EXPECT_EQ(database.NumMatchedImagePairs(), 1);
const FeatureMatches matches_read12 =
database.ReadMatches(image_id1, image_id2);
EXPECT_EQ(matches12.size(), matches_read12.size());
for (size_t i = 0; i < matches12.size(); ++i) {
EXPECT_EQ(matches12[i].point2D_idx1, matches_read12[i].point2D_idx1);
EXPECT_EQ(matches12[i].point2D_idx2, matches_read12[i].point2D_idx2);
}
const FeatureMatches matches_read21 =
database.ReadMatches(image_id2, image_id1);
EXPECT_EQ(matches12.size(), matches_read21.size());
for (size_t i = 0; i < matches12.size(); ++i) {
EXPECT_EQ(matches12[i].point2D_idx1, matches_read21[i].point2D_idx2);
EXPECT_EQ(matches12[i].point2D_idx2, matches_read21[i].point2D_idx1);
}
};

EXPECT_EQ(database.NumMatchedImagePairs(), 0);
database.WriteMatches(image_id1, image_id2, matches12);
expectValidMatches();
database.DeleteMatches(image_id1, image_id2);
EXPECT_EQ(database.NumMatchedImagePairs(), 0);
database.WriteMatches(image_id2, image_id1, matches21);
expectValidMatches();

EXPECT_EQ(database.ReadAllMatches().size(), 1);
EXPECT_EQ(database.ReadAllMatches()[0].first,
Database::ImagePairToPairId(image_id1, image_id2));
EXPECT_EQ(database.NumMatches(), 1000);
EXPECT_EQ(database.NumMatches(), kNumMatches);
database.DeleteMatches(image_id1, image_id2);
EXPECT_EQ(database.NumMatches(), 0);
database.WriteMatches(image_id1, image_id2, matches);
EXPECT_EQ(database.NumMatches(), 1000);
database.WriteMatches(image_id1, image_id2, matches12);
EXPECT_EQ(database.NumMatches(), kNumMatches);
database.ClearMatches();
EXPECT_EQ(database.NumMatches(), 0);
}
Expand Down
61 changes: 52 additions & 9 deletions src/colmap/scene/synthetic.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,42 @@
namespace colmap {
namespace {

void SynthesizeExhaustiveMatches(Reconstruction* reconstruction,
void AddOutlierMatches(double inlier_ratio,
int num_points2D1,
int num_points2D2,
FeatureMatches* matches) {
const int num_outliers = matches->size() * (1.0 - inlier_ratio);
for (int i = 0; i < num_outliers; ++i) {
matches->emplace_back(
RandomUniformInteger<point2D_t>(0, num_points2D1 - 1),
RandomUniformInteger<point2D_t>(0, num_points2D2 - 2));
}
std::shuffle(matches->begin(), matches->end(), *PRNG);
}

void SynthesizeExhaustiveMatches(double inlier_match_ratio,
Reconstruction* reconstruction,
Database* database) {
const std::vector<image_t>& reg_image_ids = reconstruction->RegImageIds();
for (size_t image_idx1 = 0; image_idx1 < reg_image_ids.size(); ++image_idx1) {
const auto& image1 = reconstruction->Image(reg_image_ids[image_idx1]);
const Eigen::Matrix3d K1 =
reconstruction->Camera(image1.CameraId()).CalibrationMatrix();
const auto num_points2D1 = image1.NumPoints2D();
for (size_t image_idx2 = 0; image_idx2 < image_idx1; ++image_idx2) {
const auto& image2 = reconstruction->Image(reg_image_ids[image_idx2]);
const Eigen::Matrix3d K2 =
reconstruction->Camera(image2.CameraId()).CalibrationMatrix();
const auto num_points2D2 = image2.NumPoints2D();

TwoViewGeometry two_view_geometry;
two_view_geometry.config = TwoViewGeometry::CALIBRATED;
const Rigid3d cam2_from_cam1 =
two_view_geometry.cam2_from_cam1 =
image2.CamFromWorld() * Inverse(image1.CamFromWorld());
two_view_geometry.E = EssentialMatrixFromPose(cam2_from_cam1);

two_view_geometry.E =
EssentialMatrixFromPose(two_view_geometry.cam2_from_cam1);
two_view_geometry.F =
FundamentalFromEssentialMatrix(K2, two_view_geometry.E, K1);
for (point2D_t point2D_idx1 = 0; point2D_idx1 < num_points2D1;
++point2D_idx1) {
const auto& point2D1 = image1.Point2D(point2D_idx1);
Expand All @@ -73,13 +93,19 @@ void SynthesizeExhaustiveMatches(Reconstruction* reconstruction,
}
}

FeatureMatches matches = two_view_geometry.inlier_matches;
AddOutlierMatches(
inlier_match_ratio, num_points2D1, num_points2D2, &matches);

database->WriteMatches(image1.ImageId(), image2.ImageId(), matches);
database->WriteTwoViewGeometry(
image1.ImageId(), image2.ImageId(), two_view_geometry);
}
}
}

void SynthesizeChainedMatches(Reconstruction* reconstruction,
void SynthesizeChainedMatches(double inlier_match_ratio,
Reconstruction* reconstruction,
Database* database) {
std::unordered_map<image_pair_t, TwoViewGeometry> two_view_geometries;
for (const auto& point3D : reconstruction->Points3D()) {
Expand All @@ -105,11 +131,26 @@ void SynthesizeChainedMatches(Reconstruction* reconstruction,
const auto image_pair =
Database::PairIdToImagePair(two_view_geometry.first);
const auto& image1 = reconstruction->Image(image_pair.first);
const auto& camera1 = reconstruction->Camera(image1.CameraId());
const auto& image2 = reconstruction->Image(image_pair.second);
const auto& camera2 = reconstruction->Camera(image2.CameraId());
two_view_geometry.second.config = TwoViewGeometry::CALIBRATED;
const Rigid3d cam2_from_cam1 =
two_view_geometry.second.cam2_from_cam1 =
image2.CamFromWorld() * Inverse(image1.CamFromWorld());
two_view_geometry.second.E = EssentialMatrixFromPose(cam2_from_cam1);
two_view_geometry.second.E =
EssentialMatrixFromPose(two_view_geometry.second.cam2_from_cam1);
two_view_geometry.second.F =
FundamentalFromEssentialMatrix(camera2.CalibrationMatrix(),
two_view_geometry.second.E,
camera1.CalibrationMatrix());

FeatureMatches matches = two_view_geometry.second.inlier_matches;
AddOutlierMatches(inlier_match_ratio,
image1.NumPoints2D(),
image2.NumPoints2D(),
&matches);

database->WriteMatches(image1.ImageId(), image2.ImageId(), matches);
database->WriteTwoViewGeometry(
image1.ImageId(), image2.ImageId(), two_view_geometry.second);
}
Expand Down Expand Up @@ -231,10 +272,12 @@ void SynthesizeDataset(const SyntheticDatasetOptions& options,
if (database != nullptr) {
switch (options.match_config) {
case SyntheticDatasetOptions::MatchConfig::EXHAUSTIVE:
SynthesizeExhaustiveMatches(reconstruction, database);
SynthesizeExhaustiveMatches(
options.inlier_match_ratio, reconstruction, database);
break;
case SyntheticDatasetOptions::MatchConfig::CHAINED:
SynthesizeChainedMatches(reconstruction, database);
SynthesizeChainedMatches(
options.inlier_match_ratio, reconstruction, database);
break;
default:
LOG(FATAL_THROW) << "Invalid MatchConfig specified";
Expand Down
2 changes: 2 additions & 0 deletions src/colmap/scene/synthetic.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ struct SyntheticDatasetOptions {
int num_points2D_without_point3D = 10;
double point2D_stddev = 0.0;

double inlier_match_ratio = 1.0;

enum class MatchConfig {
// Exhaustive matches between all 4BFD pairs of observations of a 3D point.
EXHAUSTIVE = 1,
Expand Down
Loading
0