Skip to content
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
20 changes: 16 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,35 @@ Requires `Python 3.8+`.

## Usage

The code is under continuous development but at any point in time you should be able to run the matching procedure through:
If you have images sequences, start matching procedure by calling:

```bash
cd src/python
python run_matching.py \
python run_matching_from_images.py \
--query_images <path_to_images> \
--reference_images <path_to_images> \
--dataset_name <dataset_name> \
--output_dir <path_to_folder>
--write_image_matches
```

If you have pre-computed features already:

```bash
python run_matching_from_features.py \
--query_features <path_to_features> \
--reference_features <path_to_features> \
--dataset_name <dataset_name> \
--output_dir <path_to_folder>
```

\*\* Make sure the features are stored as a correct proto message `.Feature.pb`, check [localization_protos.proto](src/localization_protos.proto) for format details.

The framework assumes that there is a _query_ image sequence, for every image of which the user wants to find the corresponding image in the _reference_ image sequence.

The `run_matching.py` script stores all the results in the user-provided `output_dir`. The user also needs to specify the name of the dataset, for example, "my_awesome_dataset".
The scripts store all the results in the user-provided `output_dir`. The user also needs to specify the name of the dataset, for example, "my_awesome_dataset".

For more details about the parameters, please use `python run_matching.py --help`.
For more details about the parameters, please use `python run_matching_from_*.py --help`.

For more details about the underlying method and the interpretation of the results, please have a look at [paper](http://www.ipb.uni-bonn.de/pdfs/vysotska16ral-icra.pdf).
Here is a sketch of what roughly is happening for those who don't like to read much ![](doc/cost_matrix_view.png)
Expand Down
168 changes: 168 additions & 0 deletions src/python/matching_scripts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
from pathlib import Path
import os
from dataclasses import dataclass, fields, asdict, replace


@dataclass
class RunParameters:
path2query_images: str = None
path2ref_images: str = None
path2qu: str = None
path2ref: str = None
costMatrix: str = None
matchingResult: str = None
matchingResultImage: str = None
expansionRate: float = 0.3
fanOut: int = 5
nonMatchCost: float = 3.7
querySize: int = None
bufferSize: int = 100


def initializeFromDict(params, params_as_dict):
init_obj = replace(params, **params_as_dict)
return init_obj


def convertToDictWithoutNoneEntries(params):
# Iterate over the fields and check if any are None
for field in fields(params):
field_value = getattr(params, field.name) # Get the value of the field
if field_value is None:
print(f"Field '{field.name}' is None")

# Remove None entries
params_as_dict = asdict(params)
dict_without_none = {
key: value for key, value in params_as_dict.items() if value is not None
}
return dict_without_none


def convertNumpyFeaturesToProtoAndWrite(
np_features, output_folder, dataset_name, feature_type
):
params = "--filename {features_file} ".format(features_file=np_features)
params += "--feature_type {feature_type} ".format(feature_type=feature_type)
params += "--output_folder {output_folder} ".format(output_folder=output_folder)
params += "--output_file_prefix {output_file_prefix} ".format(
output_file_prefix=dataset_name
)

command = "python convert_numpy_features_to_protos.py " + params
print("Calling:", command)
os.system(command)


def computeNetVLADFeatures(image_dir, output_folder):
netvlad_weights = Path("netvlad/data/Pitts30K_struct.mat")
if not netvlad_weights.exists():
print(
"ERROR: Can't find netvlad weights. Please download to netvlad/data/ from https://cvg-data.inf.ethz.ch/hloc/netvlad/Pitts30K_struct.mat"
)
exit(1)

feature_name_prefix = "NetVLAD"
params = "--data_dir {image_dir} ".format(image_dir=image_dir)
np_features_file = output_folder / (feature_name_prefix + "-features.txt")
params += "--output_file {output_file} ".format(output_file=np_features_file)
params += "--netvlad_weights_file {weights} ".format(weights=netvlad_weights)

command = "python netvlad/netvlad_extractor.py " + params
print("Calling:", command)
os.system(command)
return np_features_file


def computeFeatures(image_dir, output_folder, dataset_name, feature_type):
if output_folder.exists():
print(
"WARNING: Feature folder {folder_name} exists. Skipping feature computation.".format(
folder_name=output_folder
)
)
return
output_folder.mkdir()

if feature_type == "NetVLAD":
print("Computing NetVLAD features")
np_features_file = computeNetVLADFeatures(image_dir, output_folder)
else:
print("ERROR. Unrecognized feature type.")
exit(1)

convertNumpyFeaturesToProtoAndWrite(
np_features_file, output_folder, dataset_name, feature_type
)


def computeCostMatrix(run_params):
params = "--query_features {query_features} ".format(
query_features=run_params.path2qu
)
params += "--db_features {reference_features} ".format(
reference_features=run_params.path2ref
)
params += "--cost_matrix_file {cost_matrix_file} ".format(
cost_matrix_file=run_params.costMatrix
)
command = "python compute_cost_matrix.py " + params
print("Calling:", command)
os.system(command)


def runMatching(config_yaml_file):
binary = "../../build/src/apps/cost_matrix_based_matching/online_localizer_lsh"
command = binary + " " + str(config_yaml_file)
print("Calling:", command)
os.system(command)


def runLocalizationResultVisualization(run_params):
params = "--cost_matrix {cost_matrix} ".format(cost_matrix=run_params.costMatrix)
params += "--matching_result {matching_result} ".format(
matching_result=run_params.matchingResult
)
params += "--image_name {image_name} ".format(
image_name=run_params.matchingResultImage
)
command = "python visualize_localization_result.py " + params
print("Calling:", command)
os.system(command)


def runStoreImageMatches(run_params, output_dir):
params = "--matching_result {matching_result} ".format(
matching_result=run_params.matchingResult
)
params += "--query_images {query_images} ".format(
query_images=run_params.path2query_images
)
params += "--reference_images {ref_images} ".format(
ref_images=run_params.path2ref_images
)
params += "--output_dir {output_dir}/matched_images ".format(output_dir=output_dir)

command = "python store_image_matches.py " + params
print("Calling:", command)
os.system(command)


def linkImages(image_dir, output_folder):
if output_folder.exists():
print(
"Output {output_folder} exists. Skipping...".format(
output_folder=output_folder
)
)
return
output_folder.mkdir()
images = image_dir.glob("*")
for image in images:
image_link_name = output_folder / image.name
command = "ln {image} {image_link_name}".format(
image=image, image_link_name=image_link_name
)
print(command)
os.system(command)
print("Linked images from", image_dir)
Loading