├── .gitmodules ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── copy_to_results_raw.sh ├── data ├── test.json └── val.json ├── debug_patches.ipynb ├── detect_sift_keypoints_and_extract_patches.py ├── download_kp2d_weights.sh ├── extract_d2net.py ├── extract_delf.py ├── extract_descriptors_geodesc.py ├── extract_descriptors_hardnet.py ├── extract_descriptors_jit.py ├── extract_descriptors_kornia.py ├── extract_descriptors_l2net.py ├── extract_descriptors_logpolar.py ├── extract_descriptors_sosnet.py ├── extract_kp2d_features.py ├── extract_lanet.py ├── extract_lfnet.py ├── extract_ml_superpoint.py ├── extract_r2d2.py ├── extract_sift_kornia_affnet_desc.py ├── extract_superoint_independent.py ├── generate_image_lists.py ├── generate_yaml.py ├── misc ├── colmap │ └── extract_colmap_feats_to_db.py └── l2net │ ├── convert_l2net_weights_matconv_pytorch.py │ ├── l2net_model.py │ └── l2net_ported_weights_lib+.pth ├── run_d2net.sh ├── run_delf.py ├── run_geodesc.sh ├── run_hardnet.sh ├── run_logpolar.sh ├── run_sosnet.sh ├── run_superpoint.sh ├── sp_configs.py ├── system ├── geodesc.yml ├── hardnet.yml ├── lfnet.yml └── r2d2-python3.6.yml ├── third_party ├── l2net_config │ ├── convert_l2net_weights_matconv_pytorch.py │ ├── l2net_model.py │ ├── l2net_ported_weights_lib+.pth │ ├── test_batch_img.mat │ └── test_one.mat └── superpoint_forked │ ├── LICENSE │ └── superpoint.py └── utils.py /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/hardnet"] 2 | path = third_party/hardnet 3 | url = https://github.com/DagnyT/hardnet.git 4 | [submodule "third_party/log_polar_descriptors"] 5 | path = third_party/log_polar_descriptors 6 | url = https://github.com/cvlab-epfl/log-polar-descriptors.git 7 | [submodule "third_party/geodesc"] 8 | path = third_party/geodesc 9 | url = https://github.com/lzx551402/geodesc.git 10 | [submodule "third_party/SOSNet"] 11 | path = third_party/SOSNet 12 | url = https://github.com/yuruntian/SOSNet.git 13 | [submodule "third_party/superpoint_ml_repo"] 14 | path = third_party/superpoint_ml_repo 15 | url = https://github.com/MagicLeapResearch/SuperPointPretrainedNetwork 16 | [submodule "third_party/d2net"] 17 | path = third_party/d2net 18 | url = https://github.com/mihaidusmanu/d2-net.git 19 | [submodule "third_party/HardNet2"] 20 | path = third_party/HardNet2 21 | url = https://github.com/pultarmi/HardNet_MultiDataset 22 | [submodule "third_party/r2d2"] 23 | path = third_party/r2d2 24 | url = https://github.com/naver/r2d2.git 25 | [submodule "third_party/contextdesc"] 26 | path = third_party/contextdesc 27 | url = https://github.com/jyh2005xx/contextdesc.git 28 | [submodule "third_party/l2net"] 29 | path = third_party/l2net 30 | url = https://github.com/yuruntian/L2-Net.git 31 | [submodule "third_party/tensorflow_models"] 32 | path = third_party/tensorflow_models 33 | url = https://github.com/tensorflow/models 34 | [submodule "third_party/lfnet"] 35 | path = third_party/lfnet 36 | url = git@github.com:vcg-uvic/lf-net-release.git 37 | [submodule "third_party/KP2D"] 38 | path = third_party/KP2D 39 | url = https://github.com/TRI-ML/KP2D.git 40 | [submodule "third_party/pytorch-superpoint"] 41 | path = third_party/pytorch-superpoint 42 | url = https://github.com/ducha-aiki/pytorch-superpoint.git 43 | [submodule "third_party/lanet"] 44 | path = third_party/lanet 45 | url = https://github.com/wangch-g/lanet.git 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows 28 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright Google, University of Victoria (UVIC), Czech Technical 191 | University (CTU, Prague) 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | This repository contains utilities to extract local features for the [Image Matching Benchmark](https://github.com/ubc-vision/image-matching-benchmark) and its associated challenge. For details please refer to the [website](https://image-matching-challenge.github.io). 4 | 5 | ## Data 6 | 7 | Data can be downloaded [here](https://www.cs.ubc.ca/~kmyi/imw2020/data.html): you may want to download the images for validation and testing. Most of the scripts assume that the images are in `../imw-2020`, as follows: 8 | 9 | ``` 10 | $ ~/image-matching-benchmark-baselines $ ls ../imw-2020/ 11 | british_museum lincoln_memorial_statue milan_cathedral piazza_san_marco sacre_coeur st_pauls_cathedral united_states_capitol 12 | florence_cathedral_side london_bridge mount_rushmore reichstag sagrada_familia st_peters_square 13 | 14 | $ ~/image-matching-benchmark-baselines $ ls ../imw-2020/british_museum/ 15 | 00350405_2611802704.jpg 26237164_4796395587.jpg 45839934_4117745134.jpg [...] 16 | ``` 17 | 18 | You may need to format the validation set in this way. 19 | 20 | ## Installation 21 | 22 | Initialize the submodules by running the following: 23 | ``` 24 | git submodule update --init 25 | ``` 26 | 27 | We provide support for the following methods: 28 | 29 | * [Hardnet](https://github.com/DagnyT/hardnet.git) 30 | * [HardnetAmos](https://github.com/pultarmi/HardNet_MultiDataset) 31 | * [GeoDesc](https://github.com/lzx551402/geodesc.git) 32 | * [SOSNet](https://github.com/yuruntian/SOSNet.git) 33 | * [L2Net](https://github.com/yuruntian/L2-Net) 34 | * [Log-polar descriptor](https://github.com/DagnyT/hardnet_ptn.git) 35 | * [Superpoint](https://github.com/MagicLeapResearch/SuperPointPretrainedNetwork) 36 | * [D2-Net](https://github.com/mihaidusmanu/d2-net) 37 | * [DELF](https://github.com/tensorflow/models/blob/master/research/delf/INSTALL_INSTRUCTIONS.md) 38 | * [Contextdesc](https://github.com/lzx551402/contextdesc) 39 | * [LFNet](https://github.com/vcg-uvic/lf-net-release) 40 | * [R2D2](https://github.com/naver/r2d2) 41 | 42 | We have pre-packaged conda environments: see below for details. You can install miniconda following [these instructions](https://docs.conda.io/en/latest/miniconda.html) (we have had problems with the latest version -- consider an [older one](https://repo.continuum.io/miniconda/Miniconda3-4.5.12-Linux-x86_64.sh)). You can install an environment with: 43 | ``` 44 | conda env create -f system/.yml 45 | ``` 46 | 47 | And switch between them with: 48 | ``` 49 | conda deactivate 50 | conda activate 51 | ``` 52 | 53 | ## Patch-based descriptors 54 | 55 | ### Pre-extracting patches for patch-based descriptors 56 | 57 | Many learned descriptors require pre-generated patches. This functionality is useful by itself, so we moved it to a [separate package](https://pypi.org/project/extract-patches/). You can install it with `pip install extract_patches`: please note that this requires python 3.6, as the package is generated via nbdev). You may do do this with the `system/r2d2-python3.6.yml` environment (which also requires 3.6 due to formatted string literals) or create a different environment. 58 | 59 | To extract patches with the default configuration to `../benchmark-patches-8k`, run: 60 | ``` 61 | python detect_sift_keypoints_and_extract_patches.py 62 | ``` 63 | 64 | This will create the following HDF5 files: 65 | ``` 66 | $ stat -c "%s %n" ../benchmark-patches-8k/british_museum/* 67 | 6414352 ../benchmark-patches-8k/british_museum/angles.h5 68 | 12789024 ../benchmark-patches-8k/british_museum/keypoints.h5 69 | 2447913728 ../benchmark-patches-8k/british_museum/patches.h5 70 | 6414352 ../benchmark-patches-8k/british_museum/scales.h5 71 | 6414352 ../benchmark-patches-8k/british_museum/scores.h5 72 | ``` 73 | 74 | You can also extract patches with a fixed orientation with the flag `--force_upright=no-dups-more-points`: this option will filter out duplicate orientations and add more points until it reaches the keypoint budget (if possible). 75 | ``` 76 | python detect_sift_keypoints_and_extract_patches.py --force_upright=no-dups-more-points --folder_outp=../benchmark-patches-8k-upright-no-dups 77 | ``` 78 | 79 | These settings generate about (up to) 8000 features per image, which requires lowering the SIFT detection threshold. If you want fewer features (~2k), you may want to use the default detection threshold, as the results are typically slightly better: 80 | ``` 81 | python detect_sift_keypoints_and_extract_patches.py --n_keypoints 2048 --folder_outp=../benchmark-patches-default --lower_sift_threshold=False 82 | python detect_sift_keypoints_and_extract_patches.py --n_keypoints 2048 --force_upright=no-dups-more-points --folder_outp=../benchmark-patches-default-upright-no-dups --lower_sift_threshold=False 83 | ``` 84 | 85 | After this you can extract features with `run_.sh`, or following the instructions below. The shell scripts use reasonable defaults: please refer to each individual wrapper for further settings (upright patches, different NMS, etc). 86 | 87 | ### Extracting descriptors from pre-generated patches 88 | 89 | For HardNet (environment `hardnet`): 90 | ``` 91 | python extract_descriptors_hardnet.py 92 | ``` 93 | 94 | For SOSNet (environment `hardnet`): 95 | ``` 96 | python extract_descriptors_sosnet.py 97 | ``` 98 | 99 | For L2Net (environment `hardnet`): 100 | ``` 101 | python extract_descriptors_l2net.py 102 | ``` 103 | 104 | The Log-Polar Descriptor (environment `hardnet`) requires access to the original images. For the log-polar models, use: 105 | ``` 106 | python extract_descriptors_logpolar.py --config_file=third_party/log_polar_descriptors/configs/init_one_example_ptn_96.yml --method_name=sift8k_8000_logpolar96 107 | ``` 108 | 109 | and for the cartesian models, use: 110 | ``` 111 | python extract_descriptors_logpolar.py --config_file=third_party/log_polar_descriptors/configs/init_one_example_stn_16.yml --method_name=sift8k_8000_cartesian16 112 | ``` 113 | 114 | For Geodesc (environment `geodesc`): 115 | ``` 116 | wget http://home.cse.ust.hk/~zluoag/data/geodesc.pb -O third_party/geodesc/model/geodesc.pb 117 | python extract_descriptors_geodesc.py 118 | ``` 119 | 120 | Check the files for more options. 121 | 122 | ## End-to-end methods 123 | 124 | ### Superpoint 125 | 126 | Use environment `hardnet`. Keypoints are sorted by score and only the top `num_kp` are kept. You can extract features with default parameters with the following: 127 | ``` 128 | python third_party/superpoint_forked/superpoint.py --cuda --num_kp=2048 --method_name=superpoint_default_2048 129 | ``` 130 | 131 | You can also lower the detection threshold to extract more features, and resize the images to a fixed size (on the largest dimension), e.g.: 132 | ``` 133 | python third_party/superpoint_forked/superpoint.py --cuda --num_kp=8000 --conf_thresh=0.0001 --nms_dist=2 --resize_image_to=1024 --num_kp=8000 --method_name=superpoint_8k_resize1024_nms2 134 | ``` 135 | 136 | ### D2-Net 137 | 138 | Use environment `hardnet`. Following D2-Net's settings, you can generate text lists of the images with: 139 | ``` 140 | python generate_image_lists.py 141 | ``` 142 | Download the weights (use this set, as the default has some overlap with out test subset): 143 | ```bash 144 | mkdir third_party/d2net/models 145 | wget https://dsmn.ml/files/d2-net/d2_tf_no_phototourism.pth -O third_party/d2net/models/d2_tf_no_phototourism.pth 146 | ``` 147 | You can then extract single-scale D2-Net features with: 148 | ``` 149 | python extract_d2net.py --num_kp=8000 --method_name=d2net-default_8000 150 | ``` 151 | and multi-scale D2-Net features (add the `--cpu` flag if your GPU runs out of memory) with: 152 | ``` 153 | python extract_d2net.py --num_kp=8000 --multiscale --method_name=d2net-multiscale_8000 154 | ``` 155 | (If the multi-scale variant crashes, please check [this](https://github.com/mihaidusmanu/d2-net/issues/22).) 156 | 157 | 158 | ### ContextDesc 159 | 160 | Use environment `hardnet` and download the model weights: 161 | ``` 162 | mkdir third_party/contextdesc/pretrained 163 | wget https://research.altizure.com/data/contextdesc_models/contextdesc_pp.tar -O third_party/contextdesc/pretrained/contextdesc_pp.tar 164 | wget https://research.altizure.com/data/contextdesc_models/retrieval_model.tar -O third_party/contextdesc/pretrained/retrieval_model.tar 165 | wget https://research.altizure.com/data/contextdesc_models/contextdesc_pp_upright.tar -O third_party/contextdesc/pretrained/contextdesc_pp_upright.tar 166 | tar -C third_party/contextdesc/pretrained/ -xf third_party/contextdesc/pretrained/contextdesc_pp.tar 167 | tar -C third_party/contextdesc/pretrained/ -xf third_party/contextdesc/pretrained/contextdesc_pp_upright.tar 168 | tar -C third_party/contextdesc/pretrained/ -xf third_party/contextdesc/pretrained/retrieval_model.tar 169 | rm third_party/contextdesc/pretrained/contextdesc_pp.tar 170 | rm third_party/contextdesc/pretrained/contextdesc_pp_upright.tar 171 | rm third_party/contextdesc/pretrained/retrieval_model.tar 172 | ``` 173 | Generate the `.yaml` file for ContextDesc: 174 | ``` 175 | python generate_yaml.py --num_keypoints=8000 176 | ``` 177 | Extract ContextDesc: 178 | ``` 179 | python third_party/contextdesc/evaluations.py --config yaml/imw-2020.yaml 180 | ``` 181 | You may delete the `tmp` folder after extracting the features: 182 | ``` 183 | rm -rf ../benchmark-features/tmp_contextdesc 184 | ``` 185 | 186 | 187 | ### DELF 188 | 189 | You can install DELF from the tensorflow models repository, following [these instructions](https://github.com/tensorflow/models/blob/master/research/delf/INSTALL_INSTRUCTIONS.md). 190 | 191 | You have to download the model: 192 | ``` 193 | mkdir third_party/tensorflow_models/research/delf/delf/python/examples/parameters/ 194 | wget http://storage.googleapis.com/delf/delf_gld_20190411.tar.gz -O third_party/tensorflow_models/research/delf/delf/python/examples/parameters/delf_gld_20190411.tar.gz 195 | tar -C third_party/tensorflow_models/research/delf/delf/python/examples/parameters/ -xvf third_party/tensorflow_models/research/delf/delf/python/examples/parameters/delf_gld_20190411.tar.gz 196 | ``` 197 | and add the folder `third_party/tensorflow_models/research` to $PYTHONPATH. See `run_delf.py` for usage. 198 | 199 | 200 | ### LF-Net 201 | 202 | Use environment `lfnet` and download the model weights: 203 | ``` 204 | mkdir third_party/lfnet/release 205 | wget https://cs.ubc.ca/research/kmyi_data/files/2018/lf-net/lfnet-norotaug.tar.gz -O third_party/lfnet/release/lfnet-norotaug.tar.gz 206 | tar -C third_party/lfnet/release/ -xf third_party/lfnet/release/lfnet-norotaug.tar.gz 207 | ``` 208 | Use environment 'lfnet'. Refer to extract_lfnet.py for more options. Extract LF-Net with default 2K keypoints and without resize image: 209 | ``` 210 | python extract_lfnet.py --out_dir=../benchmark-features/lfnet 211 | ``` 212 | 213 | 214 | ### R2D2 215 | 216 | Use the environment `r2d2-python-3.6` (requires 3.6 for f-strings). For options, please see the script. The authors provide three pre-trained models which can be used with: 217 | 218 | ``` 219 | python extract_r2d2.py --model=third_party/r2d2/models/r2d2_WAF_N16.pt --num_keypoints=8000 --save_path=../benchmark-features/r2d2-waf-n16-8k 220 | python extract_r2d2.py --model=third_party/r2d2/models/r2d2_WASF_N16.pt --num_keypoints=8000 --save_path=../benchmark-features/r2d2-wasf-n16-8k 221 | python extract_r2d2.py --model=third_party/r2d2/models/r2d2_WASF_N8_big.pt --num_keypoints=8000 --save_path=../benchmark-features/r2d2-wasf-n8-big-8k 222 | ``` 223 | 224 | ## VLFeat features (via Matlab) 225 | 226 | Matlab-based features are in a [separate repository](https://github.com/ducha-aiki/sfm-benchmark-matlab-features). You can run: 227 | ```bash 228 | ./run_vlfeat_alone.sh 229 | ./run_vlfeat_with_affnet_and_hardnet.sh 230 | ``` 231 | -------------------------------------------------------------------------------- /copy_to_results_raw.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for fdir in ../benchmark-features/* 4 | do 5 | echo ${fdir} 6 | fname="$(basename "$fdir")" 7 | echo $fname 8 | for seq in ${fdir}/* 9 | do 10 | seqname="$(basename "$seq")"; 11 | mkdir ../results_raw_upright/$seqname/$fname 12 | cp $seq/* ../results_raw_upright/$seqname/$fname/ 13 | done 14 | done 15 | -------------------------------------------------------------------------------- /data/test.json: -------------------------------------------------------------------------------- 1 | [ 2 | "british_museum", 3 | "florence_cathedral_side", 4 | "lincoln_memorial_statue", 5 | "london_bridge", 6 | "milan_cathedral", 7 | "mount_rushmore", 8 | "piazza_san_marco", 9 | "sagrada_familia", 10 | "st_pauls_cathedral", 11 | "united_states_capitol" 12 | ] 13 | -------------------------------------------------------------------------------- /data/val.json: -------------------------------------------------------------------------------- 1 | [ 2 | "reichstag", 3 | "sacre_coeur", 4 | "st_peters_square" 5 | ] 6 | -------------------------------------------------------------------------------- /detect_sift_keypoints_and_extract_patches.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import cv2 4 | from tqdm import tqdm 5 | import argparse 6 | import json 7 | 8 | from extract_patches.core import extract_patches 9 | from utils import save_h5 10 | 11 | 12 | def str2bool(v): 13 | if v.lower() in ('yes', 'true', 't', 'y', '1'): 14 | return True 15 | elif v.lower() in ('no', 'false', 'f', 'n', '0'): 16 | return False 17 | 18 | 19 | def l_clahe(img): 20 | clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) 21 | lab = cv2.cvtColor(img, cv2.COLOR_RGB2Lab) 22 | lab[:, :, 0] = clahe.apply(lab[:, :, 0]) 23 | return cv2.cvtColor(lab, cv2.COLOR_Lab2RGB) 24 | 25 | 26 | def get_SIFT_keypoints(sift, img, lower_detection_th=False): 27 | 28 | # convert to gray-scale and compute SIFT keypoints 29 | keypoints = sift.detect(img, None) 30 | 31 | response = np.array([kp.response for kp in keypoints]) 32 | respSort = np.argsort(response)[::-1] 33 | 34 | pt = np.array([kp.pt for kp in keypoints])[respSort] 35 | size = np.array([kp.size for kp in keypoints])[respSort] 36 | angle = np.array([kp.angle for kp in keypoints])[respSort] 37 | response = np.array([kp.response for kp in keypoints])[respSort] 38 | 39 | return pt, size, angle, response 40 | 41 | 42 | if __name__ == '__main__': 43 | 44 | parser = argparse.ArgumentParser() 45 | 46 | parser.add_argument( 47 | "--scenes_folder", 48 | default=os.path.join('..', 'imw-2020'), 49 | help="path to config file", 50 | type=str) 51 | parser.add_argument( 52 | "--folder_outp", 53 | default=os.path.join('..', 'benchmark-patches-8k'), 54 | type=str) 55 | parser.add_argument( 56 | "--mrSize", 57 | default=12.0, 58 | type=float, 59 | help=' patch size in image is mrSize * pt.size. Default mrSize is 12') 60 | parser.add_argument( 61 | "--patchSize", 62 | default=32, 63 | type=int, 64 | help=' patch size in pixels. Default 32') 65 | parser.add_argument( 66 | "--lower_sift_threshold", 67 | default='True', 68 | type=str2bool, 69 | help='Lower detection threshold (useful to extract 8k features)') 70 | parser.add_argument( 71 | "--clahe-mode", 72 | default='None', 73 | type=str, 74 | help='can be None, detector, descriptor, both') 75 | parser.add_argument( 76 | "--subset", 77 | default='both', 78 | type=str, 79 | help='Options: "val", "test", "both", "spc-fix"') 80 | parser.add_argument( 81 | "--force_upright", 82 | default='off', 83 | type=str, 84 | help='Options: "off", "no-dups", "no-dups-more-points"') 85 | parser.add_argument("--n_keypoints", default=8000, type=int) 86 | 87 | args = parser.parse_args() 88 | 89 | if args.subset not in ['val', 'test', 'both', 'spc-fix']: 90 | raise ValueError('Unknown value for --subset') 91 | 92 | if args.lower_sift_threshold: 93 | print('Instantiating SIFT detector with a lower detection threshold') 94 | sift = cv2.xfeatures2d.SIFT_create( 95 | contrastThreshold=-10000, edgeThreshold=-10000) 96 | else: 97 | print('Instantiating SIFT detector with default values') 98 | sift = cv2.xfeatures2d.SIFT_create() 99 | 100 | if not os.path.isdir(args.folder_outp): 101 | os.makedirs(args.folder_outp) 102 | 103 | scenes = [] 104 | if args.subset == 'spc-fix': 105 | scenes += ['st_pauls_cathedral'] 106 | else: 107 | if args.subset in ['val', 'both']: 108 | with open(os.path.join('data', 'val.json')) as f: 109 | scenes += json.load(f) 110 | if args.subset in ['test', 'both']: 111 | with open(os.path.join('data', 'test.json')) as f: 112 | scenes += json.load(f) 113 | print('Processing the following scenes: {}'.format(scenes)) 114 | 115 | suffix = "" 116 | if args.clahe_mode.lower() == 'detector': 117 | suffix = "_clahe_det" 118 | elif args.clahe_mode.lower() == 'descriptor': 119 | suffix = "_clahe_desc" 120 | elif args.clahe_mode.lower() == 'both': 121 | suffix = "_clahe_det_desc" 122 | elif args.clahe_mode.lower() == 'none': 123 | pass 124 | else: 125 | raise ValueError( 126 | "unknown CLAHE mode. Try detector, descriptor or both") 127 | 128 | assert(args.mrSize > 0) 129 | if abs(args.mrSize - 12.) > 0.1: 130 | suffix += '_mrSize{:.1f}'.format(args.mrSize) 131 | 132 | assert(args.patchSize > 0) 133 | if args.patchSize != 32: 134 | suffix += '_patchSize{}'.format(args.patchSize) 135 | for scene in scenes: 136 | print('Processing "{}"'.format(scene)) 137 | scene_patches, scene_kp, scene_loc, scene_scale, \ 138 | sec_ori, sec_resp = {}, {}, {}, {}, {}, {} 139 | 140 | scene_path = os.path.join(args.scenes_folder, 141 | scene, 'set_100/images/') 142 | num_patches = [] 143 | img_list = [x for x in os.listdir(scene_path) if x.endswith('.jpg')] 144 | for im_path in tqdm(img_list): 145 | img_name = im_path.replace('.jpg', '') 146 | im = cv2.cvtColor( 147 | cv2.imread(os.path.join(scene_path, im_path)), 148 | cv2.COLOR_BGR2RGB) 149 | if args.clahe_mode.lower() in ['detector', 'both']: 150 | img_gray = cv2.cvtColor(l_clahe(im), cv2.COLOR_RGB2GRAY) 151 | else: 152 | img_gray = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY) 153 | 154 | keypoints, scales, angles, responses = get_SIFT_keypoints(sift, 155 | img_gray) 156 | 157 | if args.force_upright == 'off': 158 | # Nothing to do 159 | kpts = [ 160 | cv2.KeyPoint( 161 | x=point[0], 162 | y=point[1], 163 | _size=scales[i], 164 | _angle=angles[i]) for i, point in enumerate(keypoints) 165 | ] 166 | elif args.force_upright == 'no-dups': 167 | # Set orientation to zero, remove duplicates later 168 | # This is a subset of the previous set 169 | kpts = [ 170 | cv2.KeyPoint( 171 | x=keypoints[i][0], 172 | y=keypoints[i][1], 173 | _size=scales[i], 174 | _angle=0) for i, point in enumerate(keypoints) 175 | ] 176 | elif args.force_upright == 'no-dups-more-points': 177 | # Copy without duplicates, set orientation to zero 178 | # The cropped list may contain new points 179 | kpts = [ 180 | cv2.KeyPoint( 181 | x=keypoints[i][0], 182 | y=keypoints[i][1], 183 | _size=scales[i], 184 | _angle=0) for i, point in enumerate(keypoints) 185 | if point not in keypoints[:i] 186 | ] 187 | else: 188 | raise ValueError('Unknown --force_upright setting') 189 | 190 | # apply CLAHE 191 | im = cv2.cvtColor( 192 | cv2.imread(os.path.join(scene_path, im_path)), 193 | cv2.COLOR_BGR2RGB) 194 | if args.clahe_mode.lower() in ['descriptor', 'both']: 195 | im = l_clahe(im) 196 | 197 | # Extract patches 198 | patches = extract_patches( 199 | kpts, im, args.patchSize, args.mrSize) 200 | keypoints = np.array([(x.pt[0], x.pt[1]) for x in kpts ]).reshape(-1, 2) 201 | scales = np.array([args.mrSize * x.size for x in kpts ]).reshape(-1, 1) 202 | angles = np.array([x.angle for x in kpts ]).reshape(-1, 1) 203 | responses = np.array([x.response for x in kpts ]).reshape(-1, 1) 204 | 205 | # Crop 206 | patches = np.array(patches)[:args.n_keypoints].astype(np.uint8) 207 | keypoints = keypoints[:args.n_keypoints] 208 | scales = scales[:args.n_keypoints] 209 | angles = angles[:args.n_keypoints] 210 | responses = responses[:args.n_keypoints] 211 | 212 | # Remove duplicates after cropping 213 | if args.force_upright == 'no-dups': 214 | _, unique = np.unique(keypoints, axis=0, return_index=True) 215 | patches = patches[unique] 216 | keypoints = keypoints[unique] 217 | scales = scales[unique] 218 | angles = angles[unique] 219 | responses = responses[unique] 220 | 221 | # Patches are already uint8 222 | num_patches.append(patches.shape[0]) 223 | 224 | scene_patches[img_name] = patches 225 | scene_kp[img_name] = keypoints 226 | scene_scale[img_name] = scales 227 | sec_ori[img_name] = angles 228 | sec_resp[img_name] = responses 229 | 230 | print('Processed {} images: {} patches/image'.format( 231 | len(num_patches), np.array(num_patches).mean())) 232 | 233 | cur_path = os.path.join(args.folder_outp, scene) 234 | # if args.force_upright == 'no-dups': 235 | # cur_path += '_upright_v1' 236 | # elif args.force_upright == 'no-dups-more-points': 237 | # cur_path += '_upright_v2' 238 | if not os.path.isdir(cur_path): 239 | os.makedirs(cur_path) 240 | 241 | save_h5(scene_patches, 242 | os.path.join(cur_path, 'patches{}.h5'.format(suffix))) 243 | save_h5(scene_kp, os.path.join(cur_path, 244 | 'keypoints{}.h5'.format(suffix))) 245 | save_h5(scene_scale, os.path.join(cur_path, 246 | 'scales{}.h5'.format(suffix))) 247 | save_h5(sec_ori, os.path.join(cur_path, 'angles{}.h5'.format(suffix))) 248 | save_h5(sec_resp, os.path.join(cur_path, 'scores{}.h5'.format(suffix))) 249 | 250 | print('Done!') 251 | -------------------------------------------------------------------------------- /download_kp2d_weights.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir third_party/KP2D/data 3 | mkdir third_party/KP2D/data/models 4 | mkdir third_party/KP2D/data/models/kp2d/ 5 | wget https://tri-ml-public.s3.amazonaws.com/github/kp2d/models/pretrained_models.tar.gz 6 | mv pretrained_models.tar.gz third_party/KP2D/data/models/kp2d/ 7 | cd third_party/KP2D/data/models/kp2d/ && tar -xzf pretrained_models.tar.gz 8 | -------------------------------------------------------------------------------- /extract_d2net.py: -------------------------------------------------------------------------------- 1 | # Forked from https://github.com/mihaidusmanu/d2-net:extract_features.py 2 | 3 | import argparse 4 | import numpy as np 5 | import imageio 6 | import torch 7 | import json 8 | from tqdm import tqdm 9 | from time import time 10 | import os 11 | import sys 12 | 13 | import scipy 14 | import scipy.io 15 | import scipy.misc 16 | 17 | sys.path.append(os.path.join('third_party', 'd2net')) 18 | from lib.model_test import D2Net 19 | from lib.utils import preprocess_image 20 | from lib.pyramid import process_multiscale 21 | from utils import save_h5 22 | 23 | 24 | # Argument parsing 25 | parser = argparse.ArgumentParser(description='Feature extraction script') 26 | 27 | parser.add_argument( 28 | "--save_path", 29 | default=os.path.join('..', 'benchmark-features'), 30 | type=str, 31 | help='Path to store the features') 32 | 33 | parser.add_argument( 34 | "--method_name", default='d2-net-singlescale_8000', type=str) 35 | 36 | parser.add_argument( 37 | '--preprocessing', type=str, default='caffe', 38 | help='image preprocessing (caffe or torch)' 39 | ) 40 | parser.add_argument( 41 | '--model_file', type=str, 42 | default=os.path.join( 43 | 'third_party', 'd2net', 'models', 'd2_tf_no_phototourism.pth'), 44 | help='path to the full model' 45 | ) 46 | 47 | parser.add_argument( 48 | '--max_edge', type=int, default=1600, 49 | help='maximum image size at network input' 50 | ) 51 | parser.add_argument( 52 | '--max_sum_edges', type=int, default=2800, 53 | help='maximum sum of image sizes at network input' 54 | ) 55 | 56 | parser.add_argument( 57 | '--multiscale', dest='multiscale', action='store_true', 58 | help='extract multiscale features' 59 | ) 60 | parser.set_defaults(multiscale=False) 61 | 62 | parser.add_argument( 63 | '--no-relu', dest='use_relu', action='store_false', 64 | help='remove ReLU after the dense feature extraction module' 65 | ) 66 | parser.set_defaults(use_relu=True) 67 | 68 | parser.add_argument( 69 | '--cpu', dest='cpu', action='store_true', 70 | help='Use CPU instead of GPU' 71 | ) 72 | 73 | parser.add_argument( 74 | '--num_kp', 75 | type=int, 76 | default=0, 77 | help='Number of keypoints to save (0 to keep all)') 78 | 79 | parser.set_defaults(cpu=False) 80 | 81 | parser.add_argument( 82 | "--subset", 83 | default='both', 84 | type=str, 85 | help='Options: "val", "test", "both", "spc-fix"') 86 | 87 | args, unparsed = parser.parse_known_args() 88 | 89 | # Parse dataset 90 | if args.subset not in ['val', 'test', 'both', 'spc-fix']: 91 | raise ValueError('Unknown value for --subset') 92 | seqs = [] 93 | if args.subset == 'spc-fix': 94 | seqs += ['st_pauls_cathedral'] 95 | else: 96 | if args.subset in ['val', 'both']: 97 | with open(os.path.join('data', 'val.json')) as f: 98 | seqs += json.load(f) 99 | if args.subset in ['test', 'both']: 100 | with open(os.path.join('data', 'test.json')) as f: 101 | seqs += json.load(f) 102 | print('Processing the following scenes: {}'.format(seqs)) 103 | 104 | # CUDA 105 | if args.cpu: 106 | print('Using CPU') 107 | use_cuda = False 108 | device = None 109 | else: 110 | print('Using GPU (if available)') 111 | use_cuda = torch.cuda.is_available() 112 | device = torch.device("cuda:0" if use_cuda else "cpu") 113 | 114 | print(args) 115 | 116 | # Creating CNN model 117 | model = D2Net( 118 | model_file=args.model_file, 119 | use_relu=args.use_relu, 120 | use_cuda=use_cuda 121 | ) 122 | 123 | for seq in seqs: 124 | print('Processing "{}"'.format(seq)) 125 | seq_keypoints = {} 126 | seq_scores = {} 127 | seq_descriptors = {} 128 | seq_scales = {} 129 | 130 | # Open the text file 131 | with open(os.path.join('txt', 'list-{}.txt'.format(seq)), 'r') as f: 132 | lines = f.readlines() 133 | 134 | # Process each image 135 | for line in tqdm(lines, total=len(lines)): 136 | path = line.strip() 137 | key = os.path.basename(os.path.splitext(path)[0]) 138 | 139 | image = imageio.imread(path) 140 | if len(image.shape) == 2: 141 | image = image[:, :, np.newaxis] 142 | image = np.repeat(image, 3, -1) 143 | 144 | # TODO: switch to PIL.Image due to deprecation of scipy.misc.imresize. 145 | resized_image = image 146 | if max(resized_image.shape) > args.max_edge: 147 | resized_image = scipy.misc.imresize( 148 | resized_image, 149 | args.max_edge / max(resized_image.shape) 150 | ).astype('float') 151 | if sum(resized_image.shape[: 2]) > args.max_sum_edges: 152 | resized_image = scipy.misc.imresize( 153 | resized_image, 154 | args.max_sum_edges / sum(resized_image.shape[: 2]) 155 | ).astype('float') 156 | 157 | fact_i = image.shape[0] / resized_image.shape[0] 158 | fact_j = image.shape[1] / resized_image.shape[1] 159 | 160 | t_start = time() 161 | input_image = preprocess_image( 162 | resized_image, 163 | preprocessing=args.preprocessing 164 | ) 165 | with torch.no_grad(): 166 | if args.multiscale: 167 | keypoints, scores, descriptors = process_multiscale( 168 | torch.tensor( 169 | input_image[np.newaxis, :, :, :].astype(np.float32), 170 | device=device 171 | ), 172 | model 173 | ) 174 | else: 175 | keypoints, scores, descriptors = process_multiscale( 176 | torch.tensor( 177 | input_image[np.newaxis, :, :, :].astype(np.float32), 178 | device=device 179 | ), 180 | model, 181 | scales=[1] 182 | ) 183 | t_end = time() 184 | 185 | # Input image coordinates 186 | keypoints[:, 0] *= fact_i 187 | keypoints[:, 1] *= fact_j 188 | 189 | # Sort the scores and subsample 190 | indices = np.argsort(scores)[::-1] 191 | if args.num_kp > 0: 192 | top_k = indices[:args.num_kp] 193 | else: 194 | top_k = indices 195 | 196 | # Flip coordinates: network provides [y, x] 197 | seq_keypoints[key] = np.concatenate( 198 | [keypoints[top_k, 1][..., None], 199 | keypoints[top_k, 0][..., None]], 200 | axis=1) 201 | seq_scales[key] = keypoints[top_k, 2] 202 | seq_scores[key] = scores[top_k] 203 | seq_descriptors[key] = descriptors[top_k, :] 204 | 205 | # print('Processed "{}" in {:.02f} sec. Found {} features'.format( 206 | # key, t_end - t_start, keypoints.shape[0])) 207 | 208 | print('Average number of keypoints per image: {:.02f}'.format( 209 | np.mean([v.shape[0] for v in seq_keypoints.values()]))) 210 | 211 | cur_path = os.path.join(args.save_path, args.method_name, seq) 212 | if not os.path.exists(cur_path): 213 | os.makedirs(cur_path) 214 | save_h5(seq_descriptors, os.path.join(cur_path, 'descriptors.h5')) 215 | save_h5(seq_keypoints, os.path.join(cur_path, 'keypoints.h5')) 216 | save_h5(seq_scores, os.path.join(cur_path, 'scores.h5')) 217 | save_h5(seq_scales, os.path.join(cur_path, 'scales.h5')) 218 | 219 | print('Done') 220 | -------------------------------------------------------------------------------- /extract_delf.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 The TensorFlow Authors All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | # Forked from: 16 | # https://github.com/tensorflow/models/blob/master/research/delf/delf/python/examples/extract_features.py 17 | """Extracts DELF features from a list of images, saving them to file. 18 | 19 | The images must be in JPG format. The program checks if descriptors already 20 | exist, and skips computation for those. 21 | """ 22 | 23 | from __future__ import absolute_import 24 | from __future__ import division 25 | from __future__ import print_function 26 | 27 | import argparse 28 | import os 29 | import sys 30 | import time 31 | import json 32 | import numpy as np 33 | import h5py 34 | import tensorflow as tf 35 | 36 | from google.protobuf import text_format 37 | from tensorflow.python.platform import app 38 | from delf import delf_config_pb2 39 | from delf import feature_extractor 40 | from delf import feature_io 41 | 42 | cmd_args = None 43 | 44 | # Extension of feature files. 45 | _DELF_EXT = '.h5' 46 | 47 | # Pace to report extraction log. 48 | _STATUS_CHECK_ITERATIONS = 100 49 | 50 | 51 | def _ReadImageList(list_path): 52 | """Helper function to read image paths. 53 | 54 | Args: 55 | list_path: Path to list of images, one image path per line. 56 | 57 | Returns: 58 | image_paths: List of image paths. 59 | """ 60 | with tf.gfile.GFile(list_path, 'r') as f: 61 | image_paths = f.readlines() 62 | image_paths = [entry.rstrip() for entry in image_paths] 63 | return image_paths 64 | 65 | 66 | def MakeExtractor(sess, config, import_scope=None): 67 | """Creates a function to extract features from an image. 68 | 69 | Args: 70 | sess: TensorFlow session to use. 71 | config: DelfConfig proto containing the model configuration. 72 | import_scope: Optional scope to use for model. 73 | 74 | Returns: 75 | Function that receives an image and returns features. 76 | """ 77 | tf.saved_model.loader.load( 78 | sess, [tf.saved_model.tag_constants.SERVING], 79 | config.model_path, 80 | import_scope=import_scope) 81 | import_scope_prefix = import_scope + '/' if import_scope is not None else '' 82 | input_image = sess.graph.get_tensor_by_name('%sinput_image:0' % 83 | import_scope_prefix) 84 | input_score_threshold = sess.graph.get_tensor_by_name( 85 | '%sinput_abs_thres:0' % import_scope_prefix) 86 | input_image_scales = sess.graph.get_tensor_by_name('%sinput_scales:0' % 87 | import_scope_prefix) 88 | input_max_feature_num = sess.graph.get_tensor_by_name( 89 | '%sinput_max_feature_num:0' % import_scope_prefix) 90 | boxes = sess.graph.get_tensor_by_name('%sboxes:0' % import_scope_prefix) 91 | raw_descriptors = sess.graph.get_tensor_by_name('%sfeatures:0' % 92 | import_scope_prefix) 93 | feature_scales = sess.graph.get_tensor_by_name('%sscales:0' % 94 | import_scope_prefix) 95 | attention_with_extra_dim = sess.graph.get_tensor_by_name( 96 | '%sscores:0' % import_scope_prefix) 97 | attention = tf.reshape(attention_with_extra_dim, 98 | [tf.shape(attention_with_extra_dim)[0]]) 99 | 100 | locations, descriptors = feature_extractor.DelfFeaturePostProcessing( 101 | boxes, raw_descriptors, config) 102 | 103 | def ExtractorFn(image): 104 | """Receives an image and returns DELF features. 105 | 106 | Args: 107 | image: Uint8 array with shape (height, width 3) containing the RGB image. 108 | 109 | Returns: 110 | Tuple (locations, descriptors, feature_scales, attention) 111 | """ 112 | return sess.run([locations, descriptors, feature_scales, attention], 113 | feed_dict={ 114 | input_image: image, 115 | input_score_threshold: 116 | config.delf_local_config.score_threshold, 117 | input_image_scales: list(config.image_scales), 118 | input_max_feature_num: 119 | config.delf_local_config.max_feature_num 120 | }) 121 | 122 | return ExtractorFn 123 | 124 | 125 | def main(unused_argv): 126 | tf.logging.set_verbosity(tf.logging.INFO) 127 | 128 | # Read list of images. 129 | tf.logging.info('Reading list of images...') 130 | image_paths = _ReadImageList(cmd_args.list_images_path) 131 | num_images = len(image_paths) 132 | tf.logging.info('done! Found %d images', num_images) 133 | 134 | # Parse DelfConfig proto. 135 | config = delf_config_pb2.DelfConfig() 136 | with tf.gfile.FastGFile(cmd_args.config_path, 'r') as f: 137 | text_format.Merge(f.read(), config) 138 | 139 | # Create output directory if necessary. 140 | if not os.path.exists(cmd_args.output_dir): 141 | os.makedirs(cmd_args.output_dir) 142 | 143 | # Tell TensorFlow that the model will be built into the default Graph. 144 | with tf.Graph().as_default(): 145 | # Reading list of images. 146 | filename_queue = tf.train.string_input_producer( 147 | image_paths, shuffle=False) 148 | reader = tf.WholeFileReader() 149 | _, value = reader.read(filename_queue) 150 | image_tf = tf.image.decode_jpeg(value, channels=3) 151 | 152 | with tf.Session() as sess: 153 | init_op = tf.global_variables_initializer() 154 | sess.run(init_op) 155 | 156 | extractor_fn = MakeExtractor(sess, config) 157 | 158 | # Start input enqueue threads. 159 | coord = tf.train.Coordinator() 160 | threads = tf.train.start_queue_runners(sess=sess, coord=coord) 161 | start = time.clock() 162 | 163 | with h5py.File(os.path.join(cmd_args.output_dir, 'keypoints.h5'), 'w') as h5_kp, \ 164 | h5py.File(os.path.join(cmd_args.output_dir, 'descriptors.h5'), 'w') as h5_desc, \ 165 | h5py.File(os.path.join(cmd_args.output_dir, 'scores.h5'), 'w') as h5_score, \ 166 | h5py.File(os.path.join(cmd_args.output_dir, 'scales.h5'), 'w') as h5_scale: 167 | for i in range(num_images): 168 | key = os.path.splitext(os.path.basename(image_paths[i]))[0] 169 | print('Processing "{}"'.format(key)) 170 | 171 | # Write to log-info once in a while. 172 | if i == 0: 173 | tf.logging.info( 174 | 'Starting to extract DELF features from images...') 175 | elif i % _STATUS_CHECK_ITERATIONS == 0: 176 | elapsed = (time.clock() - start) 177 | tf.logging.info( 178 | 'Processing image %d out of %d, last %d ' 179 | 'images took %f seconds', i, num_images, 180 | _STATUS_CHECK_ITERATIONS, elapsed) 181 | start = time.clock() 182 | 183 | # # Get next image. 184 | im = sess.run(image_tf) 185 | 186 | # If descriptor already exists, skip its computation. 187 | # out_desc_filename = os.path.splitext(os.path.basename( 188 | # image_paths[i]))[0] + _DELF_EXT 189 | # out_desc_fullpath = os.path.join(cmd_args.output_dir, out_desc_filename) 190 | # if tf.gfile.Exists(out_desc_fullpath): 191 | # tf.logging.info('Skipping %s', image_paths[i]) 192 | # continue 193 | 194 | # Extract and save features. 195 | (locations_out, descriptors_out, feature_scales_out, 196 | attention_out) = extractor_fn(im) 197 | 198 | # np.savez('{}.npz'.format(config.delf_local_config.max_feature_num), keypoints=locations_out) 199 | 200 | # feature_io.WriteToFile(out_desc_fullpath, locations_out, 201 | # feature_scales_out, descriptors_out, 202 | # attention_out) 203 | h5_kp[key] = locations_out[:, ::-1] 204 | h5_desc[key] = descriptors_out 205 | h5_scale[key] = feature_scales_out 206 | h5_score[key] = attention_out 207 | 208 | # Finalize enqueue threads. 209 | coord.request_stop() 210 | coord.join(threads) 211 | 212 | 213 | if __name__ == '__main__': 214 | parser = argparse.ArgumentParser() 215 | parser.register('type', 'bool', lambda v: v.lower() == 'true') 216 | parser.add_argument( 217 | '--config_path', 218 | type=str, 219 | default='misc/delf/delf_config_example.pbtxt', 220 | help=""" 221 | Path to DelfConfig proto text file with configuration to be used for DELF 222 | extraction. 223 | """) 224 | parser.add_argument( 225 | '--list_images_path', 226 | type=str, 227 | help=""" 228 | Path to list of images whose DELF features will be extracted. 229 | """) 230 | parser.add_argument( 231 | '--output_dir', 232 | type=str, 233 | default='../benchmark-features/delf', 234 | help=""" 235 | Directory where DELF features will be written to. Each image's features 236 | will be written to a file with same name, and extension replaced by .delf. 237 | """) 238 | 239 | cmd_args, unparsed = parser.parse_known_args() 240 | app.run(main=main, argv=[sys.argv[0]] + unparsed) 241 | -------------------------------------------------------------------------------- /extract_descriptors_geodesc.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | import argparse 4 | import h5py 5 | from tqdm import tqdm 6 | import os 7 | import sys 8 | import shutil 9 | import json 10 | 11 | from utils import cv2_greyscale, cv2_scale, np_reshape, str2bool, save_h5 12 | import tensorflow as tf 13 | import torchvision.transforms as transforms 14 | 15 | sys.path.append(os.path.join('third_party', 'geodesc')) 16 | from third_party.geodesc.utils.tf import load_frozen_model 17 | 18 | 19 | def get_transforms(): 20 | 21 | transform = transforms.Compose([ 22 | transforms.Lambda(cv2_greyscale), transforms.Lambda(cv2_scale), 23 | transforms.Lambda(np_reshape) 24 | ]) 25 | 26 | return transform 27 | 28 | 29 | if __name__ == '__main__': 30 | parser = argparse.ArgumentParser() 31 | parser.add_argument( 32 | "--dataset_path", 33 | default=os.path.join('..', 'benchmark-patches-8k'), 34 | type=str, 35 | help='Path to the pre-generated patches') 36 | parser.add_argument( 37 | "--save_path", 38 | default=os.path.join('..', 'benchmark-features'), 39 | type=str, 40 | help='Path to store the features') 41 | parser.add_argument( 42 | "--method_name", default='sift8k_8000_geodesc', type=str) 43 | parser.add_argument( 44 | "--weights_path", 45 | default=os.path.join('third_party', 'geodesc', 'model', 'geodesc.pb'), 46 | type=str, 47 | help='Path to the model weights') 48 | parser.add_argument( 49 | "--subset", 50 | default='both', 51 | type=str, 52 | help='Options: "val", "test", "both", "spc-fix", "lms-fix"') 53 | parser.add_argument( 54 | "--clahe-mode", 55 | default='None', 56 | type=str, 57 | help='can be None, detector, descriptor, both') 58 | 59 | args = parser.parse_args() 60 | 61 | if args.subset not in ['val', 'test', 'both', 'spc-fix', 'lms-fix']: 62 | raise ValueError('Unknown value for --subset') 63 | seqs = [] 64 | if args.subset == 'spc-fix': 65 | seqs += ['st_pauls_cathedral'] 66 | elif args.subset == 'lms-fix': 67 | seqs += ['lincoln_memorial_statue'] 68 | else: 69 | if args.subset in ['val', 'both']: 70 | with open(os.path.join('data', 'val.json')) as f: 71 | seqs += json.load(f) 72 | if args.subset in ['test', 'both']: 73 | with open(os.path.join('data', 'test.json')) as f: 74 | seqs += json.load(f) 75 | print('Processing the following scenes: {}'.format(seqs)) 76 | 77 | suffix = "" 78 | if args.clahe_mode.lower() == 'detector': 79 | suffix = "_clahe_det" 80 | elif args.clahe_mode.lower() == 'descriptor': 81 | suffix = "_clahe_desc" 82 | elif args.clahe_mode.lower() == 'both': 83 | suffix = "_clahe_det_desc" 84 | elif args.clahe_mode.lower() == 'none': 85 | pass 86 | else: 87 | raise ValueError("unknown CLAHE mode. Try detector, descriptor or both") 88 | args.method_name += suffix 89 | 90 | print('Saving descriptors to folder: {}'.format(args.method_name)) 91 | 92 | transforms = get_transforms() 93 | 94 | graph = load_frozen_model(args.weights_path, print_nodes=False) 95 | 96 | with tf.Session(graph=graph) as sess: 97 | for idx, seq_name in enumerate(seqs): 98 | print('Processing "{}"'.format(seq_name)) 99 | 100 | seq_descriptors = {} 101 | patches_h5py_file = os.path.join(args.dataset_path, seq_name, 102 | 'patches{}.h5'.format(suffix)) 103 | 104 | with h5py.File(patches_h5py_file, 'r') as patches_h5py: 105 | for key, patches in tqdm(patches_h5py.items()): 106 | patches = patches.value 107 | bs = 128 108 | descriptors = [] 109 | 110 | for i in range(0, len(patches), bs): 111 | seq_data = patches[i:i + bs, :, :, :] 112 | seq_data = np.array( 113 | [transforms(patch) 114 | for patch in seq_data]).squeeze(axis=3) 115 | # compute output 116 | processed_seq = np.zeros( 117 | (len(seq_data), 32, 32), np.float32) 118 | 119 | for j in range(len(seq_data)): 120 | processed_seq[j] = (seq_data[j] - np.mean( 121 | seq_data[j])) / (np.std(seq_data[j]) + 1e-8) 122 | 123 | processed_seq = np.expand_dims(processed_seq, axis=-1) 124 | 125 | descs = sess.run("squeeze_1:0", 126 | feed_dict={"input:0": processed_seq}) 127 | if descs.ndim == 1: 128 | descs = descs[None, ...] 129 | descriptors.extend(descs) 130 | 131 | descriptors = np.array(descriptors) 132 | seq_descriptors[key] = descriptors.astype(np.float32) 133 | 134 | print('Processed {} images: {} descriptors/image'.format( 135 | len(seq_descriptors), 136 | np.array([s.shape[0] 137 | for s in seq_descriptors.values()]).mean())) 138 | 139 | cur_path = os.path.join(args.save_path, args.method_name, seq_name) 140 | if not os.path.exists(cur_path): 141 | os.makedirs(cur_path) 142 | save_h5(seq_descriptors, os.path.join(cur_path, 'descriptors.h5')) 143 | sub_files_in = ['keypoints{}.h5'.format(suffix), 'scales{}.h5'.format(suffix), 'angles{}.h5'.format(suffix), 'scores{}.h5'.format(suffix)] 144 | sub_files_out = ['keypoints.h5', 'scales.h5', 'angles.h5', 'scores.h5'] 145 | for sub_file_in, sub_file_out in zip(sub_files_in, sub_files_out): 146 | shutil.copyfile( 147 | os.path.join(args.dataset_path, seq_name, sub_file_in), 148 | os.path.join(cur_path, sub_file_out)) 149 | 150 | print('Done sequence: {}'.format(seq_name)) 151 | -------------------------------------------------------------------------------- /extract_descriptors_hardnet.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | import argparse 4 | import h5py 5 | from tqdm import tqdm 6 | import os 7 | import sys 8 | import shutil 9 | import json 10 | 11 | import torchvision.transforms as transforms 12 | from utils import cv2_greyscale, str2bool, save_h5 13 | 14 | 15 | def get_transforms(color): 16 | 17 | MEAN_IMAGE = 0.443728476019 18 | STD_IMAGE = 0.20197947209 19 | 20 | transform = transforms.Compose([ 21 | transforms.Lambda(cv2_greyscale), transforms.Lambda(cv2_scale), 22 | transforms.Lambda(np_reshape), transforms.ToTensor(), 23 | transforms.Normalize((MEAN_IMAGE, ), (STD_IMAGE, )) 24 | ]) 25 | 26 | return transform 27 | 28 | 29 | def remove_option(parser, arg): 30 | for action in parser._actions: 31 | if (vars(action)['option_strings'] 32 | and vars(action)['option_strings'][0] == arg) \ 33 | or vars(action)['dest'] == arg: 34 | parser._remove_action(action) 35 | 36 | for action in parser._action_groups: 37 | vars_action = vars(action) 38 | var_group_actions = vars_action['_group_actions'] 39 | for x in var_group_actions: 40 | if x.dest == arg: 41 | var_group_actions.remove(x) 42 | return 43 | 44 | 45 | if __name__ == '__main__': 46 | parser = argparse.ArgumentParser() 47 | parser.add_argument( 48 | "--dataset_path", 49 | default=os.path.join('..', 'benchmark-patches-8k'), 50 | type=str, 51 | help='Path to the pre-generated patches') 52 | parser.add_argument( 53 | "--mrSize", default=12.0, type=float, 54 | help=' patch size in image is mrSize * pt.size. Default mrSize is 12' ) 55 | parser.add_argument( 56 | "--save_path", 57 | default=os.path.join('..', 'benchmark-features'), 58 | type=str, 59 | help='Path to store the features') 60 | parser.add_argument( 61 | "--method_name", default='sift8k_8000_hardnet', type=str) 62 | parser.add_argument( 63 | "--weights_path", 64 | default=os.path.join('third_party', 'hardnet', 'pretrained', 65 | 'train_liberty_with_aug', 66 | 'checkpoint_liberty_with_aug.pth'), 67 | type=str, 68 | help='Path to the model weights') 69 | parser.add_argument( 70 | "--subset", 71 | default='both', 72 | type=str, 73 | help='Options: "val", "test", "both", "spc-fix"') 74 | parser.add_argument( 75 | "--clahe-mode", 76 | default='None', 77 | type=str, 78 | help='can be None, detector, descriptor, both') 79 | 80 | args = parser.parse_args() 81 | 82 | if args.subset not in ['val', 'test', 'both', 'spc-fix']: 83 | raise ValueError('Unknown value for --subset') 84 | seqs = [] 85 | if args.subset == 'spc-fix': 86 | seqs += ['st_pauls_cathedral'] 87 | else: 88 | if args.subset in ['val', 'both']: 89 | with open(os.path.join('data', 'val.json')) as f: 90 | seqs += json.load(f) 91 | if args.subset in ['test', 'both']: 92 | with open(os.path.join('data', 'test.json')) as f: 93 | seqs += json.load(f) 94 | print('Processing the following scenes: {}'.format(seqs)) 95 | 96 | 97 | # Hacky work-around: reset argv for the HardNet argparse 98 | sys.path.append(os.path.join('third_party', 'hardnet', 'code')) 99 | sys.argv = [sys.argv[0]] 100 | from third_party.hardnet.code.HardNet import HardNet 101 | from third_party.hardnet.code.Utils import cv2_scale, np_reshape 102 | import torch 103 | try: 104 | if torch.cuda.is_available(): 105 | device = torch.device('cuda:0') 106 | else: 107 | device = torch.device('cpu') 108 | except: 109 | device = torch.device('cpu') 110 | 111 | suffix = "" 112 | if args.clahe_mode.lower() == 'detector': 113 | suffix = "_clahe_det" 114 | elif args.clahe_mode.lower() == 'descriptor': 115 | suffix = "_clahe_desc" 116 | elif args.clahe_mode.lower() == 'both': 117 | suffix = "_clahe_det_desc" 118 | elif args.clahe_mode.lower() == 'none': 119 | pass 120 | else: 121 | raise ValueError("unknown CLAHE mode. Try detector, descriptor or both") 122 | 123 | if abs(args.mrSize - 12.) > 0.1: 124 | suffix+= '_mrSize{:.1f}'.format(args.mrSize) 125 | 126 | args.method_name += suffix 127 | print('Saving descriptors to folder: {}'.format(args.method_name)) 128 | 129 | transforms = get_transforms(False) 130 | 131 | model = HardNet() 132 | model.load_state_dict(torch.load(args.weights_path,map_location=device)['state_dict']) 133 | print('Loaded weights: {}'.format(args.weights_path)) 134 | 135 | model = model.to(device) 136 | model.eval() 137 | 138 | for idx, seq_name in enumerate(seqs): 139 | print('Processing "{}"'.format(seq_name)) 140 | 141 | seq_descriptors = {} 142 | patches_h5py_file = os.path.join(args.dataset_path, seq_name, 143 | 'patches{}.h5'.format(suffix)) 144 | 145 | with h5py.File(patches_h5py_file, 'r') as patches_h5py: 146 | for key, patches in tqdm(patches_h5py.items()): 147 | patches = patches.value 148 | bs = 128 149 | descriptors = np.zeros((len(patches), 128)) 150 | 151 | for i in range(0, len(patches), bs): 152 | data_a = patches[i:i + bs, :, :, :] 153 | data_a = torch.stack( 154 | [transforms(patch) for patch in data_a]).to(device) 155 | # compute output 156 | with torch.no_grad(): 157 | out_a = model(data_a) 158 | descriptors[i:i + bs] = out_a.cpu().detach().numpy() 159 | 160 | seq_descriptors[key] = descriptors.astype(np.float32) 161 | print('Processed {} images: {} descriptors/image'.format( 162 | len(seq_descriptors), 163 | np.array([s.shape[0] for s in seq_descriptors.values()]).mean())) 164 | 165 | cur_path = os.path.join(args.save_path, args.method_name, seq_name) 166 | if not os.path.exists(cur_path): 167 | os.makedirs(cur_path) 168 | save_h5(seq_descriptors, os.path.join(cur_path, 'descriptors.h5')) 169 | 170 | sub_files_in = ['keypoints{}.h5'.format(suffix), 'scales{}.h5'.format(suffix), 'angles{}.h5'.format(suffix), 'scores{}.h5'.format(suffix)] 171 | sub_files_out = ['keypoints.h5', 'scales.h5', 'angles.h5', 'scores.h5'] 172 | 173 | for sub_file_in, sub_file_out in zip(sub_files_in, sub_files_out): 174 | shutil.copyfile( 175 | os.path.join(args.dataset_path, seq_name, sub_file_in), 176 | os.path.join(cur_path, sub_file_out)) 177 | 178 | print('Done sequence: {}'.format(seq_name)) 179 | -------------------------------------------------------------------------------- /extract_descriptors_jit.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | import argparse 4 | import h5py 5 | from tqdm import tqdm 6 | import os 7 | import sys 8 | import shutil 9 | import json 10 | 11 | import torchvision.transforms as transforms 12 | from utils import cv2_greyscale, str2bool, save_h5 13 | 14 | 15 | def get_transforms(color): 16 | 17 | MEAN_IMAGE = 0.443728476019 18 | STD_IMAGE = 0.20197947209 19 | 20 | transform = transforms.Compose([ 21 | transforms.Lambda(cv2_greyscale),# transforms.Lambda(cv2_scale), 22 | #transforms.Lambda(np_reshape), 23 | transforms.ToTensor(), 24 | transforms.Normalize((MEAN_IMAGE, ), (STD_IMAGE, )) 25 | ]) 26 | 27 | return transform 28 | 29 | 30 | def remove_option(parser, arg): 31 | for action in parser._actions: 32 | if (vars(action)['option_strings'] 33 | and vars(action)['option_strings'][0] == arg) \ 34 | or vars(action)['dest'] == arg: 35 | parser._remove_action(action) 36 | 37 | for action in parser._action_groups: 38 | vars_action = vars(action) 39 | var_group_actions = vars_action['_group_actions'] 40 | for x in var_group_actions: 41 | if x.dest == arg: 42 | var_group_actions.remove(x) 43 | return 44 | 45 | 46 | if __name__ == '__main__': 47 | parser = argparse.ArgumentParser() 48 | parser.add_argument( 49 | "--dataset_path", 50 | default=os.path.join('..', 'benchmark-patches-8k'), 51 | type=str, 52 | help='Path to the pre-generated patches') 53 | parser.add_argument( 54 | "--mrSize", default=12.0, type=float, 55 | help=' patch size in image is mrSize * pt.size. Default mrSize is 12' ) 56 | parser.add_argument( 57 | "--save_path", 58 | default=os.path.join('..', 'benchmark-features'), 59 | type=str, 60 | help='Path to store the features') 61 | parser.add_argument( 62 | "--method_name", default='sift8k_8000_hardnet', type=str) 63 | parser.add_argument( 64 | "--weights_path", 65 | default=os.path.join('third_party', 'hardnet', 'pretrained', 66 | 'train_liberty_with_aug', 67 | 'checkpoint_liberty_with_aug.pth'), 68 | type=str, 69 | help='Path to the model weights') 70 | parser.add_argument( 71 | "--subset", 72 | default='both', 73 | type=str, 74 | help='Options: "val", "test", "both", "spc-fix"') 75 | parser.add_argument( 76 | "--clahe-mode", 77 | default='None', 78 | type=str, 79 | help='can be None, detector, descriptor, both') 80 | parser.add_argument( 81 | "--patchSize", 82 | default=32, 83 | type=int, 84 | help=' patch size in pixels. Default 32') 85 | 86 | args = parser.parse_args() 87 | 88 | if args.subset not in ['val', 'test', 'both', 'spc-fix']: 89 | raise ValueError('Unknown value for --subset') 90 | seqs = [] 91 | if args.subset == 'spc-fix': 92 | seqs += ['st_pauls_cathedral'] 93 | else: 94 | if args.subset in ['val', 'both']: 95 | with open(os.path.join('data', 'val.json')) as f: 96 | seqs += json.load(f) 97 | if args.subset in ['test', 'both']: 98 | with open(os.path.join('data', 'test.json')) as f: 99 | seqs += json.load(f) 100 | print('Processing the following scenes: {}'.format(seqs)) 101 | 102 | 103 | # Hacky work-around: reset argv for the HardNet argparse 104 | sys.path.append(os.path.join('third_party', 'hardnet', 'code')) 105 | sys.argv = [sys.argv[0]] 106 | #from third_party.hardnet.code.HardNet import HardNet 107 | from third_party.hardnet.code.Utils import cv2_scale, np_reshape 108 | import torch 109 | try: 110 | if torch.cuda.is_available(): 111 | device = torch.device('cuda:0') 112 | else: 113 | device = torch.device('cpu') 114 | except: 115 | device = torch.device('cpu') 116 | 117 | suffix = "" 118 | if args.clahe_mode.lower() == 'detector': 119 | suffix = "_clahe_det" 120 | elif args.clahe_mode.lower() == 'descriptor': 121 | suffix = "_clahe_desc" 122 | elif args.clahe_mode.lower() == 'both': 123 | suffix = "_clahe_det_desc" 124 | elif args.clahe_mode.lower() == 'none': 125 | pass 126 | else: 127 | raise ValueError("unknown CLAHE mode. Try detector, descriptor or both") 128 | 129 | if abs(args.mrSize - 12.) > 0.1: 130 | suffix+= '_mrSize{:.1f}'.format(args.mrSize) 131 | assert(args.patchSize > 0) 132 | if args.patchSize != 32: 133 | suffix += '_patchSize{}'.format(args.patchSize) 134 | 135 | args.method_name += suffix 136 | print('Saving descriptors to folder: {}'.format(args.method_name)) 137 | 138 | transforms = get_transforms(False) 139 | 140 | #model = HardNet() 141 | #try: 142 | # model.load_state_dict(torch.load(args.weights_path,map_location=device)['state_dict']) 143 | #except: 144 | # model.load_state_dict(torch.load(args.weights_path,map_location=device)) 145 | model = torch.jit.load(args.weights_path)#.cuda() 146 | print('Loaded weights: {}'.format(args.weights_path)) 147 | model = model.to(device) 148 | model.eval() 149 | 150 | for idx, seq_name in enumerate(seqs): 151 | print('Processing "{}"'.format(seq_name)) 152 | 153 | seq_descriptors = {} 154 | patches_h5py_file = os.path.join(args.dataset_path, seq_name, 155 | 'patches{}.h5'.format(suffix)) 156 | 157 | with h5py.File(patches_h5py_file, 'r') as patches_h5py: 158 | for key, patches in tqdm(patches_h5py.items()): 159 | patches = patches.value 160 | bs = 128 161 | descriptors = np.zeros((len(patches), 128)) 162 | for i in range(0, len(patches), bs): 163 | data_a = patches[i:i + bs, :, :, :] 164 | data_a = torch.stack( 165 | [transforms(patch) for patch in data_a]).to(device) 166 | # compute output 167 | with torch.no_grad(): 168 | out_a = model(data_a) 169 | descriptors[i:i + bs] = out_a.cpu().detach().numpy() 170 | 171 | seq_descriptors[key] = descriptors.astype(np.float32) 172 | print('Processed {} images: {} descriptors/image'.format( 173 | len(seq_descriptors), 174 | np.array([s.shape[0] for s in seq_descriptors.values()]).mean())) 175 | 176 | cur_path = os.path.join(args.save_path, args.method_name, seq_name) 177 | if not os.path.exists(cur_path): 178 | os.makedirs(cur_path) 179 | save_h5(seq_descriptors, os.path.join(cur_path, 'descriptors.h5')) 180 | 181 | sub_files_in = ['keypoints{}.h5'.format(suffix), 'scales{}.h5'.format(suffix), 'angles{}.h5'.format(suffix), 'scores{}.h5'.format(suffix)] 182 | sub_files_out = ['keypoints.h5', 'scales.h5', 'angles.h5', 'scores.h5'] 183 | 184 | for sub_file_in, sub_file_out in zip(sub_files_in, sub_files_out): 185 | shutil.copyfile( 186 | os.path.join(args.dataset_path, seq_name, sub_file_in), 187 | os.path.join(cur_path, sub_file_out)) 188 | 189 | print('Done sequence: {}'.format(seq_name)) 190 | -------------------------------------------------------------------------------- /extract_descriptors_kornia.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | import argparse 4 | import h5py 5 | from tqdm import tqdm 6 | import os 7 | import sys 8 | import shutil 9 | import json 10 | 11 | import torchvision.transforms as transforms 12 | from utils import cv2_greyscale, str2bool, save_h5 13 | 14 | 15 | def get_transforms(color): 16 | 17 | MEAN_IMAGE = 0.443728476019 18 | STD_IMAGE = 0.20197947209 19 | 20 | transform = transforms.Compose([ 21 | transforms.Lambda(cv2_greyscale),#, transforms.Lambda(cv2_scale), 22 | #transforms.Lambda(np_reshape), 23 | transforms.ToTensor(), 24 | transforms.Normalize((MEAN_IMAGE, ), (STD_IMAGE, )) 25 | ]) 26 | 27 | return transform 28 | 29 | 30 | def remove_option(parser, arg): 31 | for action in parser._actions: 32 | if (vars(action)['option_strings'] 33 | and vars(action)['option_strings'][0] == arg) \ 34 | or vars(action)['dest'] == arg: 35 | parser._remove_action(action) 36 | 37 | for action in parser._action_groups: 38 | vars_action = vars(action) 39 | var_group_actions = vars_action['_group_actions'] 40 | for x in var_group_actions: 41 | if x.dest == arg: 42 | var_group_actions.remove(x) 43 | return 44 | 45 | 46 | if __name__ == '__main__': 47 | parser = argparse.ArgumentParser() 48 | parser.add_argument( 49 | "--dataset_path", 50 | default=os.path.join('..', 'benchmark-patches-8k'), 51 | type=str, 52 | help='Path to the pre-generated patches') 53 | parser.add_argument( 54 | "--mrSize", default=12.0, type=float, 55 | help=' patch size in image is mrSize * pt.size. Default mrSize is 12' ) 56 | parser.add_argument( 57 | "--save_path", 58 | default=os.path.join('..', 'benchmark-features'), 59 | type=str, 60 | help='Path to store the features') 61 | parser.add_argument( 62 | "--method_name", default='sift8k_8000_', type=str) 63 | parser.add_argument( 64 | "--desc_name", 65 | default='tfeat', 66 | type=str, 67 | help='Options: tfeat, sosnet, hardnet, mkd') 68 | parser.add_argument( 69 | "--subset", 70 | default='both', 71 | type=str, 72 | help='Options: "val", "test", "both", "spc-fix"') 73 | parser.add_argument( 74 | "--clahe-mode", 75 | default='None', 76 | type=str, 77 | help='can be None, detector, descriptor, both') 78 | 79 | args = parser.parse_args() 80 | args.desc_name = args.desc_name.lower() 81 | if args.subset not in ['val', 'test', 'train', 'both', 'spc-fix']: 82 | raise ValueError('Unknown value for --subset') 83 | if args.desc_name not in ['tfeat', 'sosnet', 'hardnet', 'mkd']: 84 | raise ValueError('Unknown value for --desc_name') 85 | seqs = [] 86 | if args.subset == 'spc-fix': 87 | seqs += ['st_pauls_cathedral'] 88 | else: 89 | if args.subset in ['val', 'both']: 90 | with open(os.path.join('data', 'val.json')) as f: 91 | seqs += json.load(f) 92 | if args.subset in ['train']: 93 | with open(os.path.join('data', 'train.json')) as f: 94 | seqs += json.load(f) 95 | if args.subset in ['test', 'both']: 96 | with open(os.path.join('data', 'test.json')) as f: 97 | seqs += json.load(f) 98 | print('Processing the following scenes: {}'.format(seqs)) 99 | 100 | 101 | import torch 102 | import kornia.feature as KF 103 | try: 104 | if torch.cuda.is_available(): 105 | device = torch.device('cuda:0') 106 | else: 107 | device = torch.device('cpu') 108 | except: 109 | device = torch.device('cpu') 110 | 111 | suffix = "" 112 | if args.clahe_mode.lower() == 'detector': 113 | suffix = "_clahe_det" 114 | elif args.clahe_mode.lower() == 'descriptor': 115 | suffix = "_clahe_desc" 116 | elif args.clahe_mode.lower() == 'both': 117 | suffix = "_clahe_det_desc" 118 | elif args.clahe_mode.lower() == 'none': 119 | pass 120 | else: 121 | raise ValueError("unknown CLAHE mode. Try detector, descriptor or both") 122 | 123 | if abs(args.mrSize - 12.) > 0.1: 124 | suffix+= '_mrSize{:.1f}'.format(args.mrSize) 125 | 126 | args.method_name += args.desc_name 127 | args.method_name += suffix 128 | print('Saving descriptors to folder: {}'.format(args.method_name)) 129 | 130 | transforms = get_transforms(False) 131 | if args.desc_name == 'tfeat': 132 | model = KF.TFeat(True) 133 | elif args.desc_name == 'hardnet': 134 | model = KF.HardNet(True) 135 | elif args.desc_name == 'sosnet': 136 | model = KF.SOSNet(True) 137 | elif args.desc_name == 'mkd': 138 | model = KF.MKDDescriptor(32) 139 | else: 140 | sys.exit(1) 141 | model = model.to(device) 142 | model.eval() 143 | 144 | for idx, seq_name in enumerate(seqs): 145 | print('Processing "{}"'.format(seq_name)) 146 | 147 | seq_descriptors = {} 148 | patches_h5py_file = os.path.join(args.dataset_path, seq_name, 149 | 'patches{}.h5'.format(suffix)) 150 | 151 | with h5py.File(patches_h5py_file, 'r') as patches_h5py: 152 | for key, patches in tqdm(patches_h5py.items()): 153 | patches = patches.value 154 | bs = 128 155 | descriptors = np.zeros((len(patches), 128)) 156 | 157 | for i in range(0, len(patches), bs): 158 | data_a = patches[i:i + bs, :, :, :] 159 | data_a = torch.stack( 160 | [transforms(patch) for patch in data_a]).to(device) 161 | # compute output 162 | with torch.no_grad(): 163 | out_a = model(data_a) 164 | descriptors[i:i + bs] = out_a.cpu().detach().numpy() 165 | 166 | seq_descriptors[key] = descriptors.astype(np.float32) 167 | print('Processed {} images: {} descriptors/image'.format( 168 | len(seq_descriptors), 169 | np.array([s.shape[0] for s in seq_descriptors.values()]).mean())) 170 | 171 | cur_path = os.path.join(args.save_path, args.method_name, seq_name) 172 | if not os.path.exists(cur_path): 173 | os.makedirs(cur_path) 174 | save_h5(seq_descriptors, os.path.join(cur_path, 'descriptors.h5')) 175 | 176 | sub_files_in = ['keypoints{}.h5'.format(suffix), 'scales{}.h5'.format(suffix), 'angles{}.h5'.format(suffix), 'scores{}.h5'.format(suffix)] 177 | sub_files_out = ['keypoints.h5', 'scales.h5', 'angles.h5', 'scores.h5'] 178 | 179 | for sub_file_in, sub_file_out in zip(sub_files_in, sub_files_out): 180 | shutil.copyfile( 181 | os.path.join(args.dataset_path, seq_name, sub_file_in), 182 | os.path.join(cur_path, sub_file_out)) 183 | 184 | print('Done sequence: {}'.format(seq_name)) 185 | -------------------------------------------------------------------------------- /extract_descriptors_l2net.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | import argparse 4 | import h5py 5 | from tqdm import tqdm 6 | import os 7 | import sys 8 | import shutil 9 | import json 10 | import scipy.io as sio 11 | 12 | import torchvision.transforms as transforms 13 | from utils import cv2_greyscale, str2bool, save_h5 14 | 15 | def get_transforms(): 16 | 17 | transform = transforms.Compose([ 18 | transforms.Lambda(cv2_greyscale), 19 | transforms.Lambda(cv2_scale), 20 | transforms.Lambda(np_reshape) 21 | 22 | ]) 23 | 24 | return transform 25 | 26 | def remove_option(parser, arg): 27 | for action in parser._actions: 28 | if (vars(action)['option_strings'] 29 | and vars(action)['option_strings'][0] == arg) \ 30 | or vars(action)['dest'] == arg: 31 | parser._remove_action(action) 32 | 33 | for action in parser._action_groups: 34 | vars_action = vars(action) 35 | var_group_actions = vars_action['_group_actions'] 36 | for x in var_group_actions: 37 | if x.dest == arg: 38 | var_group_actions.remove(x) 39 | return 40 | 41 | 42 | if __name__ == '__main__': 43 | parser = argparse.ArgumentParser() 44 | parser.add_argument( 45 | "--dataset_path", 46 | default=os.path.join('..', 'benchmark-patches-8k'), 47 | type=str, 48 | help='Path to the pre-generated patches') 49 | parser.add_argument( 50 | "--mrSize", default=12.0, type=float, 51 | help=' patch size in image is mrSize * pt.size. Default mrSize is 12' ) 52 | parser.add_argument( 53 | "--save_path", 54 | default=os.path.join('..', 'benchmark-features'), 55 | type=str, 56 | help='Path to store the features') 57 | parser.add_argument( 58 | "--method_name", default='sift8k_8000_l2net', type=str) 59 | parser.add_argument( 60 | "--weights_path", 61 | default=os.path.join('third_party', 'l2net-config', 'l2net_ported_weights_lib+.pth'), 62 | type=str, 63 | help='Path to the model weights') 64 | parser.add_argument( 65 | "--matlab_weights_path", 66 | default=os.path.join('third_party', 'l2net', 'matlab', 'L2Net-LIB+.mat'), 67 | type=str, 68 | help='Path to the model weights') 69 | parser.add_argument( 70 | "--subset", 71 | default='both', 72 | type=str, 73 | help='Options: "val", "test", "both", "spc-fix"') 74 | parser.add_argument( 75 | "--clahe-mode", 76 | default='None', 77 | type=str, 78 | help='can be None, detector, descriptor, both') 79 | 80 | args = parser.parse_args() 81 | 82 | if args.subset not in ['val', 'test', 'both', 'spc-fix']: 83 | raise ValueError('Unknown value for --subset') 84 | seqs = [] 85 | if args.subset == 'spc-fix': 86 | seqs += ['st_pauls_cathedral'] 87 | else: 88 | if args.subset in ['val', 'both']: 89 | with open(os.path.join('data', 'val.json')) as f: 90 | seqs += json.load(f) 91 | if args.subset in ['test', 'both']: 92 | with open(os.path.join('data', 'test.json')) as f: 93 | seqs += json.load(f) 94 | print('Processing the following scenes: {}'.format(seqs)) 95 | 96 | 97 | # Hacky work-around: reset argv for the HardNet argparse 98 | sys.path.append(os.path.join('misc', 'l2net')) 99 | sys.argv = [sys.argv[0]] 100 | from misc.l2net.l2net_model import L2Net 101 | from third_party.hardnet.code.Utils import cv2_scale, np_reshape 102 | import torch 103 | 104 | 105 | try: 106 | if torch.cuda.is_available(): 107 | device = torch.device('cuda:0') 108 | else: 109 | device = torch.device('cpu') 110 | except: 111 | device = torch.device('cpu') 112 | 113 | suffix = "" 114 | if args.clahe_mode.lower() == 'detector': 115 | suffix = "_clahe_det" 116 | elif args.clahe_mode.lower() == 'descriptor': 117 | suffix = "_clahe_desc" 118 | elif args.clahe_mode.lower() == 'both': 119 | suffix = "_clahe_det_desc" 120 | elif args.clahe_mode.lower() == 'none': 121 | pass 122 | else: 123 | raise ValueError("unknown CLAHE mode. Try detector, descriptor or both") 124 | 125 | if abs(args.mrSize - 12.) > 0.1: 126 | suffix+= '_mrSize{:.1f}'.format(args.mrSize) 127 | args.method_name+=suffix 128 | print('Saving descriptors to folder: {}'.format(args.method_name)) 129 | 130 | # get pre-trained image mean 131 | l2net_weights = sio.loadmat(args.matlab_weights_path) 132 | imgMean = l2net_weights['pixMean'] 133 | 134 | transforms = get_transforms() 135 | 136 | model = L2Net() 137 | model.load_state_dict(torch.load(args.weights_path,map_location=device)) 138 | print('Loaded weights: {}'.format(args.weights_path)) 139 | 140 | model = model.to(device) 141 | model.eval() 142 | 143 | for idx, seq_name in enumerate(seqs): 144 | print('Processing "{}"'.format(seq_name)) 145 | 146 | seq_descriptors = {} 147 | patches_h5py_file = os.path.join(args.dataset_path, seq_name, 148 | 'patches{}.h5'.format(suffix)) 149 | 150 | with h5py.File(patches_h5py_file, 'r') as patches_h5py: 151 | for key, patches in tqdm(patches_h5py.items()): 152 | patches = patches.value 153 | 154 | bs = 128 155 | descriptors = np.zeros((len(patches), 128)) 156 | 157 | for i in range(0, len(patches), bs): 158 | data_a = patches[i:i + bs, :, :, :] 159 | data_a = torch.stack([torch.from_numpy(transforms(patch)).squeeze() for patch in data_a]).\ 160 | unsqueeze(1).float().to(device) 161 | # compute output 162 | data_a = data_a - torch.from_numpy(imgMean).to(device) 163 | with torch.no_grad(): 164 | out_a = model(data_a) 165 | descriptors[i:i + bs] = out_a.cpu().detach().numpy() 166 | 167 | seq_descriptors[key] = descriptors.astype(np.float32) 168 | print('Processed {} images: {} descriptors/image'.format( 169 | len(seq_descriptors), 170 | np.array([s.shape[0] for s in seq_descriptors.values()]).mean())) 171 | 172 | cur_path = os.path.join(args.save_path, args.method_name, seq_name) 173 | if not os.path.exists(cur_path): 174 | os.makedirs(cur_path) 175 | save_h5(seq_descriptors, os.path.join(cur_path, 'descriptors.h5')) 176 | 177 | sub_files_in = ['keypoints{}.h5'.format(suffix), 'scales{}.h5'.format(suffix), 'angles{}.h5'.format(suffix), 'scores{}.h5'.format(suffix)] 178 | sub_files_out = ['keypoints.h5', 'scales.h5', 'angles.h5', 'scores.h5'] 179 | 180 | for sub_file_in, sub_file_out in zip(sub_files_in, sub_files_out): 181 | shutil.copyfile( 182 | os.path.join(args.dataset_path, seq_name, sub_file_in), 183 | os.path.join(cur_path, sub_file_out)) 184 | 185 | print('Done sequence: {}'.format(seq_name)) 186 | -------------------------------------------------------------------------------- /extract_descriptors_logpolar.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | import argparse 4 | import h5py 5 | from tqdm import tqdm 6 | import os 7 | import sys 8 | import cv2 9 | from utils import str2bool, save_h5 10 | import shutil 11 | import json 12 | 13 | 14 | def l_clahe(img): 15 | clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) 16 | lab = cv2.cvtColor(img, cv2.COLOR_RGB2Lab) 17 | lab[:, :, 0] = clahe.apply(lab[:, :, 0]) 18 | return cv2.cvtColor(lab, cv2.COLOR_Lab2RGB) 19 | 20 | 21 | if __name__ == '__main__': 22 | parser = argparse.ArgumentParser() 23 | 24 | parser.add_argument( 25 | "--sequences_folder", 26 | default=os.path.join('..', 'imw-2020'), 27 | help="path to config file", 28 | type=str) 29 | parser.add_argument( 30 | "--dataset_path", 31 | default=os.path.join('..', 'benchmark-patches-8k'), 32 | type=str, 33 | help='Path to the pre-generated patches') 34 | parser.add_argument( 35 | "--save_path", 36 | default=os.path.join('..', 'benchmark-features'), 37 | type=str, 38 | help='Path to store the features') 39 | parser.add_argument( 40 | "--method_name", default='sift8k_8000_logpolar96', type=str) 41 | parser.add_argument( 42 | "--config_file", 43 | default='third_party/log_polar_descriptors/configs/init_one_example_ptn_96.yml', 44 | help="path to config file", 45 | type=str) 46 | parser.add_argument( 47 | "--mrSize", 48 | default=12.0, 49 | type=float, 50 | help=' patch size in image is mrSize * pt.size. Default mrSize is 12') 51 | parser.add_argument( 52 | "--subset", 53 | default='both', 54 | type=str, 55 | help='Options: "val", "test", "both", "spc-fix"') 56 | parser.add_argument( 57 | "--clahe-mode", 58 | default='None', 59 | type=str, 60 | help='can be None, detector, descriptor, both') 61 | parser.add_argument( 62 | "opts", 63 | help="Modify config options using the command-line", 64 | default=None, 65 | nargs=argparse.REMAINDER) 66 | 67 | args = parser.parse_args() 68 | 69 | if args.subset not in ['val', 'test', 'both', 'spc-fix']: 70 | raise ValueError('Unknown value for --subset') 71 | seqs = [] 72 | if args.subset == 'spc-fix': 73 | seqs += ['st_pauls_cathedral'] 74 | else: 75 | if args.subset in ['val', 'both']: 76 | with open(os.path.join('data', 'val.json')) as f: 77 | seqs += json.load(f) 78 | if args.subset in ['test', 'both']: 79 | with open(os.path.join('data', 'test.json')) as f: 80 | seqs += json.load(f) 81 | print('Processing the following scenes: {}'.format(seqs)) 82 | 83 | # Hacky work-around: reset argv for the HardNet argparse 84 | sys.path.append(os.path.join('third_party', 'log_polar_descriptors')) 85 | sys.argv = [sys.argv[0]] 86 | from third_party.log_polar_descriptors.configs.defaults import _C as cfg 87 | from third_party.log_polar_descriptors.modules.hardnet.models import HardNet 88 | suffix = "" 89 | if args.clahe_mode.lower() == 'detector': 90 | suffix = "_clahe_det" 91 | elif args.clahe_mode.lower() == 'descriptor': 92 | suffix = "_clahe_desc" 93 | elif args.clahe_mode.lower() == 'both': 94 | suffix = "_clahe_det_desc" 95 | elif args.clahe_mode.lower() == 'none': 96 | pass 97 | else: 98 | raise ValueError( 99 | "unknown CLAHE mode. Try detector, descriptor or both") 100 | 101 | args.method_name += suffix 102 | 103 | print('Saving descriptors to folder: {}'.format(args.method_name)) 104 | 105 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 106 | print (device) 107 | num_gpus = int( 108 | os.environ["WORLD_SIZE"]) if "WORLD_SIZE" in os.environ else 1 109 | 110 | if args.config_file != "": 111 | cfg.merge_from_file(args.config_file) 112 | cfg.merge_from_list(args.opts) 113 | 114 | model = HardNet( 115 | transform=cfg.TEST.TRANSFORMER, 116 | coords=cfg.TEST.COORDS, 117 | patch_size=cfg.TEST.IMAGE_SIZE, 118 | scale=cfg.TEST.SCALE, 119 | is_desc256=cfg.TEST.IS_DESC_256, 120 | orientCorrect=cfg.TEST.ORIENT_CORRECTION) 121 | 122 | model.load_state_dict( 123 | torch.load( 124 | os.path.join('third_party', 'log_polar_descriptors', 125 | cfg.TEST.MODEL_WEIGHTS))['state_dict']) 126 | model.eval() 127 | model.to(device) 128 | 129 | for idx, seq_name in enumerate(seqs): 130 | 131 | print('Processing "{}"'.format(seq_name)) 132 | 133 | keypoints = h5py.File( 134 | os.path.join(args.dataset_path, seq_name, 135 | 'keypoints{}.h5'.format(suffix)), 'r') 136 | scales = h5py.File( 137 | os.path.join(args.dataset_path, seq_name, 138 | 'scales{}.h5'.format(suffix)), 'r') 139 | angles = h5py.File( 140 | os.path.join(args.dataset_path, seq_name, 141 | 'angles{}.h5'.format(suffix)), 'r') 142 | seq_descriptors = {} 143 | scene_path = os.path.join(args.sequences_folder, 144 | seq_name, 'set_100/images/') 145 | for key, keypoints in tqdm(keypoints.items()): 146 | img = cv2.imread( 147 | os.path.join(scene_path, key + '.jpg')) 148 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 149 | if args.clahe_mode.lower() in ['descriptor', 'both']: 150 | img = l_clahe(img) 151 | img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) 152 | # pad image and fix keypoints 153 | if img.shape[0] > cfg.TEST.PAD_TO or img.shape[1] > cfg.TEST.PAD_TO: 154 | raise RuntimeError( 155 | "Image {} exceeds acceptable size".format(img.shape)) 156 | 157 | fillHeight = cfg.TEST.PAD_TO - img.shape[0] 158 | fillWidth = cfg.TEST.PAD_TO - img.shape[1] 159 | 160 | padLeft = int(np.round(fillWidth / 2)) 161 | padRight = int(fillWidth - padLeft) 162 | padUp = int(np.round(fillHeight / 2)) 163 | padDown = int(fillHeight - padUp) 164 | 165 | img = np.pad(img, 166 | pad_width=((padUp, padDown), (padLeft, padRight)), 167 | mode='reflect') 168 | 169 | # Iterate over keypoints 170 | keypoint_locations = [] 171 | for kpIDX, kp_loc in enumerate(keypoints): 172 | normKp_a = 2 * np.array([[(kp_loc[0] + padLeft) / 173 | (cfg.TEST.PAD_TO), 174 | (kp_loc[1] + padUp) / 175 | (cfg.TEST.PAD_TO)]]) - 1 176 | keypoint_locations.append(normKp_a) 177 | 178 | all_desc = [] 179 | bs = 500 180 | for i in range(0, len(keypoint_locations), bs): 181 | oris = np.array([ 182 | np.deg2rad(orient) for orient in angles[key][:][i:i + bs] 183 | ]) 184 | 185 | theta = [ 186 | torch.from_numpy(np.array( 187 | keypoint_locations)[i:i + bs]).float().squeeze(), 188 | torch.from_numpy(scales[key][:][i:i + bs]).float().squeeze(), 189 | torch.from_numpy(oris).float().squeeze() 190 | ] 191 | # due to multiplier during extraction from detect_sift_keypoints... 192 | theta[1] = theta[1]/args.mrSize 193 | 194 | imgs = torch.from_numpy(img).unsqueeze(0).to(device) 195 | img_keypoints = [ 196 | theta[0].to(device), theta[1].to(device), 197 | theta[2].to(device) 198 | ] 199 | 200 | # Deal with batches size 1 201 | if len(oris) == 1: 202 | img_keypoints[0] = img_keypoints[0].unsqueeze(0) 203 | img_keypoints[1] = img_keypoints[1].unsqueeze(0) 204 | img_keypoints[2] = img_keypoints[2].unsqueeze(0) 205 | 206 | descriptors, patches = model({ 207 | key: imgs 208 | }, img_keypoints, [key] * len(img_keypoints[0])) 209 | all_desc.append(descriptors.data.cpu().numpy()) 210 | seq_descriptors[key] = np.vstack(np.array(all_desc)).astype( 211 | np.float32) 212 | 213 | cur_path = os.path.join(args.save_path, args.method_name, seq_name) 214 | if not os.path.exists(cur_path): 215 | os.makedirs(cur_path) 216 | save_h5(seq_descriptors, os.path.join(cur_path, 'descriptors.h5')) 217 | sub_files_in = [ 218 | 'keypoints{}.h5'.format(suffix), 'scales{}.h5'.format(suffix), 219 | 'angles{}.h5'.format(suffix), 'scores{}.h5'.format(suffix) 220 | ] 221 | sub_files_out = ['keypoints.h5', 'scales.h5', 'angles.h5', 'scores.h5'] 222 | 223 | for sub_file_in, sub_file_out in zip(sub_files_in, sub_files_out): 224 | shutil.copyfile( 225 | os.path.join(args.dataset_path, seq_name, sub_file_in), 226 | os.path.join(cur_path, sub_file_out)) 227 | 228 | print('Done sequence: {}'.format(seq_name)) 229 | -------------------------------------------------------------------------------- /extract_descriptors_sosnet.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import torch 3 | import numpy as np 4 | import h5py 5 | from tqdm import tqdm 6 | import os 7 | import sys 8 | import shutil 9 | import json 10 | 11 | from utils import cv2_scale, cv2_greyscale, np_reshape, str2bool, save_h5 12 | from third_party.SOSNet.codes.sosnet_model import SOSNet32x32 13 | import torchvision.transforms as transforms 14 | 15 | 16 | def get_transforms(): 17 | 18 | transform = transforms.Compose([ 19 | transforms.Lambda(cv2_greyscale), transforms.Lambda(cv2_scale), 20 | transforms.Lambda(np_reshape), transforms.ToTensor() 21 | ]) 22 | 23 | return transform 24 | 25 | 26 | if __name__ == '__main__': 27 | parser = argparse.ArgumentParser() 28 | parser.add_argument( 29 | "--dataset_path", 30 | default=os.path.join('..', 'benchmark-patches-8k'), 31 | type=str, 32 | help='Path to the pre-generated patches') 33 | parser.add_argument( 34 | "--save_path", 35 | default=os.path.join('..', 'benchmark-features'), 36 | type=str, 37 | help='Path to store the features') 38 | parser.add_argument( 39 | "--method_name", default='sift8k_8000_sosnet', type=str) 40 | parser.add_argument( 41 | "--weights_path", 42 | default=os.path.join('third_party', 'SOSNet', 'sosnet-weights', 43 | 'sosnet-32x32-liberty.pth'), 44 | type=str, 45 | help='Path to the model weights') 46 | parser.add_argument( 47 | "--subset", 48 | default='both', 49 | type=str, 50 | help='Options: "val", "test", "both", "spc-fix"') 51 | parser.add_argument( 52 | "--clahe-mode", 53 | default='None', 54 | type=str, 55 | help='can be None, detector, descriptor, both') 56 | 57 | args = parser.parse_args() 58 | 59 | if args.subset not in ['val', 'test', 'both', 'spc-fix']: 60 | raise ValueError('Unknown value for --subset') 61 | seqs = [] 62 | if args.subset == 'spc-fix': 63 | seqs += ['st_pauls_cathedral'] 64 | else: 65 | if args.subset in ['val', 'both']: 66 | with open(os.path.join('data', 'val.json')) as f: 67 | seqs += json.load(f) 68 | if args.subset in ['test', 'both']: 69 | with open(os.path.join('data', 'test.json')) as f: 70 | seqs += json.load(f) 71 | print('Processing the following scenes: {}'.format(seqs)) 72 | 73 | suffix = "" 74 | if args.clahe_mode.lower() == 'detector': 75 | suffix = "_clahe_det" 76 | elif args.clahe_mode.lower() == 'descriptor': 77 | suffix = "_clahe_desc" 78 | elif args.clahe_mode.lower() == 'both': 79 | suffix = "_clahe_det_desc" 80 | elif args.clahe_mode.lower() == 'none': 81 | pass 82 | else: 83 | raise ValueError("unknown CLAHE mode. Try detector, descriptor or both") 84 | 85 | args.method_name += suffix 86 | 87 | print('Saving descriptors to folder: {}'.format(args.method_name)) 88 | 89 | transforms = get_transforms() 90 | 91 | model = SOSNet32x32() 92 | model.load_state_dict(torch.load(args.weights_path)) 93 | print('Loaded weights: {}'.format(args.weights_path)) 94 | 95 | model.cuda() 96 | model.eval() 97 | 98 | for idx, seq_name in enumerate(seqs): 99 | print('Processing "{}"'.format(seq_name)) 100 | 101 | seq_descriptors = {} 102 | patches_h5py_file = os.path.join(args.dataset_path, seq_name, 103 | 'patches{}.h5'.format(suffix)) 104 | 105 | with h5py.File(patches_h5py_file, 'r') as patches_h5py: 106 | for key, patches in tqdm(patches_h5py.items()): 107 | patches = patches.value 108 | bs = 128 109 | descriptors = np.zeros((len(patches), 128)) 110 | 111 | for i in range(0, len(patches), bs): 112 | data_a = patches[i:i + bs, :, :, :] 113 | data_a = torch.stack( 114 | [transforms(patch) for patch in data_a]).cuda() 115 | # compute output 116 | with torch.no_grad(): 117 | out_a = model(data_a) 118 | descriptors[i:i + bs] = out_a.cpu().detach().numpy() 119 | 120 | seq_descriptors[key] = descriptors.astype(np.float32) 121 | 122 | print('Processed {} images: {} descriptors/image'.format( 123 | len(seq_descriptors), 124 | np.array([s.shape[0] for s in seq_descriptors.values()]).mean())) 125 | 126 | cur_path = os.path.join(args.save_path, args.method_name, seq_name) 127 | if not os.path.exists(cur_path): 128 | os.makedirs(cur_path) 129 | save_h5(seq_descriptors, os.path.join(cur_path, 'descriptors.h5')) 130 | 131 | sub_files_in = ['keypoints{}.h5'.format(suffix), 'scales{}.h5'.format(suffix), 'angles{}.h5'.format(suffix), 'scores{}.h5'.format(suffix)] 132 | sub_files_out = ['keypoints.h5', 'scales.h5', 'angles.h5', 'scores.h5'] 133 | 134 | for sub_file_in, sub_file_out in zip(sub_files_in, sub_files_out): 135 | shutil.copyfile( 136 | os.path.join(args.dataset_path, seq_name, sub_file_in), 137 | os.path.join(cur_path, sub_file_out)) 138 | 139 | print('Done sequence: {}'.format(seq_name)) 140 | -------------------------------------------------------------------------------- /extract_kp2d_features.py: -------------------------------------------------------------------------------- 1 | import os 2 | import h5py 3 | from tqdm import tqdm 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | import cv2 7 | import torch 8 | import torch.nn.functional as F 9 | import argparse 10 | from utils import str2bool, save_h5 11 | import sys 12 | sys.path.insert(0, f'{os.getcwd()}/third_party/KP2D') 13 | 14 | import kornia as K 15 | from kp2d.datasets.patches_dataset import PatchesDataset 16 | from kp2d.evaluation.evaluate import evaluate_keypoint_net 17 | from kp2d.networks.keypoint_net import KeypointNet 18 | from kp2d.networks.keypoint_resnet import KeypointResnet 19 | 20 | def convert_imc(kps, resps): 21 | keypoints = kps.reshape(-1, 2).detach().cpu().numpy() 22 | nkp = len(keypoints) 23 | scales = np.ones((nkp, 1)).astype(np.float32) 24 | angles = np.zeros((nkp, 1)).astype(np.float32) 25 | responses = resps.detach().reshape(-1, 1).cpu().numpy() 26 | return keypoints, scales, angles, responses 27 | 28 | 29 | def extract_features(img_fname, keypoint_net, device, MAX_KP, max_size, norm_desc): 30 | img = cv2.cvtColor(cv2.imread(img_fname), cv2.COLOR_BGR2RGB) 31 | timg = K.image_to_tensor(img, False).float()/255. 32 | timg = timg.to(device) 33 | #timg_gray = K.color.rgb_to_grayscale(timg) 34 | H, W = timg.shape[2:] 35 | if max_size>0: 36 | if max_size % 16 != 0: 37 | max_size = int(max_size - (max_size % 16)) 38 | min_size = int(min(H, W) * max_size / float(max(H, W))) 39 | if min_size % 16 !=0: 40 | min_size = int(min_size - (min_size % 16)) 41 | if H > W: 42 | out_size = (max_size, min_size) 43 | else: 44 | out_size = (min_size, max_size) 45 | with torch.no_grad(): 46 | timg_res = K.geometry.resize(timg, out_size) 47 | else: 48 | timg_res = timg 49 | with torch.no_grad(): 50 | H2, W2 = timg_res.shape[2:] 51 | coef_h = (H/float(H2)) 52 | coef_w = (W/float(W2)) 53 | score_1, coord_1, desc1 = keypoint_net(timg_res) 54 | coord_1 = coord_1.permute(0, 2, 3, 1).reshape(-1, 2) 55 | desc1 = desc1.permute(0, 2, 3, 1).reshape(-1, 256) 56 | if norm_desc: 57 | desc1 = F.normalize(desc1, dim=1, p=2) 58 | score_1 = score_1.reshape(-1) 59 | sorted_sc, indices = torch.sort(score_1, descending=True) 60 | idxs = indices[:MAX_KP] 61 | resps = score_1[idxs] 62 | kps = coord_1[idxs] 63 | kps[:, 0] *= coef_w 64 | kps[:, 1] *= coef_h 65 | descs = desc1[idxs] 66 | return kps.reshape(-1, 2), resps, descs 67 | 68 | if __name__ == '__main__': 69 | parser = argparse.ArgumentParser() 70 | parser.add_argument( 71 | "--datasets_folder", 72 | default=os.path.join('..', 'imw-2020'), 73 | help="path to datasets folder", 74 | type=str) 75 | parser.add_argument( 76 | '--num_kp', 77 | type=int, 78 | default=2048, 79 | help='Detector confidence threshold (default: 0.015).') 80 | parser.add_argument( 81 | '--resize_image_to', 82 | type=int, 83 | default=1024, 84 | help='Resize the largest image dimension to this value (default: 1024, ' 85 | '0 does nothing).') 86 | parser.add_argument( 87 | '--model_version', 88 | type=str, 89 | default='v4', 90 | choices=["v0", "v1", "v2", "v3", "v4"]) 91 | parser.add_argument( 92 | '--device', 93 | type=str, 94 | default='cpu', 95 | choices=["cpu", 'cuda', 'mps'] 96 | ) 97 | parser.add_argument( 98 | "--save_path", 99 | default=os.path.join('..', 'benchmark-features'), 100 | type=str, 101 | help='Path to store the features') 102 | parser.add_argument( 103 | "--method_name", default='kp2d', type=str) 104 | parser.add_argument( 105 | "--dataset", 106 | default='all', 107 | type=str, 108 | choices=["all", "phototourism", "pragueparks"]) 109 | parser.add_argument( 110 | "--norm_desc", 111 | default=False, 112 | type=str2bool, 113 | help='L2Norm of descriptors') 114 | opt, unparsed = parser.parse_known_args() 115 | print(opt) 116 | vv = opt.model_version 117 | device = torch.device(opt.device) 118 | checkpoint = torch.load(f'third_party/KP2D/data/models/kp2d/{vv}.ckpt', 119 | map_location=device) 120 | model_args = checkpoint['config']['model']['params'] 121 | keypoint_net = KeypointNet() 122 | keypoint_net.load_state_dict(checkpoint['state_dict']) 123 | keypoint_net.eval() 124 | keypoint_net=keypoint_net.to(device) 125 | INPUT_DIR = opt.datasets_folder 126 | modelname = f'{opt.method_name}_{opt.model_version}' 127 | if opt.norm_desc: 128 | modelname+='_norm' 129 | if opt.resize_image_to > 0: 130 | modelname+= f'_{opt.resize_image_to}' 131 | else: 132 | modelname+= f'_fullres' 133 | OUT_DIR = os.path.join(opt.save_path, modelname) 134 | os.makedirs(OUT_DIR, exist_ok=True) 135 | print (f"Will save to {OUT_DIR}") 136 | if opt.dataset == 'all': 137 | datasets = ['phototourism', 'pragueparks']#[x for x in os.listdir(INPUT_DIR) if (os.path.isdir(os.path.join(INPUT_DIR, x)))] 138 | else: 139 | datasets = [opt.dataset] 140 | for ds in datasets: 141 | ds_in_path = os.path.join(INPUT_DIR, ds) 142 | ds_out_path = os.path.join(OUT_DIR, ds) 143 | os.makedirs(ds_out_path, exist_ok=True) 144 | seqs = [x for x in os.listdir(ds_in_path) if os.path.isdir(os.path.join(ds_in_path, x))] 145 | for seq in seqs: 146 | print (seq) 147 | if os.path.isdir(os.path.join(ds_in_path, seq, 'set_100')): 148 | seq_in_path = os.path.join(ds_in_path, seq, 'set_100', 'images') 149 | else: 150 | seq_in_path = os.path.join(ds_in_path, seq) 151 | seq_out_path = os.path.join(ds_out_path, seq) 152 | os.makedirs(seq_out_path, exist_ok=True) 153 | img_fnames = os.listdir(seq_in_path) 154 | num_kp = [] 155 | with h5py.File(f'{seq_out_path}/keypoints.h5', mode='w') as f_kp, \ 156 | h5py.File(f'{seq_out_path}/descriptors.h5', mode='w') as f_desc, \ 157 | h5py.File(f'{seq_out_path}/scores.h5', mode='w') as f_score, \ 158 | h5py.File(f'{seq_out_path}/angles.h5', mode='w') as f_ang, \ 159 | h5py.File(f'{seq_out_path}/scales.h5', mode='w') as f_scale: 160 | for img_fname in tqdm(img_fnames): 161 | img_fname_full = os.path.join(seq_in_path, img_fname) 162 | key = os.path.splitext(os.path.basename(img_fname))[0] 163 | kps, resps, descs = extract_features(img_fname_full, keypoint_net, device, 164 | opt.num_kp, 165 | opt.resize_image_to, 166 | opt.norm_desc) 167 | keypoints, scales, angles, responses = convert_imc(kps, resps) 168 | f_desc[key] = descs.reshape(-1, 256).detach().cpu().numpy() 169 | f_score[key] = responses 170 | f_ang[key] = angles 171 | f_kp[key] = keypoints 172 | f_scale[key] = scales 173 | num_kp.append(len(keypoints)) 174 | print(f'Finished processing "{ds}/{seq}" -> {np.array(num_kp).mean()} features/image') 175 | print (f"Result is saved to {OUT_DIR}") 176 | -------------------------------------------------------------------------------- /extract_lanet.py: -------------------------------------------------------------------------------- 1 | import os 2 | import h5py 3 | from tqdm import tqdm 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | import cv2 7 | import torch 8 | import torch.nn.functional as F 9 | import argparse 10 | import sys 11 | 12 | def str2bool(v): 13 | if v.lower() in ('yes', 'true', 't', 'y', '1'): 14 | return True 15 | elif v.lower() in ('no', 'false', 'f', 'n', '0'): 16 | return False 17 | 18 | def save_h5(dict_to_save, filename): 19 | """Saves dictionary to hdf5 file""" 20 | 21 | with h5py.File(filename, 'w') as f: 22 | for key in dict_to_save: 23 | f.create_dataset(key, data=dict_to_save[key]) 24 | 25 | sys.path.insert(0, f'{os.getcwd()}/third_party/lanet') 26 | 27 | import kornia as K 28 | from network_v1.model import PointModel as PointModel_v1 29 | from network_v0.model import PointModel as PointModel_v0 30 | 31 | def convert_imc(kps, resps): 32 | keypoints = kps.reshape(-1, 2).detach().cpu().numpy() 33 | nkp = len(keypoints) 34 | scales = np.ones((nkp, 1)).astype(np.float32) 35 | angles = np.zeros((nkp, 1)).astype(np.float32) 36 | responses = resps.detach().reshape(-1, 1).cpu().numpy() 37 | return keypoints, scales, angles, responses 38 | 39 | 40 | def extract_features(img_fname, keypoint_net, device, MAX_KP, max_size, norm_desc): 41 | img = cv2.cvtColor(cv2.imread(img_fname), cv2.COLOR_BGR2RGB) 42 | timg = K.image_to_tensor(img, False).float()/255. 43 | timg = timg.to(device) 44 | #timg_gray = K.color.rgb_to_grayscale(timg) 45 | H, W = timg.shape[2:] 46 | if max_size>0: 47 | if max_size % 16 != 0: 48 | max_size = int(max_size - (max_size % 16)) 49 | min_size = int(min(H, W) * max_size / float(max(H, W))) 50 | if min_size % 16 !=0: 51 | min_size = int(min_size - (min_size % 16)) 52 | if H > W: 53 | out_size = (max_size, min_size) 54 | else: 55 | out_size = (min_size, max_size) 56 | with torch.no_grad(): 57 | timg_res = K.geometry.resize(timg, out_size) 58 | else: 59 | timg_res = timg 60 | with torch.no_grad(): 61 | H2, W2 = timg_res.shape[2:] 62 | coef_h = (H/float(H2)) 63 | coef_w = (W/float(W2)) 64 | score_1, coord_1, desc1 = keypoint_net(timg_res) 65 | coord_1 = coord_1.permute(0, 2, 3, 1).reshape(-1, 2) 66 | desc1 = desc1.permute(0, 2, 3, 1).reshape(-1, 256) 67 | if norm_desc: 68 | desc1 = F.normalize(desc1, dim=1, p=2) 69 | score_1 = score_1.reshape(-1) 70 | sorted_sc, indices = torch.sort(score_1, descending=True) 71 | idxs = indices[:MAX_KP] 72 | resps = score_1[idxs] 73 | kps = coord_1[idxs] 74 | kps[:, 0] *= coef_w 75 | kps[:, 1] *= coef_h 76 | descs = desc1[idxs] 77 | return kps.reshape(-1, 2), resps, descs 78 | 79 | if __name__ == '__main__': 80 | parser = argparse.ArgumentParser() 81 | parser.add_argument( 82 | "--datasets_folder", 83 | default=os.path.join('..', 'imw-2020'), 84 | help="path to datasets folder", 85 | type=str) 86 | parser.add_argument( 87 | '--num_kp', 88 | type=int, 89 | default=2048, 90 | help='number of keypoints') 91 | parser.add_argument( 92 | '--resize_image_to', 93 | type=int, 94 | default=1024, 95 | help='Resize the largest image dimension to this value (default: 1024, ' 96 | '0 does nothing).') 97 | parser.add_argument( 98 | '--model_version', 99 | type=str, 100 | default='v1', 101 | choices=["v0", "v1"]) 102 | parser.add_argument( 103 | '--device', 104 | type=str, 105 | default='cpu', 106 | choices=["cpu", 'cuda', 'mps'] 107 | ) 108 | parser.add_argument( 109 | "--save_path", 110 | default=os.path.join('..', 'benchmark-features'), 111 | type=str, 112 | help='Path to store the features') 113 | parser.add_argument( 114 | "--method_name", default='lanet', type=str) 115 | parser.add_argument( 116 | "--dataset", 117 | default='all', 118 | type=str, 119 | choices=["all", "phototourism", "pragueparks"]) 120 | parser.add_argument( 121 | "--norm_desc", 122 | default=False, 123 | type=str2bool, 124 | help='L2Norm of descriptors') 125 | opt, unparsed = parser.parse_known_args() 126 | print(opt) 127 | vv = opt.model_version 128 | device = torch.device(opt.device) 129 | if vv == 'v1': 130 | keypoint_net = PointModel_v1(is_test=True) 131 | checkpoint = torch.load('third_party/lanet/checkpoints/PointModel_v1.pth', 132 | map_location=device) 133 | else: 134 | keypoint_net = PointModel_v0(is_test=True) 135 | checkpoint = torch.load('third_party/lanet/checkpoints/PointModel_v0.pth', 136 | map_location=device) 137 | 138 | keypoint_net.load_state_dict(checkpoint['model_state']) 139 | keypoint_net.eval() 140 | keypoint_net=keypoint_net.to(device) 141 | INPUT_DIR = opt.datasets_folder 142 | modelname = f'{opt.method_name}_{opt.model_version}' 143 | if opt.norm_desc: 144 | modelname+='_norm' 145 | if opt.resize_image_to > 0: 146 | modelname+= f'_{opt.resize_image_to}' 147 | else: 148 | modelname+= f'_fullres' 149 | OUT_DIR = os.path.join(opt.save_path, modelname) 150 | os.makedirs(OUT_DIR, exist_ok=True) 151 | print (f"Will save to {OUT_DIR}") 152 | if opt.dataset == 'all': 153 | datasets = ['phototourism', 'pragueparks']#[x for x in os.listdir(INPUT_DIR) if (os.path.isdir(os.path.join(INPUT_DIR, x)))] 154 | else: 155 | datasets = [opt.dataset] 156 | for ds in datasets: 157 | ds_in_path = os.path.join(INPUT_DIR, ds) 158 | ds_out_path = os.path.join(OUT_DIR, ds) 159 | os.makedirs(ds_out_path, exist_ok=True) 160 | seqs = [x for x in os.listdir(ds_in_path) if os.path.isdir(os.path.join(ds_in_path, x))] 161 | for seq in seqs: 162 | print (seq) 163 | if os.path.isdir(os.path.join(ds_in_path, seq, 'set_100')): 164 | seq_in_path = os.path.join(ds_in_path, seq, 'set_100', 'images') 165 | else: 166 | seq_in_path = os.path.join(ds_in_path, seq) 167 | seq_out_path = os.path.join(ds_out_path, seq) 168 | os.makedirs(seq_out_path, exist_ok=True) 169 | img_fnames = os.listdir(seq_in_path) 170 | num_kp = [] 171 | with h5py.File(f'{seq_out_path}/keypoints.h5', mode='w') as f_kp, \ 172 | h5py.File(f'{seq_out_path}/descriptors.h5', mode='w') as f_desc, \ 173 | h5py.File(f'{seq_out_path}/scores.h5', mode='w') as f_score, \ 174 | h5py.File(f'{seq_out_path}/angles.h5', mode='w') as f_ang, \ 175 | h5py.File(f'{seq_out_path}/scales.h5', mode='w') as f_scale: 176 | for img_fname in tqdm(img_fnames): 177 | img_fname_full = os.path.join(seq_in_path, img_fname) 178 | key = os.path.splitext(os.path.basename(img_fname))[0] 179 | kps, resps, descs = extract_features(img_fname_full, keypoint_net, device, 180 | opt.num_kp, 181 | opt.resize_image_to, 182 | opt.norm_desc) 183 | keypoints, scales, angles, responses = convert_imc(kps, resps) 184 | f_desc[key] = descs.reshape(-1, 256).detach().cpu().numpy() 185 | f_score[key] = responses 186 | f_ang[key] = angles 187 | f_kp[key] = keypoints 188 | f_scale[key] = scales 189 | num_kp.append(len(keypoints)) 190 | print(f'Finished processing "{ds}/{seq}" -> {np.array(num_kp).mean()} features/image') 191 | print (f"Result is saved to {OUT_DIR}") 192 | -------------------------------------------------------------------------------- /extract_lfnet.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import os 3 | import sys 4 | from PIL import Image 5 | import numpy as np 6 | import argparse 7 | import glob 8 | import h5py 9 | import json 10 | import tensorflow as tf 11 | import importlib 12 | import time 13 | import cv2 14 | from tqdm import tqdm 15 | import pickle 16 | 17 | sys.path.insert(0,os.path.join('third_party','lfnet')) 18 | 19 | from mydatasets import * 20 | 21 | from det_tools import * 22 | from eval_tools import draw_keypoints 23 | from common.tf_train_utils import get_optimizer 24 | from common.argparse_utils import * 25 | from imageio import imread, imsave 26 | from inference import * 27 | from run_lfnet import build_networks 28 | 29 | MODEL_PATH = './third_party/lfnet/models' 30 | if MODEL_PATH not in sys.path: 31 | sys.path.append(MODEL_PATH) 32 | 33 | # Adapted from third_party/r2d2/extract.py 34 | if __name__ == '__main__': 35 | parser = get_parser() 36 | 37 | general_arg = add_argument_group('General', parser) 38 | general_arg.add_argument('--num_threads', type=int, default=8, 39 | help='the number of threads (for dataset)') 40 | general_arg.add_argument( 41 | "--subset", 42 | default='both', 43 | type=str, 44 | help='Options: "val", "test", "both"') 45 | 46 | io_arg = add_argument_group('In/Out', parser) 47 | io_arg.add_argument('--in_dir', type=str, default=os.path.join('..', 'imw-2020'), 48 | help='input image directory') 49 | # io_arg.add_argument('--in_dir', type=str, default='./release/outdoor_examples/images/sacre_coeur/dense/images', 50 | # help='input image directory') 51 | io_arg.add_argument('--out_dir', type=str, required=True, 52 | help='where to save keypoints') 53 | 54 | model_arg = add_argument_group('Model', parser) 55 | model_arg.add_argument('--model', type=str, default='./third_party/lfnet/release/lfnet-norotaug/', 56 | help='model file or directory') 57 | model_arg.add_argument('--top_k', type=int, default=2000, 58 | help='number of keypoints') 59 | model_arg.add_argument('--max_longer_edge', type=int, default=0, 60 | help='resize image (do nothing if max_longer_edge <= 0)') 61 | 62 | tmp_config, unparsed = get_config(parser) 63 | 64 | if len(unparsed) > 0: 65 | raise ValueError('Miss finding argument: unparsed={}\n'.format(unparsed)) 66 | 67 | # restore other hyperparams to build model 68 | if os.path.isdir(tmp_config.model): 69 | config_path = os.path.join(tmp_config.model, 'config.pkl') 70 | else: 71 | config_path = os.path.join(os.path.dirname(tmp_config.model), 'config.pkl') 72 | try: 73 | with open(config_path, 'rb') as f: 74 | config = pickle.load(f) 75 | except: 76 | raise ValueError('Fail to open {}'.format(config_path)) 77 | 78 | for attr, dst_val in sorted(vars(tmp_config).items()): 79 | if hasattr(config, attr): 80 | src_val = getattr(config, attr) 81 | if src_val != dst_val: 82 | setattr(config, attr, dst_val) 83 | else: 84 | setattr(config, attr, dst_val) 85 | 86 | seqs = [] 87 | if config.subset not in ['val', 'test', 'both']: 88 | raise ValueError('Unknown value for --subset') 89 | if config.subset in ['val', 'both']: 90 | with open(os.path.join('data', 'val.json')) as f: 91 | seqs += json.load(f) 92 | if config.subset in ['test', 'both']: 93 | with open(os.path.join('data', 'test.json')) as f: 94 | seqs += json.load(f) 95 | 96 | 97 | # Build Networks 98 | tf.reset_default_graph() 99 | 100 | photo_ph = tf.placeholder(tf.float32, [1, None, None, 1]) # input grayscale image, normalized by 0~1 101 | is_training = tf.constant(False) # Always False in testing 102 | 103 | ops = build_networks(config, photo_ph, is_training) 104 | 105 | tfconfig = tf.ConfigProto() 106 | tfconfig.gpu_options.allow_growth = True 107 | sess = tf.Session(config=tfconfig) 108 | sess.run(tf.global_variables_initializer()) 109 | 110 | # load model 111 | saver = tf.train.Saver() 112 | print('Load trained models...') 113 | 114 | if os.path.isdir(config.model): 115 | checkpoint = tf.train.latest_checkpoint(config.model) 116 | model_dir = config.model 117 | else: 118 | checkpoint = config.model 119 | model_dir = os.path.dirname(config.model) 120 | 121 | 122 | if checkpoint is not None: 123 | print('Checkpoint', os.path.basename(checkpoint)) 124 | print("[{}] Resuming...".format(time.asctime())) 125 | saver.restore(sess, checkpoint) 126 | else: 127 | raise ValueError('Cannot load model from {}'.format(model_dir)) 128 | print('Done.') 129 | 130 | 131 | print('Processing the following scenes: {}'.format(seqs)) 132 | for seq in seqs: 133 | 134 | print('Processing scene "{}"'.format(seq)) 135 | 136 | if not os.path.isdir('{}/{}'.format(config.out_dir, seq)): 137 | os.makedirs('{}/{}'.format(config.out_dir, seq)) 138 | 139 | images = glob.glob('{}/{}/*.jpg'.format(config.in_dir, seq)) 140 | 141 | num_kp = [] 142 | with h5py.File('{}/{}/keypoints.h5'.format(config.out_dir, seq), 'w') as f_kp, \ 143 | h5py.File('{}/{}/descriptors.h5'.format(config.out_dir, seq), 'w') as f_desc: 144 | for fn in images: 145 | key = os.path.splitext(os.path.basename(fn))[0] 146 | print(key) 147 | photo = imread(fn) 148 | height, width = photo.shape[:2] 149 | longer_edge = max(height, width) 150 | if config.max_longer_edge > 0 and longer_edge > config.max_longer_edge: 151 | if height > width: 152 | new_height = config.max_longer_edge 153 | new_width = int(width * config.max_longer_edge / height) 154 | else: 155 | new_height = int(height * config.max_longer_edge / width) 156 | new_width = config.max_longer_edge 157 | photo = cv2.resize(photo, (new_width, new_height)) 158 | height, width = photo.shape[:2] 159 | rgb = photo.copy() 160 | if photo.ndim == 3 and photo.shape[-1] == 3: 161 | photo = cv2.cvtColor(photo, cv2.COLOR_RGB2GRAY) 162 | photo = photo[None,...,None].astype(np.float32) / 255.0 # normalize 0-1 163 | assert photo.ndim == 4 # [1,H,W,1] 164 | 165 | feed_dict = { 166 | photo_ph: photo, 167 | } 168 | 169 | fetch_dict = { 170 | 'kpts': ops['kpts'], 171 | 'feats': ops['feats'], 172 | 'kpts_scale': ops['kpts_scale'], 173 | 'kpts_ori': ops['kpts_ori'], 174 | 'scale_maps': ops['scale_maps'], 175 | 'degree_maps': ops['degree_maps'], 176 | } 177 | 178 | outs = sess.run(fetch_dict, feed_dict=feed_dict) 179 | 180 | 181 | f_kp[key] = outs['kpts'] 182 | f_desc[key] = outs['feats'] 183 | num_kp.append(len(f_kp[key])) 184 | 185 | print('Image "{}/{}" -> {} features'.format( 186 | seq, key, num_kp[-1])) 187 | 188 | print('Finished processing scene "{}" -> {} features/image'.format( 189 | seq, np.array(num_kp).mean())) 190 | -------------------------------------------------------------------------------- /extract_ml_superpoint.py: -------------------------------------------------------------------------------- 1 | import os 2 | import h5py 3 | from tqdm import tqdm 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | import cv2 7 | import torch 8 | import torch.nn.functional as F 9 | import argparse 10 | import sys 11 | import yaml 12 | from copy import deepcopy 13 | torch.set_default_tensor_type(torch.FloatTensor) 14 | 15 | def str2bool(v): 16 | if v.lower() in ('yes', 'true', 't', 'y', '1'): 17 | return True 18 | elif v.lower() in ('no', 'false', 'f', 'n', '0'): 19 | return False 20 | 21 | def save_h5(dict_to_save, filename): 22 | """Saves dictionary to hdf5 file""" 23 | 24 | with h5py.File(filename, 'w') as f: 25 | for key in dict_to_save: 26 | f.create_dataset(key, data=dict_to_save[key]) 27 | 28 | sys.path.insert(0, f'{os.getcwd()}/third_party/superpoint_forked') 29 | from superpoint import SuperPointFrontend 30 | import kornia as K 31 | # data loading 32 | 33 | 34 | 35 | def convert_imc(kps, resps): 36 | keypoints = kps.reshape(-1, 2) 37 | nkp = len(keypoints) 38 | scales = np.ones((nkp, 1)).astype(np.float32) 39 | angles = np.zeros((nkp, 1)).astype(np.float32) 40 | responses = resps.reshape(-1, 1) 41 | return keypoints, scales, angles, responses 42 | 43 | 44 | def extract_features(img_fname, keypoint_net, device, MAX_KP, max_size, norm_desc): 45 | img = cv2.cvtColor(cv2.imread(img_fname), cv2.COLOR_BGR2RGB) 46 | timg = K.image_to_tensor(img, False).float()/255. 47 | timg = timg.to(device) 48 | timg = K.color.rgb_to_grayscale(timg) 49 | H, W = timg.shape[2:] 50 | if max_size>0: 51 | if max_size % 16 != 0: 52 | max_size = int(max_size - (max_size % 16)) 53 | min_size = int(min(H, W) * max_size / float(max(H, W))) 54 | if min_size % 16 !=0: 55 | min_size = int(min_size - (min_size % 16)) 56 | if H > W: 57 | out_size = (max_size, min_size) 58 | else: 59 | out_size = (min_size, max_size) 60 | with torch.no_grad(): 61 | timg_res = K.geometry.resize(timg, out_size) 62 | else: 63 | timg_res = timg 64 | with torch.no_grad(): 65 | H2, W2 = timg_res.shape[2:] 66 | coef_h = (H/float(H2)) 67 | coef_w = (W/float(W2)) 68 | kp1, descs1, heatmap1 = superpoint.run(timg_res[0,0].detach().cpu().numpy()) 69 | kp1, descs1, heatmap1 = torch.from_numpy(kp1), torch.from_numpy(descs1), torch.from_numpy(heatmap1) 70 | coord_1 = kp1.T 71 | score_1 = deepcopy(coord_1[:, 2]) 72 | coord_1 = deepcopy(coord_1[:, :2]) 73 | desc1 = descs1.T 74 | if norm_desc: 75 | desc1 = F.normalize(desc1, dim=1, p=2) 76 | score_1 = score_1.reshape(-1) 77 | sorted_sc, indices = torch.sort(score_1, descending=True) 78 | idxs = indices[:MAX_KP].numpy() 79 | resps = score_1[idxs].detach().cpu().numpy() 80 | kps = coord_1[idxs] 81 | kps[:, 0] *= coef_w 82 | kps[:, 1] *= coef_h 83 | descs = desc1[idxs] 84 | return kps.detach().cpu().numpy().reshape(-1, 2), resps, descs.detach().cpu().numpy() 85 | 86 | if __name__ == '__main__': 87 | parser = argparse.ArgumentParser() 88 | parser.add_argument( 89 | "--datasets_folder", 90 | default=os.path.join('..', 'imw-2020'), 91 | help="path to datasets folder", 92 | type=str) 93 | parser.add_argument( 94 | '--num_kp', 95 | type=int, 96 | default=2048, 97 | help='number of keypoints') 98 | parser.add_argument( 99 | '--resize_image_to', 100 | type=int, 101 | default=1024, 102 | help='Resize the largest image dimension to this value (default: 1024, ' 103 | '0 does nothing).') 104 | parser.add_argument( 105 | '--device', 106 | type=str, 107 | default='cpu', 108 | choices=["cpu", 'cuda', 'mps'] 109 | ) 110 | parser.add_argument( 111 | "--save_path", 112 | default=os.path.join('..', 'benchmark-features'), 113 | type=str, 114 | help='Path to store the features') 115 | parser.add_argument( 116 | "--method_name", default='superpoint_magicleap', type=str) 117 | parser.add_argument( 118 | "--dataset", 119 | default='all', 120 | type=str, 121 | choices=["all", "phototourism", "pragueparks"]) 122 | parser.add_argument( 123 | "--norm_desc", 124 | default=False, 125 | type=str2bool, 126 | help='L2Norm of descriptors') 127 | opt, unparsed = parser.parse_known_args() 128 | device = torch.device(opt.device) 129 | sp_weights_fname = 'third_party/superpoint_forked/superpoint_v1.pth' 130 | superpoint = SuperPointFrontend(sp_weights_fname, 4, 0.00015, 0.7, cuda=opt.device=='cuda') 131 | superpoint.net = superpoint.net.to(device) 132 | 133 | INPUT_DIR = opt.datasets_folder 134 | modelname = f'{opt.method_name}' 135 | if opt.norm_desc: 136 | modelname+='_norm' 137 | if opt.resize_image_to > 0: 138 | modelname+= f'_{opt.resize_image_to}' 139 | else: 140 | modelname+= f'_fullres' 141 | OUT_DIR = os.path.join(opt.save_path, modelname) 142 | os.makedirs(OUT_DIR, exist_ok=True) 143 | print (f"Will save to {OUT_DIR}") 144 | if opt.dataset == 'all': 145 | datasets = [x for x in os.listdir(INPUT_DIR) if (os.path.isdir(os.path.join(INPUT_DIR, x)))] 146 | else: 147 | datasets = [opt.dataset] 148 | for ds in datasets: 149 | ds_in_path = os.path.join(INPUT_DIR, ds) 150 | ds_out_path = os.path.join(OUT_DIR, ds) 151 | os.makedirs(ds_out_path, exist_ok=True) 152 | seqs = [x for x in os.listdir(ds_in_path) if os.path.isdir(os.path.join(ds_in_path, x))] 153 | for seq in seqs: 154 | print (seq) 155 | if os.path.isdir(os.path.join(ds_in_path, seq, 'set_100')): 156 | seq_in_path = os.path.join(ds_in_path, seq, 'set_100', 'images') 157 | else: 158 | seq_in_path = os.path.join(ds_in_path, seq) 159 | seq_out_path = os.path.join(ds_out_path, seq) 160 | os.makedirs(seq_out_path, exist_ok=True) 161 | img_fnames = os.listdir(seq_in_path) 162 | num_kp = [] 163 | with h5py.File(f'{seq_out_path}/keypoints.h5', mode='w') as f_kp, \ 164 | h5py.File(f'{seq_out_path}/descriptors.h5', mode='w') as f_desc, \ 165 | h5py.File(f'{seq_out_path}/scores.h5', mode='w') as f_score, \ 166 | h5py.File(f'{seq_out_path}/angles.h5', mode='w') as f_ang, \ 167 | h5py.File(f'{seq_out_path}/scales.h5', mode='w') as f_scale: 168 | for img_fname in tqdm(img_fnames): 169 | img_fname_full = os.path.join(seq_in_path, img_fname) 170 | key = os.path.splitext(os.path.basename(img_fname))[0] 171 | kps, resps, descs = extract_features(img_fname_full, superpoint, device, 172 | opt.num_kp, 173 | opt.resize_image_to, 174 | opt.norm_desc) 175 | keypoints, scales, angles, responses = convert_imc(kps, resps) 176 | f_desc[key] = descs.reshape(-1, 256) 177 | f_score[key] = responses 178 | f_kp[key] = keypoints 179 | f_ang[key] = angles 180 | f_scale[key] = scales 181 | num_kp.append(len(keypoints)) 182 | print(f'Finished processing "{ds}/{seq}" -> {np.array(num_kp).mean()} features/image') 183 | print (f"Result is saved to {OUT_DIR}") 184 | 185 | -------------------------------------------------------------------------------- /extract_r2d2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from PIL import Image 4 | import numpy as np 5 | import torch 6 | import argparse 7 | from glob import glob 8 | import h5py 9 | import json 10 | 11 | sys.path.append(os.path.join('third_party', 'r2d2')) 12 | from third_party.r2d2.tools import common 13 | from third_party.r2d2.tools.dataloader import norm_RGB 14 | from third_party.r2d2.nets.patchnet import * 15 | from third_party.r2d2.extract import load_network, NonMaxSuppression, extract_multiscale 16 | 17 | # Adapted from third_party/r2d2/extract.py 18 | if __name__ == '__main__': 19 | parser = argparse.ArgumentParser("Extract R2D2 features for IMW2020") 20 | 21 | parser.add_argument("--model", type=str, required=True, help='Model path') 22 | parser.add_argument( 23 | "--num_keypoints", type=int, default=5000, help='Number of keypoints') 24 | parser.add_argument("--scale-f", type=float, default=2**0.25) 25 | parser.add_argument("--min-size", type=int, default=256) 26 | parser.add_argument("--max-size", type=int, default=1024) 27 | parser.add_argument("--min-scale", type=float, default=0) 28 | parser.add_argument("--max-scale", type=float, default=1) 29 | parser.add_argument("--reliability-thr", type=float, default=0.7) 30 | parser.add_argument("--repeatability-thr", type=float, default=0.7) 31 | parser.add_argument( 32 | "--gpu", type=int, nargs='+', default=[0], help='Use -1 for CPU') 33 | parser.add_argument( 34 | "--data_path", type=str, default=os.path.join('..', 'imw-2020')) 35 | parser.add_argument( 36 | "--save_path", 37 | type=str, 38 | required=True, 39 | help='Path to store the features') 40 | parser.add_argument( 41 | "--subset", 42 | default='both', 43 | type=str, 44 | help='Options: "val", "test", "both"') 45 | 46 | args = parser.parse_args() 47 | 48 | seqs = [] 49 | if args.subset not in ['val', 'test', 'both']: 50 | raise ValueError('Unknown value for --subset') 51 | if args.subset in ['val', 'both']: 52 | with open(os.path.join('data', 'val.json')) as f: 53 | seqs += json.load(f) 54 | if args.subset in ['test', 'both']: 55 | with open(os.path.join('data', 'test.json')) as f: 56 | seqs += json.load(f) 57 | print('Processing the following scenes: {}'.format(seqs)) 58 | 59 | iscuda = common.torch_set_gpu(args.gpu) 60 | 61 | net = load_network(args.model) 62 | if iscuda: 63 | net = net.cuda() 64 | 65 | detector = NonMaxSuppression( 66 | rel_thr=args.reliability_thr, rep_thr=args.repeatability_thr) 67 | 68 | for seq in seqs: 69 | print('Processing scene "{}"'.format(seq)) 70 | if not os.path.isdir('{}/{}'.format(args.save_path, seq)): 71 | os.makedirs('{}/{}'.format(args.save_path, seq)) 72 | 73 | images = glob('{}/{}/*.jpg'.format(args.data_path, seq)) 74 | 75 | num_kp = [] 76 | with h5py.File('{}/{}/keypoints.h5'.format(args.save_path, seq), 'w') as f_kp, \ 77 | h5py.File('{}/{}/descriptors.h5'.format(args.save_path, seq), 'w') as f_desc, \ 78 | h5py.File('{}/{}/scores.h5'.format(args.save_path, seq), 'w') as f_score, \ 79 | h5py.File('{}/{}/scales.h5'.format(args.save_path, seq), 'w') as f_scale: 80 | for fn in images: 81 | key = os.path.splitext(os.path.basename(fn))[0] 82 | img = Image.open(fn).convert('RGB') 83 | img = norm_RGB(img)[None] 84 | if iscuda: 85 | img = img.cuda() 86 | 87 | xys, desc, scores = extract_multiscale( 88 | net, 89 | img, 90 | detector, 91 | scale_f=args.scale_f, 92 | min_scale=args.min_scale, 93 | max_scale=args.max_scale, 94 | min_size=args.min_size, 95 | max_size=args.max_size, 96 | verbose=False) 97 | 98 | kp = xys.cpu().numpy()[:, :2] 99 | scales = xys.cpu().numpy()[:, 2] 100 | desc = desc.cpu().numpy() 101 | scores = scores.cpu().numpy() 102 | idxs = scores.argsort()[-args.num_keypoints:] 103 | 104 | f_kp[key] = kp[idxs] 105 | f_desc[key] = desc[idxs] 106 | f_score[key] = scores[idxs] 107 | f_scale[key] = scales[idxs] 108 | num_kp.append(len(f_kp[key])) 109 | 110 | print('Image "{}/{}" -> {} features'.format( 111 | seq, key, num_kp[-1])) 112 | 113 | print('Finished processing scene "{}" -> {} features/image'.format( 114 | seq, np.array(num_kp).mean())) 115 | -------------------------------------------------------------------------------- /extract_sift_kornia_affnet_desc.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from PIL import Image 4 | import numpy as np 5 | import torch.nn as nn 6 | import torch 7 | import argparse 8 | from glob import glob 9 | import h5py 10 | import json 11 | from torch import tensor 12 | sys.path.append(os.path.join('third_party', 'r2d2')) 13 | import kornia as K 14 | import kornia.feature as KF 15 | from kornia_moons.feature import * 16 | import cv2 17 | def get_local_descriptors(img, cv2_sift_kpts, kornia_descriptor, aff): 18 | #We will not train anything, so let's save time and memory by no_grad() 19 | with torch.no_grad(): 20 | timg = K.color.rgb_to_grayscale(K.image_to_tensor(img, False))/255. 21 | timg = timg.cuda() 22 | lafs = laf_from_opencv_SIFT_kpts(cv2_sift_kpts).cuda() 23 | angles = KF.laf.get_laf_orientation(lafs) 24 | # We will estimate affine shape of the feature and re-orient the keypoints with the OriNet 25 | lafs_new = aff(lafs,timg) 26 | patches = KF.extract_patches_from_pyramid(timg,lafs_new, 32) 27 | B, N, CH, H, W = patches.size() 28 | # Descriptor accepts standard tensor [B, CH, H, W], while patches are [B, N, CH, H, W] shape 29 | # So we need to reshape a bit :) 30 | descs = kornia_descriptor(patches.view(B * N, CH, H, W)).view(B * N, -1) 31 | return descs.detach().cpu().numpy() 32 | 33 | if __name__ == '__main__': 34 | parser = argparse.ArgumentParser("Extract Kornia handcrafted features for IMW2020") 35 | 36 | parser.add_argument( 37 | "--num_keypoints", type=int, default=8000, help='Number of keypoints') 38 | parser.add_argument("--mrsize", type=float, default=6.0) 39 | parser.add_argument("--patchsize", type=float, default=32) 40 | parser.add_argument("--upright", type=bool, default=True) 41 | parser.add_argument("--affine", type=bool, default=True) 42 | parser.add_argument("--descriptor", type=str, default='HardNet', help='hardnet, sift, rootsift, tfeat, sosnet') 43 | parser.add_argument( 44 | "--data_path", type=str, default=os.path.join('..', 'data')) 45 | parser.add_argument( 46 | "--save_path", 47 | type=str, 48 | required=True, 49 | help='Path to store the features') 50 | parser.add_argument( 51 | "--subset", 52 | default='both', 53 | type=str, 54 | help='Options: "val", "test", "both"') 55 | 56 | args = parser.parse_args() 57 | 58 | seqs = [] 59 | if args.subset not in ['val', 'test', 'both']: 60 | raise ValueError('Unknown value for --subset') 61 | if args.subset in ['val', 'both']: 62 | with open(os.path.join('data', 'val.json')) as f: 63 | seqs += json.load(f) 64 | if args.subset in ['test', 'both']: 65 | with open(os.path.join('data', 'test.json')) as f: 66 | seqs += json.load(f) 67 | print('Processing the following scenes: {}'.format(seqs)) 68 | PS = args.patchsize 69 | device = torch.device('cpu') 70 | try: 71 | if torch.cuda.is_available(): 72 | device = torch.device('cuda') 73 | except: 74 | print ('CPU mode') 75 | sift = cv2.SIFT_create( 76 | contrastThreshold=-10000, edgeThreshold=-10000) 77 | if args.descriptor.lower() == 'sift': 78 | descriptor = KF.SIFTDescriptor(PS, rootsift=False) 79 | elif args.descriptor.lower() == 'rootsift': 80 | descriptor = KF.SIFTDescriptor(PS, rootsift=True) 81 | elif args.descriptor.lower() == 'hardnet': 82 | PS = 32 83 | descriptor = KF.HardNet(True) 84 | elif args.descriptor.lower() == 'sosnet': 85 | PS = 32 86 | descriptor = KF.SOSNet(True) 87 | elif args.descriptor.lower() == 'tfeat': 88 | PS = 32 89 | descriptor = KF.TFeat(True) 90 | else: 91 | raise ValueError('Unknown descriptor') 92 | descriptor = descriptor.to(device) 93 | print (device) 94 | descriptor.eval() 95 | aff_est = KF.LAFAffNetShapeEstimator(True).to(device) 96 | aff_est.eval() 97 | from tqdm import tqdm 98 | def get_SIFT_keypoints(sift, img, lower_detection_th=False): 99 | # convert to gray-scale and compute SIFT keypoints 100 | keypoints = sift.detect(img, None) 101 | response = np.array([kp.response for kp in keypoints]) 102 | respSort = np.argsort(response)[::-1] 103 | pt = np.array([kp.pt for kp in keypoints])[respSort] 104 | size = np.array([kp.size for kp in keypoints])[respSort] 105 | angle = np.array([kp.angle for kp in keypoints])[respSort] 106 | response = np.array([kp.response for kp in keypoints])[respSort] 107 | return pt, size, angle, response 108 | NUM_KP = args.num_keypoints 109 | for seq in seqs: 110 | print('Processing scene "{}"'.format(seq)) 111 | if not os.path.isdir('{}/{}'.format(args.save_path, seq)): 112 | os.makedirs('{}/{}'.format(args.save_path, seq)) 113 | images = glob('{}/{}/set_100/images/*.jpg'.format(args.data_path, seq)) 114 | num_kp = [] 115 | with h5py.File('{}/{}/keypoints.h5'.format(args.save_path, seq), 'w') as f_kp, \ 116 | h5py.File('{}/{}/descriptors.h5'.format(args.save_path, seq), 'w') as f_desc, \ 117 | h5py.File('{}/{}/scores.h5'.format(args.save_path, seq), 'w') as f_score, \ 118 | h5py.File('{}/{}/angles.h5'.format(args.save_path, seq), 'w') as f_ang, \ 119 | h5py.File('{}/{}/scales.h5'.format(args.save_path, seq), 'w') as f_scale: 120 | for fn in tqdm(images): 121 | key = os.path.splitext(os.path.basename(fn))[0] 122 | im = cv2.cvtColor(cv2.imread(fn), cv2.COLOR_BGR2RGB) 123 | pts, size, angle, response = get_SIFT_keypoints(sift, im) 124 | if args.upright: 125 | kpts = [ 126 | cv2.KeyPoint( 127 | x=pt[0], 128 | y=pt[1], 129 | _size=size[i], 130 | _angle=0, _response=response[i]) for i, pt in enumerate(pts) if (pt not in pts[:i]) ] 131 | kpts = kpts[:NUM_KP] 132 | else: 133 | kpts = [ 134 | cv2.KeyPoint( 135 | x=pt[0], 136 | y=pt[1], 137 | _size=size[i], 138 | _angle=angle[i], _response=response[i]) for i, pt in enumerate(pts) ] 139 | kpts = kpts[:NUM_KP] 140 | with torch.no_grad(): 141 | descs = get_local_descriptors(im, kpts, descriptor, aff_est) 142 | keypoints = np.array([(x.pt[0], x.pt[1]) for x in kpts ]).reshape(-1, 2) 143 | scales = np.array([12.0* x.size for x in kpts ]).reshape(-1, 1) 144 | angles = np.array([x.angle for x in kpts ]).reshape(-1, 1) 145 | responses = np.array([x.response for x in kpts ]).reshape(-1, 1) 146 | f_kp[key] = keypoints 147 | f_desc[key] = descs 148 | f_score[key] = responses 149 | f_ang[key] = angles 150 | f_scale[key] = scales 151 | num_kp.append(len(keypoints)) 152 | print('Finished processing scene "{}" -> {} features/image'.format( 153 | seq, np.array(num_kp).mean())) 154 | -------------------------------------------------------------------------------- /extract_superoint_independent.py: -------------------------------------------------------------------------------- 1 | import os 2 | import h5py 3 | from tqdm import tqdm 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | import cv2 7 | import torch 8 | import torch.nn.functional as F 9 | import argparse 10 | import sys 11 | import yaml 12 | from copy import deepcopy 13 | torch.set_default_tensor_type(torch.FloatTensor) 14 | 15 | def str2bool(v): 16 | if v.lower() in ('yes', 'true', 't', 'y', '1'): 17 | return True 18 | elif v.lower() in ('no', 'false', 'f', 'n', '0'): 19 | return False 20 | 21 | def save_h5(dict_to_save, filename): 22 | """Saves dictionary to hdf5 file""" 23 | 24 | with h5py.File(filename, 'w') as f: 25 | for key in dict_to_save: 26 | f.create_dataset(key, data=dict_to_save[key]) 27 | 28 | sys.path.insert(0, f'{os.getcwd()}/third_party/pytorch-superpoint') 29 | 30 | import kornia as K 31 | # data loading 32 | from utils.loader import dataLoader_test as dataLoader 33 | from Val_model_heatmap import Val_model_heatmap as model_wrapper 34 | 35 | 36 | def convert_imc(kps, resps): 37 | keypoints = kps.reshape(-1, 2) 38 | nkp = len(keypoints) 39 | scales = np.ones((nkp, 1)).astype(np.float32) 40 | angles = np.zeros((nkp, 1)).astype(np.float32) 41 | responses = resps.reshape(-1, 1) 42 | return keypoints, scales, angles, responses 43 | 44 | 45 | def extract_features(img_fname, keypoint_net, device, MAX_KP, max_size, norm_desc, subpixel=False): 46 | img = cv2.cvtColor(cv2.imread(img_fname), cv2.COLOR_BGR2RGB) 47 | timg = K.image_to_tensor(img, False).float()/255. 48 | timg = timg.to(device) 49 | #timg_gray = K.color.rgb_to_grayscale(timg) 50 | H, W = timg.shape[2:] 51 | if max_size>0: 52 | if max_size % 16 != 0: 53 | max_size = int(max_size - (max_size % 16)) 54 | min_size = int(min(H, W) * max_size / float(max(H, W))) 55 | if min_size % 16 !=0: 56 | min_size = int(min_size - (min_size % 16)) 57 | if H > W: 58 | out_size = (max_size, min_size) 59 | else: 60 | out_size = (min_size, max_size) 61 | with torch.no_grad(): 62 | timg_res = K.geometry.resize(timg, out_size) 63 | else: 64 | timg_res = timg 65 | with torch.no_grad(): 66 | H2, W2 = timg_res.shape[2:] 67 | coef_h = (H/float(H2)) 68 | coef_w = (W/float(W2)) 69 | heatmap_batch = keypoint_net.run(K.color.rgb_to_grayscale(timg_res)) # heatmap: numpy [batch, 1, H, W] 70 | # heatmap to pts 71 | pts = val_agent.heatmap_to_pts() 72 | if subpixel: 73 | pts_subpixel = val_agent.soft_argmax_points(pts) 74 | pts = pts_subpixel[0] 75 | else: 76 | pts = pts[0] 77 | # heatmap, pts to desc 78 | 79 | coord_1 = pts.T 80 | score_1 = deepcopy(coord_1[:, 2]) 81 | coord_1 = deepcopy(coord_1[:, :2]) 82 | desc1 = val_agent.desc_to_sparseDesc()[0].T 83 | if norm_desc: 84 | desc1 = F.normalize(torch.from_numpy(desc1), dim=1, p=2).numpy() 85 | score_1 = score_1.reshape(-1) 86 | sorted_sc, indices = torch.sort(torch.from_numpy(score_1), descending=True) 87 | idxs = indices[:MAX_KP].numpy() 88 | resps = score_1[idxs] 89 | kps = coord_1[idxs] 90 | kps[:, 0] *= coef_w 91 | kps[:, 1] *= coef_h 92 | descs = desc1[idxs] 93 | return kps.reshape(-1, 2), resps, descs 94 | 95 | if __name__ == '__main__': 96 | parser = argparse.ArgumentParser() 97 | parser.add_argument( 98 | "--datasets_folder", 99 | default=os.path.join('..', 'imw-2020'), 100 | help="path to datasets folder", 101 | type=str) 102 | parser.add_argument( 103 | '--num_kp', 104 | type=int, 105 | default=2048, 106 | help='number of keypoints') 107 | parser.add_argument( 108 | '--resize_image_to', 109 | type=int, 110 | default=1024, 111 | help='Resize the largest image dimension to this value (default: 1024, ' 112 | '0 does nothing).') 113 | parser.add_argument( 114 | '--trainset', 115 | type=str, 116 | default='coco', 117 | choices=["coco", "kitty"]) 118 | parser.add_argument( 119 | '--subpix', 120 | type=str2bool, 121 | default=False) 122 | parser.add_argument( 123 | '--device', 124 | type=str, 125 | default='cpu', 126 | choices=["cpu", 'cuda', 'mps'] 127 | ) 128 | parser.add_argument( 129 | "--save_path", 130 | default=os.path.join('..', 'benchmark-features'), 131 | type=str, 132 | help='Path to store the features') 133 | parser.add_argument( 134 | "--method_name", default='superpoint_indep', type=str) 135 | parser.add_argument( 136 | "--dataset", 137 | default='all', 138 | type=str, 139 | choices=["all", "phototourism", "pragueparks"]) 140 | parser.add_argument( 141 | "--norm_desc", 142 | default=False, 143 | type=str2bool, 144 | help='L2Norm of descriptors') 145 | opt, unparsed = parser.parse_known_args() 146 | device = torch.device(opt.device) 147 | print(opt) 148 | if opt.trainset == 'coco': 149 | conf_filename = 'third_party/pytorch-superpoint/logs/superpoint_coco_heat2_0/config.yml' 150 | weights_fname = 'third_party/pytorch-superpoint/logs/superpoint_coco_heat2_0/checkpoints/superPointNet_170000_checkpoint.pth.tar' 151 | elif opt.trainset == 'kitty': 152 | 153 | weights_fname = 'third_party/pytorch-superpoint/logs/superpoint_kitti_heat2_0/checkpoints/superPointNet_50000_checkpoint.pth.tar' 154 | conf_filename = 'third_party/pytorch-superpoint/logs/superpoint_kitti_heat2_0/config.yml' 155 | else: 156 | pass 157 | with open(conf_filename, 'r') as f: 158 | config = yaml.load(f) 159 | config['model']['pretrained']=weights_fname 160 | config['model']['nn_thresh'] = 1.0 161 | config['model']['detection_threshold'] = 0.001 162 | # load frontend 163 | val_agent = model_wrapper(config['model'], device=device) 164 | val_agent.loadModel() 165 | val_agent.net.eval() 166 | val_agent.net=val_agent.net.to(device) 167 | 168 | INPUT_DIR = opt.datasets_folder 169 | modelname = f'{opt.method_name}_{opt.trainset}' 170 | if opt.norm_desc: 171 | modelname+='_norm' 172 | if opt.resize_image_to > 0: 173 | modelname+= f'_{opt.resize_image_to}' 174 | else: 175 | modelname+= f'_fullres' 176 | if opt.subpix: 177 | modelname+='_subpix' 178 | OUT_DIR = os.path.join(opt.save_path, modelname) 179 | os.makedirs(OUT_DIR, exist_ok=True) 180 | print (f"Will save to {OUT_DIR}") 181 | if opt.dataset == 'all': 182 | datasets = ['phototourism', 'pragueparks']#[x for x in os.listdir(INPUT_DIR) if (os.path.isdir(os.path.join(INPUT_DIR, x)))] 183 | else: 184 | datasets = [opt.dataset] 185 | for ds in datasets: 186 | ds_in_path = os.path.join(INPUT_DIR, ds) 187 | ds_out_path = os.path.join(OUT_DIR, ds) 188 | os.makedirs(ds_out_path, exist_ok=True) 189 | seqs = [x for x in os.listdir(ds_in_path) if os.path.isdir(os.path.join(ds_in_path, x))] 190 | for seq in seqs: 191 | print (seq) 192 | if os.path.isdir(os.path.join(ds_in_path, seq, 'set_100')): 193 | seq_in_path = os.path.join(ds_in_path, seq, 'set_100', 'images') 194 | else: 195 | seq_in_path = os.path.join(ds_in_path, seq) 196 | seq_out_path = os.path.join(ds_out_path, seq) 197 | os.makedirs(seq_out_path, exist_ok=True) 198 | img_fnames = os.listdir(seq_in_path) 199 | num_kp = [] 200 | with h5py.File(f'{seq_out_path}/keypoints.h5', mode='w') as f_kp, \ 201 | h5py.File(f'{seq_out_path}/descriptors.h5', mode='w') as f_desc, \ 202 | h5py.File(f'{seq_out_path}/scores.h5', mode='w') as f_score, \ 203 | h5py.File(f'{seq_out_path}/angles.h5', mode='w') as f_ang, \ 204 | h5py.File(f'{seq_out_path}/scales.h5', mode='w') as f_scale: 205 | for img_fname in tqdm(img_fnames): 206 | img_fname_full = os.path.join(seq_in_path, img_fname) 207 | key = os.path.splitext(os.path.basename(img_fname))[0] 208 | kps, resps, descs = extract_features(img_fname_full, val_agent, device, 209 | opt.num_kp, 210 | opt.resize_image_to, 211 | opt.norm_desc, 212 | opt.subpix) 213 | keypoints, scales, angles, responses = convert_imc(kps, resps) 214 | f_desc[key] = descs.reshape(-1, 256) 215 | f_score[key] = responses 216 | f_ang[key] = angles 217 | f_scale[key] = scales 218 | f_kp[key] = keypoints 219 | num_kp.append(len(keypoints)) 220 | print(f'Finished processing "{ds}/{seq}" -> {np.array(num_kp).mean()} features/image') 221 | print (f"Result is saved to {OUT_DIR}") 222 | -------------------------------------------------------------------------------- /generate_image_lists.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | 4 | src = os.path.join('..', 'imw-2020') 5 | 6 | seqs = [os.path.basename(p) for p in glob(os.path.join(src, '*'))] 7 | print(seqs) 8 | 9 | if not os.path.isdir('txt'): 10 | os.makedirs('txt') 11 | 12 | for seq in seqs: 13 | ims = glob(os.path.join(src, seq, '*.jpg')) 14 | with open(os.path.join('txt', 'list-{}.txt'.format(seq)), 'w') as f: 15 | for im in ims: 16 | f.write('{}\n'.format(im)) 17 | -------------------------------------------------------------------------------- /generate_yaml.py: -------------------------------------------------------------------------------- 1 | 2 | import argparse 3 | import yaml 4 | import os 5 | if not os.path.isdir('yaml'): 6 | os.makedirs('yaml') 7 | 8 | model_dict = {} 9 | model_dict['contextdesc++'] = 'contextdesc++/model.ckpt-400000' 10 | model_dict['reg_model'] = 'retrieval_model/model.ckpt-550000' 11 | model_dict['contextdesc++_upright'] ='contextdesc++_upright/model.ckpt-390000' 12 | 13 | parser = argparse.ArgumentParser(description='Geenerate yaml for contextdesc script') 14 | 15 | parser.add_argument( 16 | '--data_root', 17 | default = '../imw-2020', 18 | type = str, 19 | help = 'path to dataset folder') 20 | 21 | parser.add_argument( 22 | '--dump_root', 23 | default = '../benchmark-features', 24 | type = str, 25 | help = 'path to dump folder') 26 | 27 | parser.add_argument( 28 | '--num_keypoints', 29 | default = 8000, 30 | type = int, 31 | help = 'number of keypoints to extract' 32 | ) 33 | 34 | parser.add_argument( 35 | '--upright', 36 | action='store_true', 37 | default=False, 38 | help = 'number of keypoints to extract' 39 | ) 40 | 41 | args, unparsed = parser.parse_known_args() 42 | 43 | dict_file = {} 44 | dict_file['data_name']='imw2019' 45 | dict_file['data_split'] ='' 46 | dict_file['data_root'] = args.data_root 47 | dict_file['all_jpeg'] = True 48 | dict_file['truncate'] = [0, None] 49 | 50 | dict_file['pretrained'] = {} 51 | dict_file['pretrained']['reg_model'] = 'third_party/contextdesc/pretrained/' + model_dict['reg_model'] 52 | 53 | if args.upright: 54 | dict_file['pretrained']['loc_model'] = 'third_party/contextdesc/pretrained/' + model_dict['contextdesc++_upright'] 55 | else: 56 | dict_file['pretrained']['loc_model'] = 'third_party/contextdesc/pretrained/' + model_dict['contextdesc++'] 57 | 58 | dict_file['reg_feat'] ={} 59 | dict_file['reg_feat']['infer'] = True 60 | dict_file['reg_feat']['overwrite']= False 61 | dict_file['reg_feat']['max_dim']= 1024 62 | 63 | dict_file['loc_feat'] = {} 64 | dict_file['loc_feat']['infer']= True 65 | dict_file['loc_feat']['overwrite']= False 66 | dict_file['loc_feat']['n_feature']= args.num_keypoints 67 | dict_file['loc_feat']['batch_size']= 512 68 | dict_file['loc_feat']['dense_desc']= False 69 | dict_file['loc_feat']['peak_thld']= -10000 70 | dict_file['loc_feat']['edge_thld']= -10000 71 | dict_file['loc_feat']['max_dim']= 1280 72 | dict_file['loc_feat']['upright']= args.upright 73 | dict_file['loc_feat']['scale_diff']= True 74 | 75 | dict_file['aug_feat'] = {} 76 | dict_file['aug_feat']['infer']= True 77 | dict_file['aug_feat']['overwrite']= False 78 | dict_file['aug_feat']['reg_feat_dim']= 2048 79 | dict_file['aug_feat']['quantz']= False 80 | 81 | dict_file['post_format'] = {} 82 | dict_file['post_format']['enable'] = True 83 | dict_file['post_format']['suffix'] = '' 84 | 85 | dict_file['dump_root'] = os.path.join(args.dump_root,'tmp_contextdesc') 86 | dict_file['submission_root'] = os.path.join(args.dump_root,'contextdesc_{}'.format(dict_file['loc_feat']['n_feature'])) 87 | 88 | with open(r'yaml/imw-2020.yaml', 'w') as file: 89 | documents = yaml.dump(dict_file, file) -------------------------------------------------------------------------------- /misc/colmap/extract_colmap_feats_to_db.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | 4 | def extract_features(dsp, upright_data_path, save_path): 5 | 6 | if not os.path.exists(save_path): 7 | os.makedirs(save_path) 8 | 9 | for folder in os.listdir(upright_data_path): 10 | 11 | images_path = os.path.join(upright_data_path, folder, 'set_100', 'images') 12 | database_path = os.path.join(save_path, folder+'.db') 13 | print('colmap database_creator --database_path {}'.format(database_path)) 14 | 15 | os.system('colmap database_creator --database_path {}'.format(database_path)) 16 | 17 | if dsp: 18 | os.system( 19 | 'CUDA_VISIBLE_DEVICES="0" colmap feature_extractor --database_path {} --image_path {} --SiftExtraction.gpu_index 0' 20 | ' --SiftExtraction.estimate_affine_shape 1 --SiftExtraction.edge_threshold 10000 --SiftExtraction.peak_threshold 0.00000001 --SiftExtraction.domain_size_pooling 1'.format(database_path, 21 | images_path)) 22 | else: 23 | os.system('CUDA_VISIBLE_DEVICES="0" colmap feature_extractor --database_path {} --image_path {}' 24 | ' --SiftExtraction.estimate_affine_shape 1 --SiftExtraction.gpu_index 0 --SiftExtraction.edge_threshold 10000 --SiftExtraction.peak_threshold 0.00000001'.format(database_path, images_path)) 25 | 26 | if __name__ == '__main__': 27 | 28 | parser = argparse.ArgumentParser() 29 | 30 | parser.add_argument( 31 | "--upright_data_path", 32 | default=os.path.join('../../..', 'data_upright'), 33 | type=str, 34 | help='Path to the dataset') 35 | 36 | parser.add_argument( 37 | "--save_path", 38 | default=os.path.join('../../..', 'colmap_descriptors'), 39 | type=str, 40 | help='Path to store the features') 41 | 42 | args = parser.parse_args() 43 | 44 | if not os.path.exists(args.save_path): 45 | os.makedirs(args.save_path) 46 | 47 | extract_features(False, args.upright_data_path, args.save_path) 48 | extract_features(True, args.upright_data_path, args.save_path.replace('colmap_descriptors','colmap_descriptors_dsp')) -------------------------------------------------------------------------------- /misc/l2net/convert_l2net_weights_matconv_pytorch.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy.io as sio 3 | import torch 4 | import torch.nn.init 5 | from misc.l2net.l2net_model import L2Net 6 | 7 | eps = 1e-10 8 | 9 | def check_ported(l2net_model, test_patch, img_mean): 10 | 11 | test_patch = test_patch.transpose(3, 2, 0, 1)-img_mean 12 | desc = l2net_model(torch.from_numpy(test_patch)) 13 | print(desc) 14 | return desc 15 | 16 | if __name__ == '__main__': 17 | 18 | path_to_l2net_weights = 'descriptors/sfm-evaluation-benchmarking/third_party/l2net/matlab/L2Net-LIB+.mat' 19 | 20 | l2net_weights = sio.loadmat(path_to_l2net_weights) 21 | 22 | l2net_model = L2Net() 23 | l2net_model.eval() 24 | 25 | new_state_dict = l2net_model.state_dict().copy() 26 | 27 | conv_layers, bn_layers = {}, {} 28 | all_layer_weights = l2net_weights['net']['layers'][0][0][0] 29 | img_mean = l2net_weights['pixMean'] 30 | conv_layers_to_track, bn_layers_to_track = [0,3,6,9,12,15,18], \ 31 | [1,4,7,10,13,16,19] 32 | conv_i, bn_i = 0,0 33 | 34 | for layer in all_layer_weights: 35 | 36 | if 'weights' not in layer.dtype.names: 37 | continue 38 | layer_name = layer[0][0][0][0] 39 | layer_value = layer['weights'][0][0][0] 40 | if layer_name == 'conv': 41 | conv_layers[conv_layers_to_track[conv_i]] = layer_value 42 | conv_i+=1 43 | elif layer_name == 'bnormPair': 44 | bn_layers[bn_layers_to_track[bn_i]] = layer_value 45 | bn_i+=1 46 | 47 | for key, value in new_state_dict.items(): 48 | layer_number = int(key.split('.')[1]) 49 | if layer_number in conv_layers.keys(): 50 | if 'weight' in key: 51 | new_state_dict[key] = torch.from_numpy(conv_layers[layer_number][0].transpose((3,2,0,1))) 52 | elif 'bias' in key: 53 | new_state_dict[key] = torch.from_numpy(conv_layers[layer_number][1]).squeeze() 54 | elif layer_number in bn_layers.keys(): 55 | if 'running_mean' in key: 56 | new_state_dict[key] = torch.from_numpy(np.array([x[0] for x in bn_layers[layer_number][2]])).squeeze() 57 | elif 'running_var' in key: 58 | new_state_dict[key] = torch.from_numpy(np.array([x[1] for x in bn_layers[layer_number][2]] )** 2 -eps).squeeze() 59 | elif 'weight' in key: 60 | new_state_dict[key] = torch.from_numpy(np.ones(value.size()[0])).squeeze() 61 | 62 | else: 63 | continue 64 | 65 | l2net_model.load_state_dict(new_state_dict) 66 | l2net_model.eval() 67 | 68 | torch.save(l2net_model.state_dict(),'l2net_ported_weights_lib+.pth') 69 | 70 | # compare desc on test patch with matlab implementation 71 | # test_patch_batch = sio.loadmat('test_batch_img.mat')['testPatch'] 72 | # check_ported(l2net_model, test_patch_batch, img_mean) 73 | # 74 | # test_patch_one = sio.loadmat('test_one.mat')['testPatch'] 75 | # check_ported(l2net_model, np.expand_dims(np.expand_dims(test_patch_one, axis=2),axis=2), img_mean) 76 | -------------------------------------------------------------------------------- /misc/l2net/l2net_model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn.init 3 | import torch.nn as nn 4 | 5 | eps = 1e-10 6 | 7 | class L2Norm(nn.Module): 8 | 9 | def __init__(self): 10 | super(L2Norm,self).__init__() 11 | self.eps = 1e-10 12 | 13 | def forward(self, x): 14 | norm = torch.sqrt(torch.sum(x * x, dim = 1) + self.eps) 15 | x= x / norm.unsqueeze(-1).expand_as(x) 16 | return x 17 | 18 | class L2Net(nn.Module): 19 | 20 | def __init__(self): 21 | super(L2Net, self).__init__() 22 | self.features = nn.Sequential( 23 | nn.Conv2d(1, 32, kernel_size=3, padding=1, bias=True), 24 | nn.BatchNorm2d(32, affine=True, eps=eps), 25 | nn.ReLU(), 26 | nn.Conv2d(32, 32, kernel_size=3, padding=1, bias=True), 27 | nn.BatchNorm2d(32, affine=True, eps=eps), 28 | nn.ReLU(), 29 | nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1, bias=True), 30 | nn.BatchNorm2d(64, affine=True, eps=eps), 31 | nn.ReLU(), 32 | nn.Conv2d(64, 64, kernel_size=3, padding=1, bias=True), 33 | nn.BatchNorm2d(64, affine=True, eps=eps), 34 | nn.ReLU(), 35 | nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1, bias=True), 36 | nn.BatchNorm2d(128, affine=True, eps=eps), 37 | nn.ReLU(), 38 | nn.Conv2d(128, 128, kernel_size=3, padding=1, bias=True), 39 | nn.BatchNorm2d(128, affine=True, eps=eps), 40 | nn.ReLU(), 41 | nn.Conv2d(128, 128, kernel_size=8, bias=True), 42 | nn.BatchNorm2d(128, affine=True, eps=eps), 43 | ) 44 | return 45 | 46 | def input_norm(self, x): 47 | # matlab norm 48 | z = x.contiguous().transpose(2, 3).contiguous().view(x.size(0),-1) 49 | x_minus_mean = z.transpose(0,1)-z.mean(1) 50 | sp = torch.std(z,1).detach() 51 | norm_inp = x_minus_mean/(sp+1e-12) 52 | norm_inp = norm_inp.transpose(0, 1).view(-1, 1, x.size(2), x.size(3)).transpose(2,3) 53 | return norm_inp 54 | 55 | def forward(self, input): 56 | norm_img = self.input_norm(input) 57 | x_features = self.features(norm_img) 58 | return nn.LocalResponseNorm(256,1*256,0.5,0.5)(x_features).view(input.size(0),-1) 59 | -------------------------------------------------------------------------------- /misc/l2net/l2net_ported_weights_lib+.pth: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubc-vision/image-matching-benchmark-baselines/35dcd21c889583a22a4f612389a6b0d7c5af506b/misc/l2net/l2net_ported_weights_lib+.pth -------------------------------------------------------------------------------- /run_d2net.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # D2-net 4 | echo "Extracting D2-net" 5 | python extract_d2net.py --method_name=d2net-singlescale_8000 --num_kp=8000 6 | python extract_d2net.py --multiscale --method_name=d2net-multiscale_8000 --num_kp=8000 7 | -------------------------------------------------------------------------------- /run_delf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | seqs = [] 5 | with open(os.path.join('data', 'val.json')) as f: 6 | seqs += json.load(f) 7 | with open(os.path.join('data', 'test.json')) as f: 8 | seqs += json.load(f) 9 | 10 | for seq in seqs: 11 | # cvpr'19 model 12 | os.system('python extract_delf.py --config_path=third_party/delf-config/delf_config_example.pbtxt --list_images_path=txt/list-{}.txt --output_dir=../benchmark-features/delf-gld-default/{}'.format(seq, seq)) 13 | -------------------------------------------------------------------------------- /run_geodesc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # geodesc 4 | echo "Extracting Geodesc" 5 | 6 | python extract_descriptors_geodesc.py --dataset_path=../benchmark-patches-8k --method_name=sift8k_8000_geodesc 7 | python extract_descriptors_geodesc.py --dataset_path=../benchmark-patches-8k-upright-no-dups --method_name=sift8k_8000_geodesc-upright-no-dups 8 | 9 | #python extract_descriptors_geodesc.py --dataset_path=../benchmark-patches-default --method_name=siftdef_2048_geodesc 10 | #python extract_descriptors_geodesc.py --dataset_path=../benchmark-patches-default-upright-no-dups --method_name=siftdef_2048_geodesc-upright-no-dups 11 | -------------------------------------------------------------------------------- /run_hardnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # hardnet 4 | echo "Extracting Hardnet" 5 | 6 | python extract_descriptors_hardnet.py --dataset_path=../benchmark-patches-8k --method_name=sift8k_8000_hardnet 7 | python extract_descriptors_hardnet.py --dataset_path=../benchmark-patches-8k-upright-no-dups --method_name=sift8k_8000_hardnet-upright-no-dups 8 | 9 | #python extract_descriptors_hardnet.py --dataset_path=../benchmark-patches-default --method_name=siftdef_2048_hardnet 10 | #python extract_descriptors_hardnet.py --dataset_path=../benchmark-patches-default-upright-no-dups --method_name=siftdef_2048_hardnet-upright-no-dups 11 | -------------------------------------------------------------------------------- /run_logpolar.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # logpolar 4 | echo "Extracting Log-polar descriptors" 5 | 6 | python extract_descriptors_logpolar.py --dataset_path=../benchmark-patches-8k --config_file=third_party/log_polar_descriptors/configs/init_one_example_ptn_96.yml --method_name=sift8k_8000_logpolar96 7 | python extract_descriptors_logpolar.py --dataset_path=../benchmark-patches-8k-upright-no-dups --config_file=third_party/log_polar_descriptors/configs/init_one_example_ptn_96.yml --method_name=sift8k_8000_logpolar96-upright-no-dups 8 | 9 | #python extract_descriptors_logpolar.py --dataset_path=../benchmark-patches-default --config_file=third_party/log_polar_descriptors/configs/init_one_example_ptn_96.yml --method_name=siftdef_2048_logpolar96 10 | #python extract_descriptors_logpolar.py --dataset_path=../benchmark-patches-default-upright-no-dups --config_file=third_party/log_polar_descriptors/configs/init_one_example_ptn_96.yml --method_name=siftdef_2048_logpolar96-upright-no-dups 11 | -------------------------------------------------------------------------------- /run_sosnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # sosnet 4 | echo "Extracting SOSnet" 5 | 6 | python extract_descriptors_sosnet.py --dataset_path=../benchmark-patches-8k --method_name=sift8k_8000_sosnet 7 | python extract_descriptors_sosnet.py --dataset_path=../benchmark-patches-8k-upright-no-dups --method_name=sift8k_8000_sosnet-upright-no-dups 8 | 9 | python extract_descriptors_sosnet.py --dataset_path=../benchmark-patches-default --method_name=siftdef_2048_sosnet 10 | python extract_descriptors_sosnet.py --dataset_path=../benchmark-patches-default-upright-no-dups --method_name=siftdef_2048_sosnet-upright-no-dups 11 | -------------------------------------------------------------------------------- /run_superpoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # superpoint 4 | echo "Extracting Superpoint" 5 | python third_party/superpoint_forked/superpoint.py --cuda --num_kp=2048 --nms_dist=4 --resize_image_to=1200 --method_name=superpoint-nms4-r1200-lowerdet --conf_thresh=0.0001 6 | python third_party/superpoint_forked/superpoint.py --cuda --num_kp=2048 --nms_dist=3 --resize_image_to=1200 --method_name=superpoint-nms3-r1200-lowerdet --conf_thresh=0.0001 7 | python third_party/superpoint_forked/superpoint.py --cuda --num_kp=2048 --nms_dist=2 --resize_image_to=1200 --method_name=superpoint-nms2-r1200-lowerdet --conf_thresh=0.0001 8 | python third_party/superpoint_forked/superpoint.py --cuda --num_kp=2048 --nms_dist=1 --resize_image_to=1200 --method_name=superpoint-nms1-r1200-lowerdet --conf_thresh=0.0001 9 | -------------------------------------------------------------------------------- /sp_configs.py: -------------------------------------------------------------------------------- 1 | import json 2 | from copy import deepcopy 3 | 4 | 5 | pdfdict = {"lanet": "https://openaccess.thecvf.com/content/ACCV2022/papers/Wang_Rethinking_Low-level_Features_for_Interest_Point_Detection_and_Description_ACCV_2022_paper.pdf", 6 | "kp2d": "https://openreview.net/pdf?id=Skx82ySYPH", 7 | "superpoint_indep": "https://arxiv.org/abs/2007.15122", 8 | "superpoint": "https://arxiv.org/abs/1712.07629", 9 | } 10 | repo_dict = {"lanet": "https://github.com/wangch-g/lanet", 11 | "kp2d":"https://github.com/TRI-ML/KP2D", 12 | "superpoint": "https://github.com/magicleap/SuperPointPretrainedNetwork", 13 | "superpoint_indep": "https://github.com/eric-yyjau/pytorch-superpoint", 14 | } 15 | method_name_dict = {"lanet": "LANet", 16 | "kp2d": "KeypointNet", 17 | "superpoint_indep": "DeepFEPE SuperPoint", 18 | "superpoint": "MagicLeap SuperPoint"} 19 | 20 | metadata_dict = { 21 | "publish_anonymously": False, 22 | "authors": "Submitted by benchmark orgs, implementation by paper authors", 23 | "contact_email": "ducha.aiki@gmail.com", 24 | "method_name": "", 25 | "method_description": 26 | r"""Baseline, PUT 2048 features 27 | Matched using the built-in matcher (bidirectional filter with the 'both' strategy, 28 | hopefully optimal inlier and ratio test thresholds) with DEGENSAC""", 29 | "link_to_website": "", 30 | "link_to_pdf": "" 31 | } 32 | 33 | config_common_dict = {"json_label": "rootsiftfix", 34 | "keypoint": "korniadogfix", 35 | "descriptor": "rootsift", 36 | "num_keypoints": 2048} 37 | 38 | 39 | matcher_template_dict = { 40 | "method": "nn", 41 | "distance": "L2", 42 | "flann": False, 43 | "num_nn": 1, 44 | "filtering": { 45 | "type": "snn_ratio_pairwise", 46 | "threshold": 0.99, 47 | }, 48 | "symmetric": { 49 | "enabled": True, 50 | "reduce": "both", 51 | } 52 | } 53 | 54 | geom_template_dict = {"method": "cmp-degensac-f", 55 | "threshold": 0.75, 56 | "confidence": 0.999999, 57 | "max_iter": 100000, 58 | "error_type": "sampson", 59 | "degeneracy_check": True, 60 | } 61 | 62 | base_config = { 63 | "metadata": metadata_dict, 64 | "config_common": config_common_dict, 65 | "config_phototourism_stereo": { 66 | "use_custom_matches": False, 67 | "matcher": deepcopy(matcher_template_dict), 68 | "outlier_filter": { "method": "none" }, 69 | "geom": deepcopy(geom_template_dict) 70 | }, 71 | "config_pragueparks_stereo": { 72 | "use_custom_matches": False, 73 | "matcher": deepcopy(matcher_template_dict), 74 | "outlier_filter": { "method": "none" }, 75 | "geom": deepcopy(geom_template_dict) 76 | }, 77 | 78 | } 79 | 80 | if __name__ == '__main__': 81 | kp = 'kp2d' 82 | for v in ['v0','v1', 'v2', 'v3', 'v4']: 83 | for norm in ['', '_norm']: 84 | json_fn = f'{kp}_{v}{norm}_1024' 85 | json_fn2 = f'{kp}_{v}{norm}_1024'.replace('_','-') 86 | md = deepcopy(metadata_dict) 87 | md['link_to_website'] = repo_dict[kp] 88 | md['link_to_pdf'] = pdfdict[kp] 89 | md['method_name'] = method_name_dict[kp] 90 | md['method_description']=md['method_description'].replace('PUT', method_name_dict[kp]) 91 | common = {"json_label": json_fn2, 92 | "keypoint": json_fn2, 93 | "descriptor": json_fn2, 94 | "num_keypoints": 2048} 95 | base_config = { 96 | "metadata": md, 97 | "config_common": common, 98 | "config_phototourism_stereo": { 99 | "use_custom_matches": False, 100 | "matcher": deepcopy(matcher_template_dict), 101 | "outlier_filter": { "method": "none" }, 102 | "geom": deepcopy(geom_template_dict) 103 | }, 104 | "config_pragueparks_stereo": { 105 | "use_custom_matches": False, 106 | "matcher": deepcopy(matcher_template_dict), 107 | "outlier_filter": { "method": "none" }, 108 | "geom": deepcopy(geom_template_dict) 109 | } 110 | } 111 | with open(f'OUT_JSON/{json_fn}.json', 'w') as f: 112 | json.dump([base_config], f, indent=2) 113 | match_ths = [0.85, 0.9, 0.95, 0.99] 114 | inl_ths = [0.5, 0.75, 1.0, 1.25] 115 | configs = [] 116 | for match_th in match_ths: 117 | for inl_th in inl_ths: 118 | current_config = deepcopy(base_config) 119 | for dset in ['phototourism', 'pragueparks']: 120 | current_config[f'config_{dset}_stereo']['geom']['threshold'] = inl_th 121 | current_config[f'config_{dset}_stereo']['matcher']['filtering']['threshold'] = match_th 122 | label = current_config['config_common']['json_label'] 123 | current_config['config_common']['json_label'] = f'{label}-snnth-{match_th}-inlth-{inl_th}' 124 | configs.append(current_config) 125 | with open(f'OUT_JSON_RANSAC/{json_fn}_tuning.json', 'w') as f: 126 | json.dump(configs, f, indent=2) 127 | kp = 'superpoint' 128 | for v in ['_magicleap']: 129 | for norm in ['', '_norm']: 130 | json_fn = f'{kp}{v}{norm}_1024' 131 | json_fn2 = f'{kp}{v}{norm}_1024'.replace('_','-') 132 | md = deepcopy(metadata_dict) 133 | md['link_to_website'] = repo_dict[kp] 134 | md['link_to_pdf'] = pdfdict[kp] 135 | md['method_name'] = method_name_dict[kp] 136 | md['method_description']=md['method_description'].replace('PUT', method_name_dict[kp]) 137 | common = {"json_label": json_fn2, 138 | "keypoint": json_fn2, 139 | "descriptor": json_fn2, 140 | "num_keypoints": 2048} 141 | base_config = { 142 | "metadata": md, 143 | "config_common": common, 144 | "config_phototourism_stereo": { 145 | "use_custom_matches": False, 146 | "matcher": deepcopy(matcher_template_dict), 147 | "outlier_filter": { "method": "none" }, 148 | "geom": deepcopy(geom_template_dict) 149 | }, 150 | "config_pragueparks_stereo": { 151 | "use_custom_matches": False, 152 | "matcher": deepcopy(matcher_template_dict), 153 | "outlier_filter": { "method": "none" }, 154 | "geom": deepcopy(geom_template_dict) 155 | } 156 | } 157 | with open(f'OUT_JSON/{json_fn}.json', 'w') as f: 158 | json.dump([base_config], f, indent=2) 159 | match_ths = [0.85, 0.9, 0.95, 0.99] 160 | inl_ths = [0.5, 0.75, 1.0, 1.25, 1.5] 161 | configs = [] 162 | for match_th in match_ths: 163 | for inl_th in inl_ths: 164 | current_config = deepcopy(base_config) 165 | for dset in ['phototourism', 'pragueparks']: 166 | current_config[f'config_{dset}_stereo']['geom']['threshold'] = inl_th 167 | current_config[f'config_{dset}_stereo']['matcher']['filtering']['threshold'] = match_th 168 | label = current_config['config_common']['json_label'] 169 | current_config['config_common']['json_label'] = f'{label}-snnth-{match_th}-inlth-{inl_th}' 170 | configs.append(current_config) 171 | with open(f'OUT_JSON_RANSAC/{json_fn}_tuning.json', 'w') as f: 172 | json.dump(configs, f, indent=2) 173 | kp = 'lanet' 174 | for v in ['v0','v1']: 175 | for norm in ['', '_norm']: 176 | json_fn = f'{kp}_{v}{norm}_1024' 177 | json_fn2 = f'{kp}_{v}{norm}_1024'.replace('_','-') 178 | md = deepcopy(metadata_dict) 179 | md['link_to_website'] = repo_dict[kp] 180 | md['link_to_pdf'] = pdfdict[kp] 181 | md['method_name'] = method_name_dict[kp] 182 | md['method_description']=md['method_description'].replace('PUT', method_name_dict[kp]) 183 | common = {"json_label": json_fn2, 184 | "keypoint": json_fn2, 185 | "descriptor": json_fn2, 186 | "num_keypoints": 2048} 187 | base_config = { 188 | "metadata": md, 189 | "config_common": common, 190 | "config_phototourism_stereo": { 191 | "use_custom_matches": False, 192 | "matcher": deepcopy(matcher_template_dict), 193 | "outlier_filter": { "method": "none" }, 194 | "geom": deepcopy(geom_template_dict) 195 | }, 196 | "config_pragueparks_stereo": { 197 | "use_custom_matches": False, 198 | "matcher": deepcopy(matcher_template_dict), 199 | "outlier_filter": { "method": "none" }, 200 | "geom": deepcopy(geom_template_dict) 201 | } 202 | } 203 | with open(f'OUT_JSON/{json_fn}.json', 'w') as f: 204 | json.dump([base_config], f, indent=2) 205 | match_ths = [0.85, 0.9, 0.95, 0.99] 206 | inl_ths = [0.5, 0.75, 1.0, 1.25] 207 | configs = [] 208 | for match_th in match_ths: 209 | for inl_th in inl_ths: 210 | current_config = deepcopy(base_config) 211 | for dset in ['phototourism', 'pragueparks']: 212 | current_config[f'config_{dset}_stereo']['geom']['threshold'] = inl_th 213 | current_config[f'config_{dset}_stereo']['matcher']['filtering']['threshold'] = match_th 214 | label = current_config['config_common']['json_label'] 215 | current_config['config_common']['json_label'] = f'{label}-snnth-{match_th}-inlth-{inl_th}' 216 | configs.append(current_config) 217 | with open(f'OUT_JSON_RANSAC/{json_fn}_tuning.json', 'w') as f: 218 | json.dump(configs, f, indent=2) 219 | kp = 'superpoint_indep' 220 | for v in ['coco', 'kitty']: 221 | for norm in ['', '_norm']: 222 | for sp in ['', '_subpix']: 223 | json_fn = f'{kp}_{v}{norm}_1024{sp}' 224 | json_fn2 = f'{kp}_{v}{norm}_1024{sp}'.replace('_','-') 225 | md = deepcopy(metadata_dict) 226 | md['link_to_website'] = repo_dict[kp] 227 | md['link_to_pdf'] = pdfdict[kp] 228 | md['method_name'] = method_name_dict[kp] 229 | md['method_description']=md['method_description'].replace('PUT', method_name_dict[kp]) 230 | common = {"json_label": json_fn2, 231 | "keypoint": json_fn2, 232 | "descriptor": json_fn2, 233 | "num_keypoints": 2048} 234 | base_config = { 235 | "metadata": md, 236 | "config_common": common, 237 | "config_phototourism_stereo": { 238 | "use_custom_matches": False, 239 | "matcher": deepcopy(matcher_template_dict), 240 | "outlier_filter": { "method": "none" }, 241 | "geom": deepcopy(geom_template_dict) 242 | }, 243 | "config_pragueparks_stereo": { 244 | "use_custom_matches": False, 245 | "matcher": deepcopy(matcher_template_dict), 246 | "outlier_filter": { "method": "none" }, 247 | "geom": deepcopy(geom_template_dict) 248 | } 249 | } 250 | with open(f'OUT_JSON/{json_fn}.json', 'w') as f: 251 | json.dump([base_config], f, indent=2) 252 | match_ths = [0.85, 0.9, 0.95, 0.99] 253 | inl_ths = [0.5, 0.75, 1.0, 1.25] 254 | configs = [] 255 | for match_th in match_ths: 256 | for inl_th in inl_ths: 257 | current_config = deepcopy(base_config) 258 | for dset in ['phototourism', 'pragueparks']: 259 | current_config[f'config_{dset}_stereo']['geom']['threshold'] = inl_th 260 | current_config[f'config_{dset}_stereo']['matcher']['filtering']['threshold'] = match_th 261 | label = current_config['config_common']['json_label'] 262 | current_config['config_common']['json_label'] = f'{label}-snnth-{match_th}-inlth-{inl_th}' 263 | configs.append(current_config) 264 | with open(f'OUT_JSON_RANSAC/{json_fn}_tuning.json', 'w') as f: 265 | json.dump(configs, f, indent=2) 266 | 267 | -------------------------------------------------------------------------------- /system/geodesc.yml: -------------------------------------------------------------------------------- 1 | name: geodesc 2 | channels: 3 | - defaults 4 | dependencies: 5 | - _libgcc_mutex=0.1=main 6 | - _tflow_select=2.1.0=gpu 7 | - absl-py=0.4.1=py35_0 8 | - astor=0.7.1=py35_0 9 | - blas=1.0=mkl 10 | - bzip2=1.0.8=h7b6447c_0 11 | - ca-certificates=2019.8.28=0 12 | - cairo=1.14.12=h8948797_3 13 | - certifi=2018.8.24=py35_1 14 | - cudatoolkit=9.2=0 15 | - cudnn=7.6.0=cuda9.2_0 16 | - cupti=9.2.148=0 17 | - ffmpeg=4.0=hcdf2ecd_0 18 | - fontconfig=2.13.0=h9420a91_0 19 | - freeglut=3.0.0=hf484d3e_5 20 | - freetype=2.9.1=h8a8886c_1 21 | - gast=0.3.2=py_0 22 | - glib=2.56.2=hd408876_0 23 | - graphite2=1.3.13=h23475e2_0 24 | - grpcio=1.12.1=py35hdbcaa40_0 25 | - h5py=2.8.0=py35h989c5e5_3 26 | - harfbuzz=1.8.8=hffaf4a1_0 27 | - hdf5=1.10.2=hba1933b_1 28 | - icu=58.2=h9c2bf20_1 29 | - intel-openmp=2019.4=243 30 | - jasper=2.0.14=h07fcdf6_1 31 | - jpeg=9b=h024ee3a_2 32 | - libedit=3.1.20181209=hc058e9b_0 33 | - libffi=3.2.1=hd88cf55_4 34 | - libgcc-ng=9.1.0=hdf63c60_0 35 | - libgfortran-ng=7.3.0=hdf63c60_0 36 | - libglu=9.0.0=hf484d3e_1 37 | - libopencv=3.4.2=hb342d67_1 38 | - libopus=1.3=h7b6447c_0 39 | - libpng=1.6.37=hbc83047_0 40 | - libprotobuf=3.6.0=hdbcaa40_0 41 | - libstdcxx-ng=9.1.0=hdf63c60_0 42 | - libtiff=4.0.10=h2733197_2 43 | - libuuid=1.0.3=h1bed415_2 44 | - libvpx=1.7.0=h439df22_0 45 | - libxcb=1.13=h1bed415_1 46 | - libxml2=2.9.9=hea5a465_1 47 | - markdown=2.6.11=py35_0 48 | - mkl=2019.4=243 49 | - ncurses=6.1=he6710b0_1 50 | - numpy=1.14.2=py35hdbf6ddf_0 51 | - opencv=3.4.2=py35h6fd60c2_1 52 | - openssl=1.0.2t=h7b6447c_1 53 | - pcre=8.43=he6710b0_0 54 | - pip=10.0.1=py35_0 55 | - pixman=0.38.0=h7b6447c_0 56 | - protobuf=3.6.0=py35hf484d3e_0 57 | - py-opencv=3.4.2=py35hb342d67_1 58 | - python=3.5.6=hc3d631a_0 59 | - readline=7.0=h7b6447c_5 60 | - setuptools=40.2.0=py35_0 61 | - six=1.11.0=py35_1 62 | - sqlite=3.29.0=h7b6447c_0 63 | - tensorboard=1.10.0=py35hf484d3e_0 64 | - tensorflow=1.10.0=gpu_py35hd9c640d_0 65 | - tensorflow-base=1.10.0=gpu_py35had579c0_0 66 | - tensorflow-gpu=1.10.0=hf154084_0 67 | - termcolor=1.1.0=py35_1 68 | - tk=8.6.8=hbc83047_0 69 | - tqdm=4.36.1=py_0 70 | - werkzeug=0.16.0=py_0 71 | - wheel=0.31.1=py35_0 72 | - xz=5.2.4=h14c3975_4 73 | - zlib=1.2.11=h7b6447c_3 74 | - zstd=1.3.7=h0b5b093_0 75 | prefix: /home/trulls/miniconda3/envs/geodesc 76 | 77 | -------------------------------------------------------------------------------- /system/hardnet.yml: -------------------------------------------------------------------------------- 1 | name: hardnet 2 | channels: 3 | - anaconda 4 | - pytorch 5 | - conda-forge 6 | - defaults 7 | dependencies: 8 | - _libgcc_mutex=0.1=main 9 | - backcall=0.1.0=py35_0 10 | - blas=1.0=mkl 11 | - bzip2=1.0.8=h7b6447c_0 12 | - ca-certificates=2019.11.28=hecc5488_0 13 | - cairo=1.14.12=h8948797_3 14 | - certifi=2016.9.26=py35_0 15 | - cffi=1.11.5=py35he75722e_1 16 | - cudatoolkit=10.0.130=0 17 | - cycler=0.10.0=py35hc4d5149_0 18 | - dbus=1.13.6=h746ee38_0 19 | - decorator=4.4.0=py_0 20 | - expat=2.2.6=he6710b0_0 21 | - ffmpeg=4.0=hcdf2ecd_0 22 | - fontconfig=2.13.0=h9420a91_0 23 | - freeglut=3.0.0=hf484d3e_5 24 | - freetype=2.9.1=h8a8886c_1 25 | - glib=2.56.2=hd408876_0 26 | - graphite2=1.3.13=h23475e2_0 27 | - gst-plugins-base=1.14.0=hbbd80ab_1 28 | - gstreamer=1.14.0=hb453b48_1 29 | - h5py=2.8.0=py35h989c5e5_3 30 | - harfbuzz=1.8.8=hffaf4a1_0 31 | - hdf5=1.10.2=hba1933b_1 32 | - icu=58.2=h9c2bf20_1 33 | - intel-openmp=2019.4=243 34 | - ipython=6.5.0=py35_0 35 | - ipython_genutils=0.2.0=py35hc9e07d0_0 36 | - jasper=2.0.14=h07fcdf6_1 37 | - jedi=0.12.1=py35_0 38 | - jpeg=9b=h024ee3a_2 39 | - kiwisolver=1.0.1=py35hf484d3e_0 40 | - libedit=3.1.20181209=hc058e9b_0 41 | - libffi=3.2.1=hd88cf55_4 42 | - libgcc-ng=9.1.0=hdf63c60_0 43 | - libgfortran-ng=7.3.0=hdf63c60_0 44 | - libglu=9.0.0=hf484d3e_1 45 | - libopencv=3.4.2=hb342d67_1 46 | - libopus=1.3=h7b6447c_0 47 | - libpng=1.6.37=hbc83047_0 48 | - libstdcxx-ng=9.1.0=hdf63c60_0 49 | - libtiff=4.0.10=h2733197_2 50 | - libuuid=1.0.3=h1bed415_2 51 | - libvpx=1.7.0=h439df22_0 52 | - libxcb=1.13=h1bed415_1 53 | - libxml2=2.9.9=hea5a465_1 54 | - mkl=2018.0.3=1 55 | - ncurses=6.1=he6710b0_1 56 | - ninja=1.8.2=py35h6bb024c_1 57 | - numpy=1.14.2=py35hdbf6ddf_0 58 | - olefile=0.46=py35_0 59 | - opencv=3.4.2=py35h6fd60c2_1 60 | - openssl=1.0.2u=h516909a_0 61 | - pandas=0.23.4=py35h04863e7_0 62 | - parso=0.5.1=py_0 63 | - patsy=0.5.0=py35_0 64 | - pcre=8.43=he6710b0_0 65 | - pexpect=4.6.0=py35_0 66 | - pickleshare=0.7.4=py35hd57304d_0 67 | - pillow=5.2.0=py35heded4f4_0 68 | - pip=10.0.1=py35_0 69 | - pixman=0.38.0=h7b6447c_0 70 | - progressbar2=3.47.0=py_0 71 | - prompt_toolkit=1.0.15=py35hc09de7a_0 72 | - ptyprocess=0.6.0=py35_0 73 | - py=1.8.1=py_0 74 | - py-opencv=3.4.2=py35hb342d67_1 75 | - pycparser=2.19=py35_0 76 | - pygments=2.4.2=py_0 77 | - pyparsing=2.4.2=py_0 78 | - pyqt=5.9.2=py35h05f1152_2 79 | - pytest=3.2.2=py_0 80 | - pytest-runner=5.2=py_0 81 | - python=3.5.6=hc3d631a_0 82 | - python-dateutil=2.7.3=py35_0 83 | - python-utils=2.3.0=py_1 84 | - pytorch=1.2.0=py3.5_cuda10.0.130_cudnn7.6.2_0 85 | - pytz=2019.2=py_0 86 | - qt=5.9.6=h8703b6f_2 87 | - readline=7.0=h7b6447c_5 88 | - scipy=1.1.0=py35hd20e5f9_0 89 | - seaborn=0.9.0=py35_0 90 | - setuptools=40.2.0=py35_0 91 | - simplegeneric=0.8.1=py35_2 92 | - sip=4.19.8=py35hf484d3e_0 93 | - six=1.11.0=py35_1 94 | - sqlite=3.29.0=h7b6447c_0 95 | - statsmodels=0.9.0=py35h3010b51_0 96 | - tk=8.6.8=hbc83047_0 97 | - torchvision=0.4.0=py35_cu100 98 | - tornado=5.1.1=py35h7b6447c_0 99 | - tqdm=4.36.1=py_0 100 | - traitlets=4.3.2=py35ha522a97_0 101 | - wcwidth=0.1.7=py35hcd08066_0 102 | - wheel=0.31.1=py35_0 103 | - xz=5.2.4=h14c3975_4 104 | - yaml=0.1.7=h96e3832_1 105 | - zlib=1.2.11=h7b6447c_3 106 | - zstd=1.3.7=h0b5b093_0 107 | - pip: 108 | - absl-py==0.8.0 109 | - astor==0.8.0 110 | - autopep8==1.4.4 111 | - gast==0.3.2 112 | - google-pasta==0.1.7 113 | - grpcio==1.24.0 114 | - imageio==2.5.0 115 | - keras-applications==1.0.8 116 | - keras-preprocessing==1.1.0 117 | - markdown==3.1.1 118 | - matplotlib==3.0.3 119 | - networkx==2.3 120 | - protobuf==3.9.2 121 | - pycodestyle==2.5.0 122 | - pywavelets==1.0.3 123 | - pyyaml==5.1.2 124 | - scikit-image==0.15.0 125 | - tensorboard==1.14.0 126 | - tensorflow-estimator==1.14.0 127 | - tensorflow-gpu==1.14.0 128 | - termcolor==1.1.0 129 | - werkzeug==0.16.0 130 | - wrapt==1.11.2 131 | - yacs==0.1.6 132 | - extract_patches 133 | prefix: /home/yuhe/anaconda3/envs/hardnet 134 | 135 | -------------------------------------------------------------------------------- /system/lfnet.yml: -------------------------------------------------------------------------------- 1 | name: lfnet 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - _libgcc_mutex=0.1=conda_forge 7 | - _openmp_mutex=4.5=0_gnu 8 | - _tflow_select=2.1.0=gpu 9 | - absl-py=0.9.0=py36_0 10 | - astor=0.7.1=py_0 11 | - blas=1.1=openblas 12 | - bzip2=1.0.8=h516909a_2 13 | - c-ares=1.15.0=h516909a_1001 14 | - ca-certificates=2019.11.28=hecc5488_0 15 | - cairo=1.16.0=hfb77d84_1002 16 | - certifi=2019.11.28=py36h9f0ad1d_1 17 | - dbus=1.13.6=he372182_0 18 | - expat=2.2.9=he1b5a44_2 19 | - ffmpeg=4.1.3=h167e202_0 20 | - fontconfig=2.13.1=h86ecdb6_1001 21 | - freetype=2.10.0=he983fc9_1 22 | - gast=0.3.3=py_0 23 | - gettext=0.19.8.1=hc5be6a0_1002 24 | - giflib=5.2.1=h516909a_2 25 | - glib=2.58.3=py36h6f030ca_1002 26 | - gmp=6.2.0=he1b5a44_2 27 | - gnutls=3.6.5=hd3a4fd2_1002 28 | - graphite2=1.3.13=hf484d3e_1000 29 | - grpcio=1.23.0=py36he9ae1f9_0 30 | - gst-plugins-base=1.14.5=h0935bb2_2 31 | - gstreamer=1.14.5=h36ae1b5_2 32 | - h5py=2.9.0=nompi_py36hf008753_1102 33 | - harfbuzz=2.4.0=h9f30f68_3 34 | - hdf5=1.10.4=nompi_h3c11f04_1106 35 | - icu=64.2=he1b5a44_1 36 | - imageio=2.8.0=py_0 37 | - jasper=1.900.1=h07fcdf6_1006 38 | - jpeg=9c=h14c3975_1001 39 | - keras-applications=1.0.8=py_1 40 | - keras-preprocessing=1.1.0=py_0 41 | - lame=3.100=h14c3975_1001 42 | - ld_impl_linux-64=2.33.1=h53a641e_8 43 | - libblas=3.8.0=11_openblas 44 | - libcblas=3.8.0=11_openblas 45 | - libffi=3.2.1=he1b5a44_1006 46 | - libgcc-ng=9.2.0=h24d8f2e_2 47 | - libgfortran=3.0.0=1 48 | - libgfortran-ng=7.3.0=hdf63c60_5 49 | - libgomp=9.2.0=h24d8f2e_2 50 | - libiconv=1.15=h516909a_1005 51 | - liblapack=3.8.0=11_openblas 52 | - liblapacke=3.8.0=11_openblas 53 | - libopenblas=0.3.6=h5a2b251_2 54 | - libpng=1.6.37=hed695b0_0 55 | - libprotobuf=3.11.3=h8b12597_0 56 | - libstdcxx-ng=9.2.0=hdf63c60_2 57 | - libtiff=4.1.0=hc3755c2_3 58 | - libuuid=2.32.1=h14c3975_1000 59 | - libwebp=1.0.2=h56121f0_5 60 | - libxcb=1.13=h14c3975_1002 61 | - libxml2=2.9.10=hee79883_0 62 | - lz4-c=1.8.3=he1b5a44_1001 63 | - markdown=3.2.1=py_0 64 | - mock=3.0.5=py36_0 65 | - ncurses=6.1=hf484d3e_1002 66 | - nettle=3.4.1=h1bed415_1002 67 | - numpy=1.14.5=py36_blas_openblashd3ea46f_202 68 | - olefile=0.46=py_0 69 | - openblas=0.2.20=8 70 | - opencv=4.1.0=py36h4a2692f_3 71 | - openh264=1.8.0=hdbcaa40_1000 72 | - openssl=1.1.1d=h516909a_0 73 | - pandas=0.24.2=py36hf484d3e_0 74 | - pcre=8.44=he1b5a44_0 75 | - pillow=7.0.0=py36h8328e55_1 76 | - pip=20.0.2=py_2 77 | - pixman=0.38.0=h516909a_1003 78 | - protobuf=3.11.3=py36he1b5a44_0 79 | - pthread-stubs=0.4=h14c3975_1001 80 | - python=3.6.10=h9d8adfe_1009_cpython 81 | - python-dateutil=2.8.1=py_0 82 | - python_abi=3.6=1_cp36m 83 | - pytz=2019.3=py_0 84 | - qt=5.9.7=h0c104cb_3 85 | - readline=8.0=hf8c457e_0 86 | - scipy=1.2.1=py36h09a28d5_1 87 | - setuptools=46.0.0=py36h9f0ad1d_2 88 | - six=1.14.0=py_1 89 | - sqlite=3.30.1=hcee41ef_0 90 | - tensorboard=1.13.1=py36_0 91 | - tensorflow=1.13.1=py36h90a7d86_1 92 | - tensorflow-estimator=1.13.0=py_0 93 | - tensorflow-gpu=1.13.1=h0d30ee6_0 94 | - termcolor=1.1.0=py_2 95 | - tk=8.6.10=hed695b0_0 96 | - tqdm=4.43.0=py_0 97 | - werkzeug=1.0.0=py_0 98 | - wheel=0.34.2=py_1 99 | - x264=1!152.20180806=h14c3975_0 100 | - xorg-kbproto=1.0.7=h14c3975_1002 101 | - xorg-libice=1.0.10=h516909a_0 102 | - xorg-libsm=1.2.3=h84519dc_1000 103 | - xorg-libx11=1.6.9=h516909a_0 104 | - xorg-libxau=1.0.9=h14c3975_0 105 | - xorg-libxdmcp=1.1.3=h516909a_0 106 | - xorg-libxext=1.3.4=h516909a_0 107 | - xorg-libxrender=0.9.10=h516909a_1002 108 | - xorg-renderproto=0.11.1=h14c3975_1002 109 | - xorg-xextproto=7.3.0=h14c3975_1002 110 | - xorg-xproto=7.0.31=h14c3975_1007 111 | - xz=5.2.4=h14c3975_1001 112 | - zlib=1.2.11=h516909a_1006 113 | - zstd=1.4.4=h3b9ef0a_1 114 | prefix: /home/yuhe/anaconda3/envs/lfnet 115 | 116 | -------------------------------------------------------------------------------- /system/r2d2-python3.6.yml: -------------------------------------------------------------------------------- 1 | name: r2d2-python3.6 2 | channels: 3 | - pytorch 4 | - defaults 5 | dependencies: 6 | - _libgcc_mutex=0.1=main 7 | - backcall=0.1.0=py37_0 8 | - blas=1.0=mkl 9 | - bzip2=1.0.8=h7b6447c_0 10 | - ca-certificates=2020.1.1=0 11 | - cairo=1.14.12=h8948797_3 12 | - certifi=2019.11.28=py37_0 13 | - cffi=1.13.2=py37h2e261b9_0 14 | - cudatoolkit=10.0.130=0 15 | - cycler=0.10.0=py37_0 16 | - dbus=1.13.12=h746ee38_0 17 | - decorator=4.4.1=py_0 18 | - expat=2.2.6=he6710b0_0 19 | - ffmpeg=4.0=hcdf2ecd_0 20 | - fontconfig=2.13.0=h9420a91_0 21 | - freeglut=3.0.0=hf484d3e_5 22 | - freetype=2.9.1=h8a8886c_1 23 | - glib=2.63.1=h5a9c865_0 24 | - graphite2=1.3.13=h23475e2_0 25 | - gst-plugins-base=1.14.0=hbbd80ab_1 26 | - gstreamer=1.14.0=hb453b48_1 27 | - h5py=2.8.0=py37h989c5e5_3 28 | - harfbuzz=1.8.8=hffaf4a1_0 29 | - hdf5=1.10.2=hba1933b_1 30 | - icu=58.2=h9c2bf20_1 31 | - intel-openmp=2019.4=243 32 | - ipython=7.10.2=py37h39e3cac_0 33 | - ipython_genutils=0.2.0=py37_0 34 | - jasper=2.0.14=h07fcdf6_1 35 | - jedi=0.15.1=py37_0 36 | - jpeg=9b=h024ee3a_2 37 | - kiwisolver=1.1.0=py37he6710b0_0 38 | - libedit=3.1.20181209=hc058e9b_0 39 | - libffi=3.2.1=hd88cf55_4 40 | - libgcc-ng=9.1.0=hdf63c60_0 41 | - libgfortran-ng=7.3.0=hdf63c60_0 42 | - libglu=9.0.0=hf484d3e_1 43 | - libopencv=3.4.2=hb342d67_1 44 | - libopus=1.3=h7b6447c_0 45 | - libpng=1.6.37=hbc83047_0 46 | - libstdcxx-ng=9.1.0=hdf63c60_0 47 | - libtiff=4.1.0=h2733197_0 48 | - libuuid=1.0.3=h1bed415_2 49 | - libvpx=1.7.0=h439df22_0 50 | - libxcb=1.13=h1bed415_1 51 | - libxml2=2.9.9=hea5a465_1 52 | - matplotlib=3.1.1=py37h5429711_0 53 | - mkl=2019.4=243 54 | - mkl-service=2.3.0=py37he904b0f_0 55 | - mkl_fft=1.0.15=py37ha843d7b_0 56 | - mkl_random=1.1.0=py37hd6b4f25_0 57 | - ncurses=6.1=he6710b0_1 58 | - ninja=1.9.0=py37hfd86e86_0 59 | - numpy=1.17.4=py37hc1035e2_0 60 | - numpy-base=1.17.4=py37hde5b4d6_0 61 | - olefile=0.46=py_0 62 | - opencv=3.4.2=py37h6fd60c2_1 63 | - openssl=1.1.1d=h7b6447c_4 64 | - parso=0.5.2=py_0 65 | - pcre=8.43=he6710b0_0 66 | - pexpect=4.7.0=py37_0 67 | - pickleshare=0.7.5=py37_0 68 | - pillow=6.2.1=py37h34e0f95_0 69 | - pip=19.3.1=py37_0 70 | - pixman=0.38.0=h7b6447c_0 71 | - prompt_toolkit=3.0.2=py_0 72 | - ptyprocess=0.6.0=py37_0 73 | - py-opencv=3.4.2=py37hb342d67_1 74 | - pycparser=2.19=py_0 75 | - pygments=2.5.2=py_0 76 | - pyparsing=2.4.5=py_0 77 | - pyqt=5.9.2=py37h05f1152_2 78 | - python=3.7.5=h0371630_0 79 | - python-dateutil=2.8.1=py_0 80 | - pytz=2019.3=py_0 81 | - qt=5.9.7=h5867ecd_1 82 | - readline=7.0=h7b6447c_5 83 | - scipy=1.3.2=py37h7c811a0_0 84 | - setuptools=42.0.2=py37_0 85 | - sip=4.19.8=py37hf484d3e_0 86 | - six=1.13.0=py37_0 87 | - sqlite=3.30.1=h7b6447c_0 88 | - tk=8.6.8=hbc83047_0 89 | - tornado=6.0.3=py37h7b6447c_0 90 | - tqdm=4.40.2=py_0 91 | - traitlets=4.3.3=py37_0 92 | - wcwidth=0.1.7=py37_0 93 | - wheel=0.33.6=py37_0 94 | - xz=5.2.4=h14c3975_4 95 | - zlib=1.2.11=h7b6447c_3 96 | - zstd=1.3.7=h0b5b093_0 97 | - pytorch=1.3.1=py3.7_cuda10.0.130_cudnn7.6.3_0 98 | - torchvision=0.4.2=py37_cu100 99 | - pip: 100 | - extract-patches==0.1.1 101 | - torch==1.3.1 102 | prefix: /home/trulls/miniconda3/envs/r2d2-python3.6 103 | 104 | -------------------------------------------------------------------------------- /third_party/l2net_config/convert_l2net_weights_matconv_pytorch.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy.io as sio 3 | import torch 4 | import torch.nn.init 5 | from misc.l2net.l2net_model import L2Net 6 | 7 | eps = 1e-10 8 | 9 | def check_ported(l2net_model, test_patch, img_mean): 10 | 11 | test_patch = test_patch.transpose(3, 2, 0, 1)-img_mean 12 | desc = l2net_model(torch.from_numpy(test_patch)) 13 | print(desc) 14 | return desc 15 | 16 | if __name__ == '__main__': 17 | 18 | path_to_l2net_weights = '/cvlabsrc1/cvlab/datasets_anastasiia/descriptors/sfm-evaluation-benchmarking/third_party/l2net/matlab/L2Net-LIB+.mat' 19 | 20 | l2net_weights = sio.loadmat(path_to_l2net_weights) 21 | 22 | l2net_model = L2Net() 23 | l2net_model.eval() 24 | 25 | new_state_dict = l2net_model.state_dict().copy() 26 | 27 | conv_layers, bn_layers = {}, {} 28 | all_layer_weights = l2net_weights['net']['layers'][0][0][0] 29 | img_mean = l2net_weights['pixMean'] 30 | conv_layers_to_track, bn_layers_to_track = [0,3,6,9,12,15,18], \ 31 | [1,4,7,10,13,16,19] 32 | conv_i, bn_i = 0,0 33 | 34 | for layer in all_layer_weights: 35 | 36 | if 'weights' not in layer.dtype.names: 37 | continue 38 | layer_name = layer[0][0][0][0] 39 | layer_value = layer['weights'][0][0][0] 40 | if layer_name == 'conv': 41 | conv_layers[conv_layers_to_track[conv_i]] = layer_value 42 | conv_i+=1 43 | elif layer_name == 'bnormPair': 44 | bn_layers[bn_layers_to_track[bn_i]] = layer_value 45 | bn_i+=1 46 | 47 | for key, value in new_state_dict.items(): 48 | layer_number = int(key.split('.')[1]) 49 | if layer_number in conv_layers.keys(): 50 | if 'weight' in key: 51 | new_state_dict[key] = torch.from_numpy(conv_layers[layer_number][0].transpose((3,2,0,1))) 52 | elif 'bias' in key: 53 | new_state_dict[key] = torch.from_numpy(conv_layers[layer_number][1]).squeeze() 54 | elif layer_number in bn_layers.keys(): 55 | if 'running_mean' in key: 56 | new_state_dict[key] = torch.from_numpy(np.array([x[0] for x in bn_layers[layer_number][2]])).squeeze() 57 | elif 'running_var' in key: 58 | new_state_dict[key] = torch.from_numpy(np.array([x[1] for x in bn_layers[layer_number][2]] )** 2 -eps).squeeze() 59 | elif 'weight' in key: 60 | new_state_dict[key] = torch.from_numpy(np.ones(value.size()[0])).squeeze() 61 | 62 | else: 63 | continue 64 | 65 | l2net_model.load_state_dict(new_state_dict) 66 | l2net_model.eval() 67 | 68 | torch.save(l2net_model.state_dict(),'l2net_ported_weights_lib+.pth') 69 | 70 | # compare desc on test patch with matlab implementation 71 | # test_patch_batch = sio.loadmat('test_batch_img.mat')['testPatch'] 72 | # check_ported(l2net_model, test_patch_batch, img_mean) 73 | # 74 | # test_patch_one = sio.loadmat('test_one.mat')['testPatch'] 75 | # check_ported(l2net_model, np.expand_dims(np.expand_dims(test_patch_one, axis=2),axis=2), img_mean) -------------------------------------------------------------------------------- /third_party/l2net_config/l2net_model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn.init 3 | import torch.nn as nn 4 | 5 | eps = 1e-10 6 | 7 | class L2Norm(nn.Module): 8 | 9 | def __init__(self): 10 | super(L2Norm,self).__init__() 11 | self.eps = 1e-10 12 | 13 | def forward(self, x): 14 | norm = torch.sqrt(torch.sum(x * x, dim = 1) + self.eps) 15 | x= x / norm.unsqueeze(-1).expand_as(x) 16 | return x 17 | 18 | class L2Net(nn.Module): 19 | 20 | def __init__(self): 21 | super(L2Net, self).__init__() 22 | self.features = nn.Sequential( 23 | nn.Conv2d(1, 32, kernel_size=3, padding=1, bias=True), 24 | nn.BatchNorm2d(32, affine=True, eps=eps), 25 | nn.ReLU(), 26 | nn.Conv2d(32, 32, kernel_size=3, padding=1, bias=True), 27 | nn.BatchNorm2d(32, affine=True, eps=eps), 28 | nn.ReLU(), 29 | nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1, bias=True), 30 | nn.BatchNorm2d(64, affine=True, eps=eps), 31 | nn.ReLU(), 32 | nn.Conv2d(64, 64, kernel_size=3, padding=1, bias=True), 33 | nn.BatchNorm2d(64, affine=True, eps=eps), 34 | nn.ReLU(), 35 | nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1, bias=True), 36 | nn.BatchNorm2d(128, affine=True, eps=eps), 37 | nn.ReLU(), 38 | nn.Conv2d(128, 128, kernel_size=3, padding=1, bias=True), 39 | nn.BatchNorm2d(128, affine=True, eps=eps), 40 | nn.ReLU(), 41 | nn.Conv2d(128, 128, kernel_size=8, bias=True), 42 | nn.BatchNorm2d(128, affine=True, eps=eps), 43 | ) 44 | return 45 | 46 | def input_norm(self, x): 47 | # matlab norm 48 | z = x.contiguous().transpose(2, 3).contiguous().view(x.size(0),-1) 49 | x_minus_mean = z.transpose(0,1)-z.mean(1) 50 | sp = torch.std(z,1).detach() 51 | norm_inp = x_minus_mean/(sp+1e-12) 52 | norm_inp = norm_inp.transpose(0, 1).view(-1, 1, x.size(2), x.size(3)).transpose(2,3) 53 | return norm_inp 54 | 55 | def forward(self, input): 56 | norm_img = self.input_norm(input) 57 | x_features = self.features(norm_img) 58 | return nn.LocalResponseNorm(256,1*256,0.5,0.5)(x_features).view(input.size(0),-1) 59 | -------------------------------------------------------------------------------- /third_party/l2net_config/l2net_ported_weights_lib+.pth: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubc-vision/image-matching-benchmark-baselines/35dcd21c889583a22a4f612389a6b0d7c5af506b/third_party/l2net_config/l2net_ported_weights_lib+.pth -------------------------------------------------------------------------------- /third_party/l2net_config/test_batch_img.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubc-vision/image-matching-benchmark-baselines/35dcd21c889583a22a4f612389a6b0d7c5af506b/third_party/l2net_config/test_batch_img.mat -------------------------------------------------------------------------------- /third_party/l2net_config/test_one.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubc-vision/image-matching-benchmark-baselines/35dcd21c889583a22a4f612389a6b0d7c5af506b/third_party/l2net_config/test_one.mat -------------------------------------------------------------------------------- /third_party/superpoint_forked/LICENSE: -------------------------------------------------------------------------------- 1 | SUPERPOINT: SELF-SUPERVISED INTEREST POINT DETECTION AND DESCRIPTION 2 | SOFTWARE LICENSE AGREEMENT 3 | ACADEMIC OR NON-PROFIT ORGANIZATION NONCOMMERCIAL RESEARCH USE ONLY 4 | 5 | BY USING OR DOWNLOADING THE SOFTWARE, YOU ARE AGREEING TO THE TERMS OF THIS LICENSE AGREEMENT. IF YOU DO NOT AGREE WITH THESE TERMS, YOU MAY NOT USE OR DOWNLOAD THE SOFTWARE. 6 | 7 | This is a license agreement ("Agreement") between your academic institution or non-profit organization or self (called "Licensee" or "You" in this Agreement) and Magic Leap, Inc. (called "Licensor" in this Agreement). All rights not specifically granted to you in this Agreement are reserved for Licensor. 8 | 9 | RESERVATION OF OWNERSHIP AND GRANT OF LICENSE: 10 | Licensor retains exclusive ownership of any copy of the Software (as defined below) licensed under this Agreement and hereby grants to Licensee a personal, non-exclusive, non-transferable license to use the Software for noncommercial research purposes, without the right to sublicense, pursuant to the terms and conditions of this Agreement. As used in this Agreement, the term "Software" means (i) the actual copy of all or any portion of code for program routines made accessible to Licensee by Licensor pursuant to this Agreement, inclusive of backups, updates, and/or merged copies permitted hereunder or subsequently supplied by Licensor, including all or any file structures, programming instructions, user interfaces and screen formats and sequences as well as any and all documentation and instructions related to it, and (ii) all or any derivatives and/or modifications created or made by You to any of the items specified in (i). 11 | 12 | CONFIDENTIALITY: Licensee acknowledges that the Software is proprietary to Licensor, and as such, Licensee agrees to receive all such materials in confidence and use the Software only in accordance with the terms of this Agreement. Licensee agrees to use reasonable effort to protect the Software from unauthorized use, reproduction, distribution, or publication. 13 | 14 | COPYRIGHT: The Software is owned by Licensor and is protected by United States copyright laws and applicable international treaties and/or conventions. 15 | 16 | PERMITTED USES: The Software may be used for your own noncommercial internal research purposes. You understand and agree that Licensor is not obligated to implement any suggestions and/or feedback you might provide regarding the Software, but to the extent Licensor does so, you are not entitled to any compensation related thereto. 17 | 18 | DERIVATIVES: You may create derivatives of or make modifications to the Software, however, You agree that all and any such derivatives and modifications will be owned by Licensor and become a part of the Software licensed to You under this Agreement. You may only use such derivatives and modifications for your own noncommercial internal research purposes, and you may not otherwise use, distribute or copy such derivatives and modifications in violation of this Agreement. 19 | 20 | BACKUPS: If Licensee is an organization, it may make that number of copies of the Software necessary for internal noncommercial use at a single site within its organization provided that all information appearing in or on the original labels, including the copyright and trademark notices are copied onto the labels of the copies. 21 | 22 | USES NOT PERMITTED: You may not distribute, copy or use the Software except as explicitly permitted herein. Licensee has not been granted any trademark license as part of this Agreement and may not use the name or mark "Magic Leap" or any renditions thereof without the prior written permission of Licensor. 23 | 24 | You may not sell, rent, lease, sublicense, lend, time-share or transfer, in whole or in part, or provide third parties access to prior or present versions (or any parts thereof) of the Software. 25 | 26 | ASSIGNMENT: You may not assign this Agreement or your rights hereunder without the prior written consent of Licensor. Any attempted assignment without such consent shall be null and void. 27 | 28 | TERM: The term of the license granted by this Agreement is from Licensee's acceptance of this Agreement by downloading the Software or by using the Software until terminated as provided below. 29 | 30 | The Agreement automatically terminates without notice if you fail to comply with any provision of this Agreement. Licensee may terminate this Agreement by ceasing using the Software. Upon any termination of this Agreement, Licensee will delete any and all copies of the Software. You agree that all provisions which operate to protect the proprietary rights of Licensor shall remain in force should breach occur and that the obligation of confidentiality described in this Agreement is binding in perpetuity and, as such, survives the term of the Agreement. 31 | 32 | FEE: Provided Licensee abides completely by the terms and conditions of this Agreement, there is no fee due to Licensor for Licensee's use of the Software in accordance with this Agreement. 33 | 34 | DISCLAIMER OF WARRANTIES: THE SOFTWARE IS PROVIDED "AS-IS" WITHOUT WARRANTY OF ANY KIND INCLUDING ANY WARRANTIES OF PERFORMANCE OR MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE OR PURPOSE OR OF NON-INFRINGEMENT. LICENSEE BEARS ALL RISK RELATING TO QUALITY AND PERFORMANCE OF THE SOFTWARE AND RELATED MATERIALS. 35 | 36 | SUPPORT AND MAINTENANCE: No Software support or training by the Licensor is provided as part of this Agreement. 37 | 38 | EXCLUSIVE REMEDY AND LIMITATION OF LIABILITY: To the maximum extent permitted under applicable law, Licensor shall not be liable for direct, indirect, special, incidental, or consequential damages or lost profits related to Licensee's use of and/or inability to use the Software, even if Licensor is advised of the possibility of such damage. 39 | 40 | EXPORT REGULATION: Licensee agrees to comply with any and all applicable U.S. export control laws, regulations, and/or other laws related to embargoes and sanction programs administered by the Office of Foreign Assets Control. 41 | 42 | SEVERABILITY: If any provision(s) of this Agreement shall be held to be invalid, illegal, or unenforceable by a court or other tribunal of competent jurisdiction, the validity, legality and enforceability of the remaining provisions shall not in any way be affected or impaired thereby. 43 | 44 | NO IMPLIED WAIVERS: No failure or delay by Licensor in enforcing any right or remedy under this Agreement shall be construed as a waiver of any future or other exercise of such right or remedy by Licensor. 45 | 46 | GOVERNING LAW: This Agreement shall be construed and enforced in accordance with the laws of the State of Florida without reference to conflict of laws principles. You consent to the personal jurisdiction of the courts of this County and waive their rights to venue outside of Broward County, Florida. 47 | 48 | ENTIRE AGREEMENT AND AMENDMENTS: This Agreement constitutes the sole and entire agreement between Licensee and Licensor as to the matter set forth herein and supersedes any previous agreements, understandings, and arrangements between the parties relating hereto. 49 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | import h5py 4 | 5 | cv2_greyscale = lambda x: cv2.cvtColor(x, cv2.COLOR_BGR2GRAY) 6 | 7 | cv2_scale = lambda x: cv2.resize(x, dsize=(32, 32), 8 | interpolation=cv2.INTER_LINEAR) 9 | # reshape image 10 | np_reshape = lambda x: np.reshape(x, (32, 32, 1)) 11 | 12 | 13 | def str2bool(v): 14 | if v.lower() in ('yes', 'true', 't', 'y', '1'): 15 | return True 16 | elif v.lower() in ('no', 'false', 'f', 'n', '0'): 17 | return False 18 | 19 | 20 | def save_h5(dict_to_save, filename): 21 | """Saves dictionary to hdf5 file""" 22 | 23 | with h5py.File(filename, 'w') as f: 24 | for key in dict_to_save: 25 | f.create_dataset(key, data=dict_to_save[key]) 26 | --------------------------------------------------------------------------------