Skip to content

More flexible cameras.yaml #88

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

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ RUN python3 -m pip install --upgrade pip
RUN pip3 install setuptools
RUN pip3 install torch torchvision
RUN pip3 install -e .
RUN pip3 install pycolmap
RUN pip3 install pycolmap==0.6.1

# Running the tests:
RUN python -m pytest
14 changes: 11 additions & 3 deletions config/cameras.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ general:
camera_model: "pinhole" # ["simple-pinhole", "pinhole", "simple-radial", "opencv"]
openmvg_camera_model: "pinhole_radial_k3" # ["pinhole", "pinhole_radial_k3", "pinhole_brown_t2"]
single_camera: True
intrinsics: ~ # None

cam0:
camera_model: "pinhole"
images : ""
intrinsics: [
481.14, 478.43, 481.44, 383.72
]
images : "cam0_*.jpg"

cam1:
camera_model: "pinhole"
images : "DSC_6468.jpg,DSC_6468.jpg"
camera_model: "opencv"
intrinsics: [
481.91, 482.20, 482.70, 384.33,
0.0, 0.0, 0.0, 0.0
]
images : "cam1_*.jpg"
25 changes: 19 additions & 6 deletions docs/camera_models.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ For the COLMAP database, by default, DIM assigns camera models to images based o

For images not assigned to specific `cam<x>` camera groups, the options specified under `general` are applied. The `camera_model` can be selected from `["simple-pinhole", "pinhole", "simple-radial", "opencv"]`. It's worth noting that it's easily possible to extend this to include all the classical COLMAP camera models. Cameras can either be shared among all images (`single_camera == True`), or each camera can have a different camera model (`single_camera == False`).

A subset of images can share intrinsics using `cam<x>` key, by specifying the `camera_model` along with the names of the images separated by commas.
Note that you must specify the full image name, including the extension.
A subset of images can share intrinsics using `cam<x>` key, by specifying the `camera_model` along with the names of the images separated by commas, and the `intrinsics` corresponding to the `camera_model`.

Note that you must specify the full image name, including the extension. Image name supports globbing, so you can use `*` to match multiple images.

If you want to read intrinsics from the EXIF data, you can set the `intrinsics` to `~` (null).

For instance:

```python
cam0:
camera_model: "pinhole"
images: "DSC_6468.jpg,DSC_6469.jpg"
images: "DSC_64*.jpg,DSC_65*.jpg"
intrinsics: [481.14, 478.43, 481.44, 383.72]
```

There's no limit to the number of `cam<x>` entries you can use, just add them following the provided format.
Expand All @@ -23,14 +28,22 @@ general:
camera_model: "pinhole" # ["simple-pinhole", "pinhole", "simple-radial", "opencv"]
openmvg_camera_model: "pinhole_radial_k3" # ["pinhole", "pinhole_radial_k3", "pinhole_brown_t2"]
single_camera: True
intrinsics: ~

cam0:
camera_model: "pinhole"
images : ""
intrinsics: [
481.14, 478.43, 481.44, 383.72
]
images : "cam0_*.jpg"

cam1:
camera_model: "pinhole"
images : "DSC_6468.jpg,DSC_6468.jpg"
camera_model: "opencv"
intrinsics: [
481.91, 482.20, 482.70, 384.33,
0.0, 0.0, 0.0, 0.0
]
images : "cam1_*.jpg"
```

For OpenMVG and MICMAC, refer to their respective sections.
11 changes: 1 addition & 10 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,10 @@ matcher:
th: 0.85
```

### Default configuration

Note that all the defaults configurations of DIM, including local features, matchers and geometric verification, are in `src/deep_image_matching/config.py`.


<!--
**Page under construction...**
### The Config Class

::: deep_image_matching.config.Config
options:
show_root_heading: true
show_source: false
members:
-->


4 changes: 1 addition & 3 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Example: `python main.py --dir ./assets/example_cyprus --pipeline superpoint+lig

Other optional parameters are:

- `--config_file` `-c`: the path to the YAML configuration file containing the custom configuration. See the [Advanced configuration](#advanced-configuration) section (default: `None`, so default configuration is used)
- `--config_file` `-c`: the path to the YAML configuration file containing the custom configuration. See the [Advanced configuration](./advanced_configuration.md) section (default: `None`, so default configuration is used)
- `--strategy` `-s`: the strategy to use for matching the images. It can be `matching_lowres`, `bruteforce`, `sequential`, `retrieval`, `custom_pairs`. See [Matching strategies](#matching-strategies) section (default: `matching_lowres`)
- `--quality` `-q`: the quality of the images to be matched. It can be `lowest`, `low`, `medium`, `high` or `highest`. See [Quality](#quality) section (default: `high`).
- `tiling` `-t`: if passed, the images are tiled in 4 parts and each part is matched separately. This is useful for high-resolution images if you do not want to resize them. See [Tiling](#tiling) section (default: `None`).
Expand Down Expand Up @@ -54,7 +54,6 @@ The GUI loads the available configurations from [`config.py`](https://github.com

If you want to use Deep_Image_Matching from a Jupyter notebook, you can check the examples in the [`notebooks`](https://github.com/3DOM-FBK/deep-image-matching/tree/master/notebooks) folder.


## Pipelines

The `pipeline` parameter defines the combination of local feature extractor and matcher to be used for the matching is is defined by the `--pipeline` option in the CLI.
Expand Down Expand Up @@ -130,4 +129,3 @@ If you want to run the matching by tile, you can choose different approaches for
- `exhaustive`: the images are divided into a regular grid of size 2400x2000 px to extract the features. The matching is carried out by matching all the possible combinations of tiles (brute-force). This method can be very slow for large images or in combination with the `highest` quality option and, in some cases, it may lead to error in the geometric verification if too many wrong matches are detected.

To control the tile size and the tile overlap, refer to the [Advanced configuration](./advanced_configuration.md) section.

25 changes: 7 additions & 18 deletions src/deep_image_matching/hloc/utils/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,9 @@

# This script is based on an original implementation by True Price.

import sys
import sqlite3
import numpy as np


IS_PYTHON3 = sys.version_info[0] >= 3
import numpy as np

MAX_IMAGE_ID = 2**31 - 1

Expand Down Expand Up @@ -68,9 +65,7 @@
prior_tz REAL,
CONSTRAINT image_id_check CHECK(image_id >= 0 and image_id < {}),
FOREIGN KEY(camera_id) REFERENCES cameras(camera_id))
""".format(
MAX_IMAGE_ID
)
""".format(MAX_IMAGE_ID)

CREATE_TWO_VIEW_GEOMETRIES_TABLE = """
CREATE TABLE IF NOT EXISTS two_view_geometries (
Expand Down Expand Up @@ -128,17 +123,11 @@ def pair_id_to_image_ids(pair_id):


def array_to_blob(array):
if IS_PYTHON3:
return array.tobytes()
else:
return np.getbuffer(array)
return array.tobytes()


def blob_to_array(blob, dtype, shape=(-1,)):
if IS_PYTHON3:
return np.fromstring(blob, dtype=dtype).reshape(*shape)
else:
return np.frombuffer(blob, dtype=dtype).reshape(*shape)
return np.frombuffer(blob, dtype=dtype).reshape(*shape)


class COLMAPDatabase(sqlite3.Connection):
Expand Down Expand Up @@ -183,8 +172,8 @@ def add_image(
self,
name,
camera_id,
prior_q=np.full(4, np.NaN),
prior_t=np.full(3, np.NaN),
prior_q=np.full(4, np.nan),
prior_t=np.full(3, np.nan),
image_id=None,
):
cursor = self.execute(
Expand Down Expand Up @@ -277,8 +266,8 @@ def add_two_view_geometry(


def example_usage():
import os
import argparse
import os

parser = argparse.ArgumentParser()
parser.add_argument("--database_path", default="database.db")
Expand Down
41 changes: 32 additions & 9 deletions src/deep_image_matching/io/h5_to_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,24 +144,32 @@ def get_focal(image_path: Path, err_on_default: bool = False) -> float:
return focal


def create_camera(db: Path, image_path: Path, camera_model: str):
def create_camera(
db: Path, image_path: Path, camera_model: str, param_arr: np.array = None
):
image = Image.open(image_path)
width, height = image.size

focal = get_focal(image_path)

if camera_model == "simple-pinhole":
model = 0 # simple pinhole
param_arr = np.array([focal, width / 2, height / 2])
if param_arr is None:
param_arr = np.array([focal, width / 2, height / 2])
elif camera_model == "pinhole":
model = 1 # pinhole
param_arr = np.array([focal, focal, width / 2, height / 2])
if param_arr is None:
param_arr = np.array([focal, focal, width / 2, height / 2])
elif camera_model == "simple-radial":
model = 2 # simple radial
param_arr = np.array([focal, width / 2, height / 2, 0.1])
if param_arr is None:
param_arr = np.array([focal, width / 2, height / 2, 0.1])
elif camera_model == "opencv":
model = 4 # opencv
param_arr = np.array([focal, focal, width / 2, height / 2, 0.0, 0.0, 0.0, 0.0])
if param_arr is None:
param_arr = np.array(
[focal, focal, width / 2, height / 2, 0.0, 0.0, 0.0, 0.0]
)
else:
raise RuntimeError(f"Invalid camera model {camera_model}")

Expand Down Expand Up @@ -192,13 +200,22 @@ def parse_camera_options(
n_cameras = len(camera_options.keys()) - 1
for camera in range(n_cameras):
cam_opt = camera_options[f"cam{camera}"]
images = cam_opt["images"].split(",")
# images = cam_opt["images"].split(",")
# use glob pattern to find images
patterns = cam_opt["images"].split(",")
images = []
for pattern in patterns:
images.extend(img.name for img in Path(image_path).glob(pattern))
images = sorted(images)

for i, img in enumerate(images):
grouped_images[img] = {"camera_id": camera + 1}
if i == 0:
path = os.path.join(image_path, img)
try:
create_camera(db, path, cam_opt["camera_model"])
create_camera(
db, path, cam_opt["camera_model"], cam_opt["intrinsics"]
)
except:
logger.warning(
f"Was not possible to load the first image to initialize cam{camera}"
Expand Down Expand Up @@ -242,12 +259,18 @@ def add_keypoints(
if filename not in list(grouped_images.keys()):
if camera_options["general"]["single_camera"] is False:
camera_id = create_camera(
db, path, camera_options["general"]["camera_model"]
db,
path,
camera_options["general"]["camera_model"],
camera_options["general"]["intrinsics"],
)
elif camera_options["general"]["single_camera"] is True:
if k == 0:
camera_id = create_camera(
db, path, camera_options["general"]["camera_model"]
db,
path,
camera_options["general"]["camera_model"],
camera_options["general"]["intrinsics"],
)
single_camera_id = camera_id
k += 1
Expand Down
23 changes: 6 additions & 17 deletions src/deep_image_matching/utils/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,9 @@
# This script is based on an original implementation by True Price.

import sqlite3
import sys

import numpy as np

IS_PYTHON3 = sys.version_info[0] >= 3

MAX_IMAGE_ID = 2**31 - 1

CREATE_CAMERAS_TABLE = """CREATE TABLE IF NOT EXISTS cameras (
Expand Down Expand Up @@ -68,9 +65,7 @@
prior_tz REAL,
CONSTRAINT image_id_check CHECK(image_id >= 0 and image_id < {}),
FOREIGN KEY(camera_id) REFERENCES cameras(camera_id))
""".format(
MAX_IMAGE_ID
)
""".format(MAX_IMAGE_ID)

CREATE_TWO_VIEW_GEOMETRIES_TABLE = """
CREATE TABLE IF NOT EXISTS two_view_geometries (
Expand Down Expand Up @@ -126,17 +121,11 @@ def pair_id_to_image_ids(pair_id):


def array_to_blob(array):
if IS_PYTHON3:
return array.tostring()
else:
return np.getbuffer(array)
return array.tobytes()


def blob_to_array(blob, dtype, shape=(-1,)):
if IS_PYTHON3:
return np.fromstring(blob, dtype=dtype).reshape(*shape)
else:
return np.frombuffer(blob, dtype=dtype).reshape(*shape)
return np.frombuffer(blob, dtype=dtype).reshape(*shape)


class COLMAPDatabase(sqlite3.Connection):
Expand Down Expand Up @@ -295,11 +284,9 @@ def example_usage():
args = parser.parse_args()

if os.path.exists(args.database_path):
print("ERROR: database path already exists -- will not modify it.")
return
os.remove(args.database_path)

# Open the database.

db = COLMAPDatabase.connect(args.database_path)

# For convenience, try creating all the tables upfront.
Expand Down Expand Up @@ -414,6 +401,8 @@ def example_usage():
if os.path.exists(args.database_path):
os.remove(args.database_path)

print("All tests passed.")


if __name__ == "__main__":
example_usage()