├── LICENSE
├── README.md
├── figs
├── .gitkeep
├── experiment.png
├── experiment_2.png
└── framework.png
├── reid
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-37.pyc
│ ├── association.cpython-37.pyc
│ ├── evaluators.cpython-37.pyc
│ ├── img_grouping.cpython-37.pyc
│ ├── trainers.cpython-37.pyc
│ ├── trainers_joint_source_target.cpython-37.pyc
│ ├── trainers_only_source.cpython-37.pyc
│ ├── trainers_pure_target.cpython-37.pyc
│ ├── trainers_source_target_v2.cpython-37.pyc
│ └── trainers_unsupervised_fc.cpython-37.pyc
├── datasets
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-37.pyc
│ │ ├── domain_adaptation.cpython-37.pyc
│ │ └── domain_adaptation_challenge.cpython-37.pyc
│ └── target_dataset.py
├── evaluation_metrics
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-37.pyc
│ │ ├── classification.cpython-37.pyc
│ │ └── ranking.cpython-37.pyc
│ ├── classification.py
│ └── ranking.py
├── evaluators.py
├── img_grouping.py
├── lib
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-37.pyc
│ │ └── normalize.cpython-37.pyc
│ ├── custom_transforms.py
│ ├── normalize.py
│ └── utils.py
├── loss
│ ├── CamAwareMemory.py
│ ├── __init__.py
│ └── __pycache__
│ │ ├── __init__.cpython-37.pyc
│ │ ├── circle_loss.cpython-37.pyc
│ │ ├── contrast_loss.cpython-37.pyc
│ │ ├── cross_entropy_loss.cpython-37.pyc
│ │ └── invariance.cpython-37.pyc
├── models
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-37.pyc
│ │ ├── dsbn.cpython-37.pyc
│ │ ├── pooling.cpython-37.pyc
│ │ ├── resnet.cpython-37.pyc
│ │ ├── resnet_ibn_a.cpython-37.pyc
│ │ └── stb_net.cpython-37.pyc
│ ├── dsbn.py
│ ├── pooling.py
│ ├── resnet.py
│ ├── resnet_ibn_a.py
│ └── stb_net.py
├── trainers.py
└── utils
│ ├── __init__.py
│ ├── __pycache__
│ ├── __init__.cpython-37.pyc
│ ├── faiss_rerank.cpython-37.pyc
│ ├── faiss_utils.cpython-37.pyc
│ ├── logging.cpython-37.pyc
│ ├── meters.cpython-37.pyc
│ ├── osutils.cpython-37.pyc
│ ├── rerank.cpython-37.pyc
│ ├── serialization.cpython-37.pyc
│ └── visualize.cpython-37.pyc
│ ├── data
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-37.pyc
│ │ ├── preprocessor.cpython-37.pyc
│ │ └── transforms.cpython-37.pyc
│ ├── preprocessor.py
│ ├── sampler.py
│ └── transforms.py
│ ├── evaluation_metrics
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-37.pyc
│ │ ├── classification.cpython-37.pyc
│ │ ├── ranking.cpython-37.pyc
│ │ ├── retrieval.cpython-37.pyc
│ │ └── retrieval_with_rerank.cpython-37.pyc
│ ├── classification.py
│ ├── ranking.py
│ ├── retrieval.py
│ └── retrieval_with_rerank.py
│ ├── faiss_rerank.py
│ ├── faiss_utils.py
│ ├── logging.py
│ ├── meters.py
│ ├── misc.py
│ ├── osutils.py
│ ├── rerank.py
│ ├── serialization.py
│ └── visualize.py
└── train_cap.py
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Camera-aware Proxies for Unsupervised Person Re-Identification
2 |
3 | The official implementation for [Camera-aware Proxies for Unsupervised Person Re-Identification](https://arxiv.org/abs/2012.10674), which is accepted by AAAI 2021.
4 | **CAP** (Camera-Aware Proxies) achieves state-of-the-art performance on pure unsupervised person re-ID task. It can also be applied to unsupervised vehicle re-ID with competitive performance.
5 |
6 |
7 |
8 | ### Preparation
9 |
10 | **Requirements: Pytorch>=1.1.0 and python>=3.6**
11 |
12 | 1. install [pytorch](https://pytorch.org/)
13 | 2. Download re-ID dataset
14 | - [Market-1501](https://drive.google.com/file/d/0B8-rUzbwVRk0c054eEozWG9COHM/view)
15 | - [DukeMTMC-reID](https://drive.google.com/file/d/1jjE85dRCMOgRtvJ5RQV9-Afs-2_5dY3O/view)
16 | - [MSMT17](https://arxiv.org/abs/1711.08565)
17 | - [VeRi-776](https://github.com/JDAI-CV/VeRidataset)
18 |
19 | 3. Put the data under the dataset directory. Training, query and test sub-folder should named as bounding_box_train, query, bounding_box_test, respectively.
20 |
21 | ### Training and test model for unsupervised re-ID
22 |
23 | ```python
24 | # train CAP model on Market-1501
25 | CUDA_VISIBLE_DEVICES=0 python train_cap.py --target 'Market1501' --data_dir '/folder/to/dataset' --logs_dir 'Market_logs'
26 |
27 | # test model on Market-1501
28 | CUDA_VISIBLE_DEVICES=0 python train_cap.py --target 'Market1501' --data_dir '/folder/to/dataset' --logs_dir 'Market_logs' --evaluate True --load_ckpt 'trained_model_name.pth'
29 | ```
30 |
31 | ### Results
32 |
33 |
34 |
35 | The performance of CAP on Vehicle re-ID dataset VeRi-776:
36 |
37 | Rank-1 (\%) | mAP (\%)
38 | ------------- | -------------
39 | 87.0 | 40.6
40 |
41 | ### Citation
42 |
43 | If you find this work useful in your research, please cite the following paper:
44 |
45 | ```
46 | @inproceedings{Wang2021camawareproxies,
47 | title={Camera-aware Proxies for Unsupervised Person Re-Identification},
48 | author={Menglin Wang and Baisheng Lai and Jianqiang Huang and Xiaojin Gong and Xian-Sheng Hua},
49 | booktitle={Proceedings of the AAAI Conference on Artificial Intelligence (AAAI)},
50 | year={2021},
51 | }
52 | ```
--------------------------------------------------------------------------------
/figs/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/figs/.gitkeep
--------------------------------------------------------------------------------
/figs/experiment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/figs/experiment.png
--------------------------------------------------------------------------------
/figs/experiment_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/figs/experiment_2.png
--------------------------------------------------------------------------------
/figs/framework.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/figs/framework.png
--------------------------------------------------------------------------------
/reid/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from . import datasets
4 | from . import evaluation_metrics
5 | from . import loss
6 | from . import models
7 | from . import utils
8 | from . import evaluators
9 |
--------------------------------------------------------------------------------
/reid/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/__pycache__/association.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/__pycache__/association.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/__pycache__/evaluators.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/__pycache__/evaluators.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/__pycache__/img_grouping.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/__pycache__/img_grouping.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/__pycache__/trainers.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/__pycache__/trainers.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/__pycache__/trainers_joint_source_target.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/__pycache__/trainers_joint_source_target.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/__pycache__/trainers_only_source.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/__pycache__/trainers_only_source.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/__pycache__/trainers_pure_target.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/__pycache__/trainers_pure_target.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/__pycache__/trainers_source_target_v2.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/__pycache__/trainers_source_target_v2.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/__pycache__/trainers_unsupervised_fc.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/__pycache__/trainers_unsupervised_fc.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/datasets/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from .target_dataset import DA
3 |
4 |
5 | __all__ = [
6 | 'DA',
7 | ]
8 |
--------------------------------------------------------------------------------
/reid/datasets/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/datasets/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/datasets/__pycache__/domain_adaptation.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/datasets/__pycache__/domain_adaptation.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/datasets/__pycache__/domain_adaptation_challenge.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/datasets/__pycache__/domain_adaptation_challenge.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/datasets/target_dataset.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function, absolute_import
2 | import os.path as osp
3 | import numpy as np
4 | import pdb
5 | from glob import glob
6 | import re
7 |
8 |
9 | class DA(object):
10 |
11 | def __init__(self, data_dir, target, generate_propagate_data=False):
12 |
13 | # target image root
14 | self.target_images_dir = osp.join(data_dir, target)
15 | # training image dir
16 | self.target_train_path = 'bounding_box_train'
17 | self.gallery_path = 'bounding_box_test'
18 | self.query_path = 'query'
19 |
20 | self.target_train, self.query, self.gallery = [], [], []
21 | self.num_train_ids, self.num_query_ids, self.num_gallery_ids = 0, 0, 0
22 |
23 | self.cam_dict = self.set_cam_dict()
24 | self.target_num_cam = self.cam_dict[target]
25 |
26 | self.generate_propagate_data = generate_propagate_data
27 |
28 | self.load()
29 |
30 | def set_cam_dict(self):
31 | cam_dict = {}
32 | cam_dict['Market1501'] = 6
33 | cam_dict['DukeMTMC-reID'] = 8
34 | cam_dict['MSMT17'] = 15
35 | cam_dict['VeRi'] = 20
36 | return cam_dict
37 |
38 | def preprocess(self, images_dir, path, relabel=True):
39 | pattern = re.compile(r'([-\d]+)_c([-\d]+)')
40 | all_pids = {}
41 | ret = []
42 | if 'cuhk03' in images_dir:
43 | fpaths = sorted(glob(osp.join(images_dir, path, '*.png')))
44 | else:
45 | fpaths = sorted(glob(osp.join(images_dir, path, '*.jpg')))
46 | for fpath in fpaths:
47 | fname = osp.basename(fpath)
48 | if 'cuhk03' in images_dir:
49 | name = osp.splitext(fname)[0]
50 | pid, cam = map(int, pattern.search(fname).groups())
51 | else:
52 | pid, cam = map(int, pattern.search(fname).groups())
53 | if pid == -1: continue # junk images are just ignored
54 | if relabel:
55 | if pid not in all_pids:
56 | all_pids[pid] = len(all_pids)
57 | else:
58 | if pid not in all_pids:
59 | all_pids[pid] = pid
60 | pid = all_pids[pid]
61 | cam -= 1
62 | ret.append((fname, pid, cam))
63 | return ret, int(len(all_pids))
64 |
65 |
66 | def preprocess_target_train(self, images_dir, path, relabel=True):
67 | print('train image_dir= {}'.format(osp.join(images_dir, path)))
68 | pattern = re.compile(r'([-\d]+)_c([-\d]+)')
69 | all_pids = {}
70 | all_img_prefix = {}
71 | ret = []
72 | index_to_id = {}
73 |
74 | all_img_cams = {} # camera for each global index in order
75 | if 'cuhk03' in images_dir:
76 | fpaths = sorted(glob(osp.join(images_dir, path, '*.png')))
77 | else:
78 | fpaths = sorted(glob(osp.join(images_dir, path, '*.jpg')))
79 | if ('arket' in images_dir) or ('VeRi' in images_dir):
80 | name_segment = 4
81 | else:
82 | name_segment = 3
83 |
84 | for fpath in fpaths:
85 | fname = osp.basename(fpath)
86 | if 'cuhk03' in images_dir:
87 | name = osp.splitext(fname)[0]
88 | pid, cam = map(int, pattern.search(fname).groups())
89 | # bag, pid, cam, _ = map(int, name.split('_'))
90 | # pid += bag * 1000
91 | else:
92 | pid, cam = map(int, pattern.search(fname).groups())
93 | if pid == -1: continue # junk images are just ignored
94 | cam -= 1
95 |
96 | split_list = fname.replace('.jpg', '').split('_')
97 | if name_segment == 4:
98 | this_prefix = split_list[0]+split_list[1]+split_list[2]+split_list[3]
99 | if name_segment == 3:
100 | this_prefix = split_list[0]+split_list[1]+split_list[2]
101 | if this_prefix not in all_img_prefix:
102 | all_img_prefix[this_prefix] = len(all_img_prefix)
103 | img_idx = all_img_prefix[this_prefix] # global index
104 |
105 | if relabel:
106 | if pid not in all_pids:
107 | all_pids[pid] = len(all_pids)
108 | else:
109 | if pid not in all_pids:
110 | all_pids[pid] = pid
111 | pid = all_pids[pid]
112 |
113 | ret.append((fname, pid, cam, img_idx))
114 | index_to_id[img_idx] = pid
115 |
116 | if this_prefix not in all_img_cams:
117 | all_img_cams[this_prefix] = cam
118 |
119 | all_img_cams = list(all_img_cams.values())
120 | all_img_cams = np.array(all_img_cams).astype(np.int64)
121 | print(' length of all_img_prefix= {}'.format(len(all_img_prefix)))
122 | print(' {} samples in total.'.format(len(ret)))
123 | print(' all cameras shape= {}, dtype= {}, unique values= {}'.format(all_img_cams.shape, all_img_cams.dtype, np.unique(all_img_cams)))
124 |
125 | gt_id_all_img = np.zeros(len(index_to_id.keys()))
126 | for index in index_to_id.keys():
127 | gt_id_all_img[index] = index_to_id[index]
128 |
129 | return ret, int(len(all_pids)), all_img_cams, len(all_img_prefix), gt_id_all_img
130 |
131 | def load(self):
132 | self.target_train, _, self.target_train_all_img_cams, self.target_train_ori_img_num, self.gt_id_all_img = \
133 | self.preprocess_target_train(self.target_images_dir, self.target_train_path)
134 | self.gallery, self.num_gallery_ids = self.preprocess(self.target_images_dir, self.gallery_path, False)
135 | self.query, self.num_query_ids = self.preprocess(self.target_images_dir, self.query_path, False)
136 | if self.generate_propagate_data:
137 | self.target_train_original, _, _, _, _ = self.preprocess_target_train(self.target_images_dir, self.target_train_path)
138 |
139 | print(self.__class__.__name__, "dataset loaded")
140 | print(" subset | # ids | # images")
141 | print(" ---------------------------")
142 | print(" target train | 'Unknown' | {:8d}"
143 | .format(len(self.target_train)))
144 | print(" query | {:5d} | {:8d}"
145 | .format(self.num_query_ids, len(self.query)))
146 | print(" gallery | {:5d} | {:8d}"
147 | .format(self.num_gallery_ids, len(self.gallery)))
148 | if self.generate_propagate_data:
149 | print(" target train(ori)| 'Unknown' | {:8d}"
150 | .format(len(self.target_train_original)))
151 |
--------------------------------------------------------------------------------
/reid/evaluation_metrics/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from .classification import accuracy
4 | from .ranking import cmc, mean_ap, map_cmc
5 |
6 | __all__ = [
7 | 'accuracy',
8 | 'cmc',
9 | 'mean_ap',
10 | 'map_cmc',
11 | ]
12 |
--------------------------------------------------------------------------------
/reid/evaluation_metrics/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/evaluation_metrics/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/evaluation_metrics/__pycache__/classification.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/evaluation_metrics/__pycache__/classification.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/evaluation_metrics/__pycache__/ranking.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/evaluation_metrics/__pycache__/ranking.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/evaluation_metrics/classification.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from ..utils import to_torch
4 |
5 |
6 | def accuracy(output, target, topk=(1,)):
7 | output, target = to_torch(output), to_torch(target)
8 | maxk = max(topk)
9 | batch_size = target.size(0)
10 |
11 | _, pred = output.topk(maxk, 1, True, True)
12 | pred = pred.t()
13 | correct = pred.eq(target.view(1, -1).expand_as(pred))
14 |
15 | ret = []
16 | for k in topk:
17 | correct_k = correct[:k].view(-1).float().sum(dim=0, keepdim=True)
18 | ret.append(correct_k.mul_(1. / batch_size))
19 | return ret
20 |
--------------------------------------------------------------------------------
/reid/evaluation_metrics/ranking.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from collections import defaultdict
3 |
4 | import numpy as np
5 | from sklearn.metrics.base import _average_binary_score
6 | from sklearn.metrics import precision_recall_curve, auc
7 | # from sklearn.metrics import average_precision_score
8 |
9 |
10 | from ..utils import to_numpy
11 |
12 |
13 | def _unique_sample(ids_dict, num):
14 | mask = np.zeros(num, dtype=np.bool)
15 | for _, indices in ids_dict.items():
16 | i = np.random.choice(indices)
17 | mask[i] = True
18 | return mask
19 |
20 |
21 | def average_precision_score(y_true, y_score, average="macro",
22 | sample_weight=None):
23 | def _binary_average_precision(y_true, y_score, sample_weight=None):
24 | precision, recall, thresholds = precision_recall_curve(
25 | y_true, y_score, sample_weight=sample_weight)
26 | return auc(recall, precision)
27 |
28 | return _average_binary_score(_binary_average_precision, y_true, y_score,
29 | average, sample_weight=sample_weight)
30 |
31 |
32 | def map_cmc(distmat, query_ids=None, gallery_ids=None,
33 | query_cams=None, gallery_cams=None, topk=100):
34 | distmat = to_numpy(distmat)
35 | m, n = distmat.shape
36 | # Fill up default values
37 | if query_ids is None:
38 | query_ids = np.arange(m)
39 | if gallery_ids is None:
40 | gallery_ids = np.arange(n)
41 | if query_cams is None:
42 | query_cams = np.zeros(m).astype(np.int32)
43 | if gallery_cams is None:
44 | gallery_cams = np.ones(n).astype(np.int32)
45 | # Ensure numpy array
46 | query_ids = np.asarray(query_ids)
47 | gallery_ids = np.asarray(gallery_ids)
48 | query_cams = np.asarray(query_cams)
49 | gallery_cams = np.asarray(gallery_cams)
50 | # Sort and find correct matches
51 | indices = np.argsort(distmat, axis=1)
52 | matches = (gallery_ids[indices] == query_ids[:, np.newaxis])
53 | # Compute mAP and CMC for each query
54 | ret = np.zeros(topk)
55 | aps = []
56 | num_valid_queries = 0
57 | for i in range(m):
58 | # Filter out the same id and same camera
59 | valid = ((gallery_ids[indices[i]] != query_ids[i]) |
60 | (gallery_cams[indices[i]] != query_cams[i]))
61 | if not np.any(matches[i, valid]): continue
62 |
63 | # Compute mAP
64 | y_true = matches[i, valid]
65 | y_score = -distmat[i][indices[i]][valid]
66 | if not np.any(y_true): continue
67 | aps.append(average_precision_score(y_true, y_score))
68 |
69 | # Compute CMC
70 | index = np.nonzero(matches[i, valid])[0]
71 | for j, k in enumerate(index):
72 | if k >= topk: break
73 | ret[k] += 1
74 | break
75 | num_valid_queries += 1
76 | if num_valid_queries == 0:
77 | raise RuntimeError("No valid query")
78 | return np.mean(aps), ret.cumsum() / num_valid_queries
79 |
80 |
81 | def cmc(distmat, query_ids=None, gallery_ids=None,
82 | query_cams=None, gallery_cams=None, topk=100,
83 | separate_camera_set=False,
84 | single_gallery_shot=False,
85 | first_match_break=False):
86 | distmat = to_numpy(distmat)
87 | m, n = distmat.shape
88 | # Fill up default values
89 | if query_ids is None:
90 | query_ids = np.arange(m)
91 | if gallery_ids is None:
92 | gallery_ids = np.arange(n)
93 | if query_cams is None:
94 | query_cams = np.zeros(m).astype(np.int32)
95 | if gallery_cams is None:
96 | gallery_cams = np.ones(n).astype(np.int32)
97 | # Ensure numpy array
98 | query_ids = np.asarray(query_ids)
99 | gallery_ids = np.asarray(gallery_ids)
100 | query_cams = np.asarray(query_cams)
101 | gallery_cams = np.asarray(gallery_cams)
102 | # Sort and find correct matches
103 | indices = np.argsort(distmat, axis=1)
104 | matches = (gallery_ids[indices] == query_ids[:, np.newaxis])
105 | # Compute CMC for each query
106 | ret = np.zeros(topk)
107 | num_valid_queries = 0
108 | for i in range(m):
109 | # Filter out the same id and same camera
110 | valid = ((gallery_ids[indices[i]] != query_ids[i]) |
111 | (gallery_cams[indices[i]] != query_cams[i]))
112 | if separate_camera_set:
113 | # Filter out samples from same camera
114 | valid &= (gallery_cams[indices[i]] != query_cams[i])
115 | if not np.any(matches[i, valid]): continue
116 | if single_gallery_shot:
117 | repeat = 10
118 | gids = gallery_ids[indices[i][valid]]
119 | inds = np.where(valid)[0]
120 | ids_dict = defaultdict(list)
121 | for j, x in zip(inds, gids):
122 | ids_dict[x].append(j)
123 | else:
124 | repeat = 1
125 | for _ in range(repeat):
126 | if single_gallery_shot:
127 | # Randomly choose one instance for each id
128 | sampled = (valid & _unique_sample(ids_dict, len(valid)))
129 | index = np.nonzero(matches[i, sampled])[0]
130 | else:
131 | index = np.nonzero(matches[i, valid])[0]
132 | delta = 1. / (len(index) * repeat)
133 | for j, k in enumerate(index):
134 | if k - j >= topk: break
135 | if first_match_break:
136 | ret[k - j] += 1
137 | break
138 | ret[k - j] += delta
139 | num_valid_queries += 1
140 | if num_valid_queries == 0:
141 | raise RuntimeError("No valid query")
142 | return ret.cumsum() / num_valid_queries
143 |
144 |
145 | def mean_ap(distmat, query_ids=None, gallery_ids=None,
146 | query_cams=None, gallery_cams=None):
147 | distmat = to_numpy(distmat)
148 | m, n = distmat.shape
149 | # Fill up default values
150 | if query_ids is None:
151 | query_ids = np.arange(m)
152 | if gallery_ids is None:
153 | gallery_ids = np.arange(n)
154 | if query_cams is None:
155 | query_cams = np.zeros(m).astype(np.int32)
156 | if gallery_cams is None:
157 | gallery_cams = np.ones(n).astype(np.int32)
158 | # Ensure numpy array
159 | query_ids = np.asarray(query_ids)
160 | gallery_ids = np.asarray(gallery_ids)
161 | query_cams = np.asarray(query_cams)
162 | gallery_cams = np.asarray(gallery_cams)
163 | # Sort and find correct matches
164 | indices = np.argsort(distmat, axis=1)
165 | matches = (gallery_ids[indices] == query_ids[:, np.newaxis])
166 | # Compute AP for each query
167 | aps = []
168 | for i in range(m):
169 | # Filter out the same id and same camera
170 | valid = ((gallery_ids[indices[i]] != query_ids[i]) |
171 | (gallery_cams[indices[i]] != query_cams[i]))
172 | y_true = matches[i, valid]
173 | y_score = -distmat[i][indices[i]][valid]
174 | if not np.any(y_true): continue
175 | aps.append(average_precision_score(y_true, y_score))
176 | if len(aps) == 0:
177 | raise RuntimeError("No valid query")
178 | return np.mean(aps)
179 |
180 |
--------------------------------------------------------------------------------
/reid/evaluators.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function, absolute_import
2 | import time
3 | from collections import OrderedDict
4 | import pdb
5 |
6 | import torch
7 | import numpy as np
8 |
9 | from .evaluation_metrics import cmc, mean_ap, map_cmc
10 | from .utils.meters import AverageMeter
11 |
12 | from torch.autograd import Variable
13 | from .utils import to_torch
14 | from .utils import to_numpy
15 | import os.path as osp
16 | from PIL import Image
17 | from torchvision.transforms import functional as F
18 | import pdb
19 | #import visdom
20 |
21 |
22 | def extract_cnn_feature(model, inputs, output_feature=None):
23 | model.eval()
24 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
25 | inputs = to_torch(inputs)
26 | inputs = inputs.to(device)
27 | outputs = model(inputs, output_feature)
28 | outputs = outputs.data.cpu()
29 | return outputs
30 |
31 |
32 | def extract_features(model, data_loader, print_freq=1000, output_feature=None):
33 | model.eval()
34 | batch_time = AverageMeter()
35 | data_time = AverageMeter()
36 |
37 | features = OrderedDict()
38 | labels = OrderedDict()
39 |
40 | end = time.time()
41 | for i, (imgs, fnames, pids, _) in enumerate(data_loader):
42 | data_time.update(time.time() - end)
43 |
44 | outputs = extract_cnn_feature(model, imgs, output_feature)
45 | for fname, output, pid in zip(fnames, outputs, pids):
46 | features[fname] = output
47 | labels[fname] = pid
48 |
49 | batch_time.update(time.time() - end)
50 | end = time.time()
51 |
52 | if (i + 1) % print_freq == 0:
53 | print('Extract Features: [{}/{}]\t'
54 | 'Time {:.3f} ({:.3f})\t'
55 | 'Data {:.3f} ({:.3f})\t'
56 | .format(i + 1, len(data_loader),
57 | batch_time.val, batch_time.avg,
58 | data_time.val, data_time.avg))
59 |
60 | return features, labels
61 |
62 |
63 | def pairwise_distance(query_features, gallery_features, query=None, gallery=None):
64 | x = torch.cat([query_features[f].unsqueeze(0) for f, _, _ in query], 0)
65 | y = torch.cat([gallery_features[f].unsqueeze(0) for f, _, _ in gallery], 0)
66 |
67 | m, n = x.size(0), y.size(0)
68 | x = x.view(m, -1)
69 | y = y.view(n, -1)
70 |
71 | dist = torch.pow(x, 2).sum(dim=1, keepdim=True).expand(m, n) + \
72 | torch.pow(y, 2).sum(dim=1, keepdim=True).expand(n, m).t()
73 | dist.addmm_(1, -2, x, y.t())
74 | # We use clamp to keep numerical stability
75 | dist = torch.clamp(dist, 1e-8, np.inf)
76 | return dist
77 |
78 |
79 | def evaluate_all(distmat, query=None, gallery=None,
80 | query_ids=None, gallery_ids=None,
81 | query_cams=None, gallery_cams=None,
82 | cmc_topk=(1, 5, 10, 20)):
83 | if query is not None and gallery is not None:
84 | query_ids = [pid for _, pid, _ in query]
85 | gallery_ids = [pid for _, pid, _ in gallery]
86 | query_cams = [cam for _, _, cam in query]
87 | gallery_cams = [cam for _, _, cam in gallery]
88 | else:
89 | assert (query_ids is not None and gallery_ids is not None
90 | and query_cams is not None and gallery_cams is not None)
91 |
92 | # Evaluation
93 | mAP, all_cmc = map_cmc(distmat, query_ids, gallery_ids, query_cams, gallery_cams)
94 | print('Mean AP: {:4.1%}'.format(mAP))
95 | print('CMC Scores')
96 | for k in cmc_topk:
97 | print(' top-{:<4}{:12.1%}'
98 | .format(k, all_cmc[k - 1]))
99 | return
100 |
101 | # Traditional evaluation
102 | # Compute mean AP
103 | # mAP = mean_ap(distmat, query_ids, gallery_ids, query_cams, gallery_cams)
104 | # print('Mean AP: {:4.1%}'.format(mAP))
105 | #
106 | # # Compute CMC scores
107 | # cmc_configs = {
108 | # 'market1501': dict(separate_camera_set=False,
109 | # single_gallery_shot=False,
110 | # first_match_break=True)}
111 | # cmc_scores = {name: cmc(distmat, query_ids, gallery_ids,
112 | # query_cams, gallery_cams, **params)
113 | # for name, params in cmc_configs.items()}
114 | #
115 | # print('CMC Scores')
116 | # for k in cmc_topk:
117 | # print(' top-{:<4}{:12.1%}'
118 | # .format(k, cmc_scores['market1501'][k - 1]))
119 | #
120 | # return cmc_scores['market1501'][0]
121 |
122 |
123 | class Evaluator(object):
124 | def __init__(self, model):
125 | super(Evaluator, self).__init__()
126 | self.model = model
127 |
128 | def evaluate(self, query_loader, gallery_loader, query, gallery, output_feature=None):
129 | query_features, _ = extract_features(self.model, query_loader, 1000, output_feature)
130 | gallery_features, _ = extract_features(self.model, gallery_loader, 1000, output_feature)
131 | distmat = pairwise_distance(query_features, gallery_features, query, gallery)
132 | return evaluate_all(distmat, query=query, gallery=gallery)
133 |
--------------------------------------------------------------------------------
/reid/img_grouping.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import numpy as np
3 | from scipy.spatial.distance import pdist, cdist, squareform
4 | from sklearn.cluster.dbscan_ import dbscan
5 | from sklearn.cluster import KMeans
6 | from reid.utils.rerank import compute_jaccard_dist
7 | from reid.utils.faiss_rerank import faiss_compute_jaccard_dist
8 | import scipy.io as sio
9 | torch.autograd.set_detect_anomaly(True)
10 |
11 |
12 | def img_association(network, propagate_loader, min_sample=4, eps=0,
13 | rerank=False, k1=20, k2=6, intra_id_reinitialize=False):
14 |
15 | network.eval()
16 | print('Start Inference...')
17 | features = []
18 | global_labels = []
19 | all_cams = []
20 |
21 | with torch.no_grad():
22 | for c, data in enumerate(propagate_loader):
23 | images = data[0]
24 | g_label = data[3]
25 | cam = data[4]
26 |
27 | embed_feat = network(images)
28 | features.append(embed_feat.cpu())
29 |
30 | global_labels.append(g_label)
31 | all_cams.append(cam)
32 |
33 | features = torch.cat(features, dim=0).numpy()
34 | global_labels = torch.cat(global_labels, dim=0).numpy()
35 | all_cams = torch.cat(all_cams, dim=0).numpy()
36 | print(' features: shape= {}'.format(features.shape))
37 |
38 | # if needed, average camera-style transferred image features
39 | new_features = []
40 | new_cams = []
41 | for glab in np.unique(global_labels):
42 | idx = np.where(global_labels == glab)[0]
43 | new_features.append(np.mean(features[idx], axis=0))
44 | new_cams.append(all_cams[idx])
45 |
46 | new_features = np.array(new_features)
47 | new_cams = np.array(new_cams).squeeze()
48 | del features, all_cams
49 |
50 | # compute distance W
51 | new_features = new_features / np.linalg.norm(new_features, axis=1, keepdims=True) # l2-normalize
52 | if rerank:
53 | W = faiss_compute_jaccard_dist(torch.from_numpy(new_features), k1=k1, k2=k2)
54 | else:
55 | W = cdist(new_features, new_features, 'euclidean')
56 | print(' distance matrix: shape= {}'.format(W.shape))
57 |
58 | # self-similarity for association
59 | print(' perform image grouping...')
60 | _, updated_label = dbscan(W, eps=eps, min_samples=min_sample, metric='precomputed', n_jobs=8)
61 | print(' eps in cluster: {:.3f}'.format(eps))
62 | print(' updated_label: num_class= {}, {}/{} images are associated.'
63 | .format(updated_label.max() + 1, len(updated_label[updated_label >= 0]), len(updated_label)))
64 |
65 | if intra_id_reinitialize:
66 | print('re-computing initialized intra-ID feature...')
67 | intra_id_features = []
68 | intra_id_labels = []
69 | for cc in np.unique(new_cams):
70 | percam_ind = np.where(new_cams == cc)[0]
71 | percam_feature = new_features[percam_ind, :]
72 | percam_label = updated_label[percam_ind]
73 | percam_class_num = len(np.unique(percam_label[percam_label >= 0]))
74 | percam_id_feature = np.zeros((percam_class_num, percam_feature.shape[1]), dtype=np.float32)
75 | cnt = 0
76 | for lbl in np.unique(percam_label):
77 | if lbl >= 0:
78 | ind = np.where(percam_label == lbl)[0]
79 | id_feat = np.mean(percam_feature[ind], axis=0)
80 | percam_id_feature[cnt, :] = id_feat
81 | intra_id_labels.append(lbl)
82 | cnt += 1
83 | percam_id_feature = percam_id_feature / np.linalg.norm(percam_id_feature, axis=1, keepdims=True)
84 | intra_id_features.append(torch.from_numpy(percam_id_feature))
85 | return updated_label, intra_id_features
86 |
87 |
--------------------------------------------------------------------------------
/reid/lib/__init__.py:
--------------------------------------------------------------------------------
1 | # nothing
2 |
--------------------------------------------------------------------------------
/reid/lib/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/lib/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/lib/__pycache__/normalize.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/lib/__pycache__/normalize.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/lib/custom_transforms.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy
3 | import scipy.ndimage
4 | from scipy.ndimage.filters import gaussian_filter
5 | from scipy.ndimage.interpolation import map_coordinates
6 | import collections
7 | from PIL import Image
8 | import numbers
9 | import random
10 |
11 | __author__ = "Wei OUYANG"
12 | __license__ = "GPL"
13 | __version__ = "0.1.0"
14 | __status__ = "Development"
15 |
16 |
17 | def center_crop(x, center_crop_size):
18 | assert x.ndim == 3
19 | centerw, centerh = x.shape[1] // 2, x.shape[2] // 2
20 | halfw, halfh = center_crop_size[0] // 2, center_crop_size[1] // 2
21 | return x[:, centerw - halfw:centerw + halfw, centerh - halfh:centerh + halfh]
22 |
23 |
24 | def to_tensor(x):
25 | import torch
26 | x = x.transpose((2, 0, 1))
27 | return torch.from_numpy(x).float()
28 |
29 |
30 | def random_num_generator(config, random_state=np.random):
31 | if config[0] == 'uniform':
32 | ret = random_state.uniform(config[1], config[2], 1)[0]
33 | elif config[0] == 'lognormal':
34 | ret = random_state.lognormal(config[1], config[2], 1)[0]
35 | else:
36 | print(config)
37 | raise Exception('unsupported format')
38 | return ret
39 |
40 |
41 | def poisson_downsampling(image, peak, random_state=np.random):
42 | if not isinstance(image, np.ndarray):
43 | imgArr = np.array(image, dtype='float32')
44 | else:
45 | imgArr = image.astype('float32')
46 | Q = imgArr.max(axis=(0, 1)) / peak
47 | if Q[0] == 0:
48 | return imgArr
49 | ima_lambda = imgArr / Q
50 | noisy_img = random_state.poisson(lam=ima_lambda)
51 | return noisy_img.astype('float32')
52 |
53 |
54 | def elastic_transform(image, alpha=1000, sigma=30, spline_order=1, mode='nearest', random_state=np.random):
55 | """Elastic deformation of image as described in [Simard2003]_.
56 | .. [Simard2003] Simard, Steinkraus and Platt, "Best Practices for
57 | Convolutional Neural Networks applied to Visual Document Analysis", in
58 | Proc. of the International Conference on Document Analysis and
59 | Recognition, 2003.
60 | """
61 | assert image.ndim == 3
62 | shape = image.shape[:2]
63 |
64 | dx = gaussian_filter((random_state.rand(*shape) * 2 - 1),
65 | sigma, mode="constant", cval=0) * alpha
66 | dy = gaussian_filter((random_state.rand(*shape) * 2 - 1),
67 | sigma, mode="constant", cval=0) * alpha
68 |
69 | x, y = np.meshgrid(np.arange(shape[0]), np.arange(shape[1]), indexing='ij')
70 | indices = [np.reshape(x + dx, (-1, 1)), np.reshape(y + dy, (-1, 1))]
71 | result = np.empty_like(image)
72 | for i in range(image.shape[2]):
73 | result[:, :, i] = map_coordinates(
74 | image[:, :, i], indices, order=spline_order, mode=mode).reshape(shape)
75 | return result
76 |
77 |
78 | class Merge(object):
79 | """Merge a group of images
80 | """
81 |
82 | def __init__(self, axis=-1):
83 | self.axis = axis
84 |
85 | def __call__(self, images):
86 | if isinstance(images, collections.Sequence) or isinstance(images, np.ndarray):
87 | assert all([isinstance(i, np.ndarray)
88 | for i in images]), 'only numpy array is supported'
89 | shapes = [list(i.shape) for i in images]
90 | for s in shapes:
91 | s[self.axis] = None
92 | assert all([s == shapes[0] for s in shapes]
93 | ), 'shapes must be the same except the merge axis'
94 | return np.concatenate(images, axis=self.axis)
95 | else:
96 | raise Exception("obj is not a sequence (list, tuple, etc)")
97 |
98 |
99 | class Split(object):
100 | """Split images into individual arraies
101 | """
102 |
103 | def __init__(self, *slices, **kwargs):
104 | assert isinstance(slices, collections.Sequence)
105 | slices_ = []
106 | for s in slices:
107 | if isinstance(s, collections.Sequence):
108 | slices_.append(slice(*s))
109 | else:
110 | slices_.append(s)
111 | assert all([isinstance(s, slice) for s in slices_]
112 | ), 'slices must be consist of slice instances'
113 | self.slices = slices_
114 | self.axis = kwargs.get('axis', -1)
115 |
116 | def __call__(self, image):
117 | if isinstance(image, np.ndarray):
118 | ret = []
119 | for s in self.slices:
120 | sl = [slice(None)] * image.ndim
121 | sl[self.axis] = s
122 | ret.append(image[sl])
123 | return ret
124 | else:
125 | raise Exception("obj is not an numpy array")
126 |
127 |
128 | class ElasticTransform(object):
129 | """Apply elastic transformation on a numpy.ndarray (H x W x C)
130 | """
131 |
132 | def __init__(self, alpha, sigma):
133 | self.alpha = alpha
134 | self.sigma = sigma
135 |
136 | def __call__(self, image):
137 | if isinstance(self.alpha, collections.Sequence):
138 | alpha = random_num_generator(self.alpha)
139 | else:
140 | alpha = self.alpha
141 | if isinstance(self.sigma, collections.Sequence):
142 | sigma = random_num_generator(self.sigma)
143 | else:
144 | sigma = self.sigma
145 | return elastic_transform(image, alpha=alpha, sigma=sigma)
146 |
147 |
148 | class PoissonSubsampling(object):
149 | """Poisson subsampling on a numpy.ndarray (H x W x C)
150 | """
151 |
152 | def __init__(self, peak, random_state=np.random):
153 | self.peak = peak
154 | self.random_state = random_state
155 |
156 | def __call__(self, image):
157 | if isinstance(self.peak, collections.Sequence):
158 | peak = random_num_generator(
159 | self.peak, random_state=self.random_state)
160 | else:
161 | peak = self.peak
162 | return poisson_downsampling(image, peak, random_state=self.random_state)
163 |
164 |
165 | class AddGaussianNoise(object):
166 | """Add gaussian noise to a numpy.ndarray (H x W x C)
167 | """
168 |
169 | def __init__(self, mean, sigma, random_state=np.random):
170 | self.sigma = sigma
171 | self.mean = mean
172 | self.random_state = random_state
173 |
174 | def __call__(self, image):
175 | if isinstance(self.sigma, collections.Sequence):
176 | sigma = random_num_generator(
177 | self.sigma, random_state=self.random_state)
178 | else:
179 | sigma = self.sigma
180 | if isinstance(self.mean, collections.Sequence, random_state=self.random_state):
181 | mean = random_num_generator(self.mean)
182 | else:
183 | mean = self.mean
184 | row, col, ch = image.shape
185 | gauss = self.random_state.normal(mean, sigma, (row, col, ch))
186 | gauss = gauss.reshape(row, col, ch)
187 | image += gauss
188 | return image
189 |
190 |
191 | class AddSpeckleNoise(object):
192 | """Add speckle noise to a numpy.ndarray (H x W x C)
193 | """
194 |
195 | def __init__(self, mean, sigma, random_state=np.random):
196 | self.sigma = sigma
197 | self.mean = mean
198 | self.random_state = random_state
199 |
200 | def __call__(self, image):
201 | if isinstance(self.sigma, collections.Sequence):
202 | sigma = random_num_generator(
203 | self.sigma, random_state=self.random_state)
204 | else:
205 | sigma = self.sigma
206 | if isinstance(self.mean, collections.Sequence):
207 | mean = random_num_generator(
208 | self.mean, random_state=self.random_state)
209 | else:
210 | mean = self.mean
211 | row, col, ch = image.shape
212 | gauss = self.random_state.normal(mean, sigma, (row, col, ch))
213 | gauss = gauss.reshape(row, col, ch)
214 | image += image * gauss
215 | return image
216 |
217 |
218 | class RandomGaussianBlurring(object):
219 | """Apply gaussian blur to a numpy.ndarray (H x W x C)
220 | """
221 |
222 | def __init__(self, sigma, p=0.2, random_state=np.random):
223 | self.sigma = sigma
224 | self.p = p
225 | self.random_state = random_state
226 |
227 | def __call__(self, image):
228 | if isinstance(self.sigma, collections.Sequence):
229 | sigma = random_num_generator(
230 | self.sigma, random_state=self.random_state)
231 | else:
232 | sigma = self.sigma
233 | if random.random() < self.p:
234 | image = gaussian_filter(image, sigma=(sigma, sigma, 0))
235 | return image
236 |
237 |
238 | class AddGaussianPoissonNoise(object):
239 | """Add poisson noise with gaussian blurred image to a numpy.ndarray (H x W x C)
240 | """
241 |
242 | def __init__(self, sigma, peak, random_state=np.random):
243 | self.sigma = sigma
244 | self.peak = peak
245 | self.random_state = random_state
246 |
247 | def __call__(self, image):
248 | if isinstance(self.sigma, collections.Sequence):
249 | sigma = random_num_generator(
250 | self.sigma, random_state=self.random_state)
251 | else:
252 | sigma = self.sigma
253 | if isinstance(self.peak, collections.Sequence):
254 | peak = random_num_generator(
255 | self.peak, random_state=self.random_state)
256 | else:
257 | peak = self.peak
258 | bg = gaussian_filter(image, sigma=(sigma, sigma, 0))
259 | bg = poisson_downsampling(
260 | bg, peak=peak, random_state=self.random_state)
261 | return image + bg
262 |
263 |
264 | class MaxScaleNumpy(object):
265 | """scale with max and min of each channel of the numpy array i.e.
266 | channel = (channel - mean) / std
267 | """
268 |
269 | def __init__(self, range_min=0.0, range_max=1.0):
270 | self.scale = (range_min, range_max)
271 |
272 | def __call__(self, image):
273 | mn = image.min(axis=(0, 1))
274 | mx = image.max(axis=(0, 1))
275 | return self.scale[0] + (image - mn) * (self.scale[1] - self.scale[0]) / (mx - mn)
276 |
277 |
278 | class MedianScaleNumpy(object):
279 | """Scale with median and mean of each channel of the numpy array i.e.
280 | channel = (channel - mean) / std
281 | """
282 |
283 | def __init__(self, range_min=0.0, range_max=1.0):
284 | self.scale = (range_min, range_max)
285 |
286 | def __call__(self, image):
287 | mn = image.min(axis=(0, 1))
288 | md = np.median(image, axis=(0, 1))
289 | return self.scale[0] + (image - mn) * (self.scale[1] - self.scale[0]) / (md - mn)
290 |
291 |
292 | class NormalizeNumpy(object):
293 | """Normalize each channel of the numpy array i.e.
294 | channel = (channel - mean) / std
295 | """
296 |
297 | def __call__(self, image):
298 | image -= image.mean(axis=(0, 1))
299 | s = image.std(axis=(0, 1))
300 | s[s == 0] = 1.0
301 | image /= s
302 | return image
303 |
304 |
305 | class MutualExclude(object):
306 | """Remove elements from one channel
307 | """
308 |
309 | def __init__(self, exclude_channel, from_channel):
310 | self.from_channel = from_channel
311 | self.exclude_channel = exclude_channel
312 |
313 | def __call__(self, image):
314 | mask = image[:, :, self.exclude_channel] > 0
315 | image[:, :, self.from_channel][mask] = 0
316 | return image
317 |
318 |
319 | class RandomCropNumpy(object):
320 | """Crops the given numpy array at a random location to have a region of
321 | the given size. size can be a tuple (target_height, target_width)
322 | or an integer, in which case the target will be of a square shape (size, size)
323 | """
324 |
325 | def __init__(self, size, random_state=np.random):
326 | if isinstance(size, numbers.Number):
327 | self.size = (int(size), int(size))
328 | else:
329 | self.size = size
330 | self.random_state = random_state
331 |
332 | def __call__(self, img):
333 | w, h = img.shape[:2]
334 | th, tw = self.size
335 | if w == tw and h == th:
336 | return img
337 |
338 | x1 = self.random_state.randint(0, w - tw)
339 | y1 = self.random_state.randint(0, h - th)
340 | return img[x1:x1 + tw, y1: y1 + th, :]
341 |
342 |
343 | class CenterCropNumpy(object):
344 | """Crops the given numpy array at the center to have a region of
345 | the given size. size can be a tuple (target_height, target_width)
346 | or an integer, in which case the target will be of a square shape (size, size)
347 | """
348 |
349 | def __init__(self, size):
350 | if isinstance(size, numbers.Number):
351 | self.size = (int(size), int(size))
352 | else:
353 | self.size = size
354 |
355 | def __call__(self, img):
356 | w, h = img.shape[:2]
357 | th, tw = self.size
358 | x1 = int(round((w - tw) / 2.))
359 | y1 = int(round((h - th) / 2.))
360 | return img[x1:x1 + tw, y1: y1 + th, :]
361 |
362 |
363 | class RandomRotate(object):
364 | """Rotate a PIL.Image or numpy.ndarray (H x W x C) randomly
365 | """
366 |
367 | def __init__(self, angle_range=(0.0, 360.0), axes=(0, 1), mode='reflect', random_state=np.random):
368 | assert isinstance(angle_range, tuple)
369 | self.angle_range = angle_range
370 | self.random_state = random_state
371 | self.axes = axes
372 | self.mode = mode
373 |
374 | def __call__(self, image):
375 | angle = self.random_state.uniform(
376 | self.angle_range[0], self.angle_range[1])
377 | if isinstance(image, np.ndarray):
378 | mi, ma = image.min(), image.max()
379 | image = scipy.ndimage.interpolation.rotate(
380 | image, angle, reshape=False, axes=self.axes, mode=self.mode)
381 | return np.clip(image, mi, ma)
382 | elif isinstance(image, Image.Image):
383 | return image.rotate(angle)
384 | else:
385 | raise Exception('unsupported type')
386 |
387 |
388 | class BilinearResize(object):
389 | """Resize a PIL.Image or numpy.ndarray (H x W x C)
390 | """
391 |
392 | def __init__(self, zoom):
393 | self.zoom = [zoom, zoom, 1]
394 |
395 | def __call__(self, image):
396 | if isinstance(image, np.ndarray):
397 | return scipy.ndimage.interpolation.zoom(image, self.zoom)
398 | elif isinstance(image, Image.Image):
399 | return image.resize(self.size, Image.BILINEAR)
400 | else:
401 | raise Exception('unsupported type')
402 |
403 |
404 | class EnhancedCompose(object):
405 | """Composes several transforms together.
406 | Args:
407 | transforms (List[Transform]): list of transforms to compose.
408 | Example:
409 | >>> transforms.Compose([
410 | >>> transforms.CenterCrop(10),
411 | >>> transforms.ToTensor(),
412 | >>> ])
413 | """
414 |
415 | def __init__(self, transforms):
416 | self.transforms = transforms
417 |
418 | def __call__(self, img):
419 | for t in self.transforms:
420 | if isinstance(t, collections.Sequence):
421 | assert isinstance(img, collections.Sequence) and len(img) == len(
422 | t), "size of image group and transform group does not fit"
423 | tmp_ = []
424 | for i, im_ in enumerate(img):
425 | if callable(t[i]):
426 | tmp_.append(t[i](im_))
427 | else:
428 | tmp_.append(im_)
429 | img = tmp_
430 | elif callable(t):
431 | img = t(img)
432 | elif t is None:
433 | continue
434 | else:
435 | raise Exception('unexpected type')
436 | return img
437 |
438 |
--------------------------------------------------------------------------------
/reid/lib/normalize.py:
--------------------------------------------------------------------------------
1 | import torch
2 | from torch.autograd import Variable
3 | from torch import nn
4 |
5 | class Normalize(nn.Module):
6 |
7 | def __init__(self, power=2):
8 | super(Normalize, self).__init__()
9 | self.power = power
10 |
11 | def forward(self, x):
12 | norm = x.pow(self.power).sum(1, keepdim=True).pow(1./self.power)
13 | out = x.div(norm)
14 | return out
15 |
--------------------------------------------------------------------------------
/reid/lib/utils.py:
--------------------------------------------------------------------------------
1 | class AverageMeter(object):
2 | """Computes and stores the average and current value"""
3 | def __init__(self):
4 | self.reset()
5 |
6 | def reset(self):
7 | self.val = 0
8 | self.avg = 0
9 | self.sum = 0
10 | self.count = 0
11 |
12 | def update(self, val, n=1):
13 | self.val = val
14 | self.sum += val * n
15 | self.count += n
16 | self.avg = self.sum / self.count
17 |
--------------------------------------------------------------------------------
/reid/loss/CamAwareMemory.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function, absolute_import
2 | import torch
3 | import torch.nn.functional as F
4 | from torch import nn, autograd
5 | from torch.autograd import Variable, Function
6 | import numpy as np
7 | import math
8 |
9 | torch.autograd.set_detect_anomaly(True)
10 |
11 |
12 | class ExemplarMemory(Function):
13 | def __init__(self, em, alpha=0.01):
14 | super(ExemplarMemory, self).__init__()
15 | self.em = em
16 | self.alpha = alpha
17 |
18 | def forward(self, inputs, targets):
19 | self.save_for_backward(inputs, targets)
20 | outputs = inputs.mm(self.em.t())
21 | return outputs
22 |
23 | def backward(self, grad_outputs):
24 | inputs, targets = self.saved_tensors
25 | grad_inputs = None
26 | if self.needs_input_grad[0]:
27 | grad_inputs = grad_outputs.mm(self.em)
28 | for x, y in zip(inputs, targets):
29 | self.em[y] = self.alpha * self.em[y] + (1.0 - self.alpha) * x
30 | self.em[y] /= self.em[y].norm()
31 | return grad_inputs, None
32 |
33 |
34 | class CAPMemory(nn.Module):
35 | def __init__(self, beta=0.05, alpha=0.01, all_img_cams='', crosscam_epoch=5, bg_knn=50):
36 | super(CAPMemory, self).__init__()
37 | self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
38 | self.alpha = alpha # Memory update rate
39 | self.beta = beta # Temperature factor
40 | self.all_img_cams = torch.tensor(all_img_cams).to(torch.device('cuda'))
41 | self.unique_cams = torch.unique(self.all_img_cams)
42 | self.all_pseudo_label = ''
43 | self.crosscam_epoch = crosscam_epoch
44 | self.bg_knn = bg_knn
45 |
46 | def forward(self, features, targets, cams=None, epoch=None, all_pseudo_label='',
47 | batch_ind=-1, init_intra_id_feat=''):
48 |
49 | loss = torch.tensor([0.]).to(device='cuda')
50 | self.all_pseudo_label = all_pseudo_label
51 | self.init_intra_id_feat = init_intra_id_feat
52 |
53 | loss = self.loss_using_pseudo_percam_proxy(features, targets, cams, batch_ind, epoch)
54 |
55 | return loss
56 |
57 |
58 | def loss_using_pseudo_percam_proxy(self, features, targets, cams, batch_ind, epoch):
59 | if batch_ind == 0:
60 | # initialize proxy memory
61 | self.percam_memory = []
62 | self.memory_class_mapper = []
63 | self.concate_intra_class = []
64 | for cc in self.unique_cams:
65 | percam_ind = torch.nonzero(self.all_img_cams == cc).squeeze(-1)
66 | uniq_class = torch.unique(self.all_pseudo_label[percam_ind])
67 | uniq_class = uniq_class[uniq_class >= 0]
68 | self.concate_intra_class.append(uniq_class)
69 | cls_mapper = {int(uniq_class[j]): j for j in range(len(uniq_class))}
70 | self.memory_class_mapper.append(cls_mapper) # from pseudo label to index under each camera
71 |
72 | if len(self.init_intra_id_feat) > 0:
73 | # print('initializing ID memory from updated embedding features...')
74 | proto_memory = self.init_intra_id_feat[cc]
75 | proto_memory = proto_memory.to(torch.device('cuda'))
76 | self.percam_memory.append(proto_memory.detach())
77 | self.concate_intra_class = torch.cat(self.concate_intra_class)
78 |
79 | if epoch >= self.crosscam_epoch:
80 | percam_tempV = []
81 | for ii in self.unique_cams:
82 | percam_tempV.append(self.percam_memory[ii].detach().clone())
83 | percam_tempV = torch.cat(percam_tempV, dim=0).to(torch.device('cuda'))
84 |
85 | loss = torch.tensor([0.]).to(self.device)
86 | for cc in torch.unique(cams):
87 | inds = torch.nonzero(cams == cc).squeeze(-1)
88 | percam_targets = self.all_pseudo_label[targets[inds]]
89 | percam_feat = features[inds]
90 |
91 | # intra-camera loss
92 | mapped_targets = [self.memory_class_mapper[cc][int(k)] for k in percam_targets]
93 | mapped_targets = torch.tensor(mapped_targets).to(torch.device('cuda'))
94 | percam_inputs = ExemplarMemory(self.percam_memory[cc], alpha=self.alpha)(percam_feat, mapped_targets)
95 | percam_inputs /= self.beta # similarity score before softmax
96 | loss += F.cross_entropy(percam_inputs, mapped_targets)
97 |
98 | # global loss
99 | if epoch >= self.crosscam_epoch:
100 | associate_loss = 0
101 | target_inputs = percam_feat.mm(percam_tempV.t().clone())
102 | temp_sims = target_inputs.detach().clone()
103 | target_inputs /= self.beta
104 |
105 | for k in range(len(percam_feat)):
106 | ori_asso_ind = torch.nonzero(self.concate_intra_class == percam_targets[k]).squeeze(-1)
107 | temp_sims[k, ori_asso_ind] = -10000.0 # mask out positive
108 | sel_ind = torch.sort(temp_sims[k])[1][-self.bg_knn:]
109 | concated_input = torch.cat((target_inputs[k, ori_asso_ind], target_inputs[k, sel_ind]), dim=0)
110 | concated_target = torch.zeros((len(concated_input)), dtype=concated_input.dtype).to(torch.device('cuda'))
111 | concated_target[0:len(ori_asso_ind)] = 1.0 / len(ori_asso_ind)
112 | associate_loss += -1 * (F.log_softmax(concated_input.unsqueeze(0), dim=1) * concated_target.unsqueeze(0)).sum()
113 | loss += 0.5 * associate_loss / len(percam_feat)
114 | return loss
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/reid/loss/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from .CamAwareMemory import CAPMemory
4 |
5 | __all__ = [
6 | 'CAPMemory',
7 | ]
8 |
--------------------------------------------------------------------------------
/reid/loss/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/loss/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/loss/__pycache__/circle_loss.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/loss/__pycache__/circle_loss.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/loss/__pycache__/contrast_loss.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/loss/__pycache__/contrast_loss.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/loss/__pycache__/cross_entropy_loss.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/loss/__pycache__/cross_entropy_loss.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/loss/__pycache__/invariance.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/loss/__pycache__/invariance.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/models/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from .resnet import *
4 |
5 | __factory = {
6 | 'resnet18': resnet18,
7 | 'resnet34': resnet34,
8 | 'resnet50': resnet50,
9 | 'resnet101': resnet101,
10 | 'resnet152': resnet152,
11 | 'resnetV2': ResNetV2, # new added
12 | 'resnetV3': ResNetV3, # new added
13 | }
14 |
15 |
16 | def names():
17 | return sorted(__factory.keys())
18 |
19 |
20 | def create(name, *args, **kwargs):
21 | """
22 | Create a model instance.
23 |
24 | Parameters
25 | ----------
26 | name : str
27 | Model name. Can be one of 'inception', 'resnet18', 'resnet34',
28 | 'resnet50', 'resnet101', and 'resnet152'.
29 | pretrained : bool, optional
30 | Only applied for 'resnet*' models. If True, will use ImageNet pretrained
31 | model. Default: True
32 | cut_at_pooling : bool, optional
33 | If True, will cut the model before the last global pooling layer and
34 | ignore the remaining kwargs. Default: False
35 | num_features : int, optional
36 | If positive, will append a Linear layer after the global pooling layer,
37 | with this number of output units, followed by a BatchNorm layer.
38 | Otherwise these layers will not be appended. Default: 256 for
39 | 'inception', 0 for 'resnet*'
40 | norm : bool, optional
41 | If True, will normalize the feature to be unit L2-norm for each sample.
42 | Otherwise will append a ReLU layer after the above Linear layer if
43 | num_features > 0. Default: False
44 | dropout : float, optional
45 | If positive, will append a Dropout layer with this dropout rate.
46 | Default: 0
47 | num_classes : int, optional
48 | If positive, will append a Linear layer at the end as the classifier
49 | with this number of output units. Default: 0
50 | """
51 | if name not in __factory:
52 | raise KeyError("Unknown model:", name)
53 | return __factory[name](*args, **kwargs)
54 |
--------------------------------------------------------------------------------
/reid/models/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/models/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/models/__pycache__/dsbn.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/models/__pycache__/dsbn.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/models/__pycache__/pooling.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/models/__pycache__/pooling.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/models/__pycache__/resnet.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/models/__pycache__/resnet.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/models/__pycache__/resnet_ibn_a.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/models/__pycache__/resnet_ibn_a.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/models/__pycache__/stb_net.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/models/__pycache__/stb_net.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/models/dsbn.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 |
4 | # Domain-specific BatchNorm
5 |
6 | class DSBN2d(nn.Module):
7 | def __init__(self, planes):
8 | super(DSBN2d, self).__init__()
9 | self.num_features = planes
10 | self.BN_S = nn.BatchNorm2d(planes)
11 | self.BN_T = nn.BatchNorm2d(planes)
12 |
13 | def forward(self, x):
14 | if (not self.training):
15 | #return self.BN_S(x)
16 | return self.BN_T(x)
17 |
18 | bs = x.size(0)
19 | assert (bs%2==0)
20 | split = torch.split(x, int(bs/2), 0)
21 | out1 = self.BN_S(split[0].contiguous())
22 | out2 = self.BN_T(split[1].contiguous())
23 | out = torch.cat((out1, out2), 0)
24 | return out
25 |
26 | class DSBN1d(nn.Module):
27 | def __init__(self, planes):
28 | super(DSBN1d, self).__init__()
29 | self.num_features = planes
30 | self.BN_S = nn.BatchNorm1d(planes)
31 | self.BN_T = nn.BatchNorm1d(planes)
32 |
33 | def forward(self, x):
34 | if (not self.training):
35 | #return self.BN_S(x)
36 | return self.BN_T(x)
37 |
38 | bs = x.size(0)
39 | assert (bs%2==0)
40 | split = torch.split(x, int(bs/2), 0)
41 | out1 = self.BN_S(split[0].contiguous())
42 | out2 = self.BN_T(split[1].contiguous())
43 | out = torch.cat((out1, out2), 0)
44 | return out
45 |
46 | def convert_dsbn(model):
47 | for _, (child_name, child) in enumerate(model.named_children()):
48 | assert(not next(model.parameters()).is_cuda)
49 | if isinstance(child, nn.BatchNorm2d):
50 | m = DSBN2d(child.num_features)
51 | m.BN_S.load_state_dict(child.state_dict())
52 | m.BN_T.load_state_dict(child.state_dict())
53 | setattr(model, child_name, m)
54 | elif isinstance(child, nn.BatchNorm1d):
55 | m = DSBN1d(child.num_features)
56 | m.BN_S.load_state_dict(child.state_dict())
57 | m.BN_T.load_state_dict(child.state_dict())
58 | setattr(model, child_name, m)
59 | else:
60 | convert_dsbn(child)
61 |
62 | def convert_bn(model, use_target=True):
63 | for _, (child_name, child) in enumerate(model.named_children()):
64 | assert(not next(model.parameters()).is_cuda)
65 | if isinstance(child, DSBN2d):
66 | m = nn.BatchNorm2d(child.num_features)
67 | if use_target:
68 | m.load_state_dict(child.BN_T.state_dict())
69 | else:
70 | m.load_state_dict(child.BN_S.state_dict())
71 | setattr(model, child_name, m)
72 | elif isinstance(child, DSBN1d):
73 | m = nn.BatchNorm1d(child.num_features)
74 | if use_target:
75 | m.load_state_dict(child.BN_T.state_dict())
76 | else:
77 | m.load_state_dict(child.BN_S.state_dict())
78 | setattr(model, child_name, m)
79 | else:
80 | convert_bn(child, use_target=use_target)
81 |
82 |
--------------------------------------------------------------------------------
/reid/models/pooling.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | """
3 | @author: l1aoxingyu
4 | @contact: sherlockliao01@gmail.com
5 | """
6 |
7 | import torch
8 | import torch.nn.functional as F
9 | from torch import nn
10 |
11 |
12 | class Flatten(nn.Module):
13 | def forward(self, input):
14 | return input.view(input.size(0), -1)
15 |
16 |
17 | class GeneralizedMeanPooling(nn.Module):
18 | r"""Applies a 2D power-average adaptive pooling over an input signal composed of several input planes.
19 | The function computed is: :math:`f(X) = pow(sum(pow(X, p)), 1/p)`
20 | - At p = infinity, one gets Max Pooling
21 | - At p = 1, one gets Average Pooling
22 | The output is of size H x W, for any input size.
23 | The number of output features is equal to the number of input planes.
24 | Args:
25 | output_size: the target output size of the image of the form H x W.
26 | Can be a tuple (H, W) or a single H for a square image H x H
27 | H and W can be either a ``int``, or ``None`` which means the size will
28 | be the same as that of the input.
29 | """
30 |
31 | def __init__(self, norm, output_size=1, eps=1e-6):
32 | super(GeneralizedMeanPooling, self).__init__()
33 | assert norm > 0
34 | self.p = float(norm)
35 | self.output_size = output_size
36 | self.eps = eps
37 |
38 | def forward(self, x):
39 | x = x.clamp(min=self.eps).pow(self.p)
40 | return torch.nn.functional.adaptive_avg_pool2d(x, self.output_size).pow(1. / self.p)
41 |
42 | def __repr__(self):
43 | return self.__class__.__name__ + '(' \
44 | + str(self.p) + ', ' \
45 | + 'output_size=' + str(self.output_size) + ')'
46 |
47 |
48 | class GeneralizedMeanPoolingP(GeneralizedMeanPooling):
49 | """ Same, but norm is trainable
50 | """
51 |
52 | def __init__(self, norm=3, output_size=1, eps=1e-6):
53 | super(GeneralizedMeanPoolingP, self).__init__(norm, output_size, eps)
54 | self.p = nn.Parameter(torch.ones(1) * norm)
55 |
56 |
57 | class AdaptiveAvgMaxPool2d(nn.Module):
58 | def __init__(self):
59 | super(AdaptiveAvgMaxPool2d, self).__init__()
60 | self.avgpool = FastGlobalAvgPool2d()
61 |
62 | def forward(self, x):
63 | x_avg = self.avgpool(x, self.output_size)
64 | x_max = F.adaptive_max_pool2d(x, 1)
65 | x = x_max + x_avg
66 | return x
67 |
68 |
69 | class FastGlobalAvgPool2d(nn.Module):
70 | def __init__(self, flatten=False):
71 | super(FastGlobalAvgPool2d, self).__init__()
72 | self.flatten = flatten
73 |
74 | def forward(self, x):
75 | if self.flatten:
76 | in_size = x.size()
77 | return x.view((in_size[0], in_size[1], -1)).mean(dim=2)
78 | else:
79 | return x.view(x.size(0), x.size(1), -1).mean(-1).view(x.size(0), x.size(1), 1, 1)
80 |
81 |
--------------------------------------------------------------------------------
/reid/models/resnet.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from torch import nn
4 | from torch.nn import functional as F
5 | from torch.nn import init
6 | import torchvision
7 | from reid.lib.normalize import Normalize
8 |
9 |
10 | __all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101',
11 | 'resnet152', 'ResNetV2', 'ResNetV3']
12 |
13 |
14 | class ResNet(nn.Module):
15 | __factory = {
16 | 18: torchvision.models.resnet18,
17 | 34: torchvision.models.resnet34,
18 | 50: torchvision.models.resnet50,
19 | 101: torchvision.models.resnet101,
20 | 152: torchvision.models.resnet152,
21 | }
22 |
23 | def __init__(self, depth, pretrained=True, cut_at_pooling=False,
24 | num_features=0, norm=False, dropout=0, num_classes=0):
25 | super(ResNet, self).__init__()
26 |
27 | self.depth = depth
28 | self.pretrained = pretrained
29 | self.cut_at_pooling = cut_at_pooling
30 |
31 | # Construct base (pretrained) resnet
32 | if depth not in ResNet.__factory:
33 | raise KeyError("Unsupported depth:", depth)
34 | self.base = ResNet.__factory[depth](pretrained=pretrained)
35 |
36 | # Fix layers [conv1 ~ layer2]
37 | fixed_names = []
38 | for name, module in self.base._modules.items():
39 | if name == "layer3":
40 | # assert fixed_names == ["conv1", "bn1", "relu", "maxpool", "layer1", "layer2"]
41 | break
42 | fixed_names.append(name)
43 | for param in module.parameters():
44 | param.requires_grad = False
45 |
46 | if not self.cut_at_pooling:
47 | self.num_features = num_features
48 | self.norm = norm
49 | self.dropout = dropout
50 | self.has_embedding = num_features > 0
51 | self.num_classes = num_classes
52 | #self.num_triplet_features = num_triplet_features
53 | #self.l2norm = Normalize(2)
54 |
55 | out_planes = self.base.fc.in_features
56 |
57 | # Append new layers
58 | if self.has_embedding:
59 | self.feat = nn.Linear(out_planes, self.num_features)
60 | self.feat_bn = nn.BatchNorm1d(self.num_features)
61 | init.kaiming_normal_(self.feat.weight, mode='fan_out')
62 | init.constant_(self.feat.bias, 0)
63 | init.constant_(self.feat_bn.weight, 1)
64 | init.constant_(self.feat_bn.bias, 0)
65 | else:
66 | # Change the num_features to CNN output channels
67 | self.num_features = out_planes
68 | if self.dropout >= 0:
69 | self.drop = nn.Dropout(self.dropout)
70 | if self.num_classes > 0:
71 | self.classifier = nn.Linear(self.num_features, self.num_classes)
72 | init.normal_(self.classifier.weight, std=0.001)
73 | init.constant_(self.classifier.bias, 0)
74 |
75 | if not self.pretrained:
76 | self.reset_params()
77 |
78 | def forward(self, x, output_feature=None):
79 | for name, module in self.base._modules.items():
80 | if name == 'avgpool':
81 | break
82 | else:
83 | x = module(x)
84 |
85 | if self.cut_at_pooling:
86 | return x
87 |
88 | x = F.avg_pool2d(x, x.size()[2:])
89 | x = x.view(x.size(0), -1)
90 |
91 | if output_feature == 'pool5':
92 | x = F.normalize(x)
93 | return x
94 |
95 | if self.has_embedding:
96 | x = self.feat(x)
97 | x = self.feat_bn(x)
98 | tgt_feat = F.normalize(x)
99 | tgt_feat = self.drop(tgt_feat)
100 | if output_feature == 'tgt_feat':
101 | return tgt_feat
102 | if self.norm:
103 | x = F.normalize(x)
104 | elif self.has_embedding:
105 | x = F.relu(x)
106 | if self.dropout > 0:
107 | x = self.drop(x)
108 | if self.num_classes > 0:
109 | x = self.classifier(x)
110 | return x
111 |
112 | def reset_params(self):
113 | for m in self.modules():
114 | if isinstance(m, nn.Conv2d):
115 | init.kaiming_normal(m.weight, mode='fan_out')
116 | if m.bias is not None:
117 | init.constant(m.bias, 0)
118 | elif isinstance(m, nn.BatchNorm2d):
119 | init.constant(m.weight, 1)
120 | init.constant(m.bias, 0)
121 | elif isinstance(m, nn.Linear):
122 | init.normal(m.weight, std=0.001)
123 | if m.bias is not None:
124 | init.constant(m.bias, 0)
125 |
126 |
127 | def resnet18(**kwargs):
128 | return ResNet(18, **kwargs)
129 |
130 |
131 | def resnet34(**kwargs):
132 | return ResNet(34, **kwargs)
133 |
134 |
135 | def resnet50(**kwargs):
136 | return ResNet(50, **kwargs)
137 |
138 |
139 | def resnet101(**kwargs):
140 | return ResNet(101, **kwargs)
141 |
142 |
143 | def resnet152(**kwargs):
144 | return ResNet(152, **kwargs)
145 |
146 |
147 | def weights_init_kaiming(m):
148 | classname = m.__class__.__name__
149 | if classname.find('Linear') != -1:
150 | nn.init.kaiming_normal_(m.weight, a=0, mode='fan_out')
151 | nn.init.constant_(m.bias, 0.0)
152 | elif classname.find('Conv') != -1:
153 | nn.init.kaiming_normal_(m.weight, a=0, mode='fan_in')
154 | if m.bias is not None:
155 | nn.init.constant_(m.bias, 0.0)
156 | elif classname.find('BatchNorm') != -1:
157 | if m.affine:
158 | nn.init.constant_(m.weight, 1.0)
159 | nn.init.constant_(m.bias, 0.0)
160 |
161 |
162 | def weights_init_classifier(m):
163 | classname = m.__class__.__name__
164 | if classname.find('Linear') != -1:
165 | nn.init.normal_(m.weight, std=0.001)
166 | if m.bias:
167 | nn.init.constant_(m.bias, 0.0)
168 |
169 |
170 | class ResNetV2(nn.Module):
171 |
172 | def __init__(self, pretrained=True, cut_at_pooling=False,
173 | num_features=0, norm=False, dropout=0, num_classes=0):
174 | super(ResNetV2, self).__init__()
175 |
176 | self.pretrained = pretrained
177 | self.cut_at_pooling = cut_at_pooling
178 |
179 | # Construct base (pretrained) resnet
180 | self.base = torchvision.models.resnet50(pretrained=True)
181 |
182 | # change the stride of the last residual block (new 1)
183 | self.base.layer4[0].conv2.stride = (1, 1)
184 | self.base.layer4[0].downsample[0].stride = (1, 1)
185 |
186 | # Fix layers [conv1 ~ layer2]
187 | fixed_names = []
188 | for name, module in self.base._modules.items():
189 | if name == "layer3":
190 | # assert fixed_names == ["conv1", "bn1", "relu", "maxpool", "layer1", "layer2"]
191 | break
192 | fixed_names.append(name)
193 | for param in module.parameters():
194 | param.requires_grad = False
195 |
196 | if not self.cut_at_pooling:
197 | self.num_features = num_features
198 | self.norm = norm
199 | self.dropout = dropout
200 | self.has_embedding = num_features > 0
201 | self.num_classes = num_classes
202 | #self.num_triplet_features = num_triplet_features
203 |
204 | out_planes = self.base.fc.in_features
205 |
206 | # Append new layers
207 | # add BNNeck after GAP (new 2)
208 | #self.bottleneck = nn.BatchNorm1d(out_planes)
209 | #self.bottleneck.bias.requires_grad_(False) # no shift
210 | #self.bottleneck.apply(weights_init_kaiming)
211 |
212 | if self.has_embedding:
213 | self.feat = nn.Linear(out_planes, self.num_features)
214 | self.feat_bn = nn.BatchNorm1d(self.num_features)
215 | init.kaiming_normal_(self.feat.weight, mode='fan_out')
216 | init.constant_(self.feat.bias, 0)
217 | init.constant_(self.feat_bn.weight, 1)
218 | init.constant_(self.feat_bn.bias, 0)
219 | else:
220 | # Change the num_features to CNN output channels
221 | self.num_features = out_planes
222 | if self.dropout >= 0:
223 | self.drop = nn.Dropout(self.dropout)
224 | if self.num_classes > 0:
225 | self.classifier = nn.Linear(self.num_features, self.num_classes)
226 | #self.classifier = nn.Linear(self.num_features, self.num_classes, bias=False) # (new 3)
227 | init.normal_(self.classifier.weight, std=0.001)
228 | init.constant_(self.classifier.bias, 0)
229 |
230 | if not self.pretrained:
231 | self.reset_params()
232 |
233 | def forward(self, x, output_feature=None):
234 | for name, module in self.base._modules.items():
235 | if name == 'avgpool':
236 | break
237 | else:
238 | x = module(x)
239 |
240 | if self.cut_at_pooling:
241 | return x
242 |
243 | x = F.avg_pool2d(x, x.size()[2:])
244 | x = x.view(x.size(0), -1)
245 | trip_feat = x
246 | #x = self.bottleneck(x) # add BNNeck (new)
247 |
248 | if output_feature == 'pool5': # for evaluation
249 | x = F.normalize(x) # BNNeck feature
250 | return x
251 |
252 | if self.has_embedding:
253 | x = self.feat(x)
254 | x = self.feat_bn(x)
255 | tgt_feat = F.normalize(x)
256 | tgt_feat = self.drop(tgt_feat)
257 | if output_feature == 'tgt_feat': # for memory bank
258 | return tgt_feat, trip_feat
259 | if self.norm: # False
260 | x = F.normalize(x)
261 | elif self.has_embedding:
262 | x = F.relu(x)
263 | if self.dropout > 0:
264 | x = self.drop(x)
265 | if self.num_classes > 0:
266 | x = self.classifier(x)
267 | return x, trip_feat # x for FC classification, trip_feat for triplet loss
268 |
269 | def reset_params(self):
270 | for m in self.modules():
271 | if isinstance(m, nn.Conv2d):
272 | init.kaiming_normal(m.weight, mode='fan_out')
273 | if m.bias is not None:
274 | init.constant(m.bias, 0)
275 | elif isinstance(m, nn.BatchNorm2d):
276 | init.constant(m.weight, 1)
277 | init.constant(m.bias, 0)
278 | elif isinstance(m, nn.Linear):
279 | init.normal(m.weight, std=0.001)
280 | if m.bias is not None:
281 | init.constant(m.bias, 0)
282 |
283 |
284 | class ResNetV3(nn.Module):
285 | def __init__(self, pretrained=True, num_features=0, dropout=0, num_classes=0):
286 | super(ResNetV3, self).__init__()
287 |
288 | self.pretrained = pretrained
289 |
290 | # Construct base (pretrained) resnet
291 | self.base = torchvision.models.resnet50(pretrained=True)
292 |
293 | # change the stride of the last residual block (new 1)
294 | self.base.layer4[0].conv2.stride = (1, 1)
295 | self.base.layer4[0].downsample[0].stride = (1, 1)
296 |
297 | # Fix layers [conv1 ~ layer2]
298 | fixed_names = []
299 | for name, module in self.base._modules.items():
300 | if name == "layer3":
301 | # assert fixed_names == ["conv1", "bn1", "relu", "maxpool", "layer1", "layer2"]
302 | break
303 | fixed_names.append(name)
304 | for param in module.parameters():
305 | param.requires_grad = False
306 |
307 | self.num_features = num_features
308 | self.dropout = dropout
309 | self.has_embedding = num_features > 0
310 | self.num_classes = num_classes
311 | # self.num_triplet_features = num_triplet_features
312 |
313 | out_planes = self.base.fc.in_features
314 |
315 | # Append new layers
316 | # add BNNeck after GAP (new 2)
317 | self.bottleneck = nn.BatchNorm1d(out_planes)
318 | self.bottleneck.bias.requires_grad_(False) # no shift
319 | self.bottleneck.apply(weights_init_kaiming)
320 |
321 | if self.has_embedding:
322 | self.feat = nn.Linear(out_planes, self.num_features)
323 | self.feat_bn = nn.BatchNorm1d(self.num_features)
324 | init.kaiming_normal_(self.feat.weight, mode='fan_out')
325 | init.constant_(self.feat.bias, 0)
326 | init.constant_(self.feat_bn.weight, 1)
327 | init.constant_(self.feat_bn.bias, 0)
328 | else:
329 | # Change the num_features to CNN output channels
330 | self.num_features = out_planes
331 | if self.dropout >= 0:
332 | self.drop = nn.Dropout(self.dropout)
333 | if self.num_classes > 0:
334 | self.classifier = nn.Linear(2048, self.num_classes, bias=False)
335 | self.classifier.apply(weights_init_classifier)
336 |
337 | if not self.pretrained:
338 | self.reset_params()
339 |
340 | def forward(self, x, output_feature=None):
341 | for name, module in self.base._modules.items():
342 | if name == 'avgpool':
343 | break
344 | else:
345 | x = module(x)
346 |
347 | x = F.avg_pool2d(x, x.size()[2:])
348 | x = x.view(x.size(0), -1) # GAP feature
349 | bn = self.bottleneck(x)
350 |
351 | if output_feature == 'pool5': # for evaluation
352 | return bn
353 |
354 | if self.has_embedding:
355 | embed_feat = self.feat(bn)
356 | embed_feat = self.feat_bn(embed_feat)
357 | embed_feat = F.normalize(embed_feat)
358 | embed_feat = self.drop(embed_feat)
359 | if output_feature == 'tgt_feat': # for memory bank
360 | return embed_feat, x
361 |
362 | if self.num_classes > 0: # src dataset classification
363 | cls_score = self.classifier(bn)
364 | return cls_score
365 |
366 | def reset_params(self):
367 | for m in self.modules():
368 | if isinstance(m, nn.Conv2d):
369 | init.kaiming_normal(m.weight, mode='fan_out')
370 | if m.bias is not None:
371 | init.constant(m.bias, 0)
372 | elif isinstance(m, nn.BatchNorm2d):
373 | init.constant(m.weight, 1)
374 | init.constant(m.bias, 0)
375 | elif isinstance(m, nn.Linear):
376 | init.normal(m.weight, std=0.001)
377 | if m.bias is not None:
378 | init.constant(m.bias, 0)
379 |
380 |
381 |
382 |
--------------------------------------------------------------------------------
/reid/models/resnet_ibn_a.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | import torch
3 | import torch.nn as nn
4 | import torch.nn.functional as F
5 | import math
6 | import torch.utils.model_zoo as model_zoo
7 | from .pooling import GeneralizedMeanPoolingP
8 |
9 |
10 | __all__ = ['ResNet', 'resnet50_ibn_a', 'resnet101_ibn_a']
11 |
12 |
13 | model_urls = {
14 | 'ibn_resnet50a': './logs/pretrained/resnet50_ibn_a.pth.tar',
15 | 'ibn_resnet101a': './logs/pretrained/resnet101_ibn_a.pth.tar',
16 | }
17 |
18 | def weights_init_kaiming(m):
19 | classname = m.__class__.__name__
20 | if classname.find('Linear') != -1:
21 | nn.init.kaiming_normal_(m.weight, a=0, mode='fan_out')
22 | nn.init.constant_(m.bias, 0.0)
23 | elif classname.find('Conv') != -1:
24 | nn.init.kaiming_normal_(m.weight, a=0, mode='fan_in')
25 | if m.bias is not None:
26 | nn.init.constant_(m.bias, 0.0)
27 | elif classname.find('BatchNorm') != -1:
28 | if m.affine:
29 | nn.init.constant_(m.weight, 1.0)
30 | nn.init.constant_(m.bias, 0.0)
31 |
32 |
33 | def conv3x3(in_planes, out_planes, stride=1):
34 | "3x3 convolution with padding"
35 | return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
36 | padding=1, bias=False)
37 |
38 |
39 | class BasicBlock(nn.Module):
40 | expansion = 1
41 |
42 | def __init__(self, inplanes, planes, stride=1, downsample=None):
43 | super(BasicBlock, self).__init__()
44 | self.conv1 = conv3x3(inplanes, planes, stride)
45 | self.bn1 = nn.BatchNorm2d(planes)
46 | self.relu = nn.ReLU(inplace=True)
47 | self.conv2 = conv3x3(planes, planes)
48 | self.bn2 = nn.BatchNorm2d(planes)
49 | self.downsample = downsample
50 | self.stride = stride
51 |
52 | def forward(self, x):
53 | residual = x
54 |
55 | out = self.conv1(x)
56 | out = self.bn1(out)
57 | out = self.relu(out)
58 |
59 | out = self.conv2(out)
60 | out = self.bn2(out)
61 |
62 | if self.downsample is not None:
63 | residual = self.downsample(x)
64 |
65 | out += residual
66 | out = self.relu(out)
67 |
68 | return out
69 |
70 |
71 | class IBN(nn.Module):
72 | def __init__(self, planes):
73 | super(IBN, self).__init__()
74 | half1 = int(planes/2)
75 | self.half = half1
76 | half2 = planes - half1
77 | self.IN = nn.InstanceNorm2d(half1, affine=True)
78 | self.BN = nn.BatchNorm2d(half2)
79 |
80 | def forward(self, x):
81 | split = torch.split(x, self.half, 1)
82 | out1 = self.IN(split[0].contiguous())
83 | out2 = self.BN(split[1].contiguous())
84 | out = torch.cat((out1, out2), 1)
85 | return out
86 |
87 |
88 | class Bottleneck(nn.Module):
89 | expansion = 4
90 |
91 | def __init__(self, inplanes, planes, ibn=False, stride=1, downsample=None):
92 | super(Bottleneck, self).__init__()
93 | self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
94 | if ibn:
95 | self.bn1 = IBN(planes)
96 | else:
97 | self.bn1 = nn.BatchNorm2d(planes)
98 | self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
99 | padding=1, bias=False)
100 | self.bn2 = nn.BatchNorm2d(planes)
101 | self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False)
102 | self.bn3 = nn.BatchNorm2d(planes * self.expansion)
103 | self.relu = nn.ReLU(inplace=True)
104 | self.downsample = downsample
105 | self.stride = stride
106 |
107 | def forward(self, x):
108 | residual = x
109 |
110 | out = self.conv1(x)
111 | out = self.bn1(out)
112 | out = self.relu(out)
113 |
114 | out = self.conv2(out)
115 | out = self.bn2(out)
116 | out = self.relu(out)
117 |
118 | out = self.conv3(out)
119 | out = self.bn3(out)
120 |
121 | if self.downsample is not None:
122 | residual = self.downsample(x)
123 |
124 | out += residual
125 | out = self.relu(out)
126 |
127 | return out
128 |
129 |
130 | class ResNet(nn.Module):
131 |
132 | def __init__(self, block, layers, num_classes=1000, pool_type='avgpool'):
133 | scale = 64
134 | self.inplanes = scale
135 | super(ResNet, self).__init__()
136 | self.conv1 = nn.Conv2d(3, scale, kernel_size=7, stride=2, padding=3,
137 | bias=False)
138 | self.bn1 = nn.BatchNorm2d(scale)
139 | self.relu = nn.ReLU(inplace=True)
140 | self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
141 | self.layer1 = self._make_layer(block, scale, layers[0])
142 | self.layer2 = self._make_layer(block, scale*2, layers[1], stride=2)
143 | self.layer3 = self._make_layer(block, scale*4, layers[2], stride=2)
144 | self.layer4 = self._make_layer(block, scale*8, layers[3], stride=2)
145 |
146 | self.layer4[0].conv2.stride = (1,1) # new add by wml
147 | self.layer4[0].downsample[0].stride = (1,1) # new add by wml
148 |
149 | #self.avgpool = nn.AvgPool2d(7)
150 | #self.avgpool = nn.AdaptiveAvgPool2d(1)
151 | if pool_type == 'avgpool':
152 | self.global_pool = nn.AdaptiveAvgPool2d(1)
153 | if pool_type == 'gempool':
154 | self.global_pool = GeneralizedMeanPoolingP() # default initial norm=3
155 |
156 | self.num_classes = num_classes
157 | if self.num_classes > 0:
158 | self.fc = nn.Linear(scale * 8 * block.expansion, num_classes)
159 |
160 | self.bottleneck = nn.BatchNorm1d(2048)
161 | self.bottleneck.bias.requires_grad_(False)
162 | self.bottleneck.apply(weights_init_kaiming)
163 |
164 | for m in self.modules():
165 | if isinstance(m, nn.Conv2d):
166 | n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
167 | m.weight.data.normal_(0, math.sqrt(2. / n))
168 | elif isinstance(m, nn.BatchNorm2d):
169 | m.weight.data.fill_(1)
170 | m.bias.data.zero_()
171 | elif isinstance(m, nn.InstanceNorm2d):
172 | m.weight.data.fill_(1)
173 | m.bias.data.zero_()
174 |
175 | def _make_layer(self, block, planes, blocks, stride=1):
176 | downsample = None
177 | if stride != 1 or self.inplanes != planes * block.expansion:
178 | downsample = nn.Sequential(
179 | nn.Conv2d(self.inplanes, planes * block.expansion,
180 | kernel_size=1, stride=stride, bias=False),
181 | nn.BatchNorm2d(planes * block.expansion),
182 | )
183 |
184 | layers = []
185 | ibn = True
186 | if planes == 512:
187 | ibn = False
188 | layers.append(block(self.inplanes, planes, ibn, stride, downsample))
189 | self.inplanes = planes * block.expansion
190 | for i in range(1, blocks):
191 | layers.append(block(self.inplanes, planes, ibn))
192 |
193 | return nn.Sequential(*layers)
194 |
195 | def forward(self, x):
196 | x = self.conv1(x)
197 | x = self.bn1(x)
198 | x = self.relu(x)
199 | x = self.maxpool(x)
200 |
201 | x = self.layer1(x)
202 | x = self.layer2(x)
203 | x = self.layer3(x)
204 | x = self.layer4(x)
205 |
206 | features = self.global_pool(x)
207 | features = features.view(features.size(0), -1)
208 |
209 | bn = self.bottleneck(features)
210 |
211 | bn = F.normalize(bn, p=2, dim=1)
212 |
213 | if not self.training:
214 | return bn
215 |
216 | if self.num_classes == 0:
217 | return features, bn
218 |
219 | cls_score = self.fc(bn)
220 | return features, cls_score
221 |
222 |
223 | def resnet50_ibn_a(pretrained=False, **kwargs):
224 | """Constructs a ResNet-50 model.
225 | Args:
226 | pretrained (bool): If True, returns a model pre-trained on ImageNet
227 | """
228 | model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)
229 | if pretrained:
230 | state_dict = torch.load(model_urls['ibn_resnet50a'], map_location=torch.device('cpu'))['state_dict']
231 | state_dict = remove_module_key(state_dict)
232 | # remove classifier weight
233 | filtered_trained_dict = {k: v for k, v in state_dict.items() if (not k.startswith('module.fc')) and (not k.startswith('fc'))}
234 | model_dict = model.state_dict()
235 | model_dict.update(filtered_trained_dict)
236 | model.load_state_dict(model_dict)
237 |
238 | #model.load_state_dict(state_dict)
239 | return model
240 |
241 |
242 | def resnet101_ibn_a(pretrained=False, **kwargs):
243 | """Constructs a ResNet-101 model.
244 | Args:
245 | pretrained (bool): If True, returns a model pre-trained on ImageNet
246 | """
247 | model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs)
248 | if pretrained:
249 | state_dict = torch.load(model_urls['ibn_resnet101a'], map_location=torch.device('cpu'))['state_dict']
250 | state_dict = remove_module_key(state_dict)
251 | model.load_state_dict(state_dict)
252 | return model
253 |
254 |
255 | def remove_module_key(state_dict):
256 | for key in list(state_dict.keys()):
257 | if 'module' in key:
258 | state_dict[key.replace('module.','')] = state_dict.pop(key)
259 | return state_dict
260 |
--------------------------------------------------------------------------------
/reid/models/stb_net.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torchvision
4 | from torch.nn import init
5 | import torch.nn.functional as F
6 | from .pooling import GeneralizedMeanPoolingP
7 |
8 |
9 | def weights_init_kaiming(m):
10 | classname = m.__class__.__name__
11 | if classname.find('Linear') != -1:
12 | nn.init.kaiming_normal_(m.weight, a=0, mode='fan_out')
13 | nn.init.constant_(m.bias, 0.0)
14 | elif classname.find('Conv') != -1:
15 | nn.init.kaiming_normal_(m.weight, a=0, mode='fan_in')
16 | if m.bias is not None:
17 | nn.init.constant_(m.bias, 0.0)
18 | elif classname.find('BatchNorm') != -1:
19 | if m.affine:
20 | nn.init.constant_(m.weight, 1.0)
21 | nn.init.constant_(m.bias, 0.0)
22 |
23 |
24 | def weights_init_classifier(m):
25 | classname = m.__class__.__name__
26 | if classname.find('Linear') != -1:
27 | nn.init.normal_(m.weight, std=0.001)
28 | if m.bias:
29 | nn.init.constant_(m.bias, 0.0)
30 |
31 |
32 | class ModelV2(nn.Module):
33 |
34 | def __init__(self, class_num):
35 | super(ModelV2, self).__init__()
36 |
37 | self.class_num = class_num
38 |
39 | # backbone and optimize its architecture
40 | resnet = torchvision.models.resnet50(pretrained=True)
41 | resnet.layer4[0].conv2.stride = (1,1)
42 | resnet.layer4[0].downsample[0].stride = (1,1)
43 |
44 | # cnn backbone
45 | self.resnet_conv = nn.Sequential(
46 | resnet.conv1, resnet.bn1, resnet.maxpool, # no relu
47 | resnet.layer1, resnet.layer2, resnet.layer3, resnet.layer4)
48 | self.gap = nn.AdaptiveAvgPool2d(1)
49 |
50 | self.bottleneck = nn.BatchNorm1d(2048)
51 | self.bottleneck.bias.requires_grad_(False)
52 | self.bottleneck.apply(weights_init_kaiming)
53 |
54 | self.classifier = nn.Linear(2048, self.class_num, bias=False)
55 | self.classifier.apply(weights_init_classifier)
56 |
57 | def forward(self, x):
58 |
59 | features = self.gap(self.resnet_conv(x)).squeeze()
60 | bn = self.bottleneck(features)
61 | cls_score = self.classifier(bn)
62 |
63 | if self.training:
64 | return features, cls_score
65 | else:
66 | return bn
67 |
68 |
69 | class MemoryBankModel(nn.Module):
70 | def __init__(self, out_dim, dropout=0.5, num_classes=0, use_bnneck=True, pool_type='avgpool'):
71 | super(MemoryBankModel,self).__init__()
72 |
73 | # backbone and optimize its architecture
74 | resnet = torchvision.models.resnet50(pretrained=True)
75 | resnet.layer4[0].conv2.stride = (1,1)
76 | resnet.layer4[0].downsample[0].stride = (1,1)
77 |
78 | # cnn backbone
79 | self.resnet_conv = nn.Sequential(
80 | resnet.conv1, resnet.bn1, resnet.maxpool, # no relu
81 | resnet.layer1, resnet.layer2, resnet.layer3, resnet.layer4)
82 |
83 | if pool_type == 'avgpool':
84 | self.global_pool = nn.AdaptiveAvgPool2d(1)
85 | if pool_type == 'gempool':
86 | self.global_pool = GeneralizedMeanPoolingP() # default initial norm=3
87 |
88 | self.bottleneck = nn.BatchNorm1d(out_dim)
89 | self.bottleneck.bias.requires_grad_(False) # no shift
90 | self.bottleneck.apply(weights_init_kaiming)
91 |
92 | self.drop = nn.Dropout(dropout)
93 |
94 | self.class_num = num_classes
95 | if self.class_num > 0:
96 | self.classifier = nn.Linear(2048, self.class_num, bias=False)
97 | self.classifier.apply(weights_init_classifier)
98 |
99 | self.use_bnneck = use_bnneck
100 |
101 | def forward(self, x, output_feature=None):
102 | xx = self.resnet_conv(x)
103 | x = self.global_pool(xx).squeeze()
104 | #x = self.gap(self.resnet_conv(x)).squeeze()
105 |
106 | if self.use_bnneck:
107 | bn = self.bottleneck(x)
108 | else:
109 | bn = x
110 |
111 | if self.training == True:
112 | if output_feature=='src_feat' and self.class_num > 0:
113 | cls_score = self.classifier(bn)
114 | return x, cls_score # cls_score is for source dataset train
115 |
116 | bn = F.normalize(bn, p=2, dim=1)
117 | return x, bn
118 | else:
119 | bn = F.normalize(bn, p=2, dim=1)
120 | return bn
121 |
122 |
123 |
--------------------------------------------------------------------------------
/reid/trainers.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function, absolute_import
2 | import time
3 | import torch
4 | from .utils.meters import AverageMeter
5 | import torch.nn.functional as F
6 | import numpy as np
7 |
8 |
9 | class Trainer(object):
10 | def __init__(self, model, model_inv):
11 | super(Trainer, self).__init__()
12 | self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
13 | self.model = model
14 | self.model_inv = model_inv
15 |
16 | def train(self, epoch, target_train_loader, optimizer, num_batch=100,
17 | all_pseudo_label='', init_intra_id_feat=''):
18 |
19 | self.model.train()
20 |
21 | batch_time = AverageMeter()
22 | data_time = AverageMeter()
23 | losses = AverageMeter()
24 |
25 | end = time.time()
26 |
27 | # Target iter
28 | target_iter = iter(target_train_loader)
29 |
30 | # Train
31 | #loss_print = {}
32 | for batch_ind in range(num_batch):
33 | data_time.update(time.time() - end)
34 | loss_print = {}
35 |
36 | try:
37 | inputs = next(target_iter)
38 | except:
39 | target_iter = iter(target_train_loader)
40 | inputs = next(target_iter)
41 |
42 | ### Target inputs
43 | inputs_target = inputs[0].to(self.device)
44 | index_target = inputs[3].to(self.device)
45 | cam_target = inputs[4].to(self.device)
46 |
47 | # Target loss
48 | _, embed_feat = self.model(inputs_target)
49 | loss = self.model_inv(embed_feat, index_target, cam_target, epoch=epoch, all_pseudo_label=all_pseudo_label,
50 | batch_ind=batch_ind, init_intra_id_feat=init_intra_id_feat)
51 |
52 | loss_print['memo_loss'] = loss.item()
53 | losses.update(loss.item(), embed_feat.size(0))
54 |
55 | optimizer.zero_grad()
56 | loss.backward()
57 | optimizer.step()
58 |
59 | batch_time.update(time.time() - end)
60 | end = time.time()
61 |
62 | log = "Epoch: [{}][{}/{}], Time {:.3f} ({:.3f}), Data {:.3f} ({:.3f}), Loss {:.3f} ({:.3f})" \
63 | .format(epoch, num_batch, num_batch,
64 | batch_time.val, batch_time.avg,
65 | data_time.val, data_time.avg,
66 | losses.val, losses.avg)
67 |
68 | for tag, value in loss_print.items():
69 | log += ", {}: {:.3f}".format(tag, value)
70 | print(log)
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/reid/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | import torch
4 |
5 |
6 | def to_numpy(tensor):
7 | if torch.is_tensor(tensor):
8 | return tensor.cpu().numpy()
9 | elif type(tensor).__module__ != 'numpy':
10 | raise ValueError("Cannot convert {} to numpy array"
11 | .format(type(tensor)))
12 | return tensor
13 |
14 |
15 | def to_torch(ndarray):
16 | if type(ndarray).__module__ == 'numpy':
17 | return torch.from_numpy(ndarray)
18 | elif not torch.is_tensor(ndarray):
19 | raise ValueError("Cannot convert {} to torch tensor"
20 | .format(type(ndarray)))
21 | return ndarray
22 |
--------------------------------------------------------------------------------
/reid/utils/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/__pycache__/faiss_rerank.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/__pycache__/faiss_rerank.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/__pycache__/faiss_utils.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/__pycache__/faiss_utils.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/__pycache__/logging.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/__pycache__/logging.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/__pycache__/meters.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/__pycache__/meters.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/__pycache__/osutils.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/__pycache__/osutils.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/__pycache__/rerank.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/__pycache__/rerank.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/__pycache__/serialization.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/__pycache__/serialization.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/__pycache__/visualize.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/__pycache__/visualize.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/data/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from .preprocessor import Preprocessor
4 |
--------------------------------------------------------------------------------
/reid/utils/data/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/data/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/data/__pycache__/preprocessor.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/data/__pycache__/preprocessor.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/data/__pycache__/transforms.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/data/__pycache__/transforms.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/data/preprocessor.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | import os.path as osp
3 | from PIL import Image
4 | from torchvision.transforms import functional as F
5 | import torch
6 | import torch.utils.data as data
7 | import random
8 |
9 |
10 | class Preprocessor(object):
11 | def __init__(self, dataset, root=None, transform=None):
12 | super(Preprocessor, self).__init__()
13 | self.dataset = dataset
14 | self.root = root
15 | self.transform = transform
16 |
17 | def __len__(self):
18 | return len(self.dataset)
19 |
20 | def __getitem__(self, indices):
21 | if isinstance(indices, (tuple, list)):
22 | return [self._get_single_item(index) for index in indices]
23 | return self._get_single_item(indices)
24 |
25 | def _get_single_item(self, index):
26 | #fname, pid, camid = self.dataset[index]
27 | single_data = self.dataset[index]
28 | fname, pid, camid = single_data[0], single_data[1], single_data[2]
29 | fpath = fname
30 | if self.root is not None:
31 | fpath = osp.join(self.root, fname)
32 | img = Image.open(fpath).convert('RGB')
33 | if self.transform is not None:
34 | img = self.transform(img)
35 | return img, fname, pid, camid
36 |
37 |
38 | class SourcePreprocessor(object):
39 | def __init__(self, dataset, root=None, transform=None):
40 | super(SourcePreprocessor, self).__init__()
41 | self.dataset = dataset
42 | self.root = root
43 | self.transform = transform
44 |
45 | def __len__(self):
46 | return len(self.dataset)
47 |
48 | def __getitem__(self, indices):
49 | if isinstance(indices, (tuple, list)):
50 | return [self._get_single_item(index) for index in indices]
51 | return self._get_single_item(indices)
52 |
53 | def _get_single_item(self, index):
54 | #fname, pid, camid = self.dataset[index]
55 | fname, pid, camid, img_idx, accum_label = self.dataset[index]
56 | fpath = fname
57 | if self.root is not None:
58 | fpath = osp.join(self.root, fname)
59 | img = Image.open(fpath).convert('RGB')
60 | if self.transform is not None:
61 | img = self.transform(img)
62 | return img, fname, pid, camid, img_idx, accum_label
63 |
64 |
65 | class UnsupervisedTargetPreprocessor(object):
66 | def __init__(self, dataset, root=None, num_cam=6, transform=None, has_pseudo_label=False):
67 | super(UnsupervisedTargetPreprocessor, self).__init__()
68 | self.dataset = dataset
69 | self.root = root
70 | self.transform = transform
71 | self.num_cam = num_cam
72 | self.has_pseudo_label = has_pseudo_label
73 |
74 | def __len__(self):
75 | return len(self.dataset)
76 |
77 | def __getitem__(self, indices):
78 | if isinstance(indices, (tuple, list)):
79 | return [self._get_single_item(index) for index in indices]
80 | return self._get_single_item(indices)
81 |
82 | def _get_single_item(self, index):
83 | if self.has_pseudo_label:
84 | fname, pid, camid, img_idx, pseudo_label, accum_label = self.dataset[index]
85 | else:
86 | fname, pid, camid, img_idx = self.dataset[index]
87 |
88 | fpath = osp.join(self.root, fname)
89 | img = Image.open(fpath).convert('RGB')
90 |
91 | if self.transform is not None:
92 | img = self.transform(img)
93 |
94 | if self.has_pseudo_label:
95 | return img, fname, pid, img_idx, camid, pseudo_label, accum_label
96 | else:
97 | return img, fname, pid, img_idx, camid
98 |
99 |
100 | class ClassUniformlySampler(data.sampler.Sampler):
101 | '''
102 | random sample according to class label
103 | Arguments:
104 | data_source (Dataset): data_loader to sample from
105 | class_position (int): which one is used as class
106 | k (int): sample k images of each class
107 | '''
108 | def __init__(self, samples, class_position, k, has_outlier=False, cam_num=0):
109 |
110 | self.samples = samples
111 | self.class_position = class_position
112 | self.k = k
113 | self.has_outlier = has_outlier
114 | self.cam_num = cam_num
115 | self.class_dict = self._tuple2dict(self.samples)
116 |
117 | def __iter__(self):
118 | self.sample_list = self._generate_list(self.class_dict)
119 | return iter(self.sample_list)
120 |
121 | def __len__(self):
122 | return len(self.sample_list)
123 |
124 | def _tuple2dict(self, inputs):
125 | '''
126 | :param inputs: list with tuple elemnts, [(image_path1, class_index_1), (image_path_2, class_index_2), ...]
127 | :return: dict, {class_index_i: [samples_index1, samples_index2, ...]}
128 | '''
129 | id_dict = {}
130 | for index, each_input in enumerate(inputs):
131 | class_index = each_input[self.class_position] # from which index to obtain the label
132 | if class_index not in list(id_dict.keys()):
133 | id_dict[class_index] = [index]
134 | else:
135 | id_dict[class_index].append(index)
136 | return id_dict
137 |
138 | def _generate_list(self, id_dict):
139 | '''
140 | :param dict: dict, whose values are list
141 | :return:
142 | '''
143 | sample_list = []
144 |
145 | dict_copy = id_dict.copy()
146 | keys = list(dict_copy.keys())
147 | random.shuffle(keys)
148 | outlier_cnt = 0
149 | for key in keys:
150 | value = dict_copy[key]
151 | if self.has_outlier and len(value)<=self.cam_num:
152 | random.shuffle(value)
153 | sample_list.append(value[0]) # sample outlier only one time
154 | outlier_cnt += 1
155 | elif len(value) >= self.k:
156 | random.shuffle(value)
157 | sample_list.extend(value[0: self.k])
158 | else:
159 | value = value * self.k # copy a person's image list for k-times
160 | random.shuffle(value)
161 | sample_list.extend(value[0: self.k])
162 | if outlier_cnt > 0:
163 | print('in Sampler: outlier number= {}'.format(outlier_cnt))
164 | return sample_list
165 |
166 |
167 | class IterLoader:
168 |
169 | def __init__(self, loader):
170 | self.loader = loader
171 | self.iter = iter(self.loader)
172 |
173 | def next_one(self):
174 | try:
175 | return next(self.iter)
176 | except:
177 | self.iter = iter(self.loader)
178 | return next(self.iter)
179 |
180 |
--------------------------------------------------------------------------------
/reid/utils/data/sampler.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from collections import defaultdict
3 |
4 | import numpy as np
5 | import torch
6 | from torch.utils.data.sampler import (
7 | Sampler, SequentialSampler, RandomSampler, SubsetRandomSampler,
8 | WeightedRandomSampler)
9 |
10 |
11 | class RandomIdentitySampler(Sampler):
12 | def __init__(self, data_source, num_instances=1):
13 | self.data_source = data_source
14 | self.num_instances = num_instances
15 | self.index_dic = defaultdict(list)
16 | for index, (_, pid, _) in enumerate(data_source):
17 | self.index_dic[pid].append(index)
18 | self.pids = list(self.index_dic.keys())
19 | self.num_samples = len(self.pids)
20 |
21 | def __len__(self):
22 | return self.num_samples * self.num_instances
23 |
24 | def __iter__(self):
25 | indices = torch.randperm(self.num_samples)
26 | ret = []
27 | for i in indices:
28 | pid = self.pids[i]
29 | t = self.index_dic[pid]
30 | if len(t) >= self.num_instances:
31 | t = np.random.choice(t, size=self.num_instances, replace=False)
32 | else:
33 | t = np.random.choice(t, size=self.num_instances, replace=True)
34 | ret.extend(t)
35 | return iter(ret)
36 |
--------------------------------------------------------------------------------
/reid/utils/data/transforms.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from torchvision.transforms import *
4 | from PIL import Image
5 | import random
6 | import math
7 |
8 |
9 | class RectScale(object):
10 | def __init__(self, height, width, interpolation=Image.BILINEAR):
11 | self.height = height
12 | self.width = width
13 | self.interpolation = interpolation
14 |
15 | def __call__(self, img):
16 | w, h = img.size
17 | if h == self.height and w == self.width:
18 | return img
19 | return img.resize((self.width, self.height), self.interpolation)
20 |
21 |
22 | class RandomSizedRectCrop(object):
23 | def __init__(self, height, width, interpolation=Image.BILINEAR):
24 | self.height = height
25 | self.width = width
26 | self.interpolation = interpolation
27 |
28 | def __call__(self, img):
29 | for attempt in range(10):
30 | area = img.size[0] * img.size[1]
31 | target_area = random.uniform(0.64, 1.0) * area
32 | aspect_ratio = random.uniform(2, 3)
33 |
34 | h = int(round(math.sqrt(target_area * aspect_ratio)))
35 | w = int(round(math.sqrt(target_area / aspect_ratio)))
36 |
37 | if w <= img.size[0] and h <= img.size[1]:
38 | x1 = random.randint(0, img.size[0] - w)
39 | y1 = random.randint(0, img.size[1] - h)
40 |
41 | img = img.crop((x1, y1, x1 + w, y1 + h))
42 | assert(img.size == (w, h))
43 |
44 | return img.resize((self.width, self.height), self.interpolation)
45 |
46 | # Fallback
47 | scale = RectScale(self.height, self.width,
48 | interpolation=self.interpolation)
49 | return scale(img)
50 |
51 |
52 | class RandomErasing(object):
53 | def __init__(self, EPSILON=0.5, mean=[0.485, 0.456, 0.406]):
54 | self.EPSILON = EPSILON
55 | self.mean = mean
56 |
57 | def __call__(self, img):
58 |
59 | if random.uniform(0, 1) > self.EPSILON:
60 | return img
61 |
62 | for attempt in range(100):
63 | area = img.size()[1] * img.size()[2]
64 |
65 | target_area = random.uniform(0.02, 0.2) * area
66 | aspect_ratio = random.uniform(0.3, 3)
67 |
68 | h = int(round(math.sqrt(target_area * aspect_ratio)))
69 | w = int(round(math.sqrt(target_area / aspect_ratio)))
70 |
71 | if w <= img.size()[2] and h <= img.size()[1]:
72 | x1 = random.randint(0, img.size()[1] - h)
73 | y1 = random.randint(0, img.size()[2] - w)
74 | img[0, x1:x1 + h, y1:y1 + w] = self.mean[0]
75 | img[1, x1:x1 + h, y1:y1 + w] = self.mean[1]
76 | img[2, x1:x1 + h, y1:y1 + w] = self.mean[2]
77 |
78 | return img
79 |
80 | return img
81 |
--------------------------------------------------------------------------------
/reid/utils/evaluation_metrics/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from .classification import accuracy
4 | from .ranking import cmc, mean_ap
5 |
6 | __all__ = [
7 | 'accuracy',
8 | 'cmc',
9 | 'mean_ap',
10 | ]
11 |
12 |
--------------------------------------------------------------------------------
/reid/utils/evaluation_metrics/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/evaluation_metrics/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/evaluation_metrics/__pycache__/classification.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/evaluation_metrics/__pycache__/classification.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/evaluation_metrics/__pycache__/ranking.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/evaluation_metrics/__pycache__/ranking.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/evaluation_metrics/__pycache__/retrieval.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/evaluation_metrics/__pycache__/retrieval.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/evaluation_metrics/__pycache__/retrieval_with_rerank.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Terminator8758/CAP-master/4a04fef3fd86728b64f0a473e7e4020a8f449c4d/reid/utils/evaluation_metrics/__pycache__/retrieval_with_rerank.cpython-37.pyc
--------------------------------------------------------------------------------
/reid/utils/evaluation_metrics/classification.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | import torch
3 |
4 |
5 | def to_torch(ndarray):
6 | if type(ndarray).__module__ == 'numpy':
7 | return torch.from_numpy(ndarray)
8 | elif not torch.is_tensor(ndarray):
9 | raise ValueError("Cannot convert {} to torch tensor"
10 | .format(type(ndarray)))
11 | return ndarray
12 |
13 |
14 | def accuracy(output, target, topk=(1,)):
15 | output, target = to_torch(output), to_torch(target)
16 | maxk = max(topk)
17 | batch_size = target.size(0)
18 |
19 | _, pred = output.topk(maxk, 1, True, True)
20 | pred = pred.t()
21 | correct = pred.eq(target.view(1, -1).expand_as(pred))
22 |
23 | ret = []
24 | for k in topk:
25 | correct_k = correct[:k].view(-1).float().sum(dim=0, keepdim=True)
26 | ret.append(correct_k.mul_(1. / batch_size))
27 | return ret
28 |
29 |
--------------------------------------------------------------------------------
/reid/utils/evaluation_metrics/ranking.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from collections import defaultdict
3 |
4 | import numpy as np
5 | from sklearn.metrics.base import _average_binary_score
6 | from sklearn.metrics import precision_recall_curve, auc
7 | # from sklearn.metrics import average_precision_score
8 | import torch
9 |
10 |
11 | def to_numpy(tensor):
12 | if torch.is_tensor(tensor):
13 | return tensor.cpu().numpy()
14 | elif type(tensor).__module__ != 'numpy':
15 | raise ValueError("Cannot convert {} to numpy array"
16 | .format(type(tensor)))
17 | return tensor
18 |
19 |
20 | def _unique_sample(ids_dict, num):
21 | mask = np.zeros(num, dtype=np.bool)
22 | for _, indices in ids_dict.items():
23 | i = np.random.choice(indices)
24 | mask[i] = True
25 | return mask
26 |
27 |
28 | def average_precision_score(y_true, y_score, average="macro",
29 | sample_weight=None):
30 | def _binary_average_precision(y_true, y_score, sample_weight=None):
31 | precision, recall, thresholds = precision_recall_curve(
32 | y_true, y_score, sample_weight=sample_weight)
33 | return auc(recall, precision)
34 |
35 | return _average_binary_score(_binary_average_precision, y_true, y_score,
36 | average, sample_weight=sample_weight)
37 |
38 |
39 | def cmc(distmat, query_ids=None, gallery_ids=None,
40 | query_cams=None, gallery_cams=None, topk=100,
41 | separate_camera_set=False,
42 | single_gallery_shot=False,
43 | first_match_break=False):
44 | distmat = to_numpy(distmat)
45 | m, n = distmat.shape
46 | # Fill up default values
47 | if query_ids is None:
48 | query_ids = np.arange(m)
49 | if gallery_ids is None:
50 | gallery_ids = np.arange(n)
51 | if query_cams is None:
52 | query_cams = np.zeros(m).astype(np.int32)
53 | if gallery_cams is None:
54 | gallery_cams = np.ones(n).astype(np.int32)
55 | # Ensure numpy array
56 | query_ids = np.asarray(query_ids)
57 | gallery_ids = np.asarray(gallery_ids)
58 | query_cams = np.asarray(query_cams)
59 | gallery_cams = np.asarray(gallery_cams)
60 | # Sort and find correct matches
61 | indices = np.argsort(distmat, axis=1)
62 | matches = (gallery_ids[indices] == query_ids[:, np.newaxis])
63 | # Compute CMC for each query
64 | ret = np.zeros(topk)
65 | num_valid_queries = 0
66 | for i in range(m):
67 | # Filter out the same id and same camera
68 | valid = ((gallery_ids[indices[i]] != query_ids[i]) |
69 | (gallery_cams[indices[i]] != query_cams[i]))
70 | if separate_camera_set:
71 | # Filter out samples from same camera
72 | valid &= (gallery_cams[indices[i]] != query_cams[i])
73 | if not np.any(matches[i, valid]): continue
74 | if single_gallery_shot:
75 | repeat = 10
76 | gids = gallery_ids[indices[i][valid]]
77 | inds = np.where(valid)[0]
78 | ids_dict = defaultdict(list)
79 | for j, x in zip(inds, gids):
80 | ids_dict[x].append(j)
81 | else:
82 | repeat = 1
83 | for _ in range(repeat):
84 | if single_gallery_shot:
85 | # Randomly choose one instance for each id
86 | sampled = (valid & _unique_sample(ids_dict, len(valid)))
87 | index = np.nonzero(matches[i, sampled])[0]
88 | else:
89 | index = np.nonzero(matches[i, valid])[0]
90 | delta = 1. / (len(index) * repeat)
91 | for j, k in enumerate(index):
92 | if k - j >= topk: break
93 | if first_match_break:
94 | ret[k - j] += 1
95 | break
96 | ret[k - j] += delta
97 | num_valid_queries += 1
98 | if num_valid_queries == 0:
99 | raise RuntimeError("No valid query")
100 | return ret.cumsum() / num_valid_queries
101 |
102 |
103 | def mean_ap(distmat, query_ids=None, gallery_ids=None,
104 | query_cams=None, gallery_cams=None):
105 | distmat = to_numpy(distmat)
106 | m, n = distmat.shape
107 | # Fill up default values
108 | if query_ids is None:
109 | query_ids = np.arange(m)
110 | if gallery_ids is None:
111 | gallery_ids = np.arange(n)
112 | if query_cams is None:
113 | query_cams = np.zeros(m).astype(np.int32)
114 | if gallery_cams is None:
115 | gallery_cams = np.ones(n).astype(np.int32)
116 | # Ensure numpy array
117 | query_ids = np.asarray(query_ids)
118 | gallery_ids = np.asarray(gallery_ids)
119 | query_cams = np.asarray(query_cams)
120 | gallery_cams = np.asarray(gallery_cams)
121 | # Sort and find correct matches
122 | indices = np.argsort(distmat, axis=1)
123 | matches = (gallery_ids[indices] == query_ids[:, np.newaxis])
124 | # Compute AP for each query
125 | aps = []
126 | for i in range(m):
127 | # Filter out the same id and same camera
128 | valid = ((gallery_ids[indices[i]] != query_ids[i]) |
129 | (gallery_cams[indices[i]] != query_cams[i]))
130 | y_true = matches[i, valid]
131 | y_score = -distmat[i][indices[i]][valid]
132 | if not np.any(y_true): continue
133 | aps.append(average_precision_score(y_true, y_score))
134 | if len(aps) == 0:
135 | raise RuntimeError("No valid query")
136 | return np.mean(aps)
137 |
138 |
139 |
--------------------------------------------------------------------------------
/reid/utils/evaluation_metrics/retrieval.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from sklearn import metrics as sk_metrics
3 | import torch
4 |
5 | class PersonReIDMAP:
6 | '''
7 | Compute Rank@k and mean Average Precision (mAP) scores
8 | Used for Person ReID
9 | Test on MarKet and Duke
10 | '''
11 |
12 | def __init__(self, query_feature, query_cam, query_label, gallery_feature, gallery_cam, gallery_label, dist):
13 | '''
14 | :param query_feature: np.array, bs * feature_dim
15 | :param query_cam: np.array, 1d
16 | :param query_label: np.array, 1d
17 | :param gallery_feature: np.array, gallery_size * feature_dim
18 | :param gallery_cam: np.array, 1d
19 | :param gallery_label: np.array, 1d
20 | '''
21 |
22 | self.query_feature = query_feature
23 | self.query_cam = query_cam
24 | self.query_label = query_label
25 | self.gallery_feature = gallery_feature
26 | self.gallery_cam = gallery_cam
27 | self.gallery_label = gallery_label
28 |
29 | assert dist in ['cosine', 'euclidean']
30 | self.dist = dist
31 |
32 | # normalize feature for fast cosine computation
33 | if self.dist == 'cosine':
34 | self.query_feature = self.normalize(self.query_feature)
35 | self.gallery_feature = self.normalize(self.gallery_feature)
36 |
37 | APs = []
38 | CMC = []
39 | for i in range(len(query_label)):
40 | AP, cmc = self.evaluate(self.query_feature[i], self.query_cam[i], self.query_label[i],
41 | self.gallery_feature, self.gallery_cam, self.gallery_label)
42 | APs.append(AP)
43 | CMC.append(cmc)
44 | # print('{}/{}'.format(i, len(query_label)))
45 |
46 | self.APs = np.array(APs)
47 | self.mAP = np.mean(self.APs)
48 |
49 | min_len = 99999999
50 | for cmc in CMC:
51 | if len(cmc) < min_len:
52 | min_len = len(cmc)
53 | for i, cmc in enumerate(CMC):
54 | CMC[i] = cmc[0: min_len]
55 | self.CMC = np.mean(np.array(CMC), axis=0)
56 |
57 | def compute_AP(self, index, good_index):
58 | '''
59 | :param index: np.array, 1d
60 | :param good_index: np.array, 1d
61 | :return:
62 | '''
63 |
64 | num_good = len(good_index)
65 | hit = np.in1d(index, good_index)
66 | index_hit = np.argwhere(hit == True).flatten()
67 |
68 | if len(index_hit) == 0:
69 | AP = 0
70 | cmc = np.zeros([len(index)])
71 | else:
72 | precision = []
73 | for i in range(num_good):
74 | precision.append(float(i+1) / float((index_hit[i]+1)))
75 | AP = np.mean(np.array(precision))
76 | cmc = np.zeros([len(index)])
77 | cmc[index_hit[0]: ] = 1
78 |
79 | return AP, cmc
80 |
81 | def evaluate(self, query_feature, query_cam, query_label, gallery_feature, gallery_cam, gallery_label, rerank=False):
82 | '''
83 | :param query_feature: np.array, 1d
84 | :param query_cam: int
85 | :param query_label: int
86 | :param gallery_feature: np.array, 2d, gallerys_size * feature_dim
87 | :param gallery_cam: np.array, 1d
88 | :param gallery_label: np.array, 1d
89 | :return:
90 | '''
91 |
92 | # cosine score
93 | if self.dist is 'cosine':
94 | # feature has been normalize during intialization
95 | score = np.matmul(query_feature, gallery_feature.transpose())
96 | index = np.argsort(score)[::-1]
97 | elif self.dist is 'euclidean':
98 | #score = self.l2(query_feature.reshape([1, -1]), gallery_feature)
99 | #print('query_feature shape= {}, gallery_feature shape= {}'.format(query_feature.shape, gallery_feature.shape))
100 | score = self.l2(query_feature.reshape([1,-1]), gallery_feature)
101 | index = np.argsort(score.reshape([-1]))
102 |
103 | junk_index_1 = self.in1d(np.argwhere(query_label == gallery_label), np.argwhere(query_cam == gallery_cam))
104 | junk_index_2 = np.argwhere(gallery_label == -1)
105 | junk_index = np.append(junk_index_1, junk_index_2)
106 |
107 | good_index = self.in1d(np.argwhere(query_label == gallery_label), np.argwhere(query_cam != gallery_cam))
108 | index_wo_junk = self.notin1d(index, junk_index)
109 |
110 | return self.compute_AP(index_wo_junk, good_index)
111 |
112 | def in1d(self, array1, array2, invert=False):
113 | '''
114 | :param set1: np.array, 1d
115 | :param set2: np.array, 1d
116 | :return:
117 | '''
118 |
119 | mask = np.in1d(array1, array2, invert=invert)
120 | return array1[mask]
121 |
122 | def notin1d(self, array1, array2):
123 |
124 | return self.in1d(array1, array2, invert=True)
125 |
126 | def normalize(self, x):
127 | norm = np.tile(np.sqrt(np.sum(np.square(x), axis=1, keepdims=True)), [1, x.shape[1]])
128 | return x / norm
129 |
130 | def cosine_dist(self, x, y):
131 | return sk_metrics.pairwise.cosine_distances(x, y)
132 |
133 | def euclidean_dist(self, x, y):
134 | return sk_metrics.pairwise.euclidean_distances(x, y)
135 |
136 | def l2(self, x, y):
137 | x = torch.from_numpy(x)
138 | y = torch.from_numpy(y)
139 |
140 | m, n = x.size(0), y.size(0)
141 | x = x.view(m, -1)
142 | y = y.view(n, -1)
143 |
144 | dist = torch.pow(x, 2).sum(dim=1, keepdim=True).expand(m, n) + \
145 | torch.pow(y, 2).sum(dim=1, keepdim=True).expand(n, m).t()
146 | dist.addmm_(1, -2, x, y.t())
147 | # We use clamp to keep numerical stability
148 | dist = torch.clamp(dist, 1e-8, np.inf)
149 | return dist.numpy()
150 |
151 |
--------------------------------------------------------------------------------
/reid/utils/evaluation_metrics/retrieval_with_rerank.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from sklearn import metrics as sk_metrics
3 | import torch
4 | from reid.utils.rerank import re_ranking
5 | np.set_printoptions(linewidth=2000)
6 |
7 | class PersonReIDMAP:
8 | '''
9 | Compute Rank@k and mean Average Precision (mAP) scores
10 | Used for Person ReID
11 | Test on MarKet and Duke
12 | '''
13 |
14 | def __init__(self, query_feature, query_cam, query_label, gallery_feature, gallery_cam, gallery_label, dist, rerank=False, save_rank_result=False):
15 | '''
16 | :param query_feature: np.array, bs * feature_dim
17 | :param query_cam: np.array, 1d
18 | :param query_label: np.array, 1d
19 | :param gallery_feature: np.array, gallery_size * feature_dim
20 | :param gallery_cam: np.array, 1d
21 | :param gallery_label: np.array, 1d
22 | '''
23 |
24 | self.query_feature = query_feature
25 | self.query_cam = query_cam
26 | self.query_label = query_label
27 | self.gallery_feature = gallery_feature
28 | self.gallery_cam = gallery_cam
29 | self.gallery_label = gallery_label
30 |
31 | assert dist in ['cosine', 'euclidean']
32 | self.dist = dist
33 |
34 | # normalize feature for fast cosine computation
35 | if self.dist == 'cosine':
36 | self.query_feature = self.normalize(self.query_feature)
37 | self.gallery_feature = self.normalize(self.gallery_feature)
38 | distmat = np.matmul(self.query_feature, self.gallery_feature.transpose())
39 | #print('query-gallery cosine similarity: min= {}, max= {}'.format(distmat.min(), distmat.max()))
40 | distmat = 1-distmat #distmat.max() - distmat
41 | if rerank:
42 | print('Applying person re-ranking ...')
43 | distmat_qq = np.matmul(self.query_feature, self.query_feature.transpose())
44 | distmat_gg = np.matmul(self.gallery_feature, self.gallery_feature.transpose())
45 | #print('query-query similarity min= {}, max= {}; gallery-gallery similarity min= {}, max= {}'.format(distmat_qq.min(), distmat_qq.max(), distmat_gg.min(), distmat_gg.max()))
46 | distmat_qq = distmat_qq.max() - distmat_qq
47 | distmat_gg = distmat_gg.max() - distmat_gg
48 | distmat = re_ranking(distmat, distmat_qq, distmat_gg)
49 |
50 | if save_rank_result:
51 | indices = np.argsort(distmat, axis=1)
52 | indices = indices[:,:100]
53 | print('indices shape= {}, saving distmat to result.txt'.format(indices.shape))
54 | np.savetxt("result.txt", indices, fmt="%04d")
55 | return
56 |
57 | if self.dist == 'euclidean':
58 | distmat = self.l2(self.query_feature, self.gallery_feature)
59 | if rerank:
60 | print('Applying person re-ranking ...')
61 | distmat_qq = self.l2(self.query_feature, self.query_feature)
62 | distmat_gg = self.l2(self.gallery_feature, self.gallery_feature)
63 | distmat = re_ranking(distmat, distmat_qq, distmat_gg)
64 |
65 | APs = []
66 | CMC = []
67 | for i in range(len(query_label)):
68 | AP, cmc = self.evaluate(distmat[i], self.query_cam[i], self.query_label[i], self.gallery_cam, self.gallery_label)
69 | APs.append(AP)
70 | CMC.append(cmc)
71 | # print('{}/{}'.format(i, len(query_label)))
72 |
73 | self.APs = np.array(APs)
74 | self.mAP = np.mean(self.APs)
75 |
76 | min_len = 99999999
77 | for cmc in CMC:
78 | if len(cmc) < min_len:
79 | min_len = len(cmc)
80 | for i, cmc in enumerate(CMC):
81 | CMC[i] = cmc[0: min_len]
82 | self.CMC = np.mean(np.array(CMC), axis=0)
83 |
84 | def compute_AP(self, index, good_index):
85 | '''
86 | :param index: np.array, 1d
87 | :param good_index: np.array, 1d
88 | :return:
89 | '''
90 |
91 | num_good = len(good_index)
92 | hit = np.in1d(index, good_index)
93 | index_hit = np.argwhere(hit == True).flatten()
94 |
95 | if len(index_hit) == 0:
96 | AP = 0
97 | cmc = np.zeros([len(index)])
98 | else:
99 | precision = []
100 | for i in range(num_good):
101 | precision.append(float(i+1) / float((index_hit[i]+1)))
102 | AP = np.mean(np.array(precision))
103 | cmc = np.zeros([len(index)])
104 | cmc[index_hit[0]: ] = 1
105 |
106 | return AP, cmc
107 |
108 | def evaluate(self, per_query_dist, query_cam, query_label, gallery_cam, gallery_label):
109 | '''
110 | :param query_feature: np.array, 1d
111 | :param query_cam: int
112 | :param query_label: int
113 | :param gallery_feature: np.array, 2d, gallerys_size * feature_dim
114 | :param gallery_cam: np.array, 1d
115 | :param gallery_label: np.array, 1d
116 | :return:
117 | '''
118 |
119 | # cosine score
120 | #if self.dist is 'cosine':
121 | # # feature has been normalize during intialization
122 | # score = np.matmul(query_feature, gallery_feature.transpose())
123 | # index = np.argsort(score)[::-1]
124 | #if self.dist is 'euclidean':
125 | # #score = self.l2(query_feature.reshape([1,-1]), gallery_feature)
126 | index = np.argsort(per_query_dist)
127 |
128 | junk_index_1 = self.in1d(np.argwhere(query_label == gallery_label), np.argwhere(query_cam == gallery_cam))
129 | junk_index_2 = np.argwhere(gallery_label == -1)
130 | junk_index = np.append(junk_index_1, junk_index_2)
131 |
132 | good_index = self.in1d(np.argwhere(query_label == gallery_label), np.argwhere(query_cam != gallery_cam))
133 | index_wo_junk = self.notin1d(index, junk_index)
134 |
135 | return self.compute_AP(index_wo_junk, good_index)
136 |
137 | def in1d(self, array1, array2, invert=False):
138 | '''
139 | :param set1: np.array, 1d
140 | :param set2: np.array, 1d
141 | :return:
142 | '''
143 |
144 | mask = np.in1d(array1, array2, invert=invert)
145 | return array1[mask]
146 |
147 | def notin1d(self, array1, array2):
148 |
149 | return self.in1d(array1, array2, invert=True)
150 |
151 | def normalize(self, x):
152 | norm = np.tile(np.sqrt(np.sum(np.square(x), axis=1, keepdims=True)), [1, x.shape[1]])
153 | return x / norm
154 |
155 | def cosine_dist(self, x, y):
156 | return sk_metrics.pairwise.cosine_distances(x, y)
157 |
158 | def euclidean_dist(self, x, y):
159 | return sk_metrics.pairwise.euclidean_distances(x, y)
160 |
161 | def l2(self, x, y):
162 | x = torch.from_numpy(x)
163 | y = torch.from_numpy(y)
164 |
165 | m, n = x.size(0), y.size(0)
166 | x = x.view(m, -1)
167 | y = y.view(n, -1)
168 |
169 | dist = torch.pow(x, 2).sum(dim=1, keepdim=True).expand(m, n) + \
170 | torch.pow(y, 2).sum(dim=1, keepdim=True).expand(n, m).t()
171 | dist.addmm_(1, -2, x, y.t())
172 | # We use clamp to keep numerical stability
173 | dist = torch.clamp(dist, 1e-8, np.inf)
174 | return dist.numpy()
175 |
176 |
--------------------------------------------------------------------------------
/reid/utils/faiss_rerank.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | """
4 | CVPR2017 paper:Zhong Z, Zheng L, Cao D, et al. Re-ranking Person Re-identification with k-reciprocal Encoding[J]. 2017.
5 | url:http://openaccess.thecvf.com/content_cvpr_2017/papers/Zhong_Re-Ranking_Person_Re-Identification_CVPR_2017_paper.pdf
6 | Matlab version: https://github.com/zhunzhong07/person-re-ranking
7 | """
8 |
9 | import os, sys
10 | import time
11 | import numpy as np
12 | from scipy.spatial.distance import cdist
13 | import gc
14 | import faiss
15 |
16 | import torch
17 | import torch.nn.functional as F
18 |
19 | from .faiss_utils import search_index_pytorch, search_raw_array_pytorch, \
20 | index_init_gpu, index_init_cpu
21 |
22 | def k_reciprocal_neigh(initial_rank, i, k1):
23 | forward_k_neigh_index = initial_rank[i,:k1+1]
24 | backward_k_neigh_index = initial_rank[forward_k_neigh_index,:k1+1]
25 | fi = np.where(backward_k_neigh_index==i)[0]
26 | return forward_k_neigh_index[fi]
27 |
28 | def faiss_compute_jaccard_dist(target_features, k1=20, k2=6, print_flag=True, search_option=0, use_float16=False):
29 | end = time.time()
30 | if print_flag:
31 | print('Computing jaccard distance...')
32 |
33 | ngpus = faiss.get_num_gpus()
34 | N = target_features.size(0)
35 | mat_type = np.float16 if use_float16 else np.float32
36 |
37 | if (search_option==0):
38 | # GPU + PyTorch CUDA Tensors (1)
39 | res = faiss.StandardGpuResources()
40 | res.setDefaultNullStreamAllDevices()
41 | _, initial_rank = search_raw_array_pytorch(res, target_features, target_features, k1)
42 | initial_rank = initial_rank.cpu().numpy()
43 | elif (search_option==1):
44 | # GPU + PyTorch CUDA Tensors (2)
45 | res = faiss.StandardGpuResources()
46 | index = faiss.GpuIndexFlatL2(res, target_features.size(-1))
47 | index.add(target_features.cpu().numpy())
48 | _, initial_rank = search_index_pytorch(index, target_features, k1)
49 | res.syncDefaultStreamCurrentDevice()
50 | initial_rank = initial_rank.cpu().numpy()
51 | elif (search_option==2):
52 | # GPU
53 | index = index_init_gpu(ngpus, target_features.size(-1))
54 | index.add(target_features.cpu().numpy())
55 | _, initial_rank = index.search(target_features.cpu().numpy(), k1)
56 | else:
57 | # CPU
58 | index = index_init_cpu(target_features.size(-1))
59 | index.add(target_features.cpu().numpy())
60 | _, initial_rank = index.search(target_features.cpu().numpy(), k1)
61 |
62 |
63 | nn_k1 = []
64 | nn_k1_half = []
65 | for i in range(N):
66 | nn_k1.append(k_reciprocal_neigh(initial_rank, i, k1))
67 | nn_k1_half.append(k_reciprocal_neigh(initial_rank, i, int(np.around(k1/2))))
68 |
69 | V = np.zeros((N, N), dtype=mat_type)
70 | for i in range(N):
71 | k_reciprocal_index = nn_k1[i]
72 | k_reciprocal_expansion_index = k_reciprocal_index
73 | for candidate in k_reciprocal_index:
74 | candidate_k_reciprocal_index = nn_k1_half[candidate]
75 | if (len(np.intersect1d(candidate_k_reciprocal_index,k_reciprocal_index)) > 2/3*len(candidate_k_reciprocal_index)):
76 | k_reciprocal_expansion_index = np.append(k_reciprocal_expansion_index,candidate_k_reciprocal_index)
77 |
78 | k_reciprocal_expansion_index = np.unique(k_reciprocal_expansion_index) ## element-wise unique
79 | dist = 2-2*torch.mm(target_features[i].unsqueeze(0).contiguous(), target_features[k_reciprocal_expansion_index].t())
80 | if use_float16:
81 | V[i,k_reciprocal_expansion_index] = F.softmax(-dist, dim=1).view(-1).cpu().numpy().astype(mat_type)
82 | else:
83 | V[i,k_reciprocal_expansion_index] = F.softmax(-dist, dim=1).view(-1).cpu().numpy()
84 |
85 | del nn_k1, nn_k1_half
86 |
87 | if k2 != 1:
88 | V_qe = np.zeros_like(V, dtype=mat_type)
89 | for i in range(N):
90 | V_qe[i,:] = np.mean(V[initial_rank[i,:k2],:], axis=0)
91 | V = V_qe
92 | del V_qe
93 |
94 | del initial_rank
95 |
96 | invIndex = []
97 | for i in range(N):
98 | invIndex.append(np.where(V[:,i] != 0)[0]) #len(invIndex)=all_num
99 |
100 | jaccard_dist = np.zeros((N, N), dtype=mat_type)
101 | for i in range(N):
102 | temp_min = np.zeros((1,N), dtype=mat_type)
103 | # temp_max = np.zeros((1,N), dtype=mat_type)
104 | indNonZero = np.where(V[i,:] != 0)[0]
105 | indImages = []
106 | indImages = [invIndex[ind] for ind in indNonZero]
107 | for j in range(len(indNonZero)):
108 | temp_min[0,indImages[j]] = temp_min[0,indImages[j]]+np.minimum(V[i,indNonZero[j]],V[indImages[j],indNonZero[j]])
109 | # temp_max[0,indImages[j]] = temp_max[0,indImages[j]]+np.maximum(V[i,indNonZero[j]],V[indImages[j],indNonZero[j]])
110 |
111 | jaccard_dist[i] = 1-temp_min/(2-temp_min)
112 | # jaccard_dist[i] = 1-temp_min/(temp_max+1e-6)
113 |
114 | del invIndex, V
115 |
116 | pos_bool = (jaccard_dist < 0)
117 | jaccard_dist[pos_bool] = 0.0
118 | if print_flag:
119 | print ("Jaccard distance computing time cost: {}".format(time.time()-end))
120 |
121 | return jaccard_dist
122 |
123 |
--------------------------------------------------------------------------------
/reid/utils/faiss_utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import numpy as np
3 | import faiss
4 | import torch
5 |
6 | def swig_ptr_from_FloatTensor(x):
7 | assert x.is_contiguous()
8 | assert x.dtype == torch.float32
9 | return faiss.cast_integer_to_float_ptr(
10 | x.storage().data_ptr() + x.storage_offset() * 4)
11 |
12 | def swig_ptr_from_LongTensor(x):
13 | assert x.is_contiguous()
14 | assert x.dtype == torch.int64, 'dtype=%s' % x.dtype
15 | return faiss.cast_integer_to_long_ptr(
16 | x.storage().data_ptr() + x.storage_offset() * 8)
17 |
18 | def search_index_pytorch(index, x, k, D=None, I=None):
19 | """call the search function of an index with pytorch tensor I/O (CPU
20 | and GPU supported)"""
21 | assert x.is_contiguous()
22 | n, d = x.size()
23 | assert d == index.d
24 |
25 | if D is None:
26 | D = torch.empty((n, k), dtype=torch.float32, device=x.device)
27 | else:
28 | assert D.size() == (n, k)
29 |
30 | if I is None:
31 | I = torch.empty((n, k), dtype=torch.int64, device=x.device)
32 | else:
33 | assert I.size() == (n, k)
34 | torch.cuda.synchronize()
35 | xptr = swig_ptr_from_FloatTensor(x)
36 | Iptr = swig_ptr_from_LongTensor(I)
37 | Dptr = swig_ptr_from_FloatTensor(D)
38 | index.search_c(n, xptr,
39 | k, Dptr, Iptr)
40 | torch.cuda.synchronize()
41 | return D, I
42 |
43 | def search_raw_array_pytorch(res, xb, xq, k, D=None, I=None,
44 | metric=faiss.METRIC_L2):
45 | assert xb.device == xq.device
46 |
47 | nq, d = xq.size()
48 | if xq.is_contiguous():
49 | xq_row_major = True
50 | elif xq.t().is_contiguous():
51 | xq = xq.t() # I initially wrote xq:t(), Lua is still haunting me :-)
52 | xq_row_major = False
53 | else:
54 | raise TypeError('matrix should be row or column-major')
55 |
56 | xq_ptr = swig_ptr_from_FloatTensor(xq)
57 |
58 | nb, d2 = xb.size()
59 | assert d2 == d
60 | if xb.is_contiguous():
61 | xb_row_major = True
62 | elif xb.t().is_contiguous():
63 | xb = xb.t()
64 | xb_row_major = False
65 | else:
66 | raise TypeError('matrix should be row or column-major')
67 | xb_ptr = swig_ptr_from_FloatTensor(xb)
68 |
69 | if D is None:
70 | D = torch.empty(nq, k, device=xb.device, dtype=torch.float32)
71 | else:
72 | assert D.shape == (nq, k)
73 | assert D.device == xb.device
74 |
75 | if I is None:
76 | I = torch.empty(nq, k, device=xb.device, dtype=torch.int64)
77 | else:
78 | assert I.shape == (nq, k)
79 | assert I.device == xb.device
80 |
81 | D_ptr = swig_ptr_from_FloatTensor(D)
82 | I_ptr = swig_ptr_from_LongTensor(I)
83 |
84 | faiss.bruteForceKnn(res, metric,
85 | xb_ptr, xb_row_major, nb,
86 | xq_ptr, xq_row_major, nq,
87 | d, k, D_ptr, I_ptr)
88 |
89 | return D, I
90 |
91 | def index_init_gpu(ngpus, feat_dim):
92 | flat_config = []
93 | for i in range(ngpus):
94 | cfg = faiss.GpuIndexFlatConfig()
95 | cfg.useFloat16 = False
96 | cfg.device = i
97 | flat_config.append(cfg)
98 |
99 | res = [faiss.StandardGpuResources() for i in range(ngpus)]
100 | indexes = [faiss.GpuIndexFlatL2(res[i], feat_dim, flat_config[i]) for i in range(ngpus)]
101 | index = faiss.IndexShards(feat_dim)
102 | for sub_index in indexes:
103 | index.add_shard(sub_index)
104 | index.reset()
105 | return index
106 |
107 | def index_init_cpu(feat_dim):
108 | return faiss.IndexFlatL2(feat_dim)
109 |
110 |
--------------------------------------------------------------------------------
/reid/utils/logging.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | import os
3 | import sys
4 |
5 | from .osutils import mkdir_if_missing
6 |
7 |
8 | class Logger(object):
9 | def __init__(self, fpath=None):
10 | self.console = sys.stdout
11 | self.file = None
12 | if fpath is not None:
13 | mkdir_if_missing(os.path.dirname(fpath))
14 | self.file = open(fpath, 'w')
15 |
16 | def __del__(self):
17 | self.close()
18 |
19 | def __enter__(self):
20 | pass
21 |
22 | def __exit__(self, *args):
23 | self.close()
24 |
25 | def write(self, msg):
26 | self.console.write(msg)
27 | if self.file is not None:
28 | self.file.write(msg)
29 |
30 | def flush(self):
31 | self.console.flush()
32 | if self.file is not None:
33 | self.file.flush()
34 | os.fsync(self.file.fileno())
35 |
36 | def close(self):
37 | self.console.close()
38 | if self.file is not None:
39 | self.file.close()
40 |
--------------------------------------------------------------------------------
/reid/utils/meters.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | import torch
3 |
4 |
5 | class AverageMeter(object):
6 | """Computes and stores the average and current value"""
7 |
8 | def __init__(self):
9 | self.val = 0
10 | self.avg = 0
11 | self.sum = 0
12 | self.count = 0
13 |
14 | def reset(self):
15 | self.val = 0
16 | self.avg = 0
17 | self.sum = 0
18 | self.count = 0
19 |
20 | def update(self, val, n=1):
21 | self.val = val
22 | self.sum += val * n
23 | self.count += n
24 | self.avg = self.sum / self.count
25 |
26 |
27 | class CatMeter:
28 | '''
29 | Concatenate Meter for torch.Tensor
30 | '''
31 | def __init__(self):
32 | self.reset()
33 |
34 | def reset(self):
35 | self.val = None
36 |
37 | def update(self, val):
38 | if self.val is None:
39 | self.val = val
40 | else:
41 | self.val = torch.cat([self.val, val], dim=0)
42 | def get_val(self):
43 | return self.val
44 |
45 | def get_val_numpy(self):
46 | return self.val.data.cpu().numpy()
47 |
48 |
49 | class MultiItemAverageMeter:
50 |
51 | def __init__(self):
52 | self.content = {}
53 |
54 | def update(self, val):
55 | '''
56 | :param val: dict, keys are strs, values are torch.Tensor or np.array
57 | '''
58 | for key in list(val.keys()):
59 | value = val[key]
60 | if key not in list(self.content.keys()):
61 | self.content[key] = {'avg': value, 'sum': value, 'count': 1.0}
62 | else:
63 | self.content[key]['sum'] += value
64 | self.content[key]['count'] += 1.0
65 | self.content[key]['avg'] = self.content[key]['sum'] / self.content[key]['count']
66 |
67 | def get_val(self):
68 | keys = list(self.content.keys())
69 | values = []
70 | for key in keys:
71 | try:
72 | values.append(self.content[key]['avg'].data.cpu().numpy())
73 | except:
74 | values.append(self.content[key]['avg'])
75 | return keys, values
76 |
77 | def get_str(self):
78 |
79 | result = ''
80 | keys, values = self.get_val()
81 |
82 | for key, value in zip(keys, values):
83 | result += key
84 | result += ': '
85 | result += str(value)
86 | result += '; '
87 |
88 | return result
89 |
90 |
91 |
--------------------------------------------------------------------------------
/reid/utils/misc.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import numpy as np
4 | import torch
5 | import torch.nn.functional as F
6 | from torch import nn
7 | from collections import OrderedDict
8 | from torch.autograd import Variable
9 |
10 |
11 | def gram_matrix(y):
12 | (b, ch, h, w) = y.size()
13 | features = y.view(b, ch, w * h)
14 | features_t = features.transpose(1, 2)
15 | gram = features.bmm(features_t) / (ch * h * w)
16 | return gram
17 |
18 | def poly_lr_scheduler(optimizer, init_lr, iter, lr_decay_iter=1, max_iter=30000, power=0.9,):
19 | """Polynomial decay of learning rate
20 | :param init_lr is base learning rate
21 | :param iter is a current iteration
22 | :param lr_decay_iter how frequently decay occurs, default is 1
23 | :param max_iter is number of maximum iterations
24 | :param power is a polymomial power
25 | """
26 | if iter % lr_decay_iter or iter > max_iter:
27 | return optimizer
28 |
29 | for param_group in optimizer.param_groups:
30 | tmp = (1 - iter/max_iter)**power
31 | param_group['lr'] = init_lr*tmp
32 |
33 | def wct(content_feat, style_feat):
34 | content_feat = content_feat.data.cpu()
35 | content_feat = content_feat.squeeze(0).double()
36 |
37 | style_feat = style_feat.data.cpu()
38 | style_feat = style_feat.squeeze(0).double()
39 |
40 | C, W, H = content_feat.size()
41 |
42 | transfered = whiten_and_color(content_feat.view(C, -1),
43 | style_feat.view(C, -1))
44 |
45 | transfered = transfered.view_as(content_feat).float().unsqueeze(0)
46 |
47 | return Variable(transfered).cuda()
48 |
49 | def whiten_and_color(cF,sF):
50 | cFSize = cF.size()
51 | c_mean = torch.mean(cF,1) # c x (h x w)
52 | c_mean = c_mean.unsqueeze(1).expand_as(cF)
53 | cF = cF - c_mean
54 |
55 | contentConv = torch.mm(cF,cF.t()).div(cFSize[1]-1) + torch.eye(cFSize[0]).double()
56 | c_u,c_e,c_v = torch.svd(contentConv,some=False)
57 |
58 | k_c = cFSize[0]
59 | for i in range(cFSize[0]):
60 | if c_e[i] < 0.00001:
61 | k_c = i
62 | break
63 |
64 | sFSize = sF.size()
65 | s_mean = torch.mean(sF,1)
66 | sF = sF - s_mean.unsqueeze(1).expand_as(sF)
67 | styleConv = torch.mm(sF,sF.t()).div(sFSize[1]-1)
68 | s_u,s_e,s_v = torch.svd(styleConv,some=False)
69 |
70 | k_s = sFSize[0]
71 | for i in range(sFSize[0]):
72 | if s_e[i] < 0.00001:
73 | k_s = i
74 | break
75 |
76 | c_d = (c_e[0:k_c]).pow(-0.5)
77 | step1 = torch.mm(c_v[:,0:k_c],torch.diag(c_d))
78 | step2 = torch.mm(step1,(c_v[:,0:k_c].t()))
79 | whiten_cF = torch.mm(step2,cF)
80 |
81 | s_d = (s_e[0:k_s]).pow(0.5)
82 | targetFeature = torch.mm(torch.mm(torch.mm(s_v[:,0:k_s],torch.diag(s_d)),(s_v[:,0:k_s].t())),whiten_cF)
83 | targetFeature = targetFeature + s_mean.unsqueeze(1).expand_as(targetFeature)
84 |
85 | return targetFeature
86 |
87 |
88 |
89 | def calc_mean_std(feat, eps=1e-5):
90 | # eps is a small value added to the variance to avoid divide-by-zero.
91 | size = feat.data.size()
92 | assert (len(size) == 4)
93 | N, C = size[:2]
94 | feat_var = feat.view(N, C, -1).var(dim=2) + eps
95 | feat_std = feat_var.sqrt().view(N, C, 1, 1)
96 | feat_mean = feat.view(N, C, -1).mean(dim=2).view(N, C, 1, 1)
97 | return feat_mean, feat_std
98 |
99 |
100 | def adaptive_instance_normalization(content_feat, style_feat):
101 | assert (content_feat.data.size()[:2] == style_feat.data.size()[:2])
102 | size = content_feat.data.size()
103 | style_mean, style_std = calc_mean_std(style_feat)
104 | content_mean, content_std = calc_mean_std(content_feat)
105 |
106 | normalized_feat = (content_feat - content_mean.expand(
107 | size)) / content_std.expand(size)
108 | return normalized_feat * style_std.expand(size) + style_mean.expand(size)
109 |
110 |
111 | def load_model_filter(model, snapshot, prefix=False):
112 | pretrained_dict = torch.load(snapshot)
113 | if prefix:
114 | new_state_dict = OrderedDict()
115 | for k, v in pretrained_dict.items():
116 | name = k[7:] # remove `enc.` or `dec.`
117 | new_state_dict[name] = v
118 | pretrained_dict = new_state_dict
119 |
120 | model_dict = model.state_dict()
121 |
122 | # 1. filter out unnecessary keys
123 | pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
124 | # 2. overwrite entries in the existing state dict
125 | model_dict.update(pretrained_dict)
126 | # 3. load the new state dict
127 | model.load_state_dict(pretrained_dict)
128 |
129 | return model
130 |
131 |
132 | def check_mkdir(dir_name):
133 | if not os.path.exists(dir_name):
134 | os.mkdir(dir_name)
135 |
136 |
137 | def initialize_weights(*models):
138 | for model in models:
139 | for module in model.modules():
140 | if isinstance(module, nn.Conv2d) or isinstance(module, nn.Linear):
141 | nn.init.kaiming_normal(module.weight)
142 | if module.bias is not None:
143 | module.bias.data.zero_()
144 | elif isinstance(module, nn.BatchNorm2d):
145 | module.weight.data.fill_(1)
146 | module.bias.data.zero_()
147 |
148 |
149 | def get_upsampling_weight(in_channels, out_channels, kernel_size):
150 | factor = (kernel_size + 1) // 2
151 | if kernel_size % 2 == 1:
152 | center = factor - 1
153 | else:
154 | center = factor - 0.5
155 | og = np.ogrid[:kernel_size, :kernel_size]
156 | filt = (1 - abs(og[0] - center) / factor) * (1 - abs(og[1] - center) / factor)
157 | weight = np.zeros((in_channels, out_channels, kernel_size, kernel_size), dtype=np.float64)
158 | weight[range(in_channels), range(out_channels), :, :] = filt
159 | return torch.from_numpy(weight).float()
160 |
161 |
162 | class CrossEntropyLoss2d(nn.Module):
163 | def __init__(self, weight=None, size_average=True, ignore_index=255):
164 | super(CrossEntropyLoss2d, self).__init__()
165 | self.nll_loss = nn.NLLLoss2d(weight, size_average, ignore_index)
166 |
167 | def forward(self, inputs, targets):
168 | return self.nll_loss(F.log_softmax(inputs), targets)
169 |
170 | def cross_entropy2d(input, target, weight=None, size_average=True, ignore_index=255):
171 | n, c, h, w = input.size()
172 | log_p = F.log_softmax(input, dim=1)
173 | log_p = log_p.transpose(1, 2).transpose(2, 3).contiguous().view(-1, c)
174 | log_p = log_p[target.view(n * h * w, 1).repeat(1, c) >= 0]
175 | log_p = log_p.view(-1, c)
176 |
177 | mask = target >= 0
178 | target = target[mask]
179 | loss = F.nll_loss(log_p, target, ignore_index=ignore_index,
180 | weight=weight, size_average=False)
181 | if size_average:
182 | loss /= mask.data.sum()
183 | return loss
184 |
185 |
186 | def bootstrapped_cross_entropy2d(input, target, K, weight=None, size_average=True):
187 |
188 | batch_size = input.size()[0]
189 |
190 | def _bootstrap_xentropy_single(input, target, K, weight=None, size_average=True):
191 | n, c, h, w = input.size()
192 | log_p = F.log_softmax(input, dim=1)
193 | log_p = log_p.transpose(1, 2).transpose(2, 3).contiguous().view(-1, c)
194 | log_p = log_p[target.view(n * h * w, 1).repeat(1, c) >= 0]
195 | log_p = log_p.view(-1, c)
196 |
197 | mask = target >= 0
198 | target = target[mask]
199 | loss = F.nll_loss(log_p, target, weight=weight, ignore_index=255,
200 | reduce=False, size_average=False)
201 | topk_loss, _ = loss.topk(K)
202 | reduced_topk_loss = topk_loss.sum() / K
203 |
204 | return reduced_topk_loss
205 |
206 | loss = 0.0
207 | # Bootstrap from each image not entire batch
208 | for i in range(batch_size):
209 | loss += _bootstrap_xentropy_single(input=torch.unsqueeze(input[i], 0),
210 | target=torch.unsqueeze(target[i], 0),
211 | K=K,
212 | weight=weight,
213 | size_average=size_average)
214 | return loss / float(batch_size)
215 |
216 | def _fast_hist(label_pred, label_true, num_classes):
217 | mask = (label_true >= 0) & (label_true < num_classes)
218 | hist = np.bincount(
219 | num_classes * label_true[mask].astype(int) +
220 | label_pred[mask], minlength=num_classes ** 2).reshape(num_classes, num_classes)
221 | return hist
222 |
223 | ### T-SNE
224 | def Hbeta(D=np.array([]), beta=1.0):
225 | """
226 | Compute the perplexity and the P-row for a specific value of the
227 | precision of a Gaussian distribution.
228 | """
229 |
230 | # Compute P-row and corresponding perplexity
231 | P = np.exp(-D.copy() * beta)
232 | sumP = sum(P)
233 | H = np.log(sumP) + beta * np.sum(D * P) / sumP
234 | P = P / sumP
235 | return H, P
236 |
237 |
238 | def x2p(X=np.array([]), tol=1e-5, perplexity=30.0):
239 | """
240 | Performs a binary search to get P-values in such a way that each
241 | conditional Gaussian has the same perplexity.
242 | """
243 |
244 | # Initialize some variables
245 | print("Computing pairwise distances...")
246 | (n, d) = X.shape
247 | sum_X = np.sum(np.square(X), 1)
248 | D = np.add(np.add(-2 * np.dot(X, X.T), sum_X).T, sum_X)
249 | P = np.zeros((n, n))
250 | beta = np.ones((n, 1))
251 | logU = np.log(perplexity)
252 |
253 | # Loop over all datapoints
254 | for i in range(n):
255 |
256 | # Print progress
257 | if i % 500 == 0:
258 | print("Computing P-values for point %d of %d..." % (i, n))
259 |
260 | # Compute the Gaussian kernel and entropy for the current precision
261 | betamin = -np.inf
262 | betamax = np.inf
263 | Di = D[i, np.concatenate((np.r_[0:i], np.r_[i+1:n]))]
264 | (H, thisP) = Hbeta(Di, beta[i])
265 |
266 | # Evaluate whether the perplexity is within tolerance
267 | Hdiff = H - logU
268 | tries = 0
269 | while np.abs(Hdiff) > tol and tries < 50:
270 |
271 | # If not, increase or decrease precision
272 | if Hdiff > 0:
273 | betamin = beta[i].copy()
274 | if betamax == np.inf or betamax == -np.inf:
275 | beta[i] = beta[i] * 2.
276 | else:
277 | beta[i] = (beta[i] + betamax) / 2.
278 | else:
279 | betamax = beta[i].copy()
280 | if betamin == np.inf or betamin == -np.inf:
281 | beta[i] = beta[i] / 2.
282 | else:
283 | beta[i] = (beta[i] + betamin) / 2.
284 |
285 | # Recompute the values
286 | (H, thisP) = Hbeta(Di, beta[i])
287 | Hdiff = H - logU
288 | tries += 1
289 |
290 | # Set the final row of P
291 | P[i, np.concatenate((np.r_[0:i], np.r_[i+1:n]))] = thisP
292 |
293 | # Return final P-matrix
294 | print("Mean value of sigma: %f" % np.mean(np.sqrt(1 / beta)))
295 | return P
296 |
297 |
298 | def pca(X=np.array([]), no_dims=50):
299 | """
300 | Runs PCA on the NxD array X in order to reduce its dimensionality to
301 | no_dims dimensions.
302 | """
303 |
304 | print("Preprocessing the data using PCA...")
305 | (n, d) = X.shape
306 | X = X - np.tile(np.mean(X, 0), (n, 1))
307 | (l, M) = np.linalg.eig(np.dot(X.T, X))
308 | Y = np.dot(X, M[:, 0:no_dims])
309 | return Y
310 |
311 |
312 | def tsne(X=np.array([]), no_dims=2, initial_dims=50, perplexity=30.0, max_iter=1000):
313 | """
314 | Runs t-SNE on the dataset in the NxD array X to reduce its
315 | dimensionality to no_dims dimensions. The syntaxis of the function is
316 | `Y = tsne.tsne(X, no_dims, perplexity), where X is an NxD NumPy array.
317 | """
318 |
319 | # Check inputs
320 | if isinstance(no_dims, float):
321 | print("Error: array X should have type float.")
322 | return -1
323 | if round(no_dims) != no_dims:
324 | print("Error: number of dimensions should be an integer.")
325 | return -1
326 |
327 | # Initialize variables
328 | X = pca(X, initial_dims).real
329 | (n, d) = X.shape
330 | max_iter = max_iter
331 | initial_momentum = 0.5
332 | final_momentum = 0.8
333 | eta = 500
334 | min_gain = 0.01
335 | Y = np.random.randn(n, no_dims)
336 | dY = np.zeros((n, no_dims))
337 | iY = np.zeros((n, no_dims))
338 | gains = np.ones((n, no_dims))
339 |
340 | # Compute P-values
341 | P = x2p(X, 1e-5, perplexity)
342 | P = P + np.transpose(P)
343 | P = P / np.sum(P)
344 | P = P * 4. # early exaggeration
345 | P = np.maximum(P, 1e-12)
346 |
347 | # Run iterations
348 | for iter in range(max_iter):
349 |
350 | # Compute pairwise affinities
351 | sum_Y = np.sum(np.square(Y), 1)
352 | num = -2. * np.dot(Y, Y.T)
353 | num = 1. / (1. + np.add(np.add(num, sum_Y).T, sum_Y))
354 | num[range(n), range(n)] = 0.
355 | Q = num / np.sum(num)
356 | Q = np.maximum(Q, 1e-12)
357 |
358 | # Compute gradient
359 | PQ = P - Q
360 | for i in range(n):
361 | dY[i, :] = np.sum(np.tile(PQ[:, i] * num[:, i], (no_dims, 1)).T * (Y[i, :] - Y), 0)
362 |
363 | # Perform the update
364 | if iter < 20:
365 | momentum = initial_momentum
366 | else:
367 | momentum = final_momentum
368 | gains = (gains + 0.2) * ((dY > 0.) != (iY > 0.)) + \
369 | (gains * 0.8) * ((dY > 0.) == (iY > 0.))
370 | gains[gains < min_gain] = min_gain
371 | iY = momentum * iY - eta * (gains * dY)
372 | Y = Y + iY
373 | Y = Y - np.tile(np.mean(Y, 0), (n, 1))
374 |
375 | # Compute current value of cost function
376 | if (iter + 1) % 10 == 0:
377 | C = np.sum(P * np.log(P / Q))
378 | print("Iteration %d: error is %f" % (iter + 1, C))
379 |
380 | # Stop lying about P-values
381 | if iter == 100:
382 | P = P / 4.
383 |
384 | # Return solution
385 | return Y
386 |
387 |
388 | def evaluate(predictions, gts, num_classes):
389 | hist = np.zeros((num_classes, num_classes))
390 | for lp, lt in zip(predictions, gts):
391 | hist += _fast_hist(lp.flatten(), lt.flatten(), num_classes)
392 | # axis 0: gt, axis 1: prediction
393 | acc = np.diag(hist).sum() / hist.sum()
394 | acc_cls = np.diag(hist) / hist.sum(axis=1)
395 | acc_cls = np.nanmean(acc_cls)
396 | iu = np.diag(hist) / (hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist))
397 | mean_iu = np.nanmean(iu)
398 | freq = hist.sum(axis=1) / hist.sum()
399 | fwavacc = (freq[freq > 0] * iu[freq > 0]).sum()
400 | return acc, acc_cls, mean_iu, fwavacc, iu
401 |
402 |
403 | class AverageMeter(object):
404 | def __init__(self):
405 | self.reset()
406 |
407 | def reset(self):
408 | self.val = 0
409 | self.avg = 0
410 | self.sum = 0
411 | self.count = 0
412 |
413 | def update(self, val, n=1):
414 | self.val = val
415 | self.sum += val * n
416 | self.count += n
417 | self.avg = self.sum / self.count
418 |
419 |
420 | class PolyLR(object):
421 | def __init__(self, optimizer, curr_iter, max_iter, lr_decay):
422 | self.max_iter = float(max_iter)
423 | self.init_lr_groups = []
424 | for p in optimizer.param_groups:
425 | self.init_lr_groups.append(p['lr'])
426 | self.param_groups = optimizer.param_groups
427 | self.curr_iter = curr_iter
428 | self.lr_decay = lr_decay
429 |
430 | def step(self):
431 | for idx, p in enumerate(self.param_groups):
432 | p['lr'] = self.init_lr_groups[idx] * (1 - self.curr_iter / self.max_iter) ** self.lr_decay
433 |
434 | class LogFile:
435 | def __init__(self, fl):
436 | open(fl,'w').close()
437 | self.fl = fl
438 |
439 | def log(self, log_str):
440 | with open(self.fl, 'a') as f:
441 | f.write(log_str+'\n')
442 |
--------------------------------------------------------------------------------
/reid/utils/osutils.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | import os
3 | import errno
4 |
5 |
6 | def mkdir_if_missing(dir_path):
7 | try:
8 | os.makedirs(dir_path)
9 | except OSError as e:
10 | if e.errno != errno.EEXIST:
11 | raise
12 |
--------------------------------------------------------------------------------
/reid/utils/rerank.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Source: https://github.com/zhunzhong07/person-re-ranking
4 | Created on Mon Jun 26 14:46:56 2017
5 | @author: luohao
6 | Modified by Yixiao Ge, 2020-3-14.
7 | CVPR2017 paper:Zhong Z, Zheng L, Cao D, et al. Re-ranking Person Re-identification with k-reciprocal Encoding[J]. 2017.
8 | url:http://openaccess.thecvf.com/content_cvpr_2017/papers/Zhong_Re-Ranking_Person_Re-Identification_CVPR_2017_paper.pdf
9 | Matlab version: https://github.com/zhunzhong07/person-re-ranking
10 | API
11 | q_g_dist: query-gallery distance matrix, numpy array, shape [num_query, num_gallery]
12 | q_q_dist: query-query distance matrix, numpy array, shape [num_query, num_query]
13 | g_g_dist: gallery-gallery distance matrix, numpy array, shape [num_gallery, num_gallery]
14 | k1, k2, lambda_value: parameters, the original paper is (k1=20, k2=6, lambda_value=0.3)
15 | Returns:
16 | final_dist: re-ranked distance, numpy array, shape [num_query, num_gallery]
17 | """
18 | from __future__ import absolute_import
19 | from __future__ import print_function
20 | from __future__ import division
21 |
22 | __all__ = ['re_ranking']
23 |
24 | import numpy as np
25 | import time
26 |
27 | import torch
28 | import torch.nn.functional as F
29 |
30 | def re_ranking(q_g_dist, q_q_dist, g_g_dist, k1=20, k2=6, lambda_value=0.3):
31 |
32 | # The following naming, e.g. gallery_num, is different from outer scope.
33 | # Don't care about it.
34 |
35 | original_dist = np.concatenate(
36 | [np.concatenate([q_q_dist, q_g_dist], axis=1),
37 | np.concatenate([q_g_dist.T, g_g_dist], axis=1)],
38 | axis=0)
39 | original_dist = np.power(original_dist, 2).astype(np.float32)
40 | original_dist = np.transpose(1. * original_dist/np.max(original_dist,axis = 0))
41 | V = np.zeros_like(original_dist).astype(np.float32)
42 | initial_rank = np.argsort(original_dist).astype(np.int32)
43 |
44 | query_num = q_g_dist.shape[0]
45 | gallery_num = q_g_dist.shape[0] + q_g_dist.shape[1]
46 | all_num = gallery_num
47 |
48 | for i in range(all_num):
49 | # k-reciprocal neighbors
50 | forward_k_neigh_index = initial_rank[i,:k1+1]
51 | backward_k_neigh_index = initial_rank[forward_k_neigh_index,:k1+1]
52 | fi = np.where(backward_k_neigh_index==i)[0]
53 | k_reciprocal_index = forward_k_neigh_index[fi]
54 | k_reciprocal_expansion_index = k_reciprocal_index
55 | for j in range(len(k_reciprocal_index)):
56 | candidate = k_reciprocal_index[j]
57 | candidate_forward_k_neigh_index = initial_rank[candidate,:int(np.around(k1/2.))+1]
58 | candidate_backward_k_neigh_index = initial_rank[candidate_forward_k_neigh_index,:int(np.around(k1/2.))+1]
59 | fi_candidate = np.where(candidate_backward_k_neigh_index == candidate)[0]
60 | candidate_k_reciprocal_index = candidate_forward_k_neigh_index[fi_candidate]
61 | if len(np.intersect1d(candidate_k_reciprocal_index,k_reciprocal_index))> 2./3*len(candidate_k_reciprocal_index):
62 | k_reciprocal_expansion_index = np.append(k_reciprocal_expansion_index,candidate_k_reciprocal_index)
63 |
64 | k_reciprocal_expansion_index = np.unique(k_reciprocal_expansion_index)
65 | weight = np.exp(-original_dist[i,k_reciprocal_expansion_index])
66 | V[i,k_reciprocal_expansion_index] = 1.*weight/np.sum(weight)
67 | original_dist = original_dist[:query_num,]
68 | if k2 != 1:
69 | V_qe = np.zeros_like(V,dtype=np.float32)
70 | for i in range(all_num):
71 | V_qe[i,:] = np.mean(V[initial_rank[i,:k2],:],axis=0)
72 | V = V_qe
73 | del V_qe
74 | del initial_rank
75 | invIndex = []
76 | for i in range(gallery_num):
77 | invIndex.append(np.where(V[:,i] != 0)[0])
78 |
79 | jaccard_dist = np.zeros_like(original_dist,dtype = np.float32)
80 |
81 |
82 | for i in range(query_num):
83 | temp_min = np.zeros(shape=[1,gallery_num],dtype=np.float32)
84 | indNonZero = np.where(V[i,:] != 0)[0]
85 | indImages = []
86 | indImages = [invIndex[ind] for ind in indNonZero]
87 | for j in range(len(indNonZero)):
88 | temp_min[0,indImages[j]] = temp_min[0,indImages[j]]+ np.minimum(V[i,indNonZero[j]],V[indImages[j],indNonZero[j]])
89 | jaccard_dist[i] = 1-temp_min/(2.-temp_min)
90 |
91 | final_dist = jaccard_dist*(1-lambda_value) + original_dist*lambda_value
92 | del original_dist
93 | del V
94 | del jaccard_dist
95 | final_dist = final_dist[:query_num,query_num:]
96 | return final_dist
97 |
98 |
99 | def k_reciprocal_neigh(initial_rank, i, k1):
100 | forward_k_neigh_index = initial_rank[i,:k1+1]
101 | backward_k_neigh_index = initial_rank[forward_k_neigh_index,:k1+1]
102 | fi = torch.nonzero(backward_k_neigh_index==i)[:,0]
103 | return forward_k_neigh_index[fi]
104 |
105 | def compute_jaccard_dist(target_features, k1=20, k2=6, print_flag=True,
106 | lambda_value=0, source_features=None, use_gpu=False):
107 | end = time.time()
108 | N = target_features.size(0)
109 | if (use_gpu):
110 | # accelerate matrix distance computing
111 | target_features = target_features.cuda()
112 | if (source_features is not None):
113 | source_features = source_features.cuda()
114 |
115 | if ((lambda_value>0) and (source_features is not None)):
116 | M = source_features.size(0)
117 | sour_tar_dist = torch.pow(target_features, 2).sum(dim=1, keepdim=True).expand(N, M) + \
118 | torch.pow(source_features, 2).sum(dim=1, keepdim=True).expand(M, N).t()
119 | sour_tar_dist.addmm_(1, -2, target_features, source_features.t())
120 | sour_tar_dist = 1-torch.exp(-sour_tar_dist)
121 | sour_tar_dist = sour_tar_dist.cpu()
122 | source_dist_vec = sour_tar_dist.min(1)[0]
123 | del sour_tar_dist
124 | source_dist_vec /= source_dist_vec.max()
125 | source_dist = torch.zeros(N, N)
126 | for i in range(N):
127 | source_dist[i, :] = source_dist_vec + source_dist_vec[i]
128 | del source_dist_vec
129 |
130 |
131 | if print_flag:
132 | print('Computing original distance...')
133 | original_dist = torch.pow(target_features, 2).sum(dim=1, keepdim=True) * 2
134 | original_dist = original_dist.expand(N, N) - 2 * torch.mm(target_features, target_features.t())
135 | original_dist /= original_dist.max(0)[0]
136 | original_dist = original_dist.t()
137 | del target_features
138 | initial_rank = torch.argsort(original_dist, dim=-1)
139 |
140 | original_dist = original_dist.cpu()
141 | initial_rank = initial_rank.cpu()
142 | all_num = gallery_num = original_dist.size(0)
143 |
144 | #del target_features
145 | if (source_features is not None):
146 | del source_features
147 |
148 | if print_flag:
149 | print('Computing Jaccard distance...')
150 |
151 | nn_k1 = []
152 | nn_k1_half = []
153 | for i in range(all_num):
154 | nn_k1.append(k_reciprocal_neigh(initial_rank, i, k1))
155 | nn_k1_half.append(k_reciprocal_neigh(initial_rank, i, int(np.around(k1/2))))
156 |
157 | V = torch.zeros(all_num, all_num)
158 | for i in range(all_num):
159 | k_reciprocal_index = nn_k1[i]
160 | k_reciprocal_expansion_index = k_reciprocal_index
161 | for candidate in k_reciprocal_index:
162 | candidate_k_reciprocal_index = nn_k1_half[candidate]
163 | if (len(np.intersect1d(candidate_k_reciprocal_index,k_reciprocal_index)) > 2/3*len(candidate_k_reciprocal_index)):
164 | k_reciprocal_expansion_index = torch.cat((k_reciprocal_expansion_index,candidate_k_reciprocal_index))
165 |
166 | k_reciprocal_expansion_index = torch.unique(k_reciprocal_expansion_index) ## element-wise unique
167 | weight = torch.exp(-original_dist[i,k_reciprocal_expansion_index])
168 | V[i,k_reciprocal_expansion_index] = weight/torch.sum(weight)
169 |
170 | if k2 != 1:
171 | k2_rank = initial_rank[:,:k2].clone().view(-1)
172 | V_qe = V[k2_rank]
173 | V_qe = V_qe.view(initial_rank.size(0),k2,-1).sum(1)
174 | V_qe /= k2
175 | V = V_qe
176 | del V_qe
177 | del initial_rank
178 |
179 | invIndex = []
180 | for i in range(gallery_num):
181 | invIndex.append(torch.nonzero(V[:,i])[:,0]) #len(invIndex)=all_num
182 |
183 | jaccard_dist = torch.zeros_like(original_dist)
184 |
185 | #del original_dist # added line to save memory
186 |
187 | for i in range(all_num):
188 | temp_min = torch.zeros(1,gallery_num)
189 | indNonZero = torch.nonzero(V[i,:])[:,0]
190 | indImages = []
191 | indImages = [invIndex[ind] for ind in indNonZero]
192 | for j in range(len(indNonZero)):
193 | temp_min[0,indImages[j]] = temp_min[0,indImages[j]]+ torch.min(V[i,indNonZero[j]],V[indImages[j],indNonZero[j]])
194 | jaccard_dist[i] = 1-temp_min/(2-temp_min)
195 | del invIndex
196 |
197 | del V
198 |
199 | pos_bool = (jaccard_dist < 0)
200 | jaccard_dist[pos_bool] = 0.0
201 | if print_flag:
202 | print ("Time cost: {}".format(time.time()-end))
203 |
204 | if (lambda_value>0):
205 | original_dist[original_dist<0] = 0.0
206 | return jaccard_dist*(1-lambda_value) + original_dist*lambda_value
207 | else:
208 | return jaccard_dist #.cpu()
209 |
--------------------------------------------------------------------------------
/reid/utils/serialization.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function, absolute_import
2 | import json
3 | import os.path as osp
4 | import shutil
5 |
6 | import torch
7 | from torch.nn import Parameter
8 |
9 | from .osutils import mkdir_if_missing
10 |
11 |
12 | def save_checkpoint(state, fpath='checkpoint.pth.tar'):
13 | mkdir_if_missing(osp.dirname(fpath))
14 | torch.save(state, fpath)
15 |
16 |
17 | def load_checkpoint(fpath):
18 | if osp.isfile(fpath):
19 | checkpoint = torch.load(fpath)
20 | print("=> Loaded checkpoint '{}'".format(fpath))
21 | return checkpoint
22 | else:
23 | raise ValueError("=> No checkpoint found at '{}'".format(fpath))
24 |
25 |
26 | def copy_state_dict(state_dict, model, strip=None):
27 | tgt_state = model.state_dict()
28 | copied_names = set()
29 | for name, param in state_dict.items():
30 | if strip is not None and name.startswith(strip):
31 | name = name[len(strip):]
32 | if name not in tgt_state:
33 | continue
34 | if isinstance(param, Parameter):
35 | param = param.data
36 | if param.size() != tgt_state[name].size():
37 | print('mismatch:', name, param.size(), tgt_state[name].size())
38 | continue
39 | tgt_state[name].copy_(param)
40 | copied_names.add(name)
41 |
42 | missing = set(tgt_state.keys()) - copied_names
43 | if len(missing) > 0:
44 | print("missing keys in state_dict:", missing)
45 |
46 | return model
47 |
--------------------------------------------------------------------------------
/reid/utils/visualize.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import os
3 | import os.path as osp
4 | import shutil
5 |
6 |
7 | def make_dirs(dir):
8 | if not os.path.exists(dir):
9 | os.makedirs(dir)
10 | print('Successfully make dirs: {}'.format(dir))
11 | else:
12 | print('Existed dirs: {}'.format(dir))
13 |
14 |
15 | def visualize_ranked_results(distmat, dataset, save_dir='', topk=20, query_root='', gallery_root=''):
16 | """Visualizes ranked results.
17 | Supports both image-reid and video-reid.
18 | Args:
19 | distmat (numpy.ndarray): distance matrix of shape (num_query, num_gallery).
20 | dataset (tuple): a 2-tuple containing (query, gallery), each of which contains
21 | tuples of (img_path(s), pid, camid).
22 | save_dir (str): directory to save output images.
23 | topk (int, optional): denoting top-k images in the rank list to be visualized.
24 | """
25 | num_q, num_g = distmat.shape
26 |
27 | print('Visualizing top-{} ranks'.format(topk))
28 | print('# query: {}\n# gallery {}'.format(num_q, num_g))
29 | print('Saving images to "{}"'.format(save_dir))
30 |
31 | query, gallery = dataset
32 | assert num_q == len(query)
33 | assert num_g == len(gallery)
34 |
35 | indices = np.argsort(distmat, axis=1)
36 | make_dirs(save_dir)
37 |
38 | def _cp_img_to(src, dst, rank, prefix):
39 | """
40 | Args:
41 | src: image path or tuple (for vidreid)
42 | dst: target directory
43 | rank: int, denoting ranked position, starting from 1
44 | prefix: string
45 | """
46 | if isinstance(src, tuple) or isinstance(src, list):
47 | dst = osp.join(dst, prefix + '_top' + str(rank).zfill(3))
48 | make_dirs(dst)
49 | for img_path in src:
50 | shutil.copy(img_path, dst)
51 | else:
52 | dst = osp.join(dst, prefix + '_top' + str(rank).zfill(3) + '_name_' + osp.basename(src)[:9]+'.jpg')
53 | shutil.copy(src, dst)
54 |
55 | high_acc_list = []
56 | high_acc_thresh = 7
57 |
58 | for q_idx in range(num_q):
59 | q_infos = query[q_idx]
60 | qimg_path, qpid, qcamid = q_infos[0], q_infos[1], q_infos[2]
61 | #qimg_path, qpid, qcamid = query[q_idx]
62 | if isinstance(qimg_path, tuple) or isinstance(qimg_path, list):
63 | qdir = osp.join(save_dir, osp.basename(qimg_path[0])[:-4])
64 | else:
65 | qdir = osp.join(save_dir, osp.basename(qimg_path)[:-4])
66 | #make_dirs(qdir)
67 | #_cp_img_to(query_root + qimg_path, qdir, rank=0, prefix='query')
68 | top_hit, top_miss = 0, 0
69 |
70 | rank_idx = 1
71 | for g_idx in indices[q_idx, :]:
72 | g_infos = gallery[g_idx]
73 | gimg_path, gpid, gcamid = g_infos[0], g_infos[1], g_infos[2]
74 | #gimg_path, gpid, gcamid = gallery[g_idx]
75 | invalid = (qpid == gpid) & (qcamid == gcamid) #original version
76 | invalid2 = (gpid==-1) # added: ignore junk images
77 | if not (invalid or invalid2):
78 | if qpid != gpid: # and rank_idx == 1:
79 | top_miss += 1
80 | #_cp_img_to(gallery_root + gimg_path, qdir, rank=rank_idx, prefix='gallery')
81 | rank_idx += 1
82 | if rank_idx > topk:
83 | break
84 |
85 | if top_miss>1 and top_miss<=5: #top_miss==1: #top_hit < high_acc_thresh:
86 | high_acc_list.append(osp.basename(qimg_path)[0:7])
87 | # save top-ranked images for the query
88 | make_dirs(qdir)
89 | _cp_img_to(query_root + qimg_path, qdir, rank=0, prefix='query')
90 | rank_idx = 1
91 | for g_idx in indices[q_idx, :]:
92 | g_infos = gallery[g_idx]
93 | gimg_path, gpid, gcamid = g_infos[0], g_infos[1], g_infos[2]
94 | invalid = (qpid == gpid) & (qcamid == gcamid) #original version
95 | invalid2 = (gpid==-1) # added: ignore junk images
96 | if not (invalid or invalid2):
97 | _cp_img_to(gallery_root + gimg_path, qdir, rank=rank_idx, prefix='gallery')
98 | rank_idx += 1
99 | if rank_idx > topk:
100 | break
101 |
102 | print("Done")
103 | print('query images whose top-{} has mismatches are:'.format(topk))
104 | for elem in high_acc_list:
105 | print(elem)
106 |
--------------------------------------------------------------------------------
/train_cap.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function, absolute_import
2 | import argparse
3 | import os.path as osp
4 | import numpy as np
5 | import sys
6 | import torch
7 | from torch import nn
8 | from torch.backends import cudnn
9 | from torch.utils.data import DataLoader
10 | from reid.datasets.target_dataset import DA
11 | from reid import models
12 | from reid.models import stb_net
13 | from reid.trainers import Trainer
14 | from reid.evaluators import Evaluator
15 | from reid.utils.data import transforms as T
16 | from reid.utils.data.preprocessor import Preprocessor, UnsupervisedTargetPreprocessor, ClassUniformlySampler
17 | from reid.utils.logging import Logger
18 | from reid.loss import CAPMemory
19 | from bisect import bisect_right
20 | from reid.utils.evaluation_metrics.retrieval import PersonReIDMAP
21 | from reid.utils.meters import CatMeter
22 | from reid.img_grouping import img_association
23 |
24 |
25 | def get_data(data_dir, target, height, width, batch_size, re=0, workers=8):
26 |
27 | dataset = DA(data_dir, target, generate_propagate_data=True)
28 |
29 | normalizer = T.Normalize(mean=[0.485, 0.456, 0.406],
30 | std=[0.229, 0.224, 0.225])
31 |
32 | num_classes = dataset.num_train_ids
33 |
34 | test_transformer = T.Compose([
35 | T.Resize((height, width), interpolation=3),
36 | T.ToTensor(),
37 | normalizer,
38 | ])
39 |
40 | propagate_loader = DataLoader(
41 | UnsupervisedTargetPreprocessor(dataset.target_train_original,
42 | root=osp.join(dataset.target_images_dir, dataset.target_train_path),
43 | num_cam=dataset.target_num_cam, transform=test_transformer),
44 | batch_size=batch_size, num_workers=workers,
45 | shuffle=False, pin_memory=True)
46 |
47 | query_loader = DataLoader(
48 | Preprocessor(dataset.query,
49 | root=osp.join(dataset.target_images_dir, dataset.query_path), transform=test_transformer),
50 | batch_size=batch_size, num_workers=workers,
51 | shuffle=False, pin_memory=True)
52 |
53 | gallery_loader = DataLoader(
54 | Preprocessor(dataset.gallery,
55 | root=osp.join(dataset.target_images_dir, dataset.gallery_path), transform=test_transformer),
56 | batch_size=batch_size, num_workers=workers,
57 | shuffle=False, pin_memory=True)
58 |
59 | return dataset, num_classes, query_loader, gallery_loader, propagate_loader
60 |
61 |
62 | def update_train_loader(dataset, train_samples, updated_label, height, width, batch_size, re, workers,
63 | all_img_cams, sample_position=7):
64 | normalizer = T.Normalize(mean=[0.485, 0.456, 0.406],
65 | std=[0.229, 0.224, 0.225])
66 |
67 | train_transformer = T.Compose([
68 | T.Resize((height, width), interpolation=3),
69 | T.RandomHorizontalFlip(p=0.5),
70 | T.Pad(10),
71 | T.RandomCrop((height, width)),
72 | T.ToTensor(),
73 | normalizer,
74 | T.RandomErasing(EPSILON=re)
75 | ])
76 |
77 | # obtain global accumulated label from pseudo label and cameras
78 | pure_label = updated_label[updated_label>=0]
79 | pure_cams = all_img_cams[updated_label>=0]
80 | accumulate_labels = np.zeros(pure_label.shape, pure_label.dtype)
81 | prev_id_count = 0
82 | id_count_each_cam = []
83 | for this_cam in np.unique(pure_cams):
84 | percam_labels = pure_label[pure_cams == this_cam]
85 | unique_id = np.unique(percam_labels)
86 | id_count_each_cam.append(len(unique_id))
87 | id_dict = {ID: i for i, ID in enumerate(unique_id.tolist())}
88 | for i in range(len(percam_labels)):
89 | percam_labels[i] = id_dict[percam_labels[i]]
90 | accumulate_labels[pure_cams == this_cam] = percam_labels + prev_id_count
91 | prev_id_count += len(unique_id)
92 | print(' sum(id_count_each_cam)= {}'.format(sum(id_count_each_cam)))
93 | new_accum_labels = -1*np.ones(updated_label.shape, updated_label.dtype)
94 | new_accum_labels[updated_label>=0] = accumulate_labels
95 |
96 | # update sample list
97 | new_train_samples = []
98 | for sample in train_samples:
99 | lbl = updated_label[sample[3]]
100 | if lbl != -1:
101 | assert(new_accum_labels[sample[3]]>=0)
102 | new_sample = sample + (lbl, new_accum_labels[sample[3]])
103 | new_train_samples.append(new_sample)
104 |
105 | target_train_loader = DataLoader(
106 | UnsupervisedTargetPreprocessor(new_train_samples, root=osp.join(dataset.target_images_dir, dataset.target_train_path),
107 | num_cam=dataset.target_num_cam, transform=train_transformer, has_pseudo_label=True),
108 | batch_size=batch_size, num_workers=workers, pin_memory=True, drop_last=True,
109 | sampler=ClassUniformlySampler(new_train_samples, class_position=sample_position, k=4))
110 |
111 | return target_train_loader, len(new_train_samples)
112 |
113 |
114 | class WarmupMultiStepLR(torch.optim.lr_scheduler._LRScheduler):
115 | def __init__(self, optimizer, milestones, gamma=0.1, warmup_factor=1.0 / 3, warmup_iters=500,
116 | warmup_method="linear", last_epoch=-1):
117 | if not list(milestones) == sorted(milestones):
118 | raise ValueError(
119 | "Milestones should be a list of" " increasing integers. Got {}",
120 | milestones,)
121 |
122 | if warmup_method not in ("constant", "linear"):
123 | raise ValueError(
124 | "Only 'constant' or 'linear' warmup_method accepted"
125 | "got {}".format(warmup_method)
126 | )
127 | self.milestones = milestones
128 | self.gamma = gamma
129 | self.warmup_factor = warmup_factor
130 | self.warmup_iters = warmup_iters
131 | self.warmup_method = warmup_method
132 | super(WarmupMultiStepLR, self).__init__(optimizer, last_epoch)
133 |
134 | def get_lr(self):
135 | warmup_factor = 1
136 | if self.last_epoch < self.warmup_iters:
137 | if self.warmup_method == "constant":
138 | warmup_factor = self.warmup_factor
139 | elif self.warmup_method == "linear":
140 | alpha = float(self.last_epoch) / float(self.warmup_iters)
141 | warmup_factor = self.warmup_factor * (1 - alpha) + alpha
142 | return [
143 | base_lr
144 | * warmup_factor
145 | * self.gamma ** bisect_right(self.milestones, self.last_epoch)
146 | for base_lr in self.base_lrs
147 | ]
148 |
149 |
150 | def test_model(model, query_loader, gallery_loader):
151 | model.eval()
152 |
153 | # meters
154 | query_features_meter, query_pids_meter, query_cids_meter = CatMeter(), CatMeter(), CatMeter()
155 | gallery_features_meter, gallery_pids_meter, gallery_cids_meter = CatMeter(), CatMeter(), CatMeter()
156 |
157 | # init dataset
158 | loaders = [query_loader, gallery_loader]
159 |
160 | # compute query and gallery features
161 | with torch.no_grad():
162 | for loader_id, loader in enumerate(loaders):
163 | for data in loader:
164 | images = data[0]
165 | pids = data[2]
166 | cids = data[3]
167 | features = model(images)
168 | # save as query features
169 | if loader_id == 0:
170 | query_features_meter.update(features.data)
171 | query_pids_meter.update(pids)
172 | query_cids_meter.update(cids)
173 | # save as gallery features
174 | elif loader_id == 1:
175 | gallery_features_meter.update(features.data)
176 | gallery_pids_meter.update(pids)
177 | gallery_cids_meter.update(cids)
178 |
179 | query_features = query_features_meter.get_val_numpy()
180 | gallery_features = gallery_features_meter.get_val_numpy()
181 |
182 | # compute mAP and rank@k
183 | result = PersonReIDMAP(
184 | query_features, query_cids_meter.get_val_numpy(), query_pids_meter.get_val_numpy(),
185 | gallery_features, gallery_cids_meter.get_val_numpy(), gallery_pids_meter.get_val_numpy(), dist='cosine')
186 |
187 | return result.mAP, result.CMC[0], result.CMC[4], result.CMC[9], result.CMC[19]
188 |
189 |
190 |
191 | def main(args):
192 | cudnn.benchmark = True
193 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
194 |
195 | # Redirect print to both console and log file
196 | if not args.evaluate:
197 | sys.stdout = Logger(osp.join(args.logs_dir, 'log.txt'))
198 | print('log_dir= ', args.logs_dir)
199 |
200 | # Print logs
201 | print('args= ', args)
202 |
203 | # Create data loaders
204 | dataset, num_classes, query_loader, gallery_loader, propagate_loader = get_data(
205 | args.data_dir, args.target, args.height, args.width, args.batch_size, args.re, args.workers)
206 |
207 | # Create model
208 | model = stb_net.MemoryBankModel(out_dim=2048, use_bnneck=args.use_bnneck)
209 |
210 | # Create memory bank
211 | cap_memory = CAPMemory(beta=args.inv_beta, alpha=args.inv_alpha, all_img_cams=dataset.target_train_all_img_cams)
212 |
213 | # Set model
214 | model = nn.DataParallel(model.to(device))
215 | cap_memory = cap_memory.to(device)
216 |
217 | # Load from checkpoint
218 | if len(args.load_ckpt)>0:
219 | print(' Loading pre-trained model: {}'.format(args.load_ckpt))
220 | trained_dict = torch.load(args.load_ckpt)
221 | filtered_trained_dict = {k: v for k, v in trained_dict.items() if not k.startswith('module.classifier')}
222 | for k in filtered_trained_dict.keys():
223 | if 'embeding' in k:
224 | print('pretrained model has key= {}'.format(k))
225 | model_dict = model.state_dict()
226 | model_dict.update(filtered_trained_dict)
227 | model.load_state_dict(model_dict)
228 |
229 | # Evaluator
230 | if args.evaluate:
231 | print("Test:")
232 | eval_results = test_model(model, query_loader, gallery_loader)
233 | print('rank1: %.4f, rank5: %.4f, rank10: %.4f, rank20: %.4f, mAP: %.4f'
234 | % (eval_results[1], eval_results[2], eval_results[3], eval_results[4], eval_results[0]))
235 | return
236 |
237 | # Optimizer
238 | params = []
239 | for key, value in model.named_parameters():
240 | if not value.requires_grad:
241 | continue
242 | lr = args.base_lr
243 | weight_decay = args.weight_decay
244 | params += [{"params": [value], "lr": lr, "weight_decay": weight_decay}]
245 |
246 | optimizer = torch.optim.Adam(params)
247 | lr_scheduler = WarmupMultiStepLR(optimizer, args.milestones, gamma=0.1, warmup_factor=0.01, warmup_iters=10)
248 |
249 | # Trainer
250 | trainer = Trainer(model, cap_memory)
251 |
252 | # Start training
253 | for epoch in range(args.epochs):
254 | lr_scheduler.step(epoch)
255 |
256 | # image grouping
257 | print('Epoch {} image grouping:'.format(epoch))
258 | updated_label, init_intra_id_feat = img_association(model, propagate_loader, min_sample=4,
259 | eps=args.thresh, rerank=True, k1=20, k2=6, intra_id_reinitialize=True)
260 |
261 | # update train loader
262 | new_train_loader, loader_size = update_train_loader(dataset, dataset.target_train, updated_label, args.height, args.width,
263 | args.batch_size, args.re, args.workers, dataset.target_train_all_img_cams, sample_position=5)
264 | num_batch = int(float(loader_size)/args.batch_size)
265 |
266 | # train an epoch
267 | trainer.train(epoch, new_train_loader, optimizer,
268 | num_batch=num_batch, all_pseudo_label=torch.from_numpy(updated_label).to(torch.device('cuda')),
269 | init_intra_id_feat=init_intra_id_feat)
270 |
271 | # test
272 | if (epoch+1)%10 == 0:
273 | print('Test with epoch {} model:'.format(epoch))
274 | eval_results = test_model(model, query_loader, gallery_loader)
275 | print(' rank1: %.4f, rank5: %.4f, rank10: %.4f, rank20: %.4f, mAP: %.4f'
276 | % (eval_results[1], eval_results[2], eval_results[3], eval_results[4], eval_results[0]))
277 |
278 | # save final model
279 | if (epoch+1)%args.epochs == 0:
280 | torch.save(model.state_dict(), osp.join(args.logs_dir, 'final_model_epoch_'+str(epoch+1)+'.pth'))
281 | print('Final Model saved.')
282 |
283 |
284 | if __name__ == '__main__':
285 | parser = argparse.ArgumentParser(description="Camera Aware Proxies for Unsupervised Person Re-ID")
286 | # target dataset
287 | parser.add_argument('--target', type=str, default='market')
288 | # imgs setting
289 | parser.add_argument('--batch_size', type=int, default=32)
290 | parser.add_argument('--workers', type=int, default=8)
291 | parser.add_argument('--height', type=int, default=256, help="input height, default: 256")
292 | parser.add_argument('--width', type=int, default=128, help="input width, default: 128")
293 | # random erasing
294 | parser.add_argument('--re', type=float, default=0.5)
295 | # model
296 | parser.add_argument('--arch', type=str, default='resnet50', choices=models.names())
297 | parser.add_argument('--features', type=int, default=2048)
298 | parser.add_argument('--dropout', type=float, default=0.5)
299 | parser.add_argument('--use_bnneck', action='store_true')
300 | parser.add_argument('--pool_type', type=str, default='avgpool')
301 | # optimizer
302 | parser.add_argument('--momentum', type=float, default=0.9)
303 | parser.add_argument('--weight_decay', type=float, default=5e-4)
304 | parser.add_argument('--base_lr', type=float, default=0.00035) # for adam
305 | parser.add_argument('--milestones',type=int, nargs='+', default=[20, 40]) # for adam
306 | # training configs
307 | parser.add_argument('--resume', type=str, default='', metavar='PATH')
308 | parser.add_argument('--evaluate', action='store_true', help="evaluation only")
309 | parser.add_argument('--epochs', type=int, default=50)
310 | parser.add_argument('--print_freq', type=int, default=1)
311 | # misc
312 | working_dir = osp.dirname(osp.abspath(__file__))
313 | parser.add_argument('--data_dir', type=str, metavar='PATH', default=osp.join(working_dir, 'data'))
314 | parser.add_argument('--logs_dir', type=str, metavar='PATH', default=osp.join(working_dir, 'logs'))
315 | parser.add_argument('--load_ckpt', type=str, default='')
316 | # loss learning
317 | parser.add_argument('--inv_alpha', type=float, default=0.2, help='update rate for the memory')
318 | parser.add_argument('--inv_beta', type=float, default=0.07, help='temperature for contrastive loss')
319 | parser.add_argument('--thresh', type=int, default=0.5, help='threshold for clustering')
320 | args = parser.parse_args()
321 |
322 | args.load_ckpt = ''
323 | args.evaluate = False
324 | args.use_bnneck = True
325 | main(args)
326 |
327 | # CUDA_VISIBLE_DEVICES=0 python train_cap.py --target 'VeRi' --data_dir '/home/xxx/folder/dataset' --logs_dir 'VeRi_logs'
328 |
329 |
--------------------------------------------------------------------------------