Skip to content

Commit

Permalink
Merge branch 'master' into abhinav/add-robust-triangulation-bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
sarlinpe authored Dec 8, 2023
2 parents f0a835f + 831e9d9 commit 73ab1c8
Show file tree
Hide file tree
Showing 24 changed files with 368 additions and 803 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-new.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ macos-11, macos-12 ]
os: [ macos-12, macos-13 ]
steps:
- name: Checkout
uses: actions/checkout@v3
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/clang-format-check.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
name: clang-format Check
on: [push, pull_request]
on:
push:
branches:
- master
pull_request:
types: [ assigned, opened, synchronize, reopened ]
jobs:
formatting-check:
name: Formatting Check
Expand All @@ -10,4 +15,3 @@ jobs:
uses: jidicula/[email protected]
with:
clang-format-version: '14'
check-path: 'src/colmap'
6 changes: 0 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ project(PyCOLMAP)
set(CMAKE_CUDA_ARCHITECTURES "native")
find_package(colmap 3.9 REQUIRED)

if(${CERES_VERSION} VERSION_LESS "2.2.0")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
endif()

add_subdirectory(pybind11)

pybind11_add_module(pycolmap main.cc)
Expand Down
77 changes: 36 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Wheels for Python 8/9/10 on Linux and macOS 11/12 can be install using pip:
pip install pycolmap
```

The wheels are automatically built and pushed to [pypi](https://pypi.org/project/pycolmap/) at each release.
The wheels are automatically built and pushed to [pypi](https://pypi.org/project/pycolmap/) at each release. They are currently not built with CUDA support, which requires building from source (see below).

### Building from source

Expand All @@ -19,7 +19,7 @@ Alternatively, PyCOLMAP can be built from source. Here is how to build the most

2. Clone and build the PyCOLMAP repository and its submodules:
```bash
git clone -b v0.4.0 --recursive git@github.com:colmap/pycolmap.git
git clone -b v0.4.0 --recursive https://github.com/colmap/pycolmap.git
cd pycolmap
pip install .
```
Expand Down Expand Up @@ -67,13 +67,11 @@ pycolmap.stereo_fusion(mvs_path / "dense.ply", mvs_path)
```

PyCOLMAP can leverage the GPU for feature extraction, matching, and multi-view stereo if COLMAP was compiled with CUDA support.

Similarly, PyCOLMAP can run Delauney Triangulation if COLMAP was compiled with CGAL support.
This requires to build the package from source and is not available with the PyPI wheels.

All of the above steps are easily configurable with python dicts which are recursively merged into
their respective defaults, e.g.

their respective defaults, for example:
```python
pycolmap.extract_features(database_path, image_dir, sift_options={"max_num_features": 512})
# equivalent to
Expand Down Expand Up @@ -125,14 +123,15 @@ The object API mirrors the COLMAP C++ library. The bindings support many other o
- projecting a 3D point into an image with arbitrary camera model:

```python
uv = camera.world_to_image(image.project(point3D.xyz))
uv = camera.img_from_cam(image.cam_from_world * point3D.xyz)
```

- aligning two 3D reconstructions by their camera poses:

```python
tfm = reconstruction1.align_poses(reconstruction2) # transforms reconstruction1 in-place
print(tfm.rotation, tfm.translation)
rec2_from_rec1 = pycolmap.align_reconstructions_via_reprojections(reconstruction1, reconstrution2)
reconstruction1.transform(rec2_from_rec1)
print(rec2_from_rec1.scale, rec2_from_rec1.rotation, rec2_from_rec1.translation)
```


Expand Down Expand Up @@ -166,7 +165,7 @@ reconstruction.export_VRML("rec.images.wrl", "rec.points3D.wrl",

We provide robust RANSAC-based estimators for absolute camera pose (single-camera and multi-camera-rig), essential matrix, fundamental matrix, homography, and two-view relative pose for calibrated cameras.

All RANSAC and estimation parameters are exposed as objects that behave similarly as Python dataclasses. The RANSAC options are described in [`colmap/src/optim/ransac.h`](https://github.com/colmap/colmap/blob/dev/src/optim/ransac.h#L47-L76) and their default values are:
All RANSAC and estimation parameters are exposed as objects that behave similarly as Python dataclasses. The RANSAC options are described in [`colmap/optim/ransac.h`](https://github.com/colmap/colmap/blob/main/src/colmap/optim/ransac.h#L45-L74) and their default values are:

```python
ransac_options = pycolmap.RANSACOptions(
Expand All @@ -187,61 +186,57 @@ For instance, to estimate the absolute pose of a query camera given 2D-3D corres
# - points3D: Nx3 array; world coordinates
# - camera: pycolmap.Camera
# Optional parameters:
# - max_error_px: float; RANSAC inlier threshold in pixels (default=12.0)
# - estimation_options: dict or pycolmap.AbsolutePoseEstimationOptions
# - refinement_options: dict or pycolmap.AbsolutePoseRefinementOptions
answer = pycolmap.absolute_pose_estimation(points2D, points3D, camera, max_error_px=12.0)
# Returns: dictionary of estimation outputs
answer = pycolmap.absolute_pose_estimation(points2D, points3D, camera)
# Returns: dictionary of estimation outputs or None if failure
```

2D and 3D points are passed as Numpy arrays or lists. The options are defined in [`estimators/absolute_pose.cc`](./estimators/absolute_pose.cc#L187-L220) and can be passed as regular (nested) Python dictionaries:
2D and 3D points are passed as Numpy arrays or lists. The options are defined in [`estimators/absolute_pose.cc`](./estimators/absolute_pose.cc#L104-L152) and can be passed as regular (nested) Python dictionaries:

```python
pycolmap.absolute_pose_estimation(
points2D, points3D, camera,
estimation_options={'ransac': {'max_error': 12.0}},
refinement_options={'refine_focal_length': True},
estimation_options=dict(ransac=dict(max_error=12.0)),
refinement_options=dict(refine_focal_length=True),
)
```

### Absolute Pose Refinement

```python
# Parameters:
# - tvec: List of 3 floats, translation component of the pose (world to camera)
# - qvec: List of 4 floats, quaternion component of the pose (world to camera)
# - cam_from_world: pycolmap.Rigid3d, initial pose
# - points2D: Nx2 array; pixel coordinates
# - points3D: Nx3 array; world coordinates
# - inlier_mask: array of N bool; inlier_mask[i] is true if correpondence i is an inlier
# - camera: pycolmap.Camera
# Optional parameters:
# - refinement_options: dict or pycolmap.AbsolutePoseRefinementOptions
answer = pycolmap.pose_refinement(tvec, qvec, points2D, points3D, inlier_mask, camera)
# Returns: dictionary of refinement outputs
answer = pycolmap.pose_refinement(cam_from_world, points2D, points3D, inlier_mask, camera)
# Returns: dictionary of refinement outputs or None if failure
```

### Essential matrix estimation

```python
# Parameters:
# - points2D1: Nx2 array; pixel coordinates in image 1
# - points2D2: Nx2 array; pixel coordinates in image 2
# - points1: Nx2 array; 2D pixel coordinates in image 1
# - points2: Nx2 array; 2D pixel coordinates in image 2
# - camera1: pycolmap.Camera of image 1
# - camera2: pycolmap.Camera of image 2
# Optional parameters:
# - max_error_px: float; RANSAC inlier threshold in pixels (default=4.0)
# - options: dict or pycolmap.RANSACOptions
answer = pycolmap.essential_matrix_estimation(points2D1, points2D2, camera1, camera2)
# Returns: dictionary of estimation outputs
# - options: dict or pycolmap.RANSACOptions (default inlier threshold is 4px)
answer = pycolmap.essential_matrix_estimation(points1, points2, camera1, camera2)
# Returns: dictionary of estimation outputs or None if failure
```

### Fundamental matrix estimation

```python
answer = pycolmap.fundamental_matrix_estimation(
points2D1,
points2D2,
[max_error_px], # optional RANSAC inlier threshold in pixels
points1,
points2,
[options], # optional dict or pycolmap.RANSACOptions
)
```
Expand All @@ -250,8 +245,8 @@ answer = pycolmap.fundamental_matrix_estimation(

```python
answer = pycolmap.homography_matrix_estimation(
points2D1,
points2D2,
points1,
points2,
[max_error_px], # optional RANSAC inlier threshold in pixels
[options], # optional dict or pycolmap.RANSACOptions
)
Expand All @@ -263,29 +258,29 @@ COLMAP can also estimate a relative pose between two calibrated cameras by estim

```python
# Parameters:
# - points2D1: Nx2 array; pixel coordinates in image 1
# - points2D2: Nx2 array; pixel coordinates in image 2
# - camera1: pycolmap.Camera of image 1
# - points1: Nx2 array; 2D pixel coordinates in image 1
# - camera2: pycolmap.Camera of image 2
# - points2: Nx2 array; 2D pixel coordinates in image 2
# Optional parameters:
# - max_error_px: float; RANSAC inlier threshold in pixels (default=4.0)
# - matches: Nx2 integer array; correspondences across images
# - options: dict or pycolmap.TwoViewGeometryOptions
answer = pycolmap.homography_matrix_estimation(points2D1, points2D2)
# Returns: dictionary of estimation outputs
answer = pycolmap.estimate_calibrated_two_view_geometry(camera1, points1, camera2, points2)
# Returns: pycolmap.TwoViewGeometry
```

The options are defined in [`estimators/two_view_geometry.cc`](estimators/two_view_geometry.cc#L102-L117) and control how each model is selected. The return dictionary contains the relative pose, inlier mask, as well as the type of camera configuration, such as degenerate or planar. This type is an instance of the enum `pycolmap.TwoViewGeometry` whose values are explained in [`colmap/src/estimators/two_view_geometry.h`](https://github.com/colmap/colmap/blob/dev/src/estimators/two_view_geometry.h#L47-L67).
The `TwoViewGeometryOptions` control how each model is selected. The output structure contains the geometric model, inlier matches, the relative pose (if `options.compute_relative_pose=True`), and the type of camera configuration, which is an instance of the enum `pycolmap.TwoViewGeometryConfiguration`.

### Camera argument

All estimators expect a COLMAP camera object, which can be created as follow:
Some estimators expect a COLMAP camera object, which can be created as follow:

```python
camera = pycolmap.Camera(
COLMAP_CAMERA_MODEL_NAME,
IMAGE_WIDTH,
IMAGE_HEIGHT,
EXTRA_CAMERA_PARAMETERS,
model=camera_model_name_or_id,
width=width,
height=height,
params=params,
)
```

Expand Down
Loading

0 comments on commit 73ab1c8

Please sign in to comment.