From c8504c8e3458ad3ecc0242f5670896c15a8bf3fd Mon Sep 17 00:00:00 2001 From: zhurui <274461951@qq.com> Date: Thu, 4 Jul 2024 17:00:21 +0800 Subject: [PATCH] first commit --- .gitignore | 22 + INSTALL.md | 74 +++ LICENSE | 201 ++++++++ README.md | 148 ++++++ configs/culane.py | 88 ++++ configs/culane_copy.py | 97 ++++ configs/tusimple.py | 93 ++++ datasets/__init__.py | 4 + datasets/base_dataset.py | 86 ++++ datasets/culane.py | 72 +++ datasets/registry.py | 36 ++ datasets/tusimple.py | 150 ++++++ intro.png | Bin 0 -> 291104 bytes main.py | 73 +++ models/__init__.py | 1 + models/decoder.py | 129 ++++++ models/decoder_copy.py | 135 ++++++ models/decoder_copy2.py | 143 ++++++ models/mobilenetv2.py | 422 +++++++++++++++++ models/mobilenetv2_copy2.py | 436 ++++++++++++++++++ models/registry.py | 16 + models/resa.py | 142 ++++++ models/resnet.py | 377 +++++++++++++++ models/resnet_copy.py | 432 +++++++++++++++++ requirement.txt | 8 + runner/__init__.py | 4 + runner/evaluator/__init__.py | 2 + runner/evaluator/culane/culane.py | 158 +++++++ .../culane/lane_evaluation/.gitignore | 2 + .../evaluator/culane/lane_evaluation/Makefile | 50 ++ .../lane_evaluation/include/counter.hpp | 47 ++ .../include/hungarianGraph.hpp | 71 +++ .../lane_evaluation/include/lane_compare.hpp | 51 ++ .../culane/lane_evaluation/include/spline.hpp | 28 ++ .../culane/lane_evaluation/src/counter.cpp | 134 ++++++ .../culane/lane_evaluation/src/evaluate.cpp | 302 ++++++++++++ .../lane_evaluation/src/lane_compare.cpp | 73 +++ .../culane/lane_evaluation/src/spline.cpp | 178 +++++++ runner/evaluator/culane/prob2lines.py | 51 ++ runner/evaluator/tusimple/getLane.py | 115 +++++ runner/evaluator/tusimple/lane.py | 108 +++++ runner/evaluator/tusimple/tusimple.py | 111 +++++ runner/logger.py | 50 ++ runner/net_utils.py | 43 ++ runner/optimizer.py | 26 ++ runner/recorder.py | 100 ++++ runner/registry.py | 19 + runner/resa_trainer.py | 58 +++ runner/runner.py | 112 +++++ runner/scheduler.py | 20 + tools/generate_seg_tusimple.py | 105 +++++ utils/__init__.py | 2 + utils/config.py | 417 +++++++++++++++++ utils/registry.py | 81 ++++ utils/transforms.py | 357 ++++++++++++++ 55 files changed, 6260 insertions(+) create mode 100644 .gitignore create mode 100644 INSTALL.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 configs/culane.py create mode 100644 configs/culane_copy.py create mode 100644 configs/tusimple.py create mode 100644 datasets/__init__.py create mode 100644 datasets/base_dataset.py create mode 100644 datasets/culane.py create mode 100644 datasets/registry.py create mode 100644 datasets/tusimple.py create mode 100644 intro.png create mode 100644 main.py create mode 100644 models/__init__.py create mode 100644 models/decoder.py create mode 100644 models/decoder_copy.py create mode 100644 models/decoder_copy2.py create mode 100644 models/mobilenetv2.py create mode 100644 models/mobilenetv2_copy2.py create mode 100644 models/registry.py create mode 100644 models/resa.py create mode 100644 models/resnet.py create mode 100644 models/resnet_copy.py create mode 100644 requirement.txt create mode 100644 runner/__init__.py create mode 100644 runner/evaluator/__init__.py create mode 100644 runner/evaluator/culane/culane.py create mode 100644 runner/evaluator/culane/lane_evaluation/.gitignore create mode 100644 runner/evaluator/culane/lane_evaluation/Makefile create mode 100644 runner/evaluator/culane/lane_evaluation/include/counter.hpp create mode 100644 runner/evaluator/culane/lane_evaluation/include/hungarianGraph.hpp create mode 100644 runner/evaluator/culane/lane_evaluation/include/lane_compare.hpp create mode 100644 runner/evaluator/culane/lane_evaluation/include/spline.hpp create mode 100644 runner/evaluator/culane/lane_evaluation/src/counter.cpp create mode 100644 runner/evaluator/culane/lane_evaluation/src/evaluate.cpp create mode 100644 runner/evaluator/culane/lane_evaluation/src/lane_compare.cpp create mode 100644 runner/evaluator/culane/lane_evaluation/src/spline.cpp create mode 100644 runner/evaluator/culane/prob2lines.py create mode 100644 runner/evaluator/tusimple/getLane.py create mode 100644 runner/evaluator/tusimple/lane.py create mode 100644 runner/evaluator/tusimple/tusimple.py create mode 100644 runner/logger.py create mode 100644 runner/net_utils.py create mode 100644 runner/optimizer.py create mode 100644 runner/recorder.py create mode 100644 runner/registry.py create mode 100644 runner/resa_trainer.py create mode 100644 runner/runner.py create mode 100644 runner/scheduler.py create mode 100644 tools/generate_seg_tusimple.py create mode 100644 utils/__init__.py create mode 100644 utils/config.py create mode 100644 utils/registry.py create mode 100644 utils/transforms.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7bc69e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +work_dirs/ +predicts/ +output/ +data/ +data + +__pycache__/ +*/*.un~ +.*.swp + + + +*.egg-info/ +*.egg + +output.txt +.vscode/* +.DS_Store +tmp.* +*.pt +*.pth +*.un~ diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..63025f8 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,74 @@ + +# Install + +1. Clone the RESA repository + ``` + git clone https://github.com/zjulearning/resa.git + ``` + We call this directory as `$RESA_ROOT` + +2. Create a conda virtual environment and activate it (conda is optional) + + ```Shell + conda create -n resa python=3.8 -y + conda activate resa + ``` + +3. Install dependencies + + ```Shell + # Install pytorch firstly, the cudatoolkit version should be same in your system. (you can also use pip to install pytorch and torchvision) + conda install pytorch torchvision cudatoolkit=10.1 -c pytorch + + # Or you can install via pip + pip install torch torchvision + + # Install python packages + pip install -r requirements.txt + ``` + +4. Data preparation + + Download [CULane](https://xingangpan.github.io/projects/CULane.html) and [Tusimple](https://github.com/TuSimple/tusimple-benchmark/issues/3). Then extract them to `$CULANEROOT` and `$TUSIMPLEROOT`. Create link to `data` directory. + + ```Shell + cd $RESA_ROOT + ln -s $CULANEROOT data/CULane + ln -s $TUSIMPLEROOT data/tusimple + ``` + + For Tusimple, the segmentation annotation is not provided, hence we need to generate segmentation from the json annotation. + + ```Shell + python scripts/convert_tusimple.py --root $TUSIMPLEROOT + # this will generate segmentations and two list files: train_gt.txt and test.txt + ``` + + For CULane, you should have structure like this: + ``` + $RESA_ROOT/data/CULane/driver_xx_xxframe # data folders x6 + $RESA_ROOT/data/CULane/laneseg_label_w16 # lane segmentation labels + $RESA_ROOT/data/CULane/list # data lists + ``` + + For Tusimple, you should have structure like this: + ``` + $RESA_ROOT/data/tusimple/clips # data folders + $RESA_ROOT/data/tusimple/lable_data_xxxx.json # label json file x4 + $RESA_ROOT/data/tusimple/test_tasks_0627.json # test tasks json file + $RESA_ROOT/data/tusimple/test_label.json # test label json file + ``` + +5. Install CULane evaluation tools. + + This tools requires OpenCV C++. Please follow [here](https://docs.opencv.org/master/d7/d9f/tutorial_linux_install.html) to install OpenCV C++. Or just install opencv with command `sudo apt-get install libopencv-dev` + + + Then compile the evaluation tool of CULane. + ```Shell + cd $RESA_ROOT/runner/evaluator/culane/lane_evaluation + make + cd - + ``` + + Note that, the default `opencv` version is 3. If you use opencv2, please modify the `OPENCV_VERSION := 3` to `OPENCV_VERSION := 2` in the `Makefile`. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8df642d --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 Tu Zheng + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2063f6 --- /dev/null +++ b/README.md @@ -0,0 +1,148 @@ +# RESA +PyTorch implementation of the paper "[RESA: Recurrent Feature-Shift Aggregator for Lane Detection](https://arxiv.org/abs/2008.13719)". + +Our paper has been accepted by AAAI2021. + +**News**: We also release RESA on [LaneDet](https://github.com/Turoad/lanedet). It's also recommended for you to try LaneDet. + +## Introduction +![intro](intro.png "intro") +- RESA shifts sliced +feature map recurrently in vertical and horizontal directions +and enables each pixel to gather global information. +- RESA achieves SOTA results on CULane and Tusimple Dataset. + +## Get started +1. Clone the RESA repository + ``` + git clone https://github.com/zjulearning/resa.git + ``` + We call this directory as `$RESA_ROOT` + +2. Create a conda virtual environment and activate it (conda is optional) + + ```Shell + conda create -n resa python=3.8 -y + conda activate resa + ``` + +3. Install dependencies + + ```Shell + # Install pytorch firstly, the cudatoolkit version should be same in your system. (you can also use pip to install pytorch and torchvision) + conda install pytorch torchvision cudatoolkit=10.1 -c pytorch + + # Or you can install via pip + pip install torch torchvision + + # Install python packages + pip install -r requirements.txt + ``` + +4. Data preparation + + Download [CULane](https://xingangpan.github.io/projects/CULane.html) and [Tusimple](https://github.com/TuSimple/tusimple-benchmark/issues/3). Then extract them to `$CULANEROOT` and `$TUSIMPLEROOT`. Create link to `data` directory. + + ```Shell + cd $RESA_ROOT + mkdir -p data + ln -s $CULANEROOT data/CULane + ln -s $TUSIMPLEROOT data/tusimple + ``` + + For CULane, you should have structure like this: + ``` + $CULANEROOT/driver_xx_xxframe # data folders x6 + $CULANEROOT/laneseg_label_w16 # lane segmentation labels + $CULANEROOT/list # data lists + ``` + + For Tusimple, you should have structure like this: + ``` + $TUSIMPLEROOT/clips # data folders + $TUSIMPLEROOT/lable_data_xxxx.json # label json file x4 + $TUSIMPLEROOT/test_tasks_0627.json # test tasks json file + $TUSIMPLEROOT/test_label.json # test label json file + + ``` + + For Tusimple, the segmentation annotation is not provided, hence we need to generate segmentation from the json annotation. + + ```Shell + python tools/generate_seg_tusimple.py --root $TUSIMPLEROOT + # this will generate seg_label directory + ``` + +5. Install CULane evaluation tools. + + This tools requires OpenCV C++. Please follow [here](https://docs.opencv.org/master/d7/d9f/tutorial_linux_install.html) to install OpenCV C++. Or just install opencv with command `sudo apt-get install libopencv-dev` + + + Then compile the evaluation tool of CULane. + ```Shell + cd $RESA_ROOT/runner/evaluator/culane/lane_evaluation + make + cd - + ``` + + Note that, the default `opencv` version is 3. If you use opencv2, please modify the `OPENCV_VERSION := 3` to `OPENCV_VERSION := 2` in the `Makefile`. + + +## Training + +For training, run + +```Shell +python main.py [configs/path_to_your_config] --gpus [gpu_ids] +``` + + +For example, run +```Shell +python main.py configs/culane.py --gpus 0 1 2 3 +``` + +## Testing +For testing, run +```Shell +python main.py c[configs/path_to_your_config] --validate --load_from [path_to_your_model] [gpu_num] +``` + +For example, run +```Shell +python main.py configs/culane.py --validate --load_from culane_resnet50.pth --gpus 0 1 2 3 + +python main.py configs/tusimple.py --validate --load_from tusimple_resnet34.pth --gpus 0 1 2 3 +``` + + +We provide two trained ResNet models on CULane and Tusimple, downloading our best performed model (Tusimple: [GoogleDrive](https://drive.google.com/file/d/1M1xi82y0RoWUwYYG9LmZHXWSD2D60o0D/view?usp=sharing)/[BaiduDrive(code:s5ii)](https://pan.baidu.com/s/1CgJFrt9OHe-RUNooPpHRGA), +CULane: [GoogleDrive](https://drive.google.com/file/d/1pcqq9lpJ4ixJgFVFndlPe42VgVsjgn0Q/view?usp=sharing)/[BaiduDrive(code:rlwj)](https://pan.baidu.com/s/1ODKAZxpKrZIPXyaNnxcV3g) +) + +## Visualization +Just add `--view`. + +For example: +```Shell +python main.py configs/culane.py --validate --load_from culane_resnet50.pth --gpus 0 1 2 3 --view +``` +You will get the result in the directory: `work_dirs/[DATASET]/xxx/vis`. + +## Citation +If you use our method, please consider citing: +```BibTeX +@inproceedings{zheng2021resa, + title={RESA: Recurrent Feature-Shift Aggregator for Lane Detection}, + author={Zheng, Tu and Fang, Hao and Zhang, Yi and Tang, Wenjian and Yang, Zheng and Liu, Haifeng and Cai, Deng}, + booktitle={Proceedings of the AAAI Conference on Artificial Intelligence}, + volume={35}, + number={4}, + pages={3547--3554}, + year={2021} +} +``` + + diff --git a/configs/culane.py b/configs/culane.py new file mode 100644 index 0000000..2bc022f --- /dev/null +++ b/configs/culane.py @@ -0,0 +1,88 @@ +net = dict( + type='RESANet', +) + +backbone = dict( + type='ResNetWrapper', + resnet='resnet50', + pretrained=True, + replace_stride_with_dilation=[False, True, True], + out_conv=True, + fea_stride=8, +) + +resa = dict( + type='RESA', + alpha=2.0, + iter=4, + input_channel=128, + conv_stride=9, +) + +#decoder = 'PlainDecoder' +decoder = 'BUSD' + +trainer = dict( + type='RESA' +) + +evaluator = dict( + type='CULane', +) + +optimizer = dict( + type='sgd', + lr=0.025, + weight_decay=1e-4, + momentum=0.9 +) + +epochs = 12 +batch_size = 8 +total_iter = (88880 // batch_size) * epochs +import math +scheduler = dict( + type = 'LambdaLR', + lr_lambda = lambda _iter : math.pow(1 - _iter/total_iter, 0.9) +) + +loss_type = 'dice_loss' +seg_loss_weight = 2. +eval_ep = 6 +save_ep = epochs + +bg_weight = 0.4 + +img_norm = dict( + mean=[103.939, 116.779, 123.68], + std=[1., 1., 1.] +) + +img_height = 288 +img_width = 800 +cut_height = 240 + +dataset_path = './data/CULane' +dataset = dict( + train=dict( + type='CULane', + img_path=dataset_path, + data_list='train_gt.txt', + ), + val=dict( + type='CULane', + img_path=dataset_path, + data_list='test.txt', + ), + test=dict( + type='CULane', + img_path=dataset_path, + data_list='test.txt', + ) +) + + +workers = 12 +num_classes = 4 + 1 +ignore_label = 255 +log_interval = 500 diff --git a/configs/culane_copy.py b/configs/culane_copy.py new file mode 100644 index 0000000..d2478b6 --- /dev/null +++ b/configs/culane_copy.py @@ -0,0 +1,97 @@ +net = dict( + type='RESANet', +) + +# backbone = dict( +# type='ResNetWrapper', +# resnet='resnet50', +# pretrained=True, +# replace_stride_with_dilation=[False, True, True], +# out_conv=True, +# fea_stride=8, +# ) + +backbone = dict( + type='ResNetWrapper', + resnet='resnet34', + pretrained=True, + replace_stride_with_dilation=[False, False, False], + out_conv=False, + fea_stride=8, +) + +resa = dict( + type='RESA', + alpha=2.0, + iter=4, + input_channel=128, + conv_stride=9, +) + +#decoder = 'PlainDecoder' +decoder = 'BUSD' + +trainer = dict( + type='RESA' +) + +evaluator = dict( + type='CULane', +) + +optimizer = dict( + type='sgd', + lr=0.025, + weight_decay=1e-4, + momentum=0.9 +) + +epochs = 20 +batch_size = 8 +total_iter = (88880 // batch_size) * epochs +import math +scheduler = dict( + type = 'LambdaLR', + lr_lambda = lambda _iter : math.pow(1 - _iter/total_iter, 0.9) +) + +loss_type = 'dice_loss' +seg_loss_weight = 2. +eval_ep = 1 +save_ep = epochs + +bg_weight = 0.4 + +img_norm = dict( + mean=[103.939, 116.779, 123.68], + std=[1., 1., 1.] +) + +img_height = 288 +img_width = 800 +cut_height = 240 + +dataset_path = './data/CULane' +dataset = dict( + train=dict( + type='CULane', + img_path=dataset_path, + data_list='train_gt.txt', + ), + val=dict( + type='CULane', + img_path=dataset_path, + data_list='test.txt', + ), + test=dict( + type='CULane', + img_path=dataset_path, + data_list='test.txt', + ) +) + + +workers = 12 +num_classes = 4 + 1 +ignore_label = 255 +log_interval = 500 diff --git a/configs/tusimple.py b/configs/tusimple.py new file mode 100644 index 0000000..a075c19 --- /dev/null +++ b/configs/tusimple.py @@ -0,0 +1,93 @@ +net = dict( + type='RESANet', +) + +backbone = dict( + type='ResNetWrapper', + resnet='resnet34', + pretrained=True, + replace_stride_with_dilation=[False, True, True], + out_conv=True, + fea_stride=8, +) + +resa = dict( + type='RESA', + alpha=2.0, + iter=5, + input_channel=128, + conv_stride=9, +) + +decoder = 'BUSD' + +trainer = dict( + type='RESA' +) + +evaluator = dict( + type='Tusimple', + thresh = 0.60 +) + +optimizer = dict( + type='sgd', + lr=0.020, + weight_decay=1e-4, + momentum=0.9 +) + +total_iter = 181400 +import math +scheduler = dict( + type = 'LambdaLR', + lr_lambda = lambda _iter : math.pow(1 - _iter/total_iter, 0.9) +) + +bg_weight = 0.4 + +img_norm = dict( + mean=[103.939, 116.779, 123.68], + std=[1., 1., 1.] +) + +img_height = 368 +img_width = 640 +cut_height = 160 +seg_label = "seg_label" + +dataset_path = './data/tusimple' +test_json_file = './data/tusimple/test_label.json' + +dataset = dict( + train=dict( + type='TuSimple', + img_path=dataset_path, + data_list='train_val_gt.txt', + ), + val=dict( + type='TuSimple', + img_path=dataset_path, + data_list='test_gt.txt' + ), + test=dict( + type='TuSimple', + img_path=dataset_path, + data_list='test_gt.txt' + ) +) + + +loss_type = 'cross_entropy' +seg_loss_weight = 1.0 + + +batch_size = 4 +workers = 12 +num_classes = 6 + 1 +ignore_label = 255 +epochs = 300 +log_interval = 100 +eval_ep = 1 +save_ep = epochs +log_note = '' diff --git a/datasets/__init__.py b/datasets/__init__.py new file mode 100644 index 0000000..b94b540 --- /dev/null +++ b/datasets/__init__.py @@ -0,0 +1,4 @@ +from .registry import build_dataset, build_dataloader + +from .tusimple import TuSimple +from .culane import CULane diff --git a/datasets/base_dataset.py b/datasets/base_dataset.py new file mode 100644 index 0000000..33a7f18 --- /dev/null +++ b/datasets/base_dataset.py @@ -0,0 +1,86 @@ +import os.path as osp +import os +import numpy as np +import cv2 +import torch +from torch.utils.data import Dataset +import torchvision +import utils.transforms as tf +from .registry import DATASETS + + +@DATASETS.register_module +class BaseDataset(Dataset): + def __init__(self, img_path, data_list, list_path='list', cfg=None): + self.cfg = cfg + self.img_path = img_path + self.list_path = osp.join(img_path, list_path) + self.data_list = data_list + self.is_training = ('train' in data_list) + + self.img_name_list = [] + self.full_img_path_list = [] + self.label_list = [] + self.exist_list = [] + + self.transform = self.transform_train() if self.is_training else self.transform_val() + + self.init() + + def transform_train(self): + raise NotImplementedError() + + def transform_val(self): + val_transform = torchvision.transforms.Compose([ + tf.SampleResize((self.cfg.img_width, self.cfg.img_height)), + tf.GroupNormalize(mean=(self.cfg.img_norm['mean'], (0, )), std=( + self.cfg.img_norm['std'], (1, ))), + ]) + return val_transform + + def view(self, img, coords, file_path=None): + for coord in coords: + for x, y in coord: + if x <= 0 or y <= 0: + continue + x, y = int(x), int(y) + cv2.circle(img, (x, y), 4, (255, 0, 0), 2) + + if file_path is not None: + if not os.path.exists(osp.dirname(file_path)): + os.makedirs(osp.dirname(file_path)) + cv2.imwrite(file_path, img) + + + def init(self): + raise NotImplementedError() + + + def __len__(self): + return len(self.full_img_path_list) + + def __getitem__(self, idx): + img = cv2.imread(self.full_img_path_list[idx]).astype(np.float32) + img = img[self.cfg.cut_height:, :, :] + + if self.is_training: + label = cv2.imread(self.label_list[idx], cv2.IMREAD_UNCHANGED) + if len(label.shape) > 2: + label = label[:, :, 0] + label = label.squeeze() + label = label[self.cfg.cut_height:, :] + exist = self.exist_list[idx] + if self.transform: + img, label = self.transform((img, label)) + label = torch.from_numpy(label).contiguous().long() + else: + img, = self.transform((img,)) + + img = torch.from_numpy(img).permute(2, 0, 1).contiguous().float() + meta = {'full_img_path': self.full_img_path_list[idx], + 'img_name': self.img_name_list[idx]} + + data = {'img': img, 'meta': meta} + if self.is_training: + data.update({'label': label, 'exist': exist}) + return data diff --git a/datasets/culane.py b/datasets/culane.py new file mode 100644 index 0000000..c64a1bb --- /dev/null +++ b/datasets/culane.py @@ -0,0 +1,72 @@ +import os +import os.path as osp +import numpy as np +import torchvision +import utils.transforms as tf +from .base_dataset import BaseDataset +from .registry import DATASETS +import cv2 +import torch + + +@DATASETS.register_module +class CULane(BaseDataset): + def __init__(self, img_path, data_list, cfg=None): + super().__init__(img_path, data_list, cfg=cfg) + self.ori_imgh = 590 + self.ori_imgw = 1640 + + def init(self): + with open(osp.join(self.list_path, self.data_list)) as f: + for line in f: + line_split = line.strip().split(" ") + self.img_name_list.append(line_split[0]) + self.full_img_path_list.append(self.img_path + line_split[0]) + if not self.is_training: + continue + self.label_list.append(self.img_path + line_split[1]) + self.exist_list.append( + np.array([int(line_split[2]), int(line_split[3]), + int(line_split[4]), int(line_split[5])])) + + def transform_train(self): + train_transform = torchvision.transforms.Compose([ + tf.GroupRandomRotation(degree=(-2, 2)), + tf.GroupRandomHorizontalFlip(), + tf.SampleResize((self.cfg.img_width, self.cfg.img_height)), + tf.GroupNormalize(mean=(self.cfg.img_norm['mean'], (0, )), std=( + self.cfg.img_norm['std'], (1, ))), + ]) + return train_transform + + def probmap2lane(self, probmaps, exists, pts=18): + coords = [] + probmaps = probmaps[1:, ...] + exists = exists > 0.5 + for probmap, exist in zip(probmaps, exists): + if exist == 0: + continue + probmap = cv2.blur(probmap, (9, 9), borderType=cv2.BORDER_REPLICATE) + thr = 0.3 + coordinate = np.zeros(pts) + cut_height = self.cfg.cut_height + for i in range(pts): + line = probmap[round( + self.cfg.img_height-i*20/(self.ori_imgh-cut_height)*self.cfg.img_height)-1] + + if np.max(line) > thr: + coordinate[i] = np.argmax(line)+1 + if np.sum(coordinate > 0) < 2: + continue + + img_coord = np.zeros((pts, 2)) + img_coord[:, :] = -1 + for idx, value in enumerate(coordinate): + if value > 0: + img_coord[idx][0] = round(value*self.ori_imgw/self.cfg.img_width-1) + img_coord[idx][1] = round(self.ori_imgh-idx*20-1) + + img_coord = img_coord.astype(int) + coords.append(img_coord) + + return coords diff --git a/datasets/registry.py b/datasets/registry.py new file mode 100644 index 0000000..103a3ed --- /dev/null +++ b/datasets/registry.py @@ -0,0 +1,36 @@ +from utils import Registry, build_from_cfg + +import torch + +DATASETS = Registry('datasets') + +def build(cfg, registry, default_args=None): + if isinstance(cfg, list): + modules = [ + build_from_cfg(cfg_, registry, default_args) for cfg_ in cfg + ] + return nn.Sequential(*modules) + else: + return build_from_cfg(cfg, registry, default_args) + + +def build_dataset(split_cfg, cfg): + args = split_cfg.copy() + args.pop('type') + args = args.to_dict() + args['cfg'] = cfg + return build(split_cfg, DATASETS, default_args=args) + +def build_dataloader(split_cfg, cfg, is_train=True): + if is_train: + shuffle = True + else: + shuffle = False + + dataset = build_dataset(split_cfg, cfg) + + data_loader = torch.utils.data.DataLoader( + dataset, batch_size = cfg.batch_size, shuffle = shuffle, + num_workers = cfg.workers, pin_memory = False, drop_last = False) + + return data_loader diff --git a/datasets/tusimple.py b/datasets/tusimple.py new file mode 100644 index 0000000..3097a23 --- /dev/null +++ b/datasets/tusimple.py @@ -0,0 +1,150 @@ +import os.path as osp +import numpy as np +import cv2 +import torchvision +import utils.transforms as tf +from .base_dataset import BaseDataset +from .registry import DATASETS + + +@DATASETS.register_module +class TuSimple(BaseDataset): + def __init__(self, img_path, data_list, cfg=None): + super().__init__(img_path, data_list, 'seg_label/list', cfg) + + def transform_train(self): + input_mean = self.cfg.img_norm['mean'] + train_transform = torchvision.transforms.Compose([ + tf.GroupRandomRotation(), + tf.GroupRandomHorizontalFlip(), + tf.SampleResize((self.cfg.img_width, self.cfg.img_height)), + tf.GroupNormalize(mean=(self.cfg.img_norm['mean'], (0, )), std=( + self.cfg.img_norm['std'], (1, ))), + ]) + return train_transform + + + def init(self): + with open(osp.join(self.list_path, self.data_list)) as f: + for line in f: + line_split = line.strip().split(" ") + self.img_name_list.append(line_split[0]) + self.full_img_path_list.append(self.img_path + line_split[0]) + if not self.is_training: + continue + self.label_list.append(self.img_path + line_split[1]) + self.exist_list.append( + np.array([int(line_split[2]), int(line_split[3]), + int(line_split[4]), int(line_split[5]), + int(line_split[6]), int(line_split[7]) + ])) + + def fix_gap(self, coordinate): + if any(x > 0 for x in coordinate): + start = [i for i, x in enumerate(coordinate) if x > 0][0] + end = [i for i, x in reversed(list(enumerate(coordinate))) if x > 0][0] + lane = coordinate[start:end+1] + if any(x < 0 for x in lane): + gap_start = [i for i, x in enumerate( + lane[:-1]) if x > 0 and lane[i+1] < 0] + gap_end = [i+1 for i, + x in enumerate(lane[:-1]) if x < 0 and lane[i+1] > 0] + gap_id = [i for i, x in enumerate(lane) if x < 0] + if len(gap_start) == 0 or len(gap_end) == 0: + return coordinate + for id in gap_id: + for i in range(len(gap_start)): + if i >= len(gap_end): + return coordinate + if id > gap_start[i] and id < gap_end[i]: + gap_width = float(gap_end[i] - gap_start[i]) + lane[id] = int((id - gap_start[i]) / gap_width * lane[gap_end[i]] + ( + gap_end[i] - id) / gap_width * lane[gap_start[i]]) + if not all(x > 0 for x in lane): + print("Gaps still exist!") + coordinate[start:end+1] = lane + return coordinate + + def is_short(self, lane): + start = [i for i, x in enumerate(lane) if x > 0] + if not start: + return 1 + else: + return 0 + + def get_lane(self, prob_map, y_px_gap, pts, thresh, resize_shape=None): + """ + Arguments: + ---------- + prob_map: prob map for single lane, np array size (h, w) + resize_shape: reshape size target, (H, W) + + Return: + ---------- + coords: x coords bottom up every y_px_gap px, 0 for non-exist, in resized shape + """ + if resize_shape is None: + resize_shape = prob_map.shape + h, w = prob_map.shape + H, W = resize_shape + H -= self.cfg.cut_height + + coords = np.zeros(pts) + coords[:] = -1.0 + for i in range(pts): + y = int((H - 10 - i * y_px_gap) * h / H) + if y < 0: + break + line = prob_map[y, :] + id = np.argmax(line) + if line[id] > thresh: + coords[i] = int(id / w * W) + if (coords > 0).sum() < 2: + coords = np.zeros(pts) + self.fix_gap(coords) + #print(coords.shape) + + return coords + + def probmap2lane(self, seg_pred, exist, resize_shape=(720, 1280), smooth=True, y_px_gap=10, pts=56, thresh=0.6): + """ + Arguments: + ---------- + seg_pred: np.array size (5, h, w) + resize_shape: reshape size target, (H, W) + exist: list of existence, e.g. [0, 1, 1, 0] + smooth: whether to smooth the probability or not + y_px_gap: y pixel gap for sampling + pts: how many points for one lane + thresh: probability threshold + + Return: + ---------- + coordinates: [x, y] list of lanes, e.g.: [ [[9, 569], [50, 549]] ,[[630, 569], [647, 549]] ] + """ + if resize_shape is None: + resize_shape = seg_pred.shape[1:] # seg_pred (5, h, w) + _, h, w = seg_pred.shape + H, W = resize_shape + coordinates = [] + + for i in range(self.cfg.num_classes - 1): + prob_map = seg_pred[i + 1] + if smooth: + prob_map = cv2.blur(prob_map, (9, 9), borderType=cv2.BORDER_REPLICATE) + coords = self.get_lane(prob_map, y_px_gap, pts, thresh, resize_shape) + if self.is_short(coords): + continue + coordinates.append( + [[coords[j], H - 10 - j * y_px_gap] if coords[j] > 0 else [-1, H - 10 - j * y_px_gap] for j in + range(pts)]) + + + if len(coordinates) == 0: + coords = np.zeros(pts) + coordinates.append( + [[coords[j], H - 10 - j * y_px_gap] if coords[j] > 0 else [-1, H - 10 - j * y_px_gap] for j in + range(pts)]) + #print(coordinates) + + return coordinates diff --git a/intro.png b/intro.png new file mode 100644 index 0000000000000000000000000000000000000000..28dc6640cde6569aaae095edfcd3cd8da22dd22c GIT binary patch literal 291104 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⪻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 literal 0 HcmV?d00001 diff --git a/main.py b/main.py new file mode 100644 index 0000000..578d37b --- /dev/null +++ b/main.py @@ -0,0 +1,73 @@ +import os +import os.path as osp +import time +import shutil +import torch +import torchvision +import torch.nn.parallel +import torch.backends.cudnn as cudnn +import torch.nn.functional as F +import torch.optim +import cv2 +import numpy as np +import models +import argparse +from utils.config import Config +from runner.runner import Runner +from datasets import build_dataloader + + +def main(): + args = parse_args() + os.environ["CUDA_VISIBLE_DEVICES"] = ','.join(str(gpu) for gpu in args.gpus) + + cfg = Config.fromfile(args.config) + cfg.gpus = len(args.gpus) + + cfg.load_from = args.load_from + cfg.finetune_from = args.finetune_from + cfg.view = args.view + + cfg.work_dirs = args.work_dirs + '/' + cfg.dataset.train.type + + cudnn.benchmark = True + cudnn.fastest = True + + runner = Runner(cfg) + + if args.validate: + val_loader = build_dataloader(cfg.dataset.val, cfg, is_train=False) + runner.validate(val_loader) + else: + runner.train() + +def parse_args(): + parser = argparse.ArgumentParser(description='Train a detector') + parser.add_argument('config', help='train config file path') + parser.add_argument( + '--work_dirs', type=str, default='work_dirs', + help='work dirs') + parser.add_argument( + '--load_from', default=None, + help='the checkpoint file to resume from') + parser.add_argument( + '--finetune_from', default=None, + help='whether to finetune from the checkpoint') + parser.add_argument( + '--validate', + action='store_true', + help='whether to evaluate the checkpoint during training') + parser.add_argument( + '--view', + action='store_true', + help='whether to show visualization result') + parser.add_argument('--gpus', nargs='+', type=int, default='0') + parser.add_argument('--seed', type=int, + default=None, help='random seed') + args = parser.parse_args() + + return args + + +if __name__ == '__main__': + main() diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..fc812be --- /dev/null +++ b/models/__init__.py @@ -0,0 +1 @@ +from .resa import * diff --git a/models/decoder.py b/models/decoder.py new file mode 100644 index 0000000..f4228a7 --- /dev/null +++ b/models/decoder.py @@ -0,0 +1,129 @@ +from torch import nn +import torch.nn.functional as F + +class PlainDecoder(nn.Module): + def __init__(self, cfg): + super(PlainDecoder, self).__init__() + self.cfg = cfg + + self.dropout = nn.Dropout2d(0.1) + self.conv8 = nn.Conv2d(128, cfg.num_classes, 1) + + def forward(self, x): + x = self.dropout(x) + x = self.conv8(x) + x = F.interpolate(x, size=[self.cfg.img_height, self.cfg.img_width], + mode='bilinear', align_corners=False) + + return x + + +def conv1x1(in_planes, out_planes, stride=1): + """1x1 convolution""" + return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) + + +class non_bottleneck_1d(nn.Module): + def __init__(self, chann, dropprob, dilated): + super().__init__() + + self.conv3x1_1 = nn.Conv2d( + chann, chann, (3, 1), stride=1, padding=(1, 0), bias=True) + + self.conv1x3_1 = nn.Conv2d( + chann, chann, (1, 3), stride=1, padding=(0, 1), bias=True) + + self.bn1 = nn.BatchNorm2d(chann, eps=1e-03) + + self.conv3x1_2 = nn.Conv2d(chann, chann, (3, 1), stride=1, padding=(1 * dilated, 0), bias=True, + dilation=(dilated, 1)) + + self.conv1x3_2 = nn.Conv2d(chann, chann, (1, 3), stride=1, padding=(0, 1 * dilated), bias=True, + dilation=(1, dilated)) + + self.bn2 = nn.BatchNorm2d(chann, eps=1e-03) + + self.dropout = nn.Dropout2d(dropprob) + + def forward(self, input): + output = self.conv3x1_1(input) + output = F.relu(output) + output = self.conv1x3_1(output) + output = self.bn1(output) + output = F.relu(output) + + output = self.conv3x1_2(output) + output = F.relu(output) + output = self.conv1x3_2(output) + output = self.bn2(output) + + if (self.dropout.p != 0): + output = self.dropout(output) + + # +input = identity (residual connection) + return F.relu(output + input) + + +class UpsamplerBlock(nn.Module): + def __init__(self, ninput, noutput, up_width, up_height): + super().__init__() + + self.conv = nn.ConvTranspose2d( + ninput, noutput, 3, stride=2, padding=1, output_padding=1, bias=True) + + self.bn = nn.BatchNorm2d(noutput, eps=1e-3, track_running_stats=True) + + self.follows = nn.ModuleList() + self.follows.append(non_bottleneck_1d(noutput, 0, 1)) + self.follows.append(non_bottleneck_1d(noutput, 0, 1)) + + # interpolate + self.up_width = up_width + self.up_height = up_height + self.interpolate_conv = conv1x1(ninput, noutput) + self.interpolate_bn = nn.BatchNorm2d( + noutput, eps=1e-3, track_running_stats=True) + + def forward(self, input): + output = self.conv(input) + output = self.bn(output) + out = F.relu(output) + for follow in self.follows: + out = follow(out) + + interpolate_output = self.interpolate_conv(input) + interpolate_output = self.interpolate_bn(interpolate_output) + interpolate_output = F.relu(interpolate_output) + + interpolate = F.interpolate(interpolate_output, size=[self.up_height, self.up_width], + mode='bilinear', align_corners=False) + + return out + interpolate + +class BUSD(nn.Module): + def __init__(self, cfg): + super().__init__() + img_height = cfg.img_height + img_width = cfg.img_width + num_classes = cfg.num_classes + + self.layers = nn.ModuleList() + + self.layers.append(UpsamplerBlock(ninput=128, noutput=64, + up_height=int(img_height)//4, up_width=int(img_width)//4)) + self.layers.append(UpsamplerBlock(ninput=64, noutput=32, + up_height=int(img_height)//2, up_width=int(img_width)//2)) + self.layers.append(UpsamplerBlock(ninput=32, noutput=16, + up_height=int(img_height)//1, up_width=int(img_width)//1)) + + self.output_conv = conv1x1(16, num_classes) + + def forward(self, input): + output = input + + for layer in self.layers: + output = layer(output) + + output = self.output_conv(output) + + return output diff --git a/models/decoder_copy.py b/models/decoder_copy.py new file mode 100644 index 0000000..209a966 --- /dev/null +++ b/models/decoder_copy.py @@ -0,0 +1,135 @@ +from torch import nn +import torch.nn.functional as F +import torch + +class PlainDecoder(nn.Module): + def __init__(self, cfg): + super(PlainDecoder, self).__init__() + self.cfg = cfg + + self.dropout = nn.Dropout2d(0.1) + self.conv8 = nn.Conv2d(128, cfg.num_classes, 1) + + def forward(self, x): + x = self.dropout(x) + x = self.conv8(x) + x = F.interpolate(x, size=[self.cfg.img_height, self.cfg.img_width], + mode='bilinear', align_corners=False) + + return x + + +def conv1x1(in_planes, out_planes, stride=1): + """1x1 convolution""" + return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) + + +class non_bottleneck_1d(nn.Module): + def __init__(self, chann, dropprob, dilated): + super().__init__() + + self.conv3x1_1 = nn.Conv2d( + chann, chann, (3, 1), stride=1, padding=(1, 0), bias=True) + + self.conv1x3_1 = nn.Conv2d( + chann, chann, (1, 3), stride=1, padding=(0, 1), bias=True) + + self.bn1 = nn.BatchNorm2d(chann, eps=1e-03) + + self.conv3x1_2 = nn.Conv2d(chann, chann, (3, 1), stride=1, padding=(1 * dilated, 0), bias=True, + dilation=(dilated, 1)) + + self.conv1x3_2 = nn.Conv2d(chann, chann, (1, 3), stride=1, padding=(0, 1 * dilated), bias=True, + dilation=(1, dilated)) + + self.bn2 = nn.BatchNorm2d(chann, eps=1e-03) + + self.dropout = nn.Dropout2d(dropprob) + + def forward(self, input): + output = self.conv3x1_1(input) + output = F.relu(output) + output = self.conv1x3_1(output) + output = self.bn1(output) + output = F.relu(output) + + output = self.conv3x1_2(output) + output = F.relu(output) + output = self.conv1x3_2(output) + output = self.bn2(output) + + if (self.dropout.p != 0): + output = self.dropout(output) + + # +input = identity (residual connection) + return F.relu(output + input) + + +class UpsamplerBlock(nn.Module): + def __init__(self, ninput, noutput, up_width, up_height): + super().__init__() + + self.conv = nn.ConvTranspose2d( + ninput, noutput, 3, stride=2, padding=1, output_padding=1, bias=True) + + self.bn = nn.BatchNorm2d(noutput, eps=1e-3, track_running_stats=True) + + self.follows = nn.ModuleList() + self.follows.append(non_bottleneck_1d(noutput, 0, 1)) + self.follows.append(non_bottleneck_1d(noutput, 0, 1)) + + # interpolate + self.up_width = up_width + self.up_height = up_height + self.interpolate_conv = conv1x1(ninput, noutput) + self.interpolate_bn = nn.BatchNorm2d( + noutput, eps=1e-3, track_running_stats=True) + + def forward(self, input): + output = self.conv(input) + output = self.bn(output) + out = F.relu(output) + for follow in self.follows: + out = follow(out) + + interpolate_output = self.interpolate_conv(input) + interpolate_output = self.interpolate_bn(interpolate_output) + interpolate_output = F.relu(interpolate_output) + + interpolate = F.interpolate(interpolate_output, size=[self.up_height, self.up_width], + mode='bilinear', align_corners=False) + + return out + interpolate + +class BUSD(nn.Module): + def __init__(self, cfg): + super().__init__() + img_height = cfg.img_height + img_width = cfg.img_width + num_classes = cfg.num_classes + + self.layers = nn.ModuleList() + + self.layers.append(UpsamplerBlock(ninput=128, noutput=64, + up_height=int(img_height)//4, up_width=int(img_width)//4)) + self.layers.append(UpsamplerBlock(ninput=128, noutput=64, + up_height=int(img_height)//2, up_width=int(img_width)//2)) + self.layers.append(UpsamplerBlock(ninput=64, noutput=32, + up_height=int(img_height)//1, up_width=int(img_width)//1)) + + self.output_conv = conv1x1(32, num_classes) + + def forward(self, input): + x = input[0] + output = input[1] + + for i,layer in enumerate(self.layers): + output = layer(output) + if i == 0: + output = torch.cat((x, output), dim=1) + + + + output = self.output_conv(output) + + return output diff --git a/models/decoder_copy2.py b/models/decoder_copy2.py new file mode 100644 index 0000000..9d092a4 --- /dev/null +++ b/models/decoder_copy2.py @@ -0,0 +1,143 @@ +from torch import nn +import torch +import torch.nn.functional as F + +class PlainDecoder(nn.Module): + def __init__(self, cfg): + super(PlainDecoder, self).__init__() + self.cfg = cfg + + self.dropout = nn.Dropout2d(0.1) + self.conv8 = nn.Conv2d(128, cfg.num_classes, 1) + + def forward(self, x): + x = self.dropout(x) + x = self.conv8(x) + x = F.interpolate(x, size=[self.cfg.img_height, self.cfg.img_width], + mode='bilinear', align_corners=False) + + return x + + +def conv1x1(in_planes, out_planes, stride=1): + """1x1 convolution""" + return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) + + +class non_bottleneck_1d(nn.Module): + def __init__(self, chann, dropprob, dilated): + super().__init__() + + self.conv3x1_1 = nn.Conv2d( + chann, chann, (3, 1), stride=1, padding=(1, 0), bias=True) + + self.conv1x3_1 = nn.Conv2d( + chann, chann, (1, 3), stride=1, padding=(0, 1), bias=True) + + self.bn1 = nn.BatchNorm2d(chann, eps=1e-03) + + self.conv3x1_2 = nn.Conv2d(chann, chann, (3, 1), stride=1, padding=(1 * dilated, 0), bias=True, + dilation=(dilated, 1)) + + self.conv1x3_2 = nn.Conv2d(chann, chann, (1, 3), stride=1, padding=(0, 1 * dilated), bias=True, + dilation=(1, dilated)) + + self.bn2 = nn.BatchNorm2d(chann, eps=1e-03) + + self.dropout = nn.Dropout2d(dropprob) + + def forward(self, input): + output = self.conv3x1_1(input) + output = F.relu(output) + output = self.conv1x3_1(output) + output = self.bn1(output) + output = F.relu(output) + + output = self.conv3x1_2(output) + output = F.relu(output) + output = self.conv1x3_2(output) + output = self.bn2(output) + + if (self.dropout.p != 0): + output = self.dropout(output) + + # +input = identity (residual connection) + return F.relu(output + input) + + +class UpsamplerBlock(nn.Module): + def __init__(self, ninput, noutput, up_width, up_height): + super().__init__() + + self.conv = nn.ConvTranspose2d( + ninput, noutput, 3, stride=2, padding=1, output_padding=1, bias=True) + + self.bn = nn.BatchNorm2d(noutput, eps=1e-3, track_running_stats=True) + + self.follows = nn.ModuleList() + self.follows.append(non_bottleneck_1d(noutput, 0, 1)) + self.follows.append(non_bottleneck_1d(noutput, 0, 1)) + + # interpolate + self.up_width = up_width + self.up_height = up_height + self.interpolate_conv = conv1x1(ninput, noutput) + self.interpolate_bn = nn.BatchNorm2d( + noutput, eps=1e-3, track_running_stats=True) + + def forward(self, input): + output = self.conv(input) + output = self.bn(output) + out = F.relu(output) + for follow in self.follows: + out = follow(out) + + interpolate_output = self.interpolate_conv(input) + interpolate_output = self.interpolate_bn(interpolate_output) + interpolate_output = F.relu(interpolate_output) + + interpolate = F.interpolate(interpolate_output, size=[self.up_height, self.up_width], + mode='bilinear', align_corners=False) + + return out + interpolate + +class BUSD(nn.Module): + def __init__(self, cfg): + super().__init__() + img_height = cfg.img_height + img_width = cfg.img_width + num_classes = cfg.num_classes + + self.layers = nn.ModuleList() + + self.layers.append(UpsamplerBlock(ninput=128, noutput=64, + up_height=int(img_height)//4, up_width=int(img_width)//4)) + self.layers.append(UpsamplerBlock(ninput=64, noutput=32, + up_height=int(img_height)//2, up_width=int(img_width)//2)) + self.layers.append(UpsamplerBlock(ninput=32, noutput=16, + up_height=int(img_height)//1, up_width=int(img_width)//1)) + self.out1 = conv1x1(128, 64) + self.out2 = conv1x1(64, 32) + self.output_conv = conv1x1(16, num_classes) + + + def forward(self, input): + out1 = input[0] + out2 = input[1] + output = input[2] + + for i,layer in enumerate(self.layers): + if i == 0: + output = layer(output) + output = torch.cat((out2, output), dim=1) + output = self.out1(output) + elif i == 1: + output = layer(output) + output = torch.cat((out1, output), dim=1) + output = self.out2(output) + else: + output = layer(output) + + output = self.output_conv(output) + + return output diff --git a/models/mobilenetv2.py b/models/mobilenetv2.py new file mode 100644 index 0000000..69d7b0d --- /dev/null +++ b/models/mobilenetv2.py @@ -0,0 +1,422 @@ +from functools import partial +from typing import Any, Callable, List, Optional + +import torch +from torch import nn, Tensor + + +from torchvision.transforms._presets import ImageClassification +from torchvision.utils import _log_api_usage_once +from torchvision.models._api import Weights, WeightsEnum +from torchvision.models._meta import _IMAGENET_CATEGORIES +from torchvision.models._utils import _make_divisible, _ovewrite_named_param, handle_legacy_interface +import warnings +from typing import Callable, List, Optional, Sequence, Tuple, Union, TypeVar +import collections +from itertools import repeat +M = TypeVar("M", bound=nn.Module) + +BUILTIN_MODELS = {} +def register_model(name: Optional[str] = None) -> Callable[[Callable[..., M]], Callable[..., M]]: + def wrapper(fn: Callable[..., M]) -> Callable[..., M]: + key = name if name is not None else fn.__name__ + if key in BUILTIN_MODELS: + raise ValueError(f"An entry is already registered under the name '{key}'.") + BUILTIN_MODELS[key] = fn + return fn + + return wrapper + +def _make_ntuple(x: Any, n: int) -> Tuple[Any, ...]: + """ + Make n-tuple from input x. If x is an iterable, then we just convert it to tuple. + Otherwise, we will make a tuple of length n, all with value of x. + reference: https://github.com/pytorch/pytorch/blob/master/torch/nn/modules/utils.py#L8 + + Args: + x (Any): input value + n (int): length of the resulting tuple + """ + if isinstance(x, collections.abc.Iterable): + return tuple(x) + return tuple(repeat(x, n)) + +class ConvNormActivation(torch.nn.Sequential): + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: Union[int, Tuple[int, ...]] = 3, + stride: Union[int, Tuple[int, ...]] = 1, + padding: Optional[Union[int, Tuple[int, ...], str]] = None, + groups: int = 1, + norm_layer: Optional[Callable[..., torch.nn.Module]] = torch.nn.BatchNorm2d, + activation_layer: Optional[Callable[..., torch.nn.Module]] = torch.nn.ReLU, + dilation: Union[int, Tuple[int, ...]] = 1, + inplace: Optional[bool] = True, + bias: Optional[bool] = None, + conv_layer: Callable[..., torch.nn.Module] = torch.nn.Conv2d, + ) -> None: + + if padding is None: + if isinstance(kernel_size, int) and isinstance(dilation, int): + padding = (kernel_size - 1) // 2 * dilation + else: + _conv_dim = len(kernel_size) if isinstance(kernel_size, Sequence) else len(dilation) + kernel_size = _make_ntuple(kernel_size, _conv_dim) + dilation = _make_ntuple(dilation, _conv_dim) + padding = tuple((kernel_size[i] - 1) // 2 * dilation[i] for i in range(_conv_dim)) + if bias is None: + bias = norm_layer is None + + layers = [ + conv_layer( + in_channels, + out_channels, + kernel_size, + stride, + padding, + dilation=dilation, + groups=groups, + bias=bias, + ) + ] + + if norm_layer is not None: + layers.append(norm_layer(out_channels)) + + if activation_layer is not None: + params = {} if inplace is None else {"inplace": inplace} + layers.append(activation_layer(**params)) + super().__init__(*layers) + _log_api_usage_once(self) + self.out_channels = out_channels + + if self.__class__ == ConvNormActivation: + warnings.warn( + "Don't use ConvNormActivation directly, please use Conv2dNormActivation and Conv3dNormActivation instead." + ) + + +class Conv2dNormActivation(ConvNormActivation): + """ + Configurable block used for Convolution2d-Normalization-Activation blocks. + + Args: + in_channels (int): Number of channels in the input image + out_channels (int): Number of channels produced by the Convolution-Normalization-Activation block + kernel_size: (int, optional): Size of the convolving kernel. Default: 3 + stride (int, optional): Stride of the convolution. Default: 1 + padding (int, tuple or str, optional): Padding added to all four sides of the input. Default: None, in which case it will be calculated as ``padding = (kernel_size - 1) // 2 * dilation`` + groups (int, optional): Number of blocked connections from input channels to output channels. Default: 1 + norm_layer (Callable[..., torch.nn.Module], optional): Norm layer that will be stacked on top of the convolution layer. If ``None`` this layer won't be used. Default: ``torch.nn.BatchNorm2d`` + activation_layer (Callable[..., torch.nn.Module], optional): Activation function which will be stacked on top of the normalization layer (if not None), otherwise on top of the conv layer. If ``None`` this layer won't be used. Default: ``torch.nn.ReLU`` + dilation (int): Spacing between kernel elements. Default: 1 + inplace (bool): Parameter for the activation layer, which can optionally do the operation in-place. Default ``True`` + bias (bool, optional): Whether to use bias in the convolution layer. By default, biases are included if ``norm_layer is None``. + + """ + + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: Union[int, Tuple[int, int]] = 3, + stride: Union[int, Tuple[int, int]] = 1, + padding: Optional[Union[int, Tuple[int, int], str]] = None, + groups: int = 1, + norm_layer: Optional[Callable[..., torch.nn.Module]] = torch.nn.BatchNorm2d, + activation_layer: Optional[Callable[..., torch.nn.Module]] = torch.nn.ReLU, + dilation: Union[int, Tuple[int, int]] = 1, + inplace: Optional[bool] = True, + bias: Optional[bool] = None, + ) -> None: + + super().__init__( + in_channels, + out_channels, + kernel_size, + stride, + padding, + groups, + norm_layer, + activation_layer, + dilation, + inplace, + bias, + torch.nn.Conv2d, + ) + +__all__ = ["MobileNetV2", "MobileNet_V2_Weights", "mobilenet_v2"] + + +# necessary for backwards compatibility +class InvertedResidual(nn.Module): + def __init__( + self, inp: int, oup: int, stride: int, expand_ratio: int, norm_layer: Optional[Callable[..., nn.Module]] = None + ) -> None: + super().__init__() + self.stride = stride + if stride not in [1, 2]: + raise ValueError(f"stride should be 1 or 2 instead of {stride}") + + if norm_layer is None: + norm_layer = nn.BatchNorm2d + + hidden_dim = int(round(inp * expand_ratio)) + self.use_res_connect = self.stride == 1 and inp == oup + + layers: List[nn.Module] = [] + if expand_ratio != 1: + # pw + layers.append( + Conv2dNormActivation(inp, hidden_dim, kernel_size=1, norm_layer=norm_layer, activation_layer=nn.ReLU6) + ) + layers.extend( + [ + # dw + Conv2dNormActivation( + hidden_dim, + hidden_dim, + stride=stride, + groups=hidden_dim, + norm_layer=norm_layer, + activation_layer=nn.ReLU6, + ), + # pw-linear + nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), + norm_layer(oup), + ] + ) + self.conv = nn.Sequential(*layers) + self.out_channels = oup + self._is_cn = stride > 1 + + def forward(self, x: Tensor) -> Tensor: + if self.use_res_connect: + return x + self.conv(x) + else: + return self.conv(x) + + +class MobileNetV2(nn.Module): + def __init__( + self, + num_classes: int = 1000, + width_mult: float = 1.0, + inverted_residual_setting: Optional[List[List[int]]] = None, + round_nearest: int = 8, + block: Optional[Callable[..., nn.Module]] = None, + norm_layer: Optional[Callable[..., nn.Module]] = None, + dropout: float = 0.2, + ) -> None: + """ + MobileNet V2 main class + + Args: + num_classes (int): Number of classes + width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount + inverted_residual_setting: Network structure + round_nearest (int): Round the number of channels in each layer to be a multiple of this number + Set to 1 to turn off rounding + block: Module specifying inverted residual building block for mobilenet + norm_layer: Module specifying the normalization layer to use + dropout (float): The droupout probability + + """ + super().__init__() + _log_api_usage_once(self) + + if block is None: + block = InvertedResidual + + if norm_layer is None: + norm_layer = nn.BatchNorm2d + + input_channel = 32 + last_channel = 1280 + + if inverted_residual_setting is None: + inverted_residual_setting = [ + # t, c, n, s + [1, 16, 1, 1], + [6, 24, 2, 2], + [6, 32, 3, 2], + [6, 64, 4, 1], # ** + [6, 96, 3, 1], + [6, 160, 3, 1], # ** + [6, 320, 1, 1], + ] + + # only check the first element, assuming user knows t,c,n,s are required + if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4: + raise ValueError( + f"inverted_residual_setting should be non-empty or a 4-element list, got {inverted_residual_setting}" + ) + + # building first layer + input_channel = _make_divisible(input_channel * width_mult, round_nearest) + self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest) + features: List[nn.Module] = [ + Conv2dNormActivation(3, input_channel, stride=2, norm_layer=norm_layer, activation_layer=nn.ReLU6) + ] + # building inverted residual blocks + for t, c, n, s in inverted_residual_setting: + output_channel = _make_divisible(c * width_mult, round_nearest) + for i in range(n): + stride = s if i == 0 else 1 + features.append(block(input_channel, output_channel, stride, expand_ratio=t, norm_layer=norm_layer)) + input_channel = output_channel + # building last several layers + features.append( + Conv2dNormActivation( + input_channel, self.last_channel, kernel_size=1, norm_layer=norm_layer, activation_layer=nn.ReLU6 + ) + ) + # make it nn.Sequential + self.features = nn.Sequential(*features) + + # building classifier + self.classifier = nn.Sequential( + nn.Dropout(p=dropout), + nn.Linear(self.last_channel, num_classes), + ) + + # weight initialization + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode="fan_out") + if m.bias is not None: + nn.init.zeros_(m.bias) + elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)): + nn.init.ones_(m.weight) + nn.init.zeros_(m.bias) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, 0, 0.01) + nn.init.zeros_(m.bias) + + def _forward_impl(self, x: Tensor) -> Tensor: + # This exists since TorchScript doesn't support inheritance, so the superclass method + # (this one) needs to have a name other than `forward` that can be accessed in a subclass + x = self.features(x) + # Cannot use "squeeze" as batch-size can be 1 + # x = nn.functional.adaptive_avg_pool2d(x, (1, 1)) + # x = torch.flatten(x, 1) + # x = self.classifier(x) + return x + + def forward(self, x: Tensor) -> Tensor: + return self._forward_impl(x) + + +_COMMON_META = { + "num_params": 3504872, + "min_size": (1, 1), + "categories": _IMAGENET_CATEGORIES, +} + + +class MobileNet_V2_Weights(WeightsEnum): + IMAGENET1K_V1 = Weights( + url="https://download.pytorch.org/models/mobilenet_v2-b0353104.pth", + transforms=partial(ImageClassification, crop_size=224), + meta={ + **_COMMON_META, + "recipe": "https://github.com/pytorch/vision/tree/main/references/classification#mobilenetv2", + "_metrics": { + "ImageNet-1K": { + "acc@1": 71.878, + "acc@5": 90.286, + } + }, + "_ops": 0.301, + "_file_size": 13.555, + "_docs": """These weights reproduce closely the results of the paper using a simple training recipe.""", + }, + ) + IMAGENET1K_V2 = Weights( + url="https://download.pytorch.org/models/mobilenet_v2-7ebf99e0.pth", + transforms=partial(ImageClassification, crop_size=224, resize_size=232), + meta={ + **_COMMON_META, + "recipe": "https://github.com/pytorch/vision/issues/3995#new-recipe-with-reg-tuning", + "_metrics": { + "ImageNet-1K": { + "acc@1": 72.154, + "acc@5": 90.822, + } + }, + "_ops": 0.301, + "_file_size": 13.598, + "_docs": """ + These weights improve upon the results of the original paper by using a modified version of TorchVision's + `new training recipe + `_. + """, + }, + ) + DEFAULT = IMAGENET1K_V2 + + +# @register_model() +# @handle_legacy_interface(weights=("pretrained", MobileNet_V2_Weights.IMAGENET1K_V1)) +def mobilenet_v2( + *, weights: Optional[MobileNet_V2_Weights] = MobileNet_V2_Weights.IMAGENET1K_V1, progress: bool = True, **kwargs: Any +) -> MobileNetV2: + """MobileNetV2 architecture from the `MobileNetV2: Inverted Residuals and Linear + Bottlenecks `_ paper. + + Args: + weights (:class:`~torchvision.models.MobileNet_V2_Weights`, optional): The + pretrained weights to use. See + :class:`~torchvision.models.MobileNet_V2_Weights` below for + more details, and possible values. By default, no pre-trained + weights are used. + progress (bool, optional): If True, displays a progress bar of the + download to stderr. Default is True. + **kwargs: parameters passed to the ``torchvision.models.mobilenetv2.MobileNetV2`` + base class. Please refer to the `source code + `_ + for more details about this class. + + .. autoclass:: torchvision.models.MobileNet_V2_Weights + :members: + """ + weights = MobileNet_V2_Weights.verify(weights) + + if weights is not None: + _ovewrite_named_param(kwargs, "num_classes", len(weights.meta["categories"])) + + model = MobileNetV2(**kwargs) + + if weights is not None: + model.load_state_dict(weights.get_state_dict(progress=progress)) + + return model + + +def conv1x1(in_planes, out_planes, stride=1): + """1x1 convolution""" + return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) + +class MobileNetv2Wrapper(nn.Module): + def __init__(self): + super(MobileNetv2Wrapper, self).__init__() + weights = MobileNet_V2_Weights.verify(MobileNet_V2_Weights.IMAGENET1K_V1) + + self.model = MobileNetV2() + + if weights is not None: + self.model.load_state_dict(weights.get_state_dict(progress=True)) + self.out = conv1x1( + 1280, 128) + + def forward(self, x): + # print(x.shape) + x = self.model(x) + # print(x.shape) + if self.out: + x = self.out(x) + # print(x.shape) + return x + diff --git a/models/mobilenetv2_copy2.py b/models/mobilenetv2_copy2.py new file mode 100644 index 0000000..c17829f --- /dev/null +++ b/models/mobilenetv2_copy2.py @@ -0,0 +1,436 @@ +from functools import partial +from typing import Any, Callable, List, Optional + +import torch +from torch import nn, Tensor + + +from torchvision.transforms._presets import ImageClassification +from torchvision.utils import _log_api_usage_once +from torchvision.models._api import Weights, WeightsEnum +from torchvision.models._meta import _IMAGENET_CATEGORIES +from torchvision.models._utils import _make_divisible, _ovewrite_named_param, handle_legacy_interface +import warnings +from typing import Callable, List, Optional, Sequence, Tuple, Union, TypeVar +import collections +from itertools import repeat +M = TypeVar("M", bound=nn.Module) + +BUILTIN_MODELS = {} +def register_model(name: Optional[str] = None) -> Callable[[Callable[..., M]], Callable[..., M]]: + def wrapper(fn: Callable[..., M]) -> Callable[..., M]: + key = name if name is not None else fn.__name__ + if key in BUILTIN_MODELS: + raise ValueError(f"An entry is already registered under the name '{key}'.") + BUILTIN_MODELS[key] = fn + return fn + + return wrapper + +def _make_ntuple(x: Any, n: int) -> Tuple[Any, ...]: + """ + Make n-tuple from input x. If x is an iterable, then we just convert it to tuple. + Otherwise, we will make a tuple of length n, all with value of x. + reference: https://github.com/pytorch/pytorch/blob/master/torch/nn/modules/utils.py#L8 + + Args: + x (Any): input value + n (int): length of the resulting tuple + """ + if isinstance(x, collections.abc.Iterable): + return tuple(x) + return tuple(repeat(x, n)) + +class ConvNormActivation(torch.nn.Sequential): + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: Union[int, Tuple[int, ...]] = 3, + stride: Union[int, Tuple[int, ...]] = 1, + padding: Optional[Union[int, Tuple[int, ...], str]] = None, + groups: int = 1, + norm_layer: Optional[Callable[..., torch.nn.Module]] = torch.nn.BatchNorm2d, + activation_layer: Optional[Callable[..., torch.nn.Module]] = torch.nn.ReLU, + dilation: Union[int, Tuple[int, ...]] = 1, + inplace: Optional[bool] = True, + bias: Optional[bool] = None, + conv_layer: Callable[..., torch.nn.Module] = torch.nn.Conv2d, + ) -> None: + + if padding is None: + if isinstance(kernel_size, int) and isinstance(dilation, int): + padding = (kernel_size - 1) // 2 * dilation + else: + _conv_dim = len(kernel_size) if isinstance(kernel_size, Sequence) else len(dilation) + kernel_size = _make_ntuple(kernel_size, _conv_dim) + dilation = _make_ntuple(dilation, _conv_dim) + padding = tuple((kernel_size[i] - 1) // 2 * dilation[i] for i in range(_conv_dim)) + if bias is None: + bias = norm_layer is None + + layers = [ + conv_layer( + in_channels, + out_channels, + kernel_size, + stride, + padding, + dilation=dilation, + groups=groups, + bias=bias, + ) + ] + + if norm_layer is not None: + layers.append(norm_layer(out_channels)) + + if activation_layer is not None: + params = {} if inplace is None else {"inplace": inplace} + layers.append(activation_layer(**params)) + super().__init__(*layers) + _log_api_usage_once(self) + self.out_channels = out_channels + + if self.__class__ == ConvNormActivation: + warnings.warn( + "Don't use ConvNormActivation directly, please use Conv2dNormActivation and Conv3dNormActivation instead." + ) + + +class Conv2dNormActivation(ConvNormActivation): + """ + Configurable block used for Convolution2d-Normalization-Activation blocks. + + Args: + in_channels (int): Number of channels in the input image + out_channels (int): Number of channels produced by the Convolution-Normalization-Activation block + kernel_size: (int, optional): Size of the convolving kernel. Default: 3 + stride (int, optional): Stride of the convolution. Default: 1 + padding (int, tuple or str, optional): Padding added to all four sides of the input. Default: None, in which case it will be calculated as ``padding = (kernel_size - 1) // 2 * dilation`` + groups (int, optional): Number of blocked connections from input channels to output channels. Default: 1 + norm_layer (Callable[..., torch.nn.Module], optional): Norm layer that will be stacked on top of the convolution layer. If ``None`` this layer won't be used. Default: ``torch.nn.BatchNorm2d`` + activation_layer (Callable[..., torch.nn.Module], optional): Activation function which will be stacked on top of the normalization layer (if not None), otherwise on top of the conv layer. If ``None`` this layer won't be used. Default: ``torch.nn.ReLU`` + dilation (int): Spacing between kernel elements. Default: 1 + inplace (bool): Parameter for the activation layer, which can optionally do the operation in-place. Default ``True`` + bias (bool, optional): Whether to use bias in the convolution layer. By default, biases are included if ``norm_layer is None``. + + """ + + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: Union[int, Tuple[int, int]] = 3, + stride: Union[int, Tuple[int, int]] = 1, + padding: Optional[Union[int, Tuple[int, int], str]] = None, + groups: int = 1, + norm_layer: Optional[Callable[..., torch.nn.Module]] = torch.nn.BatchNorm2d, + activation_layer: Optional[Callable[..., torch.nn.Module]] = torch.nn.ReLU, + dilation: Union[int, Tuple[int, int]] = 1, + inplace: Optional[bool] = True, + bias: Optional[bool] = None, + ) -> None: + + super().__init__( + in_channels, + out_channels, + kernel_size, + stride, + padding, + groups, + norm_layer, + activation_layer, + dilation, + inplace, + bias, + torch.nn.Conv2d, + ) + +__all__ = ["MobileNetV2", "MobileNet_V2_Weights", "mobilenet_v2"] + + +# necessary for backwards compatibility +class InvertedResidual(nn.Module): + def __init__( + self, inp: int, oup: int, stride: int, expand_ratio: int, norm_layer: Optional[Callable[..., nn.Module]] = None + ) -> None: + super().__init__() + self.stride = stride + if stride not in [1, 2]: + raise ValueError(f"stride should be 1 or 2 instead of {stride}") + + if norm_layer is None: + norm_layer = nn.BatchNorm2d + + hidden_dim = int(round(inp * expand_ratio)) + self.use_res_connect = self.stride == 1 and inp == oup + + layers: List[nn.Module] = [] + if expand_ratio != 1: + # pw + layers.append( + Conv2dNormActivation(inp, hidden_dim, kernel_size=1, norm_layer=norm_layer, activation_layer=nn.ReLU6) + ) + layers.extend( + [ + # dw + Conv2dNormActivation( + hidden_dim, + hidden_dim, + stride=stride, + groups=hidden_dim, + norm_layer=norm_layer, + activation_layer=nn.ReLU6, + ), + # pw-linear + nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), + norm_layer(oup), + ] + ) + self.conv = nn.Sequential(*layers) + self.out_channels = oup + self._is_cn = stride > 1 + + def forward(self, x: Tensor) -> Tensor: + if self.use_res_connect: + return x + self.conv(x) + else: + return self.conv(x) + + +class MobileNetV2(nn.Module): + def __init__( + self, + num_classes: int = 1000, + width_mult: float = 1.0, + inverted_residual_setting: Optional[List[List[int]]] = None, + round_nearest: int = 8, + block: Optional[Callable[..., nn.Module]] = None, + norm_layer: Optional[Callable[..., nn.Module]] = None, + dropout: float = 0.2, + ) -> None: + """ + MobileNet V2 main class + + Args: + num_classes (int): Number of classes + width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount + inverted_residual_setting: Network structure + round_nearest (int): Round the number of channels in each layer to be a multiple of this number + Set to 1 to turn off rounding + block: Module specifying inverted residual building block for mobilenet + norm_layer: Module specifying the normalization layer to use + dropout (float): The droupout probability + + """ + super().__init__() + _log_api_usage_once(self) + + if block is None: + block = InvertedResidual + + if norm_layer is None: + norm_layer = nn.BatchNorm2d + + input_channel = 32 + last_channel = 1280 + + if inverted_residual_setting is None: + inverted_residual_setting = [ + # t, c, n, s + [1, 16, 1, 1], + [6, 24, 2, 1], + [6, 32, 3, 1], + [6, 64, 4, 2], + [6, 96, 3, 1], + [6, 160, 3, 2], + [6, 320, 1, 1], + ] + + # only check the first element, assuming user knows t,c,n,s are required + if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4: + raise ValueError( + f"inverted_residual_setting should be non-empty or a 4-element list, got {inverted_residual_setting}" + ) + + # building first layer + input_channel = _make_divisible(input_channel * width_mult, round_nearest) + self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest) + features: List[nn.Module] = [ + Conv2dNormActivation(3, input_channel, stride=2, norm_layer=norm_layer, activation_layer=nn.ReLU6) + ] + # building inverted residual blocks + for t, c, n, s in inverted_residual_setting: + output_channel = _make_divisible(c * width_mult, round_nearest) + for i in range(n): + stride = s if i == 0 else 1 + features.append(block(input_channel, output_channel, stride, expand_ratio=t, norm_layer=norm_layer)) + input_channel = output_channel + # building last several layers + features.append( + Conv2dNormActivation( + input_channel, self.last_channel, kernel_size=1, norm_layer=norm_layer, activation_layer=nn.ReLU6 + ) + ) + # make it nn.Sequential + self.features = nn.Sequential(*features) + # self.layer1 = nn.Sequential(*features[:]) + # self.layer2 = features[57:120] + # self.layer3 = features[120:] + + # building classifier + self.classifier = nn.Sequential( + nn.Dropout(p=dropout), + nn.Linear(self.last_channel, num_classes), + ) + + # weight initialization + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode="fan_out") + if m.bias is not None: + nn.init.zeros_(m.bias) + elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)): + nn.init.ones_(m.weight) + nn.init.zeros_(m.bias) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, 0, 0.01) + nn.init.zeros_(m.bias) + + def _forward_impl(self, x: Tensor) -> Tensor: + # This exists since TorchScript doesn't support inheritance, so the superclass method + # (this one) needs to have a name other than `forward` that can be accessed in a subclass + out_layers = [] + for layer in self.features.named_modules(): + for i, layer1 in enumerate(layer[1]): + # print(layer1) + x = layer1(x) + # print("第{}层,输出大小{}".format(i, x.shape)) + if i in [0, 10, 18]: + out_layers.append(x) + break + # x = self.features(x) + # Cannot use "squeeze" as batch-size can be 1 + # x = nn.functional.adaptive_avg_pool2d(x, (1, 1)) + # x = torch.flatten(x, 1) + # x = self.classifier(x) + return out_layers + + def forward(self, x: Tensor) -> Tensor: + return self._forward_impl(x) + + +_COMMON_META = { + "num_params": 3504872, + "min_size": (1, 1), + "categories": _IMAGENET_CATEGORIES, +} + + +class MobileNet_V2_Weights(WeightsEnum): + IMAGENET1K_V1 = Weights( + url="https://download.pytorch.org/models/mobilenet_v2-b0353104.pth", + transforms=partial(ImageClassification, crop_size=224), + meta={ + **_COMMON_META, + "recipe": "https://github.com/pytorch/vision/tree/main/references/classification#mobilenetv2", + "_metrics": { + "ImageNet-1K": { + "acc@1": 71.878, + "acc@5": 90.286, + } + }, + "_ops": 0.301, + "_file_size": 13.555, + "_docs": """These weights reproduce closely the results of the paper using a simple training recipe.""", + }, + ) + IMAGENET1K_V2 = Weights( + url="https://download.pytorch.org/models/mobilenet_v2-7ebf99e0.pth", + transforms=partial(ImageClassification, crop_size=224, resize_size=232), + meta={ + **_COMMON_META, + "recipe": "https://github.com/pytorch/vision/issues/3995#new-recipe-with-reg-tuning", + "_metrics": { + "ImageNet-1K": { + "acc@1": 72.154, + "acc@5": 90.822, + } + }, + "_ops": 0.301, + "_file_size": 13.598, + "_docs": """ + These weights improve upon the results of the original paper by using a modified version of TorchVision's + `new training recipe + `_. + """, + }, + ) + DEFAULT = IMAGENET1K_V2 + + +# @register_model() +# @handle_legacy_interface(weights=("pretrained", MobileNet_V2_Weights.IMAGENET1K_V1)) +def mobilenet_v2( + *, weights: Optional[MobileNet_V2_Weights] = MobileNet_V2_Weights.IMAGENET1K_V1, progress: bool = True, **kwargs: Any +) -> MobileNetV2: + """MobileNetV2 architecture from the `MobileNetV2: Inverted Residuals and Linear + Bottlenecks `_ paper. + + Args: + weights (:class:`~torchvision.models.MobileNet_V2_Weights`, optional): The + pretrained weights to use. See + :class:`~torchvision.models.MobileNet_V2_Weights` below for + more details, and possible values. By default, no pre-trained + weights are used. + progress (bool, optional): If True, displays a progress bar of the + download to stderr. Default is True. + **kwargs: parameters passed to the ``torchvision.models.mobilenetv2.MobileNetV2`` + base class. Please refer to the `source code + `_ + for more details about this class. + + .. autoclass:: torchvision.models.MobileNet_V2_Weights + :members: + """ + weights = MobileNet_V2_Weights.verify(weights) + + if weights is not None: + _ovewrite_named_param(kwargs, "num_classes", len(weights.meta["categories"])) + + model = MobileNetV2(**kwargs) + + if weights is not None: + model.load_state_dict(weights.get_state_dict(progress=progress)) + + return model + + +def conv1x1(in_planes, out_planes, stride=1): + """1x1 convolution""" + return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) + +class MobileNetv2Wrapper(nn.Module): + def __init__(self): + super(MobileNetv2Wrapper, self).__init__() + weights = MobileNet_V2_Weights.verify(MobileNet_V2_Weights.IMAGENET1K_V1) + + self.model = MobileNetV2() + + if weights is not None: + self.model.load_state_dict(weights.get_state_dict(progress=True)) + self.out3 = conv1x1(1280, 128) + + def forward(self, x): + # print(x.shape) + out_layers = self.model(x) + # print(x.shape) + + # out_layers[0] = self.out1(out_layers[0]) + # out_layers[1] = self.out2(out_layers[1]) + out_layers[2] = self.out3(out_layers[2]) + # print(x.shape) + return out_layers + + diff --git a/models/registry.py b/models/registry.py new file mode 100644 index 0000000..7b65eff --- /dev/null +++ b/models/registry.py @@ -0,0 +1,16 @@ +from utils import Registry, build_from_cfg + +NET = Registry('net') + +def build(cfg, registry, default_args=None): + if isinstance(cfg, list): + modules = [ + build_from_cfg(cfg_, registry, default_args) for cfg_ in cfg + ] + return nn.Sequential(*modules) + else: + return build_from_cfg(cfg, registry, default_args) + + +def build_net(cfg): + return build(cfg.net, NET, default_args=dict(cfg=cfg)) diff --git a/models/resa.py b/models/resa.py new file mode 100644 index 0000000..93a0f30 --- /dev/null +++ b/models/resa.py @@ -0,0 +1,142 @@ +import torch.nn as nn +import torch +import torch.nn.functional as F + +from models.registry import NET +# from .resnet_copy import ResNetWrapper +# from .resnet import ResNetWrapper +from .decoder_copy2 import BUSD, PlainDecoder +# from .decoder import BUSD, PlainDecoder +# from .mobilenetv2 import MobileNetv2Wrapper +from .mobilenetv2_copy2 import MobileNetv2Wrapper + + +class RESA(nn.Module): + def __init__(self, cfg): + super(RESA, self).__init__() + self.iter = cfg.resa.iter + chan = cfg.resa.input_channel + fea_stride = cfg.backbone.fea_stride + self.height = cfg.img_height // fea_stride + self.width = cfg.img_width // fea_stride + self.alpha = cfg.resa.alpha + conv_stride = cfg.resa.conv_stride + + for i in range(self.iter): + conv_vert1 = nn.Conv2d( + chan, chan, (1, conv_stride), + padding=(0, conv_stride//2), groups=1, bias=False) + conv_vert2 = nn.Conv2d( + chan, chan, (1, conv_stride), + padding=(0, conv_stride//2), groups=1, bias=False) + + setattr(self, 'conv_d'+str(i), conv_vert1) + setattr(self, 'conv_u'+str(i), conv_vert2) + + conv_hori1 = nn.Conv2d( + chan, chan, (conv_stride, 1), + padding=(conv_stride//2, 0), groups=1, bias=False) + conv_hori2 = nn.Conv2d( + chan, chan, (conv_stride, 1), + padding=(conv_stride//2, 0), groups=1, bias=False) + + setattr(self, 'conv_r'+str(i), conv_hori1) + setattr(self, 'conv_l'+str(i), conv_hori2) + + idx_d = (torch.arange(self.height) + self.height // + 2**(self.iter - i)) % self.height + setattr(self, 'idx_d'+str(i), idx_d) + + idx_u = (torch.arange(self.height) - self.height // + 2**(self.iter - i)) % self.height + setattr(self, 'idx_u'+str(i), idx_u) + + idx_r = (torch.arange(self.width) + self.width // + 2**(self.iter - i)) % self.width + setattr(self, 'idx_r'+str(i), idx_r) + + idx_l = (torch.arange(self.width) - self.width // + 2**(self.iter - i)) % self.width + setattr(self, 'idx_l'+str(i), idx_l) + + def forward(self, x): + x = x.clone() + + for direction in ['d', 'u']: + for i in range(self.iter): + conv = getattr(self, 'conv_' + direction + str(i)) + idx = getattr(self, 'idx_' + direction + str(i)) + x.add_(self.alpha * F.relu(conv(x[..., idx, :]))) + + for direction in ['r', 'l']: + for i in range(self.iter): + conv = getattr(self, 'conv_' + direction + str(i)) + idx = getattr(self, 'idx_' + direction + str(i)) + x.add_(self.alpha * F.relu(conv(x[..., idx]))) + + return x + + + +class ExistHead(nn.Module): + def __init__(self, cfg=None): + super(ExistHead, self).__init__() + self.cfg = cfg + + self.dropout = nn.Dropout2d(0.1) # ??? + self.conv8 = nn.Conv2d(128, cfg.num_classes, 1) + + stride = cfg.backbone.fea_stride * 2 + self.fc9 = nn.Linear( + int(cfg.num_classes * cfg.img_width / stride * cfg.img_height / stride), 128) + self.fc10 = nn.Linear(128, cfg.num_classes-1) + + def forward(self, x): + x = self.dropout(x) + x = self.conv8(x) + + x = F.softmax(x, dim=1) + x = F.avg_pool2d(x, 2, stride=2, padding=0) + x = x.view(-1, x.numel() // x.shape[0]) + x = self.fc9(x) + x = F.relu(x) + x = self.fc10(x) + x = torch.sigmoid(x) + + return x + + +@NET.register_module +class RESANet(nn.Module): + def __init__(self, cfg): + super(RESANet, self).__init__() + self.cfg = cfg + # self.backbone = ResNetWrapper(resnet='resnet34',pretrained=True, + # replace_stride_with_dilation=[False, False, False], + # out_conv=False) + self.backbone = MobileNetv2Wrapper() + self.resa = RESA(cfg) + self.decoder = eval(cfg.decoder)(cfg) + self.heads = ExistHead(cfg) + + def forward(self, batch): + # x1, fea, _, _ = self.backbone(batch) + # fea = self.resa(fea) + # # print(fea.shape) + # seg = self.decoder([x1,fea]) + # # print(seg.shape) + # exist = self.heads(fea) + + fea1,fea2,fea = self.backbone(batch) + # print('fea1',fea1.shape) + # print('fea2',fea2.shape) + # print('fea',fea.shape) + fea = self.resa(fea) + # print(fea.shape) + seg = self.decoder([fea1,fea2,fea]) + # print(seg.shape) + exist = self.heads(fea) + + output = {'seg': seg, 'exist': exist} + + return output diff --git a/models/resnet.py b/models/resnet.py new file mode 100644 index 0000000..3f10cfc --- /dev/null +++ b/models/resnet.py @@ -0,0 +1,377 @@ +import torch +from torch import nn +import torch.nn.functional as F +from torch.hub import load_state_dict_from_url + + +# This code is borrow from torchvision. + +model_urls = { + 'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth', + 'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth', + 'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth', + 'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth', + 'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth', + 'resnext50_32x4d': 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth', + 'resnext101_32x8d': 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth', + 'wide_resnet50_2': 'https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth', + 'wide_resnet101_2': 'https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth', +} + + +def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1): + """3x3 convolution with padding""" + return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, + padding=dilation, groups=groups, bias=False, dilation=dilation) + + +def conv1x1(in_planes, out_planes, stride=1): + """1x1 convolution""" + return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1, + base_width=64, dilation=1, norm_layer=None): + super(BasicBlock, self).__init__() + if norm_layer is None: + norm_layer = nn.BatchNorm2d + if groups != 1 or base_width != 64: + raise ValueError( + 'BasicBlock only supports groups=1 and base_width=64') + # if dilation > 1: + # raise NotImplementedError( + # "Dilation > 1 not supported in BasicBlock") + # Both self.conv1 and self.downsample layers downsample the input when stride != 1 + self.conv1 = conv3x3(inplanes, planes, stride, dilation=dilation) + self.bn1 = norm_layer(planes) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes, dilation=dilation) + self.bn2 = norm_layer(planes) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + identity = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1, + base_width=64, dilation=1, norm_layer=None): + super(Bottleneck, self).__init__() + if norm_layer is None: + norm_layer = nn.BatchNorm2d + width = int(planes * (base_width / 64.)) * groups + # Both self.conv2 and self.downsample layers downsample the input when stride != 1 + self.conv1 = conv1x1(inplanes, width) + self.bn1 = norm_layer(width) + self.conv2 = conv3x3(width, width, stride, groups, dilation) + self.bn2 = norm_layer(width) + self.conv3 = conv1x1(width, planes * self.expansion) + self.bn3 = norm_layer(planes * self.expansion) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + identity = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + out = self.relu(out) + + return out + + +class ResNetWrapper(nn.Module): + + def __init__(self, cfg): + super(ResNetWrapper, self).__init__() + self.cfg = cfg + self.in_channels = [64, 128, 256, 512] + if 'in_channels' in cfg.backbone: + self.in_channels = cfg.backbone.in_channels + self.model = eval(cfg.backbone.resnet)( + pretrained=cfg.backbone.pretrained, + replace_stride_with_dilation=cfg.backbone.replace_stride_with_dilation, in_channels=self.in_channels) + self.out = None + if cfg.backbone.out_conv: + out_channel = 512 + for chan in reversed(self.in_channels): + if chan < 0: continue + out_channel = chan + break + self.out = conv1x1( + out_channel * self.model.expansion, 128) + + def forward(self, x): + x = self.model(x) + if self.out: + x = self.out(x) + return x + + +class ResNet(nn.Module): + + def __init__(self, block, layers, zero_init_residual=False, + groups=1, width_per_group=64, replace_stride_with_dilation=None, + norm_layer=None, in_channels=None): + super(ResNet, self).__init__() + if norm_layer is None: + norm_layer = nn.BatchNorm2d + self._norm_layer = norm_layer + + self.inplanes = 64 + self.dilation = 1 + if replace_stride_with_dilation is None: + # each element in the tuple indicates if we should replace + # the 2x2 stride with a dilated convolution instead + replace_stride_with_dilation = [False, False, False] + if len(replace_stride_with_dilation) != 3: + raise ValueError("replace_stride_with_dilation should be None " + "or a 3-element tuple, got {}".format(replace_stride_with_dilation)) + self.groups = groups + self.base_width = width_per_group + self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3, + bias=False) + self.bn1 = norm_layer(self.inplanes) + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + self.in_channels = in_channels + self.layer1 = self._make_layer(block, in_channels[0], layers[0]) + self.layer2 = self._make_layer(block, in_channels[1], layers[1], stride=2, + dilate=replace_stride_with_dilation[0]) + self.layer3 = self._make_layer(block, in_channels[2], layers[2], stride=2, + dilate=replace_stride_with_dilation[1]) + if in_channels[3] > 0: + self.layer4 = self._make_layer(block, in_channels[3], layers[3], stride=2, + dilate=replace_stride_with_dilation[2]) + self.expansion = block.expansion + + # self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) + # self.fc = nn.Linear(512 * block.expansion, num_classes) + + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_( + m.weight, mode='fan_out', nonlinearity='relu') + elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + + # Zero-initialize the last BN in each residual branch, + # so that the residual branch starts with zeros, and each residual block behaves like an identity. + # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677 + if zero_init_residual: + for m in self.modules(): + if isinstance(m, Bottleneck): + nn.init.constant_(m.bn3.weight, 0) + elif isinstance(m, BasicBlock): + nn.init.constant_(m.bn2.weight, 0) + + def _make_layer(self, block, planes, blocks, stride=1, dilate=False): + norm_layer = self._norm_layer + downsample = None + previous_dilation = self.dilation + if dilate: + self.dilation *= stride + stride = 1 + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + conv1x1(self.inplanes, planes * block.expansion, stride), + norm_layer(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample, self.groups, + self.base_width, previous_dilation, norm_layer)) + self.inplanes = planes * block.expansion + for _ in range(1, blocks): + layers.append(block(self.inplanes, planes, groups=self.groups, + base_width=self.base_width, dilation=self.dilation, + norm_layer=norm_layer)) + + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + if self.in_channels[3] > 0: + x = self.layer4(x) + + # x = self.avgpool(x) + # x = torch.flatten(x, 1) + # x = self.fc(x) + + return x + + +def _resnet(arch, block, layers, pretrained, progress, **kwargs): + model = ResNet(block, layers, **kwargs) + if pretrained: + state_dict = load_state_dict_from_url(model_urls[arch], + progress=progress) + model.load_state_dict(state_dict, strict=False) + return model + + +def resnet18(pretrained=False, progress=True, **kwargs): + r"""ResNet-18 model from + `"Deep Residual Learning for Image Recognition" `_ + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress, + **kwargs) + + +def resnet34(pretrained=False, progress=True, **kwargs): + r"""ResNet-34 model from + `"Deep Residual Learning for Image Recognition" `_ + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress, + **kwargs) + + +def resnet50(pretrained=False, progress=True, **kwargs): + r"""ResNet-50 model from + `"Deep Residual Learning for Image Recognition" `_ + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress, + **kwargs) + + +def resnet101(pretrained=False, progress=True, **kwargs): + r"""ResNet-101 model from + `"Deep Residual Learning for Image Recognition" `_ + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained, progress, + **kwargs) + + +def resnet152(pretrained=False, progress=True, **kwargs): + r"""ResNet-152 model from + `"Deep Residual Learning for Image Recognition" `_ + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained, progress, + **kwargs) + + +def resnext50_32x4d(pretrained=False, progress=True, **kwargs): + r"""ResNeXt-50 32x4d model from + `"Aggregated Residual Transformation for Deep Neural Networks" `_ + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + kwargs['groups'] = 32 + kwargs['width_per_group'] = 4 + return _resnet('resnext50_32x4d', Bottleneck, [3, 4, 6, 3], + pretrained, progress, **kwargs) + + +def resnext101_32x8d(pretrained=False, progress=True, **kwargs): + r"""ResNeXt-101 32x8d model from + `"Aggregated Residual Transformation for Deep Neural Networks" `_ + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + kwargs['groups'] = 32 + kwargs['width_per_group'] = 8 + return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3], + pretrained, progress, **kwargs) + + +def wide_resnet50_2(pretrained=False, progress=True, **kwargs): + r"""Wide ResNet-50-2 model from + `"Wide Residual Networks" `_ + + The model is the same as ResNet except for the bottleneck number of channels + which is twice larger in every block. The number of channels in outer 1x1 + convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048 + channels, and in Wide ResNet-50-2 has 2048-1024-2048. + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + kwargs['width_per_group'] = 64 * 2 + return _resnet('wide_resnet50_2', Bottleneck, [3, 4, 6, 3], + pretrained, progress, **kwargs) + + +def wide_resnet101_2(pretrained=False, progress=True, **kwargs): + r"""Wide ResNet-101-2 model from + `"Wide Residual Networks" `_ + + The model is the same as ResNet except for the bottleneck number of channels + which is twice larger in every block. The number of channels in outer 1x1 + convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048 + channels, and in Wide ResNet-50-2 has 2048-1024-2048. + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + kwargs['width_per_group'] = 64 * 2 + return _resnet('wide_resnet101_2', Bottleneck, [3, 4, 23, 3], + pretrained, progress, **kwargs) diff --git a/models/resnet_copy.py b/models/resnet_copy.py new file mode 100644 index 0000000..4290e59 --- /dev/null +++ b/models/resnet_copy.py @@ -0,0 +1,432 @@ +import torch +from torch import nn +import torch.nn.functional as F +from torch.hub import load_state_dict_from_url + + + +model_urls = { + 'resnet18': + 'https://download.pytorch.org/models/resnet18-5c106cde.pth', + 'resnet34': + 'https://download.pytorch.org/models/resnet34-333f7ec4.pth', + 'resnet50': + 'https://download.pytorch.org/models/resnet50-19c8e357.pth', + 'resnet101': + 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth', + 'resnet152': + 'https://download.pytorch.org/models/resnet152-b121ed2d.pth', + 'resnext50_32x4d': + 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth', + 'resnext101_32x8d': + 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth', + 'wide_resnet50_2': + 'https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth', + 'wide_resnet101_2': + 'https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth', +} + + +def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1): + """3x3 convolution with padding""" + return nn.Conv2d(in_planes, + out_planes, + kernel_size=3, + stride=stride, + padding=dilation, + groups=groups, + bias=False, + dilation=dilation) + + +def conv1x1(in_planes, out_planes, stride=1): + """1x1 convolution""" + return nn.Conv2d(in_planes, + out_planes, + kernel_size=1, + stride=stride, + bias=False) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, + inplanes, + planes, + stride=1, + downsample=None, + groups=1, + base_width=64, + dilation=1, + norm_layer=None): + super(BasicBlock, self).__init__() + if norm_layer is None: + norm_layer = nn.BatchNorm2d + if groups != 1 or base_width != 64: + raise ValueError( + 'BasicBlock only supports groups=1 and base_width=64') + # if dilation > 1: + # raise NotImplementedError( + # "Dilation > 1 not supported in BasicBlock") + # Both self.conv1 and self.downsample layers downsample the input when stride != 1 + self.conv1 = conv3x3(inplanes, planes, stride, dilation=dilation) + self.bn1 = norm_layer(planes) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes, dilation=dilation) + self.bn2 = norm_layer(planes) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + identity = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, + inplanes, + planes, + stride=1, + downsample=None, + groups=1, + base_width=64, + dilation=1, + norm_layer=None): + super(Bottleneck, self).__init__() + if norm_layer is None: + norm_layer = nn.BatchNorm2d + width = int(planes * (base_width / 64.)) * groups + # Both self.conv2 and self.downsample layers downsample the input when stride != 1 + self.conv1 = conv1x1(inplanes, width) + self.bn1 = norm_layer(width) + self.conv2 = conv3x3(width, width, stride, groups, dilation) + self.bn2 = norm_layer(width) + self.conv3 = conv1x1(width, planes * self.expansion) + self.bn3 = norm_layer(planes * self.expansion) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + identity = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + out = self.relu(out) + + return out + + + +class ResNetWrapper(nn.Module): + def __init__(self, + resnet='resnet18', + pretrained=True, + replace_stride_with_dilation=[False, False, False], + out_conv=False, + fea_stride=8, + out_channel=128, + in_channels=[64, 128, 256, 512], + cfg=None): + super(ResNetWrapper, self).__init__() + self.cfg = cfg + self.in_channels = in_channels + + self.model = eval(resnet)( + pretrained=pretrained, + replace_stride_with_dilation=replace_stride_with_dilation, + in_channels=self.in_channels) + self.out = None + if out_conv: + out_channel = 512 + for chan in reversed(self.in_channels): + if chan < 0: continue + out_channel = chan + break + self.out = conv1x1(out_channel * self.model.expansion, + cfg.featuremap_out_channel) + + def forward(self, x): + x = self.model(x) + if self.out: + x[-1] = self.out(x[-1]) + return x + + +class ResNet(nn.Module): + def __init__(self, + block, + layers, + zero_init_residual=False, + groups=1, + width_per_group=64, + replace_stride_with_dilation=None, + norm_layer=None, + in_channels=None): + super(ResNet, self).__init__() + if norm_layer is None: + norm_layer = nn.BatchNorm2d + self._norm_layer = norm_layer + + self.inplanes = 64 + self.dilation = 1 + if replace_stride_with_dilation is None: + # each element in the tuple indicates if we should replace + # the 2x2 stride with a dilated convolution instead + replace_stride_with_dilation = [False, False, False] + if len(replace_stride_with_dilation) != 3: + raise ValueError("replace_stride_with_dilation should be None " + "or a 3-element tuple, got {}".format( + replace_stride_with_dilation)) + self.groups = groups + self.base_width = width_per_group + self.conv1 = nn.Conv2d(3, + self.inplanes, + kernel_size=7, + stride=2, + padding=3, + bias=False) + self.bn1 = norm_layer(self.inplanes) + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + self.in_channels = in_channels + self.layer1 = self._make_layer(block, in_channels[0], layers[0]) + self.layer2 = self._make_layer(block, + in_channels[1], + layers[1], + stride=2, + dilate=replace_stride_with_dilation[0]) + self.layer3 = self._make_layer(block, + in_channels[2], + layers[2], + stride=2, + dilate=replace_stride_with_dilation[1]) + if in_channels[3] > 0: + self.layer4 = self._make_layer( + block, + in_channels[3], + layers[3], + stride=2, + dilate=replace_stride_with_dilation[2]) + self.expansion = block.expansion + + # self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) + # self.fc = nn.Linear(512 * block.expansion, num_classes) + + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, + mode='fan_out', + nonlinearity='relu') + elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + + # Zero-initialize the last BN in each residual branch, + # so that the residual branch starts with zeros, and each residual block behaves like an identity. + # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677 + if zero_init_residual: + for m in self.modules(): + if isinstance(m, Bottleneck): + nn.init.constant_(m.bn3.weight, 0) + elif isinstance(m, BasicBlock): + nn.init.constant_(m.bn2.weight, 0) + + def _make_layer(self, block, planes, blocks, stride=1, dilate=False): + norm_layer = self._norm_layer + downsample = None + previous_dilation = self.dilation + if dilate: + self.dilation *= stride + stride = 1 + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + conv1x1(self.inplanes, planes * block.expansion, stride), + norm_layer(planes * block.expansion), + ) + + layers = [] + layers.append( + block(self.inplanes, planes, stride, downsample, self.groups, + self.base_width, previous_dilation, norm_layer)) + self.inplanes = planes * block.expansion + for _ in range(1, blocks): + layers.append( + block(self.inplanes, + planes, + groups=self.groups, + base_width=self.base_width, + dilation=self.dilation, + norm_layer=norm_layer)) + + return nn.Sequential(*layers) + + def forward(self, x): + out_layers = [] + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + # out_layers = [] + for name in ['layer1', 'layer2', 'layer3', 'layer4']: + if not hasattr(self, name): + continue + layer = getattr(self, name) + x = layer(x) + out_layers.append(x) + + return out_layers + + +def _resnet(arch, block, layers, pretrained, progress, **kwargs): + model = ResNet(block, layers, **kwargs) + if pretrained: + print('pretrained model: ', model_urls[arch]) + # state_dict = torch.load(model_urls[arch])['net'] + state_dict = load_state_dict_from_url(model_urls[arch]) + model.load_state_dict(state_dict, strict=False) + return model + + +def resnet18(pretrained=False, progress=True, **kwargs): + r"""ResNet-18 model from + `"Deep Residual Learning for Image Recognition" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress, + **kwargs) + + +def resnet34(pretrained=False, progress=True, **kwargs): + r"""ResNet-34 model from + `"Deep Residual Learning for Image Recognition" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress, + **kwargs) + + +def resnet50(pretrained=False, progress=True, **kwargs): + r"""ResNet-50 model from + `"Deep Residual Learning for Image Recognition" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress, + **kwargs) + + +def resnet101(pretrained=False, progress=True, **kwargs): + r"""ResNet-101 model from + `"Deep Residual Learning for Image Recognition" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained, + progress, **kwargs) + + +def resnet152(pretrained=False, progress=True, **kwargs): + r"""ResNet-152 model from + `"Deep Residual Learning for Image Recognition" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained, + progress, **kwargs) + + +def resnext50_32x4d(pretrained=False, progress=True, **kwargs): + r"""ResNeXt-50 32x4d model from + `"Aggregated Residual Transformation for Deep Neural Networks" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + kwargs['groups'] = 32 + kwargs['width_per_group'] = 4 + return _resnet('resnext50_32x4d', Bottleneck, [3, 4, 6, 3], pretrained, + progress, **kwargs) + + +def resnext101_32x8d(pretrained=False, progress=True, **kwargs): + r"""ResNeXt-101 32x8d model from + `"Aggregated Residual Transformation for Deep Neural Networks" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + kwargs['groups'] = 32 + kwargs['width_per_group'] = 8 + return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3], pretrained, + progress, **kwargs) + + +def wide_resnet50_2(pretrained=False, progress=True, **kwargs): + r"""Wide ResNet-50-2 model from + `"Wide Residual Networks" `_ + The model is the same as ResNet except for the bottleneck number of channels + which is twice larger in every block. The number of channels in outer 1x1 + convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048 + channels, and in Wide ResNet-50-2 has 2048-1024-2048. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + kwargs['width_per_group'] = 64 * 2 + return _resnet('wide_resnet50_2', Bottleneck, [3, 4, 6, 3], pretrained, + progress, **kwargs) + + +def wide_resnet101_2(pretrained=False, progress=True, **kwargs): + r"""Wide ResNet-101-2 model from + `"Wide Residual Networks" `_ + The model is the same as ResNet except for the bottleneck number of channels + which is twice larger in every block. The number of channels in outer 1x1 + convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048 + channels, and in Wide ResNet-50-2 has 2048-1024-2048. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + kwargs['width_per_group'] = 64 * 2 + return _resnet('wide_resnet101_2', Bottleneck, [3, 4, 23, 3], pretrained, + progress, **kwargs) diff --git a/requirement.txt b/requirement.txt new file mode 100644 index 0000000..92c0165 --- /dev/null +++ b/requirement.txt @@ -0,0 +1,8 @@ +pandas +addict +sklearn +opencv-python +pytorch_warmup +scikit-image +tqdm +termcolor diff --git a/runner/__init__.py b/runner/__init__.py new file mode 100644 index 0000000..1644223 --- /dev/null +++ b/runner/__init__.py @@ -0,0 +1,4 @@ +from .evaluator import * +from .resa_trainer import * + +from .registry import build_evaluator diff --git a/runner/evaluator/__init__.py b/runner/evaluator/__init__.py new file mode 100644 index 0000000..308528c --- /dev/null +++ b/runner/evaluator/__init__.py @@ -0,0 +1,2 @@ +from .tusimple.tusimple import Tusimple +from .culane.culane import CULane diff --git a/runner/evaluator/culane/culane.py b/runner/evaluator/culane/culane.py new file mode 100644 index 0000000..534a87d --- /dev/null +++ b/runner/evaluator/culane/culane.py @@ -0,0 +1,158 @@ +import torch.nn as nn +import torch +import torch.nn.functional as F +from runner.logger import get_logger + +from runner.registry import EVALUATOR +import json +import os +import subprocess +from shutil import rmtree +import cv2 +import numpy as np + +def check(): + import subprocess + import sys + FNULL = open(os.devnull, 'w') + result = subprocess.call( + './runner/evaluator/culane/lane_evaluation/evaluate', stdout=FNULL, stderr=FNULL) + if result > 1: + print('There is something wrong with evaluate tool, please compile it.') + sys.exit() + +def read_helper(path): + lines = open(path, 'r').readlines()[1:] + lines = ' '.join(lines) + values = lines.split(' ')[1::2] + keys = lines.split(' ')[0::2] + keys = [key[:-1] for key in keys] + res = {k : v for k,v in zip(keys,values)} + return res + +def call_culane_eval(data_dir, output_path='./output'): + if data_dir[-1] != '/': + data_dir = data_dir + '/' + detect_dir=os.path.join(output_path, 'lines')+'/' + + w_lane=30 + iou=0.5; # Set iou to 0.3 or 0.5 + im_w=1640 + im_h=590 + frame=1 + list0 = os.path.join(data_dir,'list/test_split/test0_normal.txt') + list1 = os.path.join(data_dir,'list/test_split/test1_crowd.txt') + list2 = os.path.join(data_dir,'list/test_split/test2_hlight.txt') + list3 = os.path.join(data_dir,'list/test_split/test3_shadow.txt') + list4 = os.path.join(data_dir,'list/test_split/test4_noline.txt') + list5 = os.path.join(data_dir,'list/test_split/test5_arrow.txt') + list6 = os.path.join(data_dir,'list/test_split/test6_curve.txt') + list7 = os.path.join(data_dir,'list/test_split/test7_cross.txt') + list8 = os.path.join(data_dir,'list/test_split/test8_night.txt') + if not os.path.exists(os.path.join(output_path,'txt')): + os.mkdir(os.path.join(output_path,'txt')) + out0 = os.path.join(output_path,'txt','out0_normal.txt') + out1 = os.path.join(output_path,'txt','out1_crowd.txt') + out2 = os.path.join(output_path,'txt','out2_hlight.txt') + out3 = os.path.join(output_path,'txt','out3_shadow.txt') + out4 = os.path.join(output_path,'txt','out4_noline.txt') + out5 = os.path.join(output_path,'txt','out5_arrow.txt') + out6 = os.path.join(output_path,'txt','out6_curve.txt') + out7 = os.path.join(output_path,'txt','out7_cross.txt') + out8 = os.path.join(output_path,'txt','out8_night.txt') + + eval_cmd = './runner/evaluator/culane/lane_evaluation/evaluate' + + os.system('%s -a %s -d %s -i %s -l %s -w %s -t %s -c %s -r %s -f %s -o %s'%(eval_cmd,data_dir,detect_dir,data_dir,list0,w_lane,iou,im_w,im_h,frame,out0)) + os.system('%s -a %s -d %s -i %s -l %s -w %s -t %s -c %s -r %s -f %s -o %s'%(eval_cmd,data_dir,detect_dir,data_dir,list1,w_lane,iou,im_w,im_h,frame,out1)) + os.system('%s -a %s -d %s -i %s -l %s -w %s -t %s -c %s -r %s -f %s -o %s'%(eval_cmd,data_dir,detect_dir,data_dir,list2,w_lane,iou,im_w,im_h,frame,out2)) + os.system('%s -a %s -d %s -i %s -l %s -w %s -t %s -c %s -r %s -f %s -o %s'%(eval_cmd,data_dir,detect_dir,data_dir,list3,w_lane,iou,im_w,im_h,frame,out3)) + os.system('%s -a %s -d %s -i %s -l %s -w %s -t %s -c %s -r %s -f %s -o %s'%(eval_cmd,data_dir,detect_dir,data_dir,list4,w_lane,iou,im_w,im_h,frame,out4)) + os.system('%s -a %s -d %s -i %s -l %s -w %s -t %s -c %s -r %s -f %s -o %s'%(eval_cmd,data_dir,detect_dir,data_dir,list5,w_lane,iou,im_w,im_h,frame,out5)) + os.system('%s -a %s -d %s -i %s -l %s -w %s -t %s -c %s -r %s -f %s -o %s'%(eval_cmd,data_dir,detect_dir,data_dir,list6,w_lane,iou,im_w,im_h,frame,out6)) + os.system('%s -a %s -d %s -i %s -l %s -w %s -t %s -c %s -r %s -f %s -o %s'%(eval_cmd,data_dir,detect_dir,data_dir,list7,w_lane,iou,im_w,im_h,frame,out7)) + os.system('%s -a %s -d %s -i %s -l %s -w %s -t %s -c %s -r %s -f %s -o %s'%(eval_cmd,data_dir,detect_dir,data_dir,list8,w_lane,iou,im_w,im_h,frame,out8)) + res_all = {} + res_all['normal'] = read_helper(out0) + res_all['crowd']= read_helper(out1) + res_all['night']= read_helper(out8) + res_all['noline'] = read_helper(out4) + res_all['shadow'] = read_helper(out3) + res_all['arrow']= read_helper(out5) + res_all['hlight'] = read_helper(out2) + res_all['curve']= read_helper(out6) + res_all['cross']= read_helper(out7) + return res_all + +@EVALUATOR.register_module +class CULane(nn.Module): + def __init__(self, cfg): + super(CULane, self).__init__() + # Firstly, check the evaluation tool + check() + self.cfg = cfg + self.blur = torch.nn.Conv2d( + 5, 5, 9, padding=4, bias=False, groups=5).cuda() + torch.nn.init.constant_(self.blur.weight, 1 / 81) + self.logger = get_logger('resa') + self.out_dir = os.path.join(self.cfg.work_dir, 'lines') + if cfg.view: + self.view_dir = os.path.join(self.cfg.work_dir, 'vis') + + def evaluate(self, dataset, output, batch): + seg, exists = output['seg'], output['exist'] + predictmaps = F.softmax(seg, dim=1).cpu().numpy() + exists = exists.cpu().numpy() + batch_size = seg.size(0) + img_name = batch['meta']['img_name'] + img_path = batch['meta']['full_img_path'] + for i in range(batch_size): + coords = dataset.probmap2lane(predictmaps[i], exists[i]) + outname = self.out_dir + img_name[i][:-4] + '.lines.txt' + outdir = os.path.dirname(outname) + if not os.path.exists(outdir): + os.makedirs(outdir) + f = open(outname, 'w') + for coord in coords: + for x, y in coord: + if x < 0 and y < 0: + continue + f.write('%d %d ' % (x, y)) + f.write('\n') + f.close() + + if self.cfg.view: + img = cv2.imread(img_path[i]).astype(np.float32) + dataset.view(img, coords, self.view_dir+img_name[i]) + + + def summarize(self): + self.logger.info('summarize result...') + eval_list_path = os.path.join( + self.cfg.dataset_path, "list", self.cfg.dataset.val.data_list) + #prob2lines(self.prob_dir, self.out_dir, eval_list_path, self.cfg) + res = call_culane_eval(self.cfg.dataset_path, output_path=self.cfg.work_dir) + TP,FP,FN = 0,0,0 + out_str = 'Copypaste: ' + for k, v in res.items(): + val = float(v['Fmeasure']) if 'nan' not in v['Fmeasure'] else 0 + val_tp, val_fp, val_fn = int(v['tp']), int(v['fp']), int(v['fn']) + val_p, val_r, val_f1 = float(v['precision']), float(v['recall']), float(v['Fmeasure']) + TP += val_tp + FP += val_fp + FN += val_fn + self.logger.info(k + ': ' + str(v)) + out_str += k + for metric, value in v.items(): + out_str += ' ' + str(value).rstrip('\n') + out_str += ' ' + P = TP * 1.0 / (TP + FP + 1e-9) + R = TP * 1.0 / (TP + FN + 1e-9) + F = 2*P*R/(P + R + 1e-9) + overall_result_str = ('Overall Precision: %f Recall: %f F1: %f' % (P, R, F)) + self.logger.info(overall_result_str) + out_str = out_str + overall_result_str + self.logger.info(out_str) + + # delete the tmp output + rmtree(self.out_dir) diff --git a/runner/evaluator/culane/lane_evaluation/.gitignore b/runner/evaluator/culane/lane_evaluation/.gitignore new file mode 100644 index 0000000..b501d98 --- /dev/null +++ b/runner/evaluator/culane/lane_evaluation/.gitignore @@ -0,0 +1,2 @@ +build/ +evaluate diff --git a/runner/evaluator/culane/lane_evaluation/Makefile b/runner/evaluator/culane/lane_evaluation/Makefile new file mode 100644 index 0000000..d4457b9 --- /dev/null +++ b/runner/evaluator/culane/lane_evaluation/Makefile @@ -0,0 +1,50 @@ +PROJECT_NAME:= evaluate + +# config ---------------------------------- +OPENCV_VERSION := 3 + +INCLUDE_DIRS := include +LIBRARY_DIRS := lib /usr/local/lib + +COMMON_FLAGS := -DCPU_ONLY +CXXFLAGS := -std=c++11 -fopenmp +LDFLAGS := -fopenmp -Wl,-rpath,./lib +BUILD_DIR := build + + +# make rules ------------------------------- +CXX ?= g++ +BUILD_DIR ?= ./build + +LIBRARIES += opencv_core opencv_highgui opencv_imgproc +ifeq ($(OPENCV_VERSION), 3) + LIBRARIES += opencv_imgcodecs +endif + +CXXFLAGS += $(COMMON_FLAGS) $(foreach includedir,$(INCLUDE_DIRS),-I$(includedir)) +LDFLAGS += $(COMMON_FLAGS) $(foreach includedir,$(LIBRARY_DIRS),-L$(includedir)) $(foreach library,$(LIBRARIES),-l$(library)) +SRC_DIRS += $(shell find * -type d -exec bash -c "find {} -maxdepth 1 \( -name '*.cpp' -o -name '*.proto' \) | grep -q ." \; -print) +CXX_SRCS += $(shell find src/ -name "*.cpp") +CXX_TARGETS:=$(patsubst %.cpp, $(BUILD_DIR)/%.o, $(CXX_SRCS)) +ALL_BUILD_DIRS := $(sort $(BUILD_DIR) $(addprefix $(BUILD_DIR)/, $(SRC_DIRS))) + +.PHONY: all +all: $(PROJECT_NAME) + +.PHONY: $(ALL_BUILD_DIRS) +$(ALL_BUILD_DIRS): + @mkdir -p $@ + +$(BUILD_DIR)/%.o: %.cpp | $(ALL_BUILD_DIRS) + @echo "CXX" $< + @$(CXX) $(CXXFLAGS) -c -o $@ $< + +$(PROJECT_NAME): $(CXX_TARGETS) + @echo "CXX/LD" $@ + @$(CXX) -o $@ $^ $(LDFLAGS) + +.PHONY: clean +clean: + @rm -rf $(CXX_TARGETS) + @rm -rf $(PROJECT_NAME) + @rm -rf $(BUILD_DIR) diff --git a/runner/evaluator/culane/lane_evaluation/include/counter.hpp b/runner/evaluator/culane/lane_evaluation/include/counter.hpp new file mode 100644 index 0000000..430e1d4 --- /dev/null +++ b/runner/evaluator/culane/lane_evaluation/include/counter.hpp @@ -0,0 +1,47 @@ +#ifndef COUNTER_HPP +#define COUNTER_HPP + +#include "lane_compare.hpp" +#include "hungarianGraph.hpp" +#include +#include +#include +#include +#include + +using namespace std; +using namespace cv; + +// before coming to use functions of this class, the lanes should resize to im_width and im_height using resize_lane() in lane_compare.hpp +class Counter +{ + public: + Counter(int _im_width, int _im_height, double _iou_threshold=0.4, int _lane_width=10):tp(0),fp(0),fn(0){ + im_width = _im_width; + im_height = _im_height; + sim_threshold = _iou_threshold; + lane_compare = new LaneCompare(_im_width, _im_height, _lane_width, LaneCompare::IOU); + }; + double get_precision(void); + double get_recall(void); + long getTP(void); + long getFP(void); + long getFN(void); + void setTP(long); + void setFP(long); + void setFN(long); + // direct add tp, fp, tn and fn + // first match with hungarian + tuple, long, long, long, long> count_im_pair(const vector > &anno_lanes, const vector > &detect_lanes); + void makeMatch(const vector > &similarity, vector &match1, vector &match2); + + private: + double sim_threshold; + int im_width; + int im_height; + long tp; + long fp; + long fn; + LaneCompare *lane_compare; +}; +#endif diff --git a/runner/evaluator/culane/lane_evaluation/include/hungarianGraph.hpp b/runner/evaluator/culane/lane_evaluation/include/hungarianGraph.hpp new file mode 100644 index 0000000..40c3ead --- /dev/null +++ b/runner/evaluator/culane/lane_evaluation/include/hungarianGraph.hpp @@ -0,0 +1,71 @@ +#ifndef HUNGARIAN_GRAPH_HPP +#define HUNGARIAN_GRAPH_HPP +#include +using namespace std; + +struct pipartiteGraph { + vector > mat; + vector leftUsed, rightUsed; + vector leftWeight, rightWeight; + vectorrightMatch, leftMatch; + int leftNum, rightNum; + bool matchDfs(int u) { + leftUsed[u] = true; + for (int v = 0; v < rightNum; v++) { + if (!rightUsed[v] && fabs(leftWeight[u] + rightWeight[v] - mat[u][v]) < 1e-2) { + rightUsed[v] = true; + if (rightMatch[v] == -1 || matchDfs(rightMatch[v])) { + rightMatch[v] = u; + leftMatch[u] = v; + return true; + } + } + } + return false; + } + void resize(int leftNum, int rightNum) { + this->leftNum = leftNum; + this->rightNum = rightNum; + leftMatch.resize(leftNum); + rightMatch.resize(rightNum); + leftUsed.resize(leftNum); + rightUsed.resize(rightNum); + leftWeight.resize(leftNum); + rightWeight.resize(rightNum); + mat.resize(leftNum); + for (int i = 0; i < leftNum; i++) mat[i].resize(rightNum); + } + void match() { + for (int i = 0; i < leftNum; i++) leftMatch[i] = -1; + for (int i = 0; i < rightNum; i++) rightMatch[i] = -1; + for (int i = 0; i < rightNum; i++) rightWeight[i] = 0; + for (int i = 0; i < leftNum; i++) { + leftWeight[i] = -1e5; + for (int j = 0; j < rightNum; j++) { + if (leftWeight[i] < mat[i][j]) leftWeight[i] = mat[i][j]; + } + } + + for (int u = 0; u < leftNum; u++) { + while (1) { + for (int i = 0; i < leftNum; i++) leftUsed[i] = false; + for (int i = 0; i < rightNum; i++) rightUsed[i] = false; + if (matchDfs(u)) break; + double d = 1e10; + for (int i = 0; i < leftNum; i++) { + if (leftUsed[i] ) { + for (int j = 0; j < rightNum; j++) { + if (!rightUsed[j]) d = min(d, leftWeight[i] + rightWeight[j] - mat[i][j]); + } + } + } + if (d == 1e10) return ; + for (int i = 0; i < leftNum; i++) if (leftUsed[i]) leftWeight[i] -= d; + for (int i = 0; i < rightNum; i++) if (rightUsed[i]) rightWeight[i] += d; + } + } + } +}; + + +#endif // HUNGARIAN_GRAPH_HPP diff --git a/runner/evaluator/culane/lane_evaluation/include/lane_compare.hpp b/runner/evaluator/culane/lane_evaluation/include/lane_compare.hpp new file mode 100644 index 0000000..02ddfce --- /dev/null +++ b/runner/evaluator/culane/lane_evaluation/include/lane_compare.hpp @@ -0,0 +1,51 @@ +#ifndef LANE_COMPARE_HPP +#define LANE_COMPARE_HPP + +#include "spline.hpp" +#include +#include +#include +#include + +#if CV_VERSION_EPOCH == 2 +#define OPENCV2 +#elif CV_VERSION_MAJOR == 3 +#define OPENCV3 +#else +#error Not support this OpenCV version +#endif + +#ifdef OPENCV3 +#include +#elif defined(OPENCV2) +#include +#endif + +using namespace std; +using namespace cv; + +class LaneCompare{ + public: + enum CompareMode{ + IOU, + Caltech + }; + + LaneCompare(int _im_width, int _im_height, int _lane_width = 10, CompareMode _compare_mode = IOU){ + im_width = _im_width; + im_height = _im_height; + compare_mode = _compare_mode; + lane_width = _lane_width; + } + + double get_lane_similarity(const vector &lane1, const vector &lane2); + void resize_lane(vector &curr_lane, int curr_width, int curr_height); + private: + CompareMode compare_mode; + int im_width; + int im_height; + int lane_width; + Spline splineSolver; +}; + +#endif diff --git a/runner/evaluator/culane/lane_evaluation/include/spline.hpp b/runner/evaluator/culane/lane_evaluation/include/spline.hpp new file mode 100644 index 0000000..0ae73ef --- /dev/null +++ b/runner/evaluator/culane/lane_evaluation/include/spline.hpp @@ -0,0 +1,28 @@ +#ifndef SPLINE_HPP +#define SPLINE_HPP +#include +#include +#include +#include + +using namespace cv; +using namespace std; + +struct Func { + double a_x; + double b_x; + double c_x; + double d_x; + double a_y; + double b_y; + double c_y; + double d_y; + double h; +}; +class Spline { +public: + vector splineInterpTimes(const vector &tmp_line, int times); + vector splineInterpStep(vector tmp_line, double step); + vector cal_fun(const vector &point_v); +}; +#endif diff --git a/runner/evaluator/culane/lane_evaluation/src/counter.cpp b/runner/evaluator/culane/lane_evaluation/src/counter.cpp new file mode 100644 index 0000000..f4fa6a7 --- /dev/null +++ b/runner/evaluator/culane/lane_evaluation/src/counter.cpp @@ -0,0 +1,134 @@ +/************************************************************************* + > File Name: counter.cpp + > Author: Xingang Pan, Jun Li + > Mail: px117@ie.cuhk.edu.hk + > Created Time: Thu Jul 14 20:23:08 2016 + ************************************************************************/ + +#include "counter.hpp" + +double Counter::get_precision(void) +{ + cerr<<"tp: "<, long, long, long, long> Counter::count_im_pair(const vector > &anno_lanes, const vector > &detect_lanes) +{ + vector anno_match(anno_lanes.size(), -1); + vector detect_match; + if(anno_lanes.empty()) + { + return make_tuple(anno_match, 0, detect_lanes.size(), 0, 0); + } + + if(detect_lanes.empty()) + { + return make_tuple(anno_match, 0, 0, 0, anno_lanes.size()); + } + // hungarian match first + + // first calc similarity matrix + vector > similarity(anno_lanes.size(), vector(detect_lanes.size(), 0)); + for(int i=0; i &curr_anno_lane = anno_lanes[i]; + for(int j=0; j &curr_detect_lane = detect_lanes[j]; + similarity[i][j] = lane_compare->get_lane_similarity(curr_anno_lane, curr_detect_lane); + } + } + + + + makeMatch(similarity, anno_match, detect_match); + + + int curr_tp = 0; + // count and add + for(int i=0; i=0 && similarity[i][anno_match[i]] > sim_threshold) + { + curr_tp++; + } + else + { + anno_match[i] = -1; + } + } + int curr_fn = anno_lanes.size() - curr_tp; + int curr_fp = detect_lanes.size() - curr_tp; + return make_tuple(anno_match, curr_tp, curr_fp, 0, curr_fn); +} + + +void Counter::makeMatch(const vector > &similarity, vector &match1, vector &match2) { + int m = similarity.size(); + int n = similarity[0].size(); + pipartiteGraph gra; + bool have_exchange = false; + if (m > n) { + have_exchange = true; + swap(m, n); + } + gra.resize(m, n); + for (int i = 0; i < gra.leftNum; i++) { + for (int j = 0; j < gra.rightNum; j++) { + if(have_exchange) + gra.mat[i][j] = similarity[j][i]; + else + gra.mat[i][j] = similarity[i][j]; + } + } + gra.match(); + match1 = gra.leftMatch; + match2 = gra.rightMatch; + if (have_exchange) swap(match1, match2); +} diff --git a/runner/evaluator/culane/lane_evaluation/src/evaluate.cpp b/runner/evaluator/culane/lane_evaluation/src/evaluate.cpp new file mode 100644 index 0000000..ae95bb4 --- /dev/null +++ b/runner/evaluator/culane/lane_evaluation/src/evaluate.cpp @@ -0,0 +1,302 @@ +/************************************************************************* + > File Name: evaluate.cpp + > Author: Xingang Pan, Jun Li + > Mail: px117@ie.cuhk.edu.hk + > Created Time: 2016年07月14日 星期四 18时28分45秒 + ************************************************************************/ + +#include "counter.hpp" +#include "spline.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +using namespace cv; + +void help(void) { + cout << "./evaluate [OPTIONS]" << endl; + cout << "-h : print usage help" << endl; + cout << "-a : directory for annotation files (default: " + "/data/driving/eval_data/anno_label/)" << endl; + cout << "-d : directory for detection files (default: " + "/data/driving/eval_data/predict_label/)" << endl; + cout << "-i : directory for image files (default: " + "/data/driving/eval_data/img/)" << endl; + cout << "-l : list of images used for evaluation (default: " + "/data/driving/eval_data/img/all.txt)" << endl; + cout << "-w : width of the lanes (default: 10)" << endl; + cout << "-t : threshold of iou (default: 0.4)" << endl; + cout << "-c : cols (max image width) (default: 1920)" + << endl; + cout << "-r : rows (max image height) (default: 1080)" + << endl; + cout << "-s : show visualization" << endl; + cout << "-f : start frame in the test set (default: 1)" + << endl; +} + +void read_lane_file(const string &file_name, vector> &lanes); +void visualize(string &full_im_name, vector> &anno_lanes, + vector> &detect_lanes, vector anno_match, + int width_lane, string save_path = ""); + +int main(int argc, char **argv) { + // process params + string anno_dir = "/data/driving/eval_data/anno_label/"; + string detect_dir = "/data/driving/eval_data/predict_label/"; + string im_dir = "/data/driving/eval_data/img/"; + string list_im_file = "/data/driving/eval_data/img/all.txt"; + string output_file = "./output.txt"; + int width_lane = 10; + double iou_threshold = 0.4; + int im_width = 1920; + int im_height = 1080; + int oc; + bool show = false; + int frame = 1; + string save_path = ""; + while ((oc = getopt(argc, argv, "ha:d:i:l:w:t:c:r:sf:o:p:")) != -1) { + switch (oc) { + case 'h': + help(); + return 0; + case 'a': + anno_dir = optarg; + break; + case 'd': + detect_dir = optarg; + break; + case 'i': + im_dir = optarg; + break; + case 'l': + list_im_file = optarg; + break; + case 'w': + width_lane = atoi(optarg); + break; + case 't': + iou_threshold = atof(optarg); + break; + case 'c': + im_width = atoi(optarg); + break; + case 'r': + im_height = atoi(optarg); + break; + case 's': + show = true; + break; + case 'p': + save_path = optarg; + break; + case 'f': + frame = atoi(optarg); + break; + case 'o': + output_file = optarg; + break; + } + } + + cout << "------------Configuration---------" << endl; + cout << "anno_dir: " << anno_dir << endl; + cout << "detect_dir: " << detect_dir << endl; + cout << "im_dir: " << im_dir << endl; + cout << "list_im_file: " << list_im_file << endl; + cout << "width_lane: " << width_lane << endl; + cout << "iou_threshold: " << iou_threshold << endl; + cout << "im_width: " << im_width << endl; + cout << "im_height: " << im_height << endl; + cout << "-----------------------------------" << endl; + cout << "Evaluating the results..." << endl; + // this is the max_width and max_height + + if (width_lane < 1) { + cerr << "width_lane must be positive" << endl; + help(); + return 1; + } + + ifstream ifs_im_list(list_im_file, ios::in); + if (ifs_im_list.fail()) { + cerr << "Error: file " << list_im_file << " not exist!" << endl; + return 1; + } + + Counter counter(im_width, im_height, iou_threshold, width_lane); + + vector anno_match; + string sub_im_name; + // pre-load filelist + vector filelists; + while (getline(ifs_im_list, sub_im_name)) { + filelists.push_back(sub_im_name); + } + ifs_im_list.close(); + + vector, long, long, long, long>> tuple_lists; + tuple_lists.resize(filelists.size()); + +#pragma omp parallel for + for (size_t i = 0; i < filelists.size(); i++) { + auto sub_im_name = filelists[i]; + string full_im_name = im_dir + sub_im_name; + string sub_txt_name = + sub_im_name.substr(0, sub_im_name.find_last_of(".")) + ".lines.txt"; + string anno_file_name = anno_dir + sub_txt_name; + string detect_file_name = detect_dir + sub_txt_name; + vector> anno_lanes; + vector> detect_lanes; + read_lane_file(anno_file_name, anno_lanes); + read_lane_file(detect_file_name, detect_lanes); + // cerr<(tuple_lists[i]); + visualize(full_im_name, anno_lanes, detect_lanes, anno_match, width_lane); + waitKey(0); + } + if (save_path != "") { + auto anno_match = get<0>(tuple_lists[i]); + visualize(full_im_name, anno_lanes, detect_lanes, anno_match, width_lane, + save_path); + } + } + + long tp = 0, fp = 0, tn = 0, fn = 0; + for (auto result : tuple_lists) { + tp += get<1>(result); + fp += get<2>(result); + // tn = get<3>(result); + fn += get<4>(result); + } + counter.setTP(tp); + counter.setFP(fp); + counter.setFN(fn); + + double precision = counter.get_precision(); + double recall = counter.get_recall(); + double F = 2 * precision * recall / (precision + recall); + cerr << "finished process file" << endl; + cout << "precision: " << precision << endl; + cout << "recall: " << recall << endl; + cout << "Fmeasure: " << F << endl; + cout << "----------------------------------" << endl; + + ofstream ofs_out_file; + ofs_out_file.open(output_file, ios::out); + ofs_out_file << "file: " << output_file << endl; + ofs_out_file << "tp: " << counter.getTP() << " fp: " << counter.getFP() + << " fn: " << counter.getFN() << endl; + ofs_out_file << "precision: " << precision << endl; + ofs_out_file << "recall: " << recall << endl; + ofs_out_file << "Fmeasure: " << F << endl << endl; + ofs_out_file.close(); + return 0; +} + +void read_lane_file(const string &file_name, vector> &lanes) { + lanes.clear(); + ifstream ifs_lane(file_name, ios::in); + if (ifs_lane.fail()) { + return; + } + + string str_line; + while (getline(ifs_lane, str_line)) { + vector curr_lane; + stringstream ss; + ss << str_line; + double x, y; + while (ss >> x >> y) { + curr_lane.push_back(Point2f(x, y)); + } + lanes.push_back(curr_lane); + } + + ifs_lane.close(); +} + +void visualize(string &full_im_name, vector> &anno_lanes, + vector> &detect_lanes, vector anno_match, + int width_lane, string save_path) { + Mat img = imread(full_im_name, 1); + Mat img2 = imread(full_im_name, 1); + vector curr_lane; + vector p_interp; + Spline splineSolver; + Scalar color_B = Scalar(255, 0, 0); + Scalar color_G = Scalar(0, 255, 0); + Scalar color_R = Scalar(0, 0, 255); + Scalar color_P = Scalar(255, 0, 255); + Scalar color; + for (int i = 0; i < anno_lanes.size(); i++) { + curr_lane = anno_lanes[i]; + if (curr_lane.size() == 2) { + p_interp = curr_lane; + } else { + p_interp = splineSolver.splineInterpTimes(curr_lane, 50); + } + if (anno_match[i] >= 0) { + color = color_G; + } else { + color = color_G; + } + for (int n = 0; n < p_interp.size() - 1; n++) { + line(img, p_interp[n], p_interp[n + 1], color, width_lane); + line(img2, p_interp[n], p_interp[n + 1], color, 2); + } + } + bool detected; + for (int i = 0; i < detect_lanes.size(); i++) { + detected = false; + curr_lane = detect_lanes[i]; + if (curr_lane.size() == 2) { + p_interp = curr_lane; + } else { + p_interp = splineSolver.splineInterpTimes(curr_lane, 50); + } + for (int n = 0; n < anno_lanes.size(); n++) { + if (anno_match[n] == i) { + detected = true; + break; + } + } + if (detected == true) { + color = color_B; + } else { + color = color_R; + } + for (int n = 0; n < p_interp.size() - 1; n++) { + line(img, p_interp[n], p_interp[n + 1], color, width_lane); + line(img2, p_interp[n], p_interp[n + 1], color, 2); + } + } + if (save_path != "") { + size_t pos = 0; + string s = full_im_name; + std::string token; + std::string delimiter = "/"; + vector names; + while ((pos = s.find(delimiter)) != std::string::npos) { + token = s.substr(0, pos); + names.emplace_back(token); + s.erase(0, pos + delimiter.length()); + } + names.emplace_back(s); + string file_name = names[3] + '_' + names[4] + '_' + names[5]; + // cout << file_name << endl; + imwrite(save_path + '/' + file_name, img); + } else { + namedWindow("visualize", 1); + imshow("visualize", img); + namedWindow("visualize2", 1); + imshow("visualize2", img2); + } +} diff --git a/runner/evaluator/culane/lane_evaluation/src/lane_compare.cpp b/runner/evaluator/culane/lane_evaluation/src/lane_compare.cpp new file mode 100644 index 0000000..83d08b9 --- /dev/null +++ b/runner/evaluator/culane/lane_evaluation/src/lane_compare.cpp @@ -0,0 +1,73 @@ +/************************************************************************* + > File Name: lane_compare.cpp + > Author: Xingang Pan, Jun Li + > Mail: px117@ie.cuhk.edu.hk + > Created Time: Fri Jul 15 10:26:32 2016 + ************************************************************************/ + +#include "lane_compare.hpp" + +double LaneCompare::get_lane_similarity(const vector &lane1, const vector &lane2) +{ + if(lane1.size()<2 || lane2.size()<2) + { + cerr<<"lane size must be greater or equal to 2"< p_interp1; + vector p_interp2; + if(lane1.size() == 2) + { + p_interp1 = lane1; + } + else + { + p_interp1 = splineSolver.splineInterpTimes(lane1, 50); + } + + if(lane2.size() == 2) + { + p_interp2 = lane2; + } + else + { + p_interp2 = splineSolver.splineInterpTimes(lane2, 50); + } + + Scalar color_white = Scalar(1); + for(int n=0; n &curr_lane, int curr_width, int curr_height) +{ + if(curr_width == im_width && curr_height == im_height) + { + return; + } + double x_scale = im_width/(double)curr_width; + double y_scale = im_height/(double)curr_height; + for(int n=0; n +#include +#include "spline.hpp" +using namespace std; +using namespace cv; + +vector Spline::splineInterpTimes(const vector& tmp_line, int times) { + vector res; + + if(tmp_line.size() == 2) { + double x1 = tmp_line[0].x; + double y1 = tmp_line[0].y; + double x2 = tmp_line[1].x; + double y2 = tmp_line[1].y; + + for (int k = 0; k <= times; k++) { + double xi = x1 + double((x2 - x1) * k) / times; + double yi = y1 + double((y2 - y1) * k) / times; + res.push_back(Point2f(xi, yi)); + } + } + + else if(tmp_line.size() > 2) + { + vector tmp_func; + tmp_func = this->cal_fun(tmp_line); + if (tmp_func.empty()) { + cout << "in splineInterpTimes: cal_fun failed" << endl; + return res; + } + for(int j = 0; j < tmp_func.size(); j++) + { + double delta = tmp_func[j].h / times; + for(int k = 0; k < times; k++) + { + double t1 = delta*k; + double x1 = tmp_func[j].a_x + tmp_func[j].b_x*t1 + tmp_func[j].c_x*pow(t1,2) + tmp_func[j].d_x*pow(t1,3); + double y1 = tmp_func[j].a_y + tmp_func[j].b_y*t1 + tmp_func[j].c_y*pow(t1,2) + tmp_func[j].d_y*pow(t1,3); + res.push_back(Point2f(x1, y1)); + } + } + res.push_back(tmp_line[tmp_line.size() - 1]); + } + else { + cerr << "in splineInterpTimes: not enough points" << endl; + } + return res; +} +vector Spline::splineInterpStep(vector tmp_line, double step) { + vector res; + /* + if (tmp_line.size() == 2) { + double x1 = tmp_line[0].x; + double y1 = tmp_line[0].y; + double x2 = tmp_line[1].x; + double y2 = tmp_line[1].y; + + for (double yi = std::min(y1, y2); yi < std::max(y1, y2); yi += step) { + double xi; + if (yi == y1) xi = x1; + else xi = (x2 - x1) / (y2 - y1) * (yi - y1) + x1; + res.push_back(Point2f(xi, yi)); + } + }*/ + if (tmp_line.size() == 2) { + double x1 = tmp_line[0].x; + double y1 = tmp_line[0].y; + double x2 = tmp_line[1].x; + double y2 = tmp_line[1].y; + tmp_line[1].x = (x1 + x2) / 2; + tmp_line[1].y = (y1 + y2) / 2; + tmp_line.push_back(Point2f(x2, y2)); + } + if (tmp_line.size() > 2) { + vector tmp_func; + tmp_func = this->cal_fun(tmp_line); + double ystart = tmp_line[0].y; + double yend = tmp_line[tmp_line.size() - 1].y; + bool down; + if (ystart < yend) down = 1; + else down = 0; + if (tmp_func.empty()) { + cerr << "in splineInterpStep: cal_fun failed" << endl; + } + + for(int j = 0; j < tmp_func.size(); j++) + { + for(double t1 = 0; t1 < tmp_func[j].h; t1 += step) + { + double x1 = tmp_func[j].a_x + tmp_func[j].b_x*t1 + tmp_func[j].c_x*pow(t1,2) + tmp_func[j].d_x*pow(t1,3); + double y1 = tmp_func[j].a_y + tmp_func[j].b_y*t1 + tmp_func[j].c_y*pow(t1,2) + tmp_func[j].d_y*pow(t1,3); + res.push_back(Point2f(x1, y1)); + } + } + res.push_back(tmp_line[tmp_line.size() - 1]); + } + else { + cerr << "in splineInterpStep: not enough points" << endl; + } + return res; +} + +vector Spline::cal_fun(const vector &point_v) +{ + vector func_v; + int n = point_v.size(); + if(n<=2) { + cout << "in cal_fun: point number less than 3" << endl; + return func_v; + } + + func_v.resize(point_v.size()-1); + + vector Mx(n); + vector My(n); + vector A(n-2); + vector B(n-2); + vector C(n-2); + vector Dx(n-2); + vector Dy(n-2); + vector h(n-1); + //vector func_v(n-1); + + for(int i = 0; i < n-1; i++) + { + h[i] = sqrt(pow(point_v[i+1].x - point_v[i].x, 2) + pow(point_v[i+1].y - point_v[i].y, 2)); + } + + for(int i = 0; i < n-2; i++) + { + A[i] = h[i]; + B[i] = 2*(h[i]+h[i+1]); + C[i] = h[i+1]; + + Dx[i] = 6*( (point_v[i+2].x - point_v[i+1].x)/h[i+1] - (point_v[i+1].x - point_v[i].x)/h[i] ); + Dy[i] = 6*( (point_v[i+2].y - point_v[i+1].y)/h[i+1] - (point_v[i+1].y - point_v[i].y)/h[i] ); + } + + //TDMA + C[0] = C[0] / B[0]; + Dx[0] = Dx[0] / B[0]; + Dy[0] = Dy[0] / B[0]; + for(int i = 1; i < n-2; i++) + { + double tmp = B[i] - A[i]*C[i-1]; + C[i] = C[i] / tmp; + Dx[i] = (Dx[i] - A[i]*Dx[i-1]) / tmp; + Dy[i] = (Dy[i] - A[i]*Dy[i-1]) / tmp; + } + Mx[n-2] = Dx[n-3]; + My[n-2] = Dy[n-3]; + for(int i = n-4; i >= 0; i--) + { + Mx[i+1] = Dx[i] - C[i]*Mx[i+2]; + My[i+1] = Dy[i] - C[i]*My[i+2]; + } + + Mx[0] = 0; + Mx[n-1] = 0; + My[0] = 0; + My[n-1] = 0; + + for(int i = 0; i < n-1; i++) + { + func_v[i].a_x = point_v[i].x; + func_v[i].b_x = (point_v[i+1].x - point_v[i].x)/h[i] - (2*h[i]*Mx[i] + h[i]*Mx[i+1]) / 6; + func_v[i].c_x = Mx[i]/2; + func_v[i].d_x = (Mx[i+1] - Mx[i]) / (6*h[i]); + + func_v[i].a_y = point_v[i].y; + func_v[i].b_y = (point_v[i+1].y - point_v[i].y)/h[i] - (2*h[i]*My[i] + h[i]*My[i+1]) / 6; + func_v[i].c_y = My[i]/2; + func_v[i].d_y = (My[i+1] - My[i]) / (6*h[i]); + + func_v[i].h = h[i]; + } + return func_v; +} diff --git a/runner/evaluator/culane/prob2lines.py b/runner/evaluator/culane/prob2lines.py new file mode 100644 index 0000000..f23caed --- /dev/null +++ b/runner/evaluator/culane/prob2lines.py @@ -0,0 +1,51 @@ +import os +import argparse +import numpy as np +import pandas as pd +from PIL import Image +import tqdm + + +def getLane(probmap, pts, cfg = None): + thr = 0.3 + coordinate = np.zeros(pts) + cut_height = 0 + if cfg.cut_height: + cut_height = cfg.cut_height + for i in range(pts): + line = probmap[round(cfg.img_height-i*20/(590-cut_height)*cfg.img_height)-1] + if np.max(line)/255 > thr: + coordinate[i] = np.argmax(line)+1 + if np.sum(coordinate > 0) < 2: + coordinate = np.zeros(pts) + return coordinate + + +def prob2lines(prob_dir, out_dir, list_file, cfg = None): + lists = pd.read_csv(list_file, sep=' ', header=None, + names=('img', 'probmap', 'label1', 'label2', 'label3', 'label4')) + pts = 18 + + for k, im in enumerate(lists['img'], 1): + existPath = prob_dir + im[:-4] + '.exist.txt' + outname = out_dir + im[:-4] + '.lines.txt' + prefix = '/'.join(outname.split('/')[:-1]) + if not os.path.exists(prefix): + os.makedirs(prefix) + f = open(outname, 'w') + + labels = list(pd.read_csv(existPath, sep=' ', header=None).iloc[0]) + coordinates = np.zeros((4, pts)) + for i in range(4): + if labels[i] == 1: + probfile = prob_dir + im[:-4] + '_{0}_avg.png'.format(i+1) + probmap = np.array(Image.open(probfile)) + coordinates[i] = getLane(probmap, pts, cfg) + + if np.sum(coordinates[i] > 0) > 1: + for idx, value in enumerate(coordinates[i]): + if value > 0: + f.write('%d %d ' % ( + round(value*1640/cfg.img_width)-1, round(590-idx*20)-1)) + f.write('\n') + f.close() diff --git a/runner/evaluator/tusimple/getLane.py b/runner/evaluator/tusimple/getLane.py new file mode 100644 index 0000000..8026127 --- /dev/null +++ b/runner/evaluator/tusimple/getLane.py @@ -0,0 +1,115 @@ +import cv2 +import numpy as np + +def isShort(lane): + start = [i for i, x in enumerate(lane) if x > 0] + if not start: + return 1 + else: + return 0 + +def fixGap(coordinate): + if any(x > 0 for x in coordinate): + start = [i for i, x in enumerate(coordinate) if x > 0][0] + end = [i for i, x in reversed(list(enumerate(coordinate))) if x > 0][0] + lane = coordinate[start:end+1] + if any(x < 0 for x in lane): + gap_start = [i for i, x in enumerate( + lane[:-1]) if x > 0 and lane[i+1] < 0] + gap_end = [i+1 for i, + x in enumerate(lane[:-1]) if x < 0 and lane[i+1] > 0] + gap_id = [i for i, x in enumerate(lane) if x < 0] + if len(gap_start) == 0 or len(gap_end) == 0: + return coordinate + for id in gap_id: + for i in range(len(gap_start)): + if i >= len(gap_end): + return coordinate + if id > gap_start[i] and id < gap_end[i]: + gap_width = float(gap_end[i] - gap_start[i]) + lane[id] = int((id - gap_start[i]) / gap_width * lane[gap_end[i]] + ( + gap_end[i] - id) / gap_width * lane[gap_start[i]]) + if not all(x > 0 for x in lane): + print("Gaps still exist!") + coordinate[start:end+1] = lane + return coordinate + +def getLane_tusimple(prob_map, y_px_gap, pts, thresh, resize_shape=None, cfg=None): + """ + Arguments: + ---------- + prob_map: prob map for single lane, np array size (h, w) + resize_shape: reshape size target, (H, W) + + Return: + ---------- + coords: x coords bottom up every y_px_gap px, 0 for non-exist, in resized shape + """ + if resize_shape is None: + resize_shape = prob_map.shape + h, w = prob_map.shape + H, W = resize_shape + H -= cfg.cut_height + + coords = np.zeros(pts) + coords[:] = -1.0 + for i in range(pts): + y = int((H - 10 - i * y_px_gap) * h / H) + if y < 0: + break + line = prob_map[y, :] + id = np.argmax(line) + if line[id] > thresh: + coords[i] = int(id / w * W) + if (coords > 0).sum() < 2: + coords = np.zeros(pts) + fixGap(coords) + return coords + + +def prob2lines_tusimple(seg_pred, exist, resize_shape=None, smooth=True, y_px_gap=10, pts=None, thresh=0.3, cfg=None): + """ + Arguments: + ---------- + seg_pred: np.array size (5, h, w) + resize_shape: reshape size target, (H, W) + exist: list of existence, e.g. [0, 1, 1, 0] + smooth: whether to smooth the probability or not + y_px_gap: y pixel gap for sampling + pts: how many points for one lane + thresh: probability threshold + + Return: + ---------- + coordinates: [x, y] list of lanes, e.g.: [ [[9, 569], [50, 549]] ,[[630, 569], [647, 549]] ] + """ + if resize_shape is None: + resize_shape = seg_pred.shape[1:] # seg_pred (5, h, w) + _, h, w = seg_pred.shape + H, W = resize_shape + coordinates = [] + + if pts is None: + pts = round(H / 2 / y_px_gap) + + seg_pred = np.ascontiguousarray(np.transpose(seg_pred, (1, 2, 0))) + for i in range(cfg.num_classes - 1): + prob_map = seg_pred[..., i + 1] + if smooth: + prob_map = cv2.blur(prob_map, (9, 9), borderType=cv2.BORDER_REPLICATE) + coords = getLane_tusimple(prob_map, y_px_gap, pts, thresh, resize_shape, cfg) + if isShort(coords): + continue + coordinates.append( + [[coords[j], H - 10 - j * y_px_gap] if coords[j] > 0 else [-1, H - 10 - j * y_px_gap] for j in + range(pts)]) + + + if len(coordinates) == 0: + coords = np.zeros(pts) + coordinates.append( + [[coords[j], H - 10 - j * y_px_gap] if coords[j] > 0 else [-1, H - 10 - j * y_px_gap] for j in + range(pts)]) + + + return coordinates diff --git a/runner/evaluator/tusimple/lane.py b/runner/evaluator/tusimple/lane.py new file mode 100644 index 0000000..44abf70 --- /dev/null +++ b/runner/evaluator/tusimple/lane.py @@ -0,0 +1,108 @@ +import numpy as np +from sklearn.linear_model import LinearRegression +import json as json + + +class LaneEval(object): + lr = LinearRegression() + pixel_thresh = 20 + pt_thresh = 0.85 + + @staticmethod + def get_angle(xs, y_samples): + xs, ys = xs[xs >= 0], y_samples[xs >= 0] + if len(xs) > 1: + LaneEval.lr.fit(ys[:, None], xs) + k = LaneEval.lr.coef_[0] + theta = np.arctan(k) + else: + theta = 0 + return theta + + @staticmethod + def line_accuracy(pred, gt, thresh): + pred = np.array([p if p >= 0 else -100 for p in pred]) + gt = np.array([g if g >= 0 else -100 for g in gt]) + return np.sum(np.where(np.abs(pred - gt) < thresh, 1., 0.)) / len(gt) + + @staticmethod + def bench(pred, gt, y_samples, running_time): + if any(len(p) != len(y_samples) for p in pred): + raise Exception('Format of lanes error.') + if running_time > 200 or len(gt) + 2 < len(pred): + return 0., 0., 1. + angles = [LaneEval.get_angle( + np.array(x_gts), np.array(y_samples)) for x_gts in gt] + threshs = [LaneEval.pixel_thresh / np.cos(angle) for angle in angles] + line_accs = [] + fp, fn = 0., 0. + matched = 0. + for x_gts, thresh in zip(gt, threshs): + accs = [LaneEval.line_accuracy( + np.array(x_preds), np.array(x_gts), thresh) for x_preds in pred] + max_acc = np.max(accs) if len(accs) > 0 else 0. + if max_acc < LaneEval.pt_thresh: + fn += 1 + else: + matched += 1 + line_accs.append(max_acc) + fp = len(pred) - matched + if len(gt) > 4 and fn > 0: + fn -= 1 + s = sum(line_accs) + if len(gt) > 4: + s -= min(line_accs) + return s / max(min(4.0, len(gt)), 1.), fp / len(pred) if len(pred) > 0 else 0., fn / max(min(len(gt), 4.), 1.) + + @staticmethod + def bench_one_submit(pred_file, gt_file): + try: + json_pred = [json.loads(line) + for line in open(pred_file).readlines()] + except BaseException as e: + raise Exception('Fail to load json file of the prediction.') + json_gt = [json.loads(line) for line in open(gt_file).readlines()] + if len(json_gt) != len(json_pred): + raise Exception( + 'We do not get the predictions of all the test tasks') + gts = {l['raw_file']: l for l in json_gt} + accuracy, fp, fn = 0., 0., 0. + for pred in json_pred: + if 'raw_file' not in pred or 'lanes' not in pred or 'run_time' not in pred: + raise Exception( + 'raw_file or lanes or run_time not in some predictions.') + raw_file = pred['raw_file'] + pred_lanes = pred['lanes'] + run_time = pred['run_time'] + if raw_file not in gts: + raise Exception( + 'Some raw_file from your predictions do not exist in the test tasks.') + gt = gts[raw_file] + gt_lanes = gt['lanes'] + y_samples = gt['h_samples'] + try: + a, p, n = LaneEval.bench( + pred_lanes, gt_lanes, y_samples, run_time) + except BaseException as e: + raise Exception('Format of lanes error.') + accuracy += a + fp += p + fn += n + num = len(gts) + # the first return parameter is the default ranking parameter + return json.dumps([ + {'name': 'Accuracy', 'value': accuracy / num, 'order': 'desc'}, + {'name': 'FP', 'value': fp / num, 'order': 'asc'}, + {'name': 'FN', 'value': fn / num, 'order': 'asc'} + ]), accuracy / num + + +if __name__ == '__main__': + import sys + try: + if len(sys.argv) != 3: + raise Exception('Invalid input arguments') + print(LaneEval.bench_one_submit(sys.argv[1], sys.argv[2])) + except Exception as e: + print(e.message) + sys.exit(e.message) diff --git a/runner/evaluator/tusimple/tusimple.py b/runner/evaluator/tusimple/tusimple.py new file mode 100644 index 0000000..12d8a99 --- /dev/null +++ b/runner/evaluator/tusimple/tusimple.py @@ -0,0 +1,111 @@ +import torch.nn as nn +import torch +import torch.nn.functional as F +from runner.logger import get_logger + +from runner.registry import EVALUATOR +import json +import os +import cv2 + +from .lane import LaneEval + +def split_path(path): + """split path tree into list""" + folders = [] + while True: + path, folder = os.path.split(path) + if folder != "": + folders.insert(0, folder) + else: + if path != "": + folders.insert(0, path) + break + return folders + + +@EVALUATOR.register_module +class Tusimple(nn.Module): + def __init__(self, cfg): + super(Tusimple, self).__init__() + self.cfg = cfg + exp_dir = os.path.join(self.cfg.work_dir, "output") + if not os.path.exists(exp_dir): + os.mkdir(exp_dir) + self.out_path = os.path.join(exp_dir, "coord_output") + if not os.path.exists(self.out_path): + os.mkdir(self.out_path) + self.dump_to_json = [] + self.thresh = cfg.evaluator.thresh + self.logger = get_logger('resa') + if cfg.view: + self.view_dir = os.path.join(self.cfg.work_dir, 'vis') + + def evaluate_pred(self, dataset, seg_pred, exist_pred, batch): + img_name = batch['meta']['img_name'] + img_path = batch['meta']['full_img_path'] + for b in range(len(seg_pred)): + seg = seg_pred[b] + exist = [1 if exist_pred[b, i] > + 0.5 else 0 for i in range(self.cfg.num_classes-1)] + lane_coords = dataset.probmap2lane(seg, exist, thresh = self.thresh) + for i in range(len(lane_coords)): + lane_coords[i] = sorted( + lane_coords[i], key=lambda pair: pair[1]) + + path_tree = split_path(img_name[b]) + save_dir, save_name = path_tree[-3:-1], path_tree[-1] + save_dir = os.path.join(self.out_path, *save_dir) + save_name = save_name[:-3] + "lines.txt" + save_name = os.path.join(save_dir, save_name) + if not os.path.exists(save_dir): + os.makedirs(save_dir, exist_ok=True) + + with open(save_name, "w") as f: + for l in lane_coords: + for (x, y) in l: + print("{} {}".format(x, y), end=" ", file=f) + print(file=f) + + json_dict = {} + json_dict['lanes'] = [] + json_dict['h_sample'] = [] + json_dict['raw_file'] = os.path.join(*path_tree[-4:]) + json_dict['run_time'] = 0 + for l in lane_coords: + if len(l) == 0: + continue + json_dict['lanes'].append([]) + for (x, y) in l: + json_dict['lanes'][-1].append(int(x)) + for (x, y) in lane_coords[0]: + json_dict['h_sample'].append(y) + self.dump_to_json.append(json.dumps(json_dict)) + if self.cfg.view: + img = cv2.imread(img_path[b]) + new_img_name = img_name[b].replace('/', '_') + save_dir = os.path.join(self.view_dir, new_img_name) + dataset.view(img, lane_coords, save_dir) + + + def evaluate(self, dataset, output, batch): + seg_pred, exist_pred = output['seg'], output['exist'] + seg_pred = F.softmax(seg_pred, dim=1) + seg_pred = seg_pred.detach().cpu().numpy() + exist_pred = exist_pred.detach().cpu().numpy() + self.evaluate_pred(dataset, seg_pred, exist_pred, batch) + + def summarize(self): + best_acc = 0 + output_file = os.path.join(self.out_path, 'predict_test.json') + with open(output_file, "w+") as f: + for line in self.dump_to_json: + print(line, end="\n", file=f) + + eval_result, acc = LaneEval.bench_one_submit(output_file, + self.cfg.test_json_file) + + self.logger.info(eval_result) + self.dump_to_json = [] + best_acc = max(acc, best_acc) + return best_acc diff --git a/runner/logger.py b/runner/logger.py new file mode 100644 index 0000000..189d353 --- /dev/null +++ b/runner/logger.py @@ -0,0 +1,50 @@ +import logging + +logger_initialized = {} + +def get_logger(name, log_file=None, log_level=logging.INFO): + """Initialize and get a logger by name. + If the logger has not been initialized, this method will initialize the + logger by adding one or two handlers, otherwise the initialized logger will + be directly returned. During initialization, a StreamHandler will always be + added. If `log_file` is specified and the process rank is 0, a FileHandler + will also be added. + Args: + name (str): Logger name. + log_file (str | None): The log filename. If specified, a FileHandler + will be added to the logger. + log_level (int): The logger level. Note that only the process of + rank 0 is affected, and other processes will set the level to + "Error" thus be silent most of the time. + Returns: + logging.Logger: The expected logger. + """ + logger = logging.getLogger(name) + if name in logger_initialized: + return logger + # handle hierarchical names + # e.g., logger "a" is initialized, then logger "a.b" will skip the + # initialization since it is a child of "a". + for logger_name in logger_initialized: + if name.startswith(logger_name): + return logger + + stream_handler = logging.StreamHandler() + handlers = [stream_handler] + + if log_file is not None: + file_handler = logging.FileHandler(log_file, 'w') + handlers.append(file_handler) + + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + for handler in handlers: + handler.setFormatter(formatter) + handler.setLevel(log_level) + logger.addHandler(handler) + + logger.setLevel(log_level) + + logger_initialized[name] = True + + return logger diff --git a/runner/net_utils.py b/runner/net_utils.py new file mode 100644 index 0000000..0038fe6 --- /dev/null +++ b/runner/net_utils.py @@ -0,0 +1,43 @@ +import torch +import os +from torch import nn +import numpy as np +import torch.nn.functional +from termcolor import colored +from .logger import get_logger + +def save_model(net, optim, scheduler, recorder, is_best=False): + model_dir = os.path.join(recorder.work_dir, 'ckpt') + os.system('mkdir -p {}'.format(model_dir)) + epoch = recorder.epoch + ckpt_name = 'best' if is_best else epoch + torch.save({ + 'net': net.state_dict(), + 'optim': optim.state_dict(), + 'scheduler': scheduler.state_dict(), + 'recorder': recorder.state_dict(), + 'epoch': epoch + }, os.path.join(model_dir, '{}.pth'.format(ckpt_name))) + + +def load_network_specified(net, model_dir, logger=None): + pretrained_net = torch.load(model_dir)['net'] + net_state = net.state_dict() + state = {} + for k, v in pretrained_net.items(): + if k not in net_state.keys() or v.size() != net_state[k].size(): + if logger: + logger.info('skip weights: ' + k) + continue + state[k] = v + net.load_state_dict(state, strict=False) + + +def load_network(net, model_dir, finetune_from=None, logger=None): + if finetune_from: + if logger: + logger.info('Finetune model from: ' + finetune_from) + load_network_specified(net, finetune_from, logger) + return + pretrained_model = torch.load(model_dir) + net.load_state_dict(pretrained_model['net'], strict=True) diff --git a/runner/optimizer.py b/runner/optimizer.py new file mode 100644 index 0000000..9f2f836 --- /dev/null +++ b/runner/optimizer.py @@ -0,0 +1,26 @@ +import torch + + +_optimizer_factory = { + 'adam': torch.optim.Adam, + 'sgd': torch.optim.SGD +} + + +def build_optimizer(cfg, net): + params = [] + lr = cfg.optimizer.lr + weight_decay = cfg.optimizer.weight_decay + + for key, value in net.named_parameters(): + if not value.requires_grad: + continue + params += [{"params": [value], "lr": lr, "weight_decay": weight_decay}] + + if 'adam' in cfg.optimizer.type: + optimizer = _optimizer_factory[cfg.optimizer.type](params, lr, weight_decay=weight_decay) + else: + optimizer = _optimizer_factory[cfg.optimizer.type]( + params, lr, weight_decay=weight_decay, momentum=cfg.optimizer.momentum) + + return optimizer diff --git a/runner/recorder.py b/runner/recorder.py new file mode 100644 index 0000000..2ae345b --- /dev/null +++ b/runner/recorder.py @@ -0,0 +1,100 @@ +from collections import deque, defaultdict +import torch +import os +import datetime +from .logger import get_logger + + +class SmoothedValue(object): + """Track a series of values and provide access to smoothed values over a + window or the global series average. + """ + + def __init__(self, window_size=20): + self.deque = deque(maxlen=window_size) + self.total = 0.0 + self.count = 0 + + def update(self, value): + self.deque.append(value) + self.count += 1 + self.total += value + + @property + def median(self): + d = torch.tensor(list(self.deque)) + return d.median().item() + + @property + def avg(self): + d = torch.tensor(list(self.deque)) + return d.mean().item() + + @property + def global_avg(self): + return self.total / self.count + + +class Recorder(object): + def __init__(self, cfg): + self.cfg = cfg + self.work_dir = self.get_work_dir() + cfg.work_dir = self.work_dir + self.log_path = os.path.join(self.work_dir, 'log.txt') + + self.logger = get_logger('resa', self.log_path) + self.logger.info('Config: \n' + cfg.text) + + # scalars + self.epoch = 0 + self.step = 0 + self.loss_stats = defaultdict(SmoothedValue) + self.batch_time = SmoothedValue() + self.data_time = SmoothedValue() + self.max_iter = self.cfg.total_iter + self.lr = 0. + + def get_work_dir(self): + now = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') + hyper_param_str = '_lr_%1.0e_b_%d' % (self.cfg.optimizer.lr, self.cfg.batch_size) + work_dir = os.path.join(self.cfg.work_dirs, now + hyper_param_str) + if not os.path.exists(work_dir): + os.makedirs(work_dir) + return work_dir + + def update_loss_stats(self, loss_dict): + for k, v in loss_dict.items(): + self.loss_stats[k].update(v.detach().cpu()) + + def record(self, prefix, step=-1, loss_stats=None, image_stats=None): + self.logger.info(self) + # self.write(str(self)) + + def write(self, content): + with open(self.log_path, 'a+') as f: + f.write(content) + f.write('\n') + + def state_dict(self): + scalar_dict = {} + scalar_dict['step'] = self.step + return scalar_dict + + def load_state_dict(self, scalar_dict): + self.step = scalar_dict['step'] + + def __str__(self): + loss_state = [] + for k, v in self.loss_stats.items(): + loss_state.append('{}: {:.4f}'.format(k, v.avg)) + loss_state = ' '.join(loss_state) + + recording_state = ' '.join(['epoch: {}', 'step: {}', 'lr: {:.4f}', '{}', 'data: {:.4f}', 'batch: {:.4f}', 'eta: {}']) + eta_seconds = self.batch_time.global_avg * (self.max_iter - self.step) + eta_string = str(datetime.timedelta(seconds=int(eta_seconds))) + return recording_state.format(self.epoch, self.step, self.lr, loss_state, self.data_time.avg, self.batch_time.avg, eta_string) + + +def build_recorder(cfg): + return Recorder(cfg) + diff --git a/runner/registry.py b/runner/registry.py new file mode 100644 index 0000000..c1c119b --- /dev/null +++ b/runner/registry.py @@ -0,0 +1,19 @@ +from utils import Registry, build_from_cfg + +TRAINER = Registry('trainer') +EVALUATOR = Registry('evaluator') + +def build(cfg, registry, default_args=None): + if isinstance(cfg, list): + modules = [ + build_from_cfg(cfg_, registry, default_args) for cfg_ in cfg + ] + return nn.Sequential(*modules) + else: + return build_from_cfg(cfg, registry, default_args) + +def build_trainer(cfg): + return build(cfg.trainer, TRAINER, default_args=dict(cfg=cfg)) + +def build_evaluator(cfg): + return build(cfg.evaluator, EVALUATOR, default_args=dict(cfg=cfg)) diff --git a/runner/resa_trainer.py b/runner/resa_trainer.py new file mode 100644 index 0000000..7cdad78 --- /dev/null +++ b/runner/resa_trainer.py @@ -0,0 +1,58 @@ +import torch.nn as nn +import torch +import torch.nn.functional as F + +from runner.registry import TRAINER + +def dice_loss(input, target): + input = input.contiguous().view(input.size()[0], -1) + target = target.contiguous().view(target.size()[0], -1).float() + + a = torch.sum(input * target, 1) + b = torch.sum(input * input, 1) + 0.001 + c = torch.sum(target * target, 1) + 0.001 + d = (2 * a) / (b + c) + return (1-d).mean() + +@TRAINER.register_module +class RESA(nn.Module): + def __init__(self, cfg): + super(RESA, self).__init__() + self.cfg = cfg + self.loss_type = cfg.loss_type + if self.loss_type == 'cross_entropy': + weights = torch.ones(cfg.num_classes) + weights[0] = cfg.bg_weight + weights = weights.cuda() + self.criterion = torch.nn.NLLLoss(ignore_index=self.cfg.ignore_label, + weight=weights).cuda() + + self.criterion_exist = torch.nn.BCEWithLogitsLoss().cuda() + + def forward(self, net, batch): + output = net(batch['img']) + + loss_stats = {} + loss = 0. + + if self.loss_type == 'dice_loss': + target = F.one_hot(batch['label'], num_classes=self.cfg.num_classes).permute(0, 3, 1, 2) + seg_loss = dice_loss(F.softmax( + output['seg'], dim=1)[:, 1:], target[:, 1:]) + else: + seg_loss = self.criterion(F.log_softmax( + output['seg'], dim=1), batch['label'].long()) + + loss += seg_loss * self.cfg.seg_loss_weight + + loss_stats.update({'seg_loss': seg_loss}) + + if 'exist' in output: + exist_loss = 0.1 * \ + self.criterion_exist(output['exist'], batch['exist'].float()) + loss += exist_loss + loss_stats.update({'exist_loss': exist_loss}) + + ret = {'loss': loss, 'loss_stats': loss_stats} + + return ret diff --git a/runner/runner.py b/runner/runner.py new file mode 100644 index 0000000..3e8f9c4 --- /dev/null +++ b/runner/runner.py @@ -0,0 +1,112 @@ +import time +import torch +import numpy as np +from tqdm import tqdm +import pytorch_warmup as warmup + +from models.registry import build_net +from .registry import build_trainer, build_evaluator +from .optimizer import build_optimizer +from .scheduler import build_scheduler +from datasets import build_dataloader +from .recorder import build_recorder +from .net_utils import save_model, load_network + + +class Runner(object): + def __init__(self, cfg): + self.cfg = cfg + self.recorder = build_recorder(self.cfg) + self.net = build_net(self.cfg) + self.net = torch.nn.parallel.DataParallel( + self.net, device_ids = range(self.cfg.gpus)).cuda() + self.recorder.logger.info('Network: \n' + str(self.net)) + self.resume() + self.optimizer = build_optimizer(self.cfg, self.net) + self.scheduler = build_scheduler(self.cfg, self.optimizer) + self.evaluator = build_evaluator(self.cfg) + self.warmup_scheduler = warmup.LinearWarmup( + self.optimizer, warmup_period=5000) + self.metric = 0. + + def resume(self): + if not self.cfg.load_from and not self.cfg.finetune_from: + return + load_network(self.net, self.cfg.load_from, + finetune_from=self.cfg.finetune_from, logger=self.recorder.logger) + + def to_cuda(self, batch): + for k in batch: + if k == 'meta': + continue + batch[k] = batch[k].cuda() + return batch + + def train_epoch(self, epoch, train_loader): + self.net.train() + end = time.time() + max_iter = len(train_loader) + for i, data in enumerate(train_loader): + if self.recorder.step >= self.cfg.total_iter: + break + date_time = time.time() - end + self.recorder.step += 1 + data = self.to_cuda(data) + output = self.trainer.forward(self.net, data) + self.optimizer.zero_grad() + loss = output['loss'] + loss.backward() + self.optimizer.step() + self.scheduler.step() + self.warmup_scheduler.dampen() + batch_time = time.time() - end + end = time.time() + self.recorder.update_loss_stats(output['loss_stats']) + self.recorder.batch_time.update(batch_time) + self.recorder.data_time.update(date_time) + + if i % self.cfg.log_interval == 0 or i == max_iter - 1: + lr = self.optimizer.param_groups[0]['lr'] + self.recorder.lr = lr + self.recorder.record('train') + + def train(self): + self.recorder.logger.info('start training...') + self.trainer = build_trainer(self.cfg) + train_loader = build_dataloader(self.cfg.dataset.train, self.cfg, is_train=True) + val_loader = build_dataloader(self.cfg.dataset.val, self.cfg, is_train=False) + + for epoch in range(self.cfg.epochs): + print('Epoch: [{}/{}]'.format(self.recorder.step, self.cfg.total_iter)) + print('Epoch: [{}/{}]'.format(epoch, self.cfg.epochs)) + self.recorder.epoch = epoch + self.train_epoch(epoch, train_loader) + if (epoch + 1) % self.cfg.save_ep == 0 or epoch == self.cfg.epochs - 1: + self.save_ckpt() + if (epoch + 1) % self.cfg.eval_ep == 0 or epoch == self.cfg.epochs - 1: + self.validate(val_loader) + if self.recorder.step >= self.cfg.total_iter: + break + + def validate(self, val_loader): + self.net.eval() + count = 10 + for i, data in enumerate(tqdm(val_loader, desc=f'Validate')): + start_time = time.time() + data = self.to_cuda(data) + with torch.no_grad(): + output = self.net(data['img']) + self.evaluator.evaluate(val_loader.dataset, output, data) + # print("第{}张图片检测花了{}秒".format(i,time.time()-start_time)) + + metric = self.evaluator.summarize() + if not metric: + return + if metric > self.metric: + self.metric = metric + self.save_ckpt(is_best=True) + self.recorder.logger.info('Best metric: ' + str(self.metric)) + + def save_ckpt(self, is_best=False): + save_model(self.net, self.optimizer, self.scheduler, + self.recorder, is_best) diff --git a/runner/scheduler.py b/runner/scheduler.py new file mode 100644 index 0000000..6843dc2 --- /dev/null +++ b/runner/scheduler.py @@ -0,0 +1,20 @@ +import torch +import math + + +_scheduler_factory = { + 'LambdaLR': torch.optim.lr_scheduler.LambdaLR, +} + + +def build_scheduler(cfg, optimizer): + + assert cfg.scheduler.type in _scheduler_factory + + cfg_cp = cfg.scheduler.copy() + cfg_cp.pop('type') + + scheduler = _scheduler_factory[cfg.scheduler.type](optimizer, **cfg_cp) + + + return scheduler diff --git a/tools/generate_seg_tusimple.py b/tools/generate_seg_tusimple.py new file mode 100644 index 0000000..cf8273d --- /dev/null +++ b/tools/generate_seg_tusimple.py @@ -0,0 +1,105 @@ +import json +import numpy as np +import cv2 +import os +import argparse + +TRAIN_SET = ['label_data_0313.json', 'label_data_0601.json'] +VAL_SET = ['label_data_0531.json'] +TRAIN_VAL_SET = TRAIN_SET + VAL_SET +TEST_SET = ['test_label.json'] + +def gen_label_for_json(args, image_set): + H, W = 720, 1280 + SEG_WIDTH = 30 + save_dir = args.savedir + + os.makedirs(os.path.join(args.root, args.savedir, "list"), exist_ok=True) + list_f = open(os.path.join(args.root, args.savedir, "list", "{}_gt.txt".format(image_set)), "w") + + json_path = os.path.join(args.root, args.savedir, "{}.json".format(image_set)) + with open(json_path) as f: + for line in f: + label = json.loads(line) + # ---------- clean and sort lanes ------------- + lanes = [] + _lanes = [] + slope = [] # identify 0th, 1st, 2nd, 3rd, 4th, 5th lane through slope + for i in range(len(label['lanes'])): + l = [(x, y) for x, y in zip(label['lanes'][i], label['h_samples']) if x >= 0] + if (len(l)>1): + _lanes.append(l) + slope.append(np.arctan2(l[-1][1]-l[0][1], l[0][0]-l[-1][0]) / np.pi * 180) + _lanes = [_lanes[i] for i in np.argsort(slope)] + slope = [slope[i] for i in np.argsort(slope)] + + idx = [None for i in range(6)] + for i in range(len(slope)): + if slope[i] <= 90: + idx[2] = i + idx[1] = i-1 if i > 0 else None + idx[0] = i-2 if i > 1 else None + else: + idx[3] = i + idx[4] = i+1 if i+1 < len(slope) else None + idx[5] = i+2 if i+2 < len(slope) else None + break + for i in range(6): + lanes.append([] if idx[i] is None else _lanes[idx[i]]) + + # --------------------------------------------- + + img_path = label['raw_file'] + seg_img = np.zeros((H, W, 3)) + list_str = [] # str to be written to list.txt + for i in range(len(lanes)): + coords = lanes[i] + if len(coords) < 4: + list_str.append('0') + continue + for j in range(len(coords)-1): + cv2.line(seg_img, coords[j], coords[j+1], (i+1, i+1, i+1), SEG_WIDTH//2) + list_str.append('1') + + seg_path = img_path.split("/") + seg_path, img_name = os.path.join(args.root, args.savedir, seg_path[1], seg_path[2]), seg_path[3] + os.makedirs(seg_path, exist_ok=True) + seg_path = os.path.join(seg_path, img_name[:-3]+"png") + cv2.imwrite(seg_path, seg_img) + + seg_path = "/".join([args.savedir, *img_path.split("/")[1:3], img_name[:-3]+"png"]) + if seg_path[0] != '/': + seg_path = '/' + seg_path + if img_path[0] != '/': + img_path = '/' + img_path + list_str.insert(0, seg_path) + list_str.insert(0, img_path) + list_str = " ".join(list_str) + "\n" + list_f.write(list_str) + + +def generate_json_file(save_dir, json_file, image_set): + with open(os.path.join(save_dir, json_file), "w") as outfile: + for json_name in (image_set): + with open(os.path.join(args.root, json_name)) as infile: + for line in infile: + outfile.write(line) + +def generate_label(args): + save_dir = os.path.join(args.root, args.savedir) + os.makedirs(save_dir, exist_ok=True) + generate_json_file(save_dir, "train_val.json", TRAIN_VAL_SET) + generate_json_file(save_dir, "test.json", TEST_SET) + + print("generating train_val set...") + gen_label_for_json(args, 'train_val') + print("generating test set...") + gen_label_for_json(args, 'test') + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--root', required=True, help='The root of the Tusimple dataset') + parser.add_argument('--savedir', type=str, default='seg_label', help='The root of the Tusimple dataset') + args = parser.parse_args() + + generate_label(args) diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..eb99ab0 --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1,2 @@ +from .config import Config +from .registry import Registry, build_from_cfg diff --git a/utils/config.py b/utils/config.py new file mode 100644 index 0000000..42a0ff2 --- /dev/null +++ b/utils/config.py @@ -0,0 +1,417 @@ +# Copyright (c) Open-MMLab. All rights reserved. +import ast +import os.path as osp +import shutil +import sys +import tempfile +from argparse import Action, ArgumentParser +from collections import abc +from importlib import import_module + +from addict import Dict +from yapf.yapflib.yapf_api import FormatCode + + +BASE_KEY = '_base_' +DELETE_KEY = '_delete_' +RESERVED_KEYS = ['filename', 'text', 'pretty_text'] + +def check_file_exist(filename, msg_tmpl='file "{}" does not exist'): + if not osp.isfile(filename): + raise FileNotFoundError(msg_tmpl.format(filename)) + + + +class ConfigDict(Dict): + + def __missing__(self, name): + raise KeyError(name) + + def __getattr__(self, name): + try: + value = super(ConfigDict, self).__getattr__(name) + except KeyError: + ex = AttributeError(f"'{self.__class__.__name__}' object has no " + f"attribute '{name}'") + except Exception as e: + ex = e + else: + return value + raise ex + + +def add_args(parser, cfg, prefix=''): + for k, v in cfg.items(): + if isinstance(v, str): + parser.add_argument('--' + prefix + k) + elif isinstance(v, int): + parser.add_argument('--' + prefix + k, type=int) + elif isinstance(v, float): + parser.add_argument('--' + prefix + k, type=float) + elif isinstance(v, bool): + parser.add_argument('--' + prefix + k, action='store_true') + elif isinstance(v, dict): + add_args(parser, v, prefix + k + '.') + elif isinstance(v, abc.Iterable): + parser.add_argument('--' + prefix + k, type=type(v[0]), nargs='+') + else: + print(f'cannot parse key {prefix + k} of type {type(v)}') + return parser + + +class Config: + """A facility for config and config files. + It supports common file formats as configs: python/json/yaml. The interface + is the same as a dict object and also allows access config values as + attributes. + Example: + >>> cfg = Config(dict(a=1, b=dict(b1=[0, 1]))) + >>> cfg.a + 1 + >>> cfg.b + {'b1': [0, 1]} + >>> cfg.b.b1 + [0, 1] + >>> cfg = Config.fromfile('tests/data/config/a.py') + >>> cfg.filename + "/home/kchen/projects/mmcv/tests/data/config/a.py" + >>> cfg.item4 + 'test' + >>> cfg + "Config [path: /home/kchen/projects/mmcv/tests/data/config/a.py]: " + "{'item1': [1, 2], 'item2': {'a': 0}, 'item3': True, 'item4': 'test'}" + """ + + @staticmethod + def _validate_py_syntax(filename): + with open(filename) as f: + content = f.read() + try: + ast.parse(content) + except SyntaxError: + raise SyntaxError('There are syntax errors in config ' + f'file {filename}') + + @staticmethod + def _file2dict(filename): + filename = osp.abspath(osp.expanduser(filename)) + check_file_exist(filename) + if filename.endswith('.py'): + with tempfile.TemporaryDirectory() as temp_config_dir: + temp_config_file = tempfile.NamedTemporaryFile( + dir=temp_config_dir, suffix='.py') + temp_config_name = osp.basename(temp_config_file.name) + shutil.copyfile(filename, + osp.join(temp_config_dir, temp_config_name)) + temp_module_name = osp.splitext(temp_config_name)[0] + sys.path.insert(0, temp_config_dir) + Config._validate_py_syntax(filename) + mod = import_module(temp_module_name) + sys.path.pop(0) + cfg_dict = { + name: value + for name, value in mod.__dict__.items() + if not name.startswith('__') + } + # delete imported module + del sys.modules[temp_module_name] + # close temp file + temp_config_file.close() + elif filename.endswith(('.yml', '.yaml', '.json')): + import mmcv + cfg_dict = mmcv.load(filename) + else: + raise IOError('Only py/yml/yaml/json type are supported now!') + + cfg_text = filename + '\n' + with open(filename, 'r') as f: + cfg_text += f.read() + + if BASE_KEY in cfg_dict: + cfg_dir = osp.dirname(filename) + base_filename = cfg_dict.pop(BASE_KEY) + base_filename = base_filename if isinstance( + base_filename, list) else [base_filename] + + cfg_dict_list = list() + cfg_text_list = list() + for f in base_filename: + _cfg_dict, _cfg_text = Config._file2dict(osp.join(cfg_dir, f)) + cfg_dict_list.append(_cfg_dict) + cfg_text_list.append(_cfg_text) + + base_cfg_dict = dict() + for c in cfg_dict_list: + if len(base_cfg_dict.keys() & c.keys()) > 0: + raise KeyError('Duplicate key is not allowed among bases') + base_cfg_dict.update(c) + + base_cfg_dict = Config._merge_a_into_b(cfg_dict, base_cfg_dict) + cfg_dict = base_cfg_dict + + # merge cfg_text + cfg_text_list.append(cfg_text) + cfg_text = '\n'.join(cfg_text_list) + + return cfg_dict, cfg_text + + @staticmethod + def _merge_a_into_b(a, b): + # merge dict `a` into dict `b` (non-inplace). values in `a` will + # overwrite `b`. + # copy first to avoid inplace modification + b = b.copy() + for k, v in a.items(): + if isinstance(v, dict) and k in b and not v.pop(DELETE_KEY, False): + if not isinstance(b[k], dict): + raise TypeError( + f'{k}={v} in child config cannot inherit from base ' + f'because {k} is a dict in the child config but is of ' + f'type {type(b[k])} in base config. You may set ' + f'`{DELETE_KEY}=True` to ignore the base config') + b[k] = Config._merge_a_into_b(v, b[k]) + else: + b[k] = v + return b + + @staticmethod + def fromfile(filename): + cfg_dict, cfg_text = Config._file2dict(filename) + return Config(cfg_dict, cfg_text=cfg_text, filename=filename) + + @staticmethod + def auto_argparser(description=None): + """Generate argparser from config file automatically (experimental) + """ + partial_parser = ArgumentParser(description=description) + partial_parser.add_argument('config', help='config file path') + cfg_file = partial_parser.parse_known_args()[0].config + cfg = Config.fromfile(cfg_file) + parser = ArgumentParser(description=description) + parser.add_argument('config', help='config file path') + add_args(parser, cfg) + return parser, cfg + + def __init__(self, cfg_dict=None, cfg_text=None, filename=None): + if cfg_dict is None: + cfg_dict = dict() + elif not isinstance(cfg_dict, dict): + raise TypeError('cfg_dict must be a dict, but ' + f'got {type(cfg_dict)}') + for key in cfg_dict: + if key in RESERVED_KEYS: + raise KeyError(f'{key} is reserved for config file') + + super(Config, self).__setattr__('_cfg_dict', ConfigDict(cfg_dict)) + super(Config, self).__setattr__('_filename', filename) + if cfg_text: + text = cfg_text + elif filename: + with open(filename, 'r') as f: + text = f.read() + else: + text = '' + super(Config, self).__setattr__('_text', text) + + @property + def filename(self): + return self._filename + + @property + def text(self): + return self._text + + @property + def pretty_text(self): + + indent = 4 + + def _indent(s_, num_spaces): + s = s_.split('\n') + if len(s) == 1: + return s_ + first = s.pop(0) + s = [(num_spaces * ' ') + line for line in s] + s = '\n'.join(s) + s = first + '\n' + s + return s + + def _format_basic_types(k, v, use_mapping=False): + if isinstance(v, str): + v_str = f"'{v}'" + else: + v_str = str(v) + + if use_mapping: + k_str = f"'{k}'" if isinstance(k, str) else str(k) + attr_str = f'{k_str}: {v_str}' + else: + attr_str = f'{str(k)}={v_str}' + attr_str = _indent(attr_str, indent) + + return attr_str + + def _format_list(k, v, use_mapping=False): + # check if all items in the list are dict + if all(isinstance(_, dict) for _ in v): + v_str = '[\n' + v_str += '\n'.join( + f'dict({_indent(_format_dict(v_), indent)}),' + for v_ in v).rstrip(',') + if use_mapping: + k_str = f"'{k}'" if isinstance(k, str) else str(k) + attr_str = f'{k_str}: {v_str}' + else: + attr_str = f'{str(k)}={v_str}' + attr_str = _indent(attr_str, indent) + ']' + else: + attr_str = _format_basic_types(k, v, use_mapping) + return attr_str + + def _contain_invalid_identifier(dict_str): + contain_invalid_identifier = False + for key_name in dict_str: + contain_invalid_identifier |= \ + (not str(key_name).isidentifier()) + return contain_invalid_identifier + + def _format_dict(input_dict, outest_level=False): + r = '' + s = [] + + use_mapping = _contain_invalid_identifier(input_dict) + if use_mapping: + r += '{' + for idx, (k, v) in enumerate(input_dict.items()): + is_last = idx >= len(input_dict) - 1 + end = '' if outest_level or is_last else ',' + if isinstance(v, dict): + v_str = '\n' + _format_dict(v) + if use_mapping: + k_str = f"'{k}'" if isinstance(k, str) else str(k) + attr_str = f'{k_str}: dict({v_str}' + else: + attr_str = f'{str(k)}=dict({v_str}' + attr_str = _indent(attr_str, indent) + ')' + end + elif isinstance(v, list): + attr_str = _format_list(k, v, use_mapping) + end + else: + attr_str = _format_basic_types(k, v, use_mapping) + end + + s.append(attr_str) + r += '\n'.join(s) + if use_mapping: + r += '}' + return r + + cfg_dict = self._cfg_dict.to_dict() + text = _format_dict(cfg_dict, outest_level=True) + # copied from setup.cfg + yapf_style = dict( + based_on_style='pep8', + blank_line_before_nested_class_or_def=True, + split_before_expression_after_opening_paren=True) + text, _ = FormatCode(text, style_config=yapf_style, verify=True) + + return text + + def __repr__(self): + return f'Config (path: {self.filename}): {self._cfg_dict.__repr__()}' + + def __len__(self): + return len(self._cfg_dict) + + def __getattr__(self, name): + return getattr(self._cfg_dict, name) + + def __getitem__(self, name): + return self._cfg_dict.__getitem__(name) + + def __setattr__(self, name, value): + if isinstance(value, dict): + value = ConfigDict(value) + self._cfg_dict.__setattr__(name, value) + + def __setitem__(self, name, value): + if isinstance(value, dict): + value = ConfigDict(value) + self._cfg_dict.__setitem__(name, value) + + def __iter__(self): + return iter(self._cfg_dict) + + def dump(self, file=None): + cfg_dict = super(Config, self).__getattribute__('_cfg_dict').to_dict() + if self.filename.endswith('.py'): + if file is None: + return self.pretty_text + else: + with open(file, 'w') as f: + f.write(self.pretty_text) + else: + import mmcv + if file is None: + file_format = self.filename.split('.')[-1] + return mmcv.dump(cfg_dict, file_format=file_format) + else: + mmcv.dump(cfg_dict, file) + + def merge_from_dict(self, options): + """Merge list into cfg_dict + Merge the dict parsed by MultipleKVAction into this cfg. + Examples: + >>> options = {'model.backbone.depth': 50, + ... 'model.backbone.with_cp':True} + >>> cfg = Config(dict(model=dict(backbone=dict(type='ResNet')))) + >>> cfg.merge_from_dict(options) + >>> cfg_dict = super(Config, self).__getattribute__('_cfg_dict') + >>> assert cfg_dict == dict( + ... model=dict(backbone=dict(depth=50, with_cp=True))) + Args: + options (dict): dict of configs to merge from. + """ + option_cfg_dict = {} + for full_key, v in options.items(): + d = option_cfg_dict + key_list = full_key.split('.') + for subkey in key_list[:-1]: + d.setdefault(subkey, ConfigDict()) + d = d[subkey] + subkey = key_list[-1] + d[subkey] = v + + cfg_dict = super(Config, self).__getattribute__('_cfg_dict') + super(Config, self).__setattr__( + '_cfg_dict', Config._merge_a_into_b(option_cfg_dict, cfg_dict)) + + +class DictAction(Action): + """ + argparse action to split an argument into KEY=VALUE form + on the first = and append to a dictionary. List options should + be passed as comma separated values, i.e KEY=V1,V2,V3 + """ + + @staticmethod + def _parse_int_float_bool(val): + try: + return int(val) + except ValueError: + pass + try: + return float(val) + except ValueError: + pass + if val.lower() in ['true', 'false']: + return True if val.lower() == 'true' else False + return val + + def __call__(self, parser, namespace, values, option_string=None): + options = {} + for kv in values: + key, val = kv.split('=', maxsplit=1) + val = [self._parse_int_float_bool(v) for v in val.split(',')] + if len(val) == 1: + val = val[0] + options[key] = val + setattr(namespace, self.dest, options) diff --git a/utils/registry.py b/utils/registry.py new file mode 100644 index 0000000..e850f5c --- /dev/null +++ b/utils/registry.py @@ -0,0 +1,81 @@ +import inspect + +import six + +# borrow from mmdetection + +def is_str(x): + """Whether the input is an string instance.""" + return isinstance(x, six.string_types) + +class Registry(object): + + def __init__(self, name): + self._name = name + self._module_dict = dict() + + def __repr__(self): + format_str = self.__class__.__name__ + '(name={}, items={})'.format( + self._name, list(self._module_dict.keys())) + return format_str + + @property + def name(self): + return self._name + + @property + def module_dict(self): + return self._module_dict + + def get(self, key): + return self._module_dict.get(key, None) + + def _register_module(self, module_class): + """Register a module. + + Args: + module (:obj:`nn.Module`): Module to be registered. + """ + if not inspect.isclass(module_class): + raise TypeError('module must be a class, but got {}'.format( + type(module_class))) + module_name = module_class.__name__ + if module_name in self._module_dict: + raise KeyError('{} is already registered in {}'.format( + module_name, self.name)) + self._module_dict[module_name] = module_class + + def register_module(self, cls): + self._register_module(cls) + return cls + + +def build_from_cfg(cfg, registry, default_args=None): + """Build a module from config dict. + + Args: + cfg (dict): Config dict. It should at least contain the key "type". + registry (:obj:`Registry`): The registry to search the type from. + default_args (dict, optional): Default initialization arguments. + + Returns: + obj: The constructed object. + """ + assert isinstance(cfg, dict) and 'type' in cfg + assert isinstance(default_args, dict) or default_args is None + args = {} + obj_type = cfg.type + if is_str(obj_type): + obj_cls = registry.get(obj_type) + if obj_cls is None: + raise KeyError('{} is not in the {} registry'.format( + obj_type, registry.name)) + elif inspect.isclass(obj_type): + obj_cls = obj_type + else: + raise TypeError('type must be a str or valid type, but got {}'.format( + type(obj_type))) + if default_args is not None: + for name, value in default_args.items(): + args.setdefault(name, value) + return obj_cls(**args) diff --git a/utils/transforms.py b/utils/transforms.py new file mode 100644 index 0000000..0960b68 --- /dev/null +++ b/utils/transforms.py @@ -0,0 +1,357 @@ +import random +import cv2 +import numpy as np +import numbers +import collections + +# copy from: https://github.com/cardwing/Codes-for-Lane-Detection/blob/master/ERFNet-CULane-PyTorch/utils/transforms.py + +__all__ = ['GroupRandomCrop', 'GroupCenterCrop', 'GroupRandomPad', 'GroupCenterPad', + 'GroupRandomScale', 'GroupRandomHorizontalFlip', 'GroupNormalize'] + + +class SampleResize(object): + def __init__(self, size): + assert (isinstance(size, collections.Iterable) and len(size) == 2) + self.size = size + + def __call__(self, sample): + out = list() + out.append(cv2.resize(sample[0], self.size, + interpolation=cv2.INTER_CUBIC)) + if len(sample) > 1: + out.append(cv2.resize(sample[1], self.size, + interpolation=cv2.INTER_NEAREST)) + return out + + +class GroupRandomCrop(object): + def __init__(self, size): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + + def __call__(self, img_group): + h, w = img_group[0].shape[0:2] + th, tw = self.size + + out_images = list() + h1 = random.randint(0, max(0, h - th)) + w1 = random.randint(0, max(0, w - tw)) + h2 = min(h1 + th, h) + w2 = min(w1 + tw, w) + + for img in img_group: + assert (img.shape[0] == h and img.shape[1] == w) + out_images.append(img[h1:h2, w1:w2, ...]) + return out_images + + +class GroupRandomCropRatio(object): + def __init__(self, size): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + + def __call__(self, img_group): + h, w = img_group[0].shape[0:2] + tw, th = self.size + + out_images = list() + h1 = random.randint(0, max(0, h - th)) + w1 = random.randint(0, max(0, w - tw)) + h2 = min(h1 + th, h) + w2 = min(w1 + tw, w) + + for img in img_group: + assert (img.shape[0] == h and img.shape[1] == w) + out_images.append(img[h1:h2, w1:w2, ...]) + return out_images + + +class GroupCenterCrop(object): + def __init__(self, size): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + + def __call__(self, img_group): + h, w = img_group[0].shape[0:2] + th, tw = self.size + + out_images = list() + h1 = max(0, int((h - th) / 2)) + w1 = max(0, int((w - tw) / 2)) + h2 = min(h1 + th, h) + w2 = min(w1 + tw, w) + + for img in img_group: + assert (img.shape[0] == h and img.shape[1] == w) + out_images.append(img[h1:h2, w1:w2, ...]) + return out_images + + +class GroupRandomPad(object): + def __init__(self, size, padding): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + self.padding = padding + + def __call__(self, img_group): + assert (len(self.padding) == len(img_group)) + h, w = img_group[0].shape[0:2] + th, tw = self.size + + out_images = list() + h1 = random.randint(0, max(0, th - h)) + w1 = random.randint(0, max(0, tw - w)) + h2 = max(th - h - h1, 0) + w2 = max(tw - w - w1, 0) + + for img, padding in zip(img_group, self.padding): + assert (img.shape[0] == h and img.shape[1] == w) + out_images.append(cv2.copyMakeBorder( + img, h1, h2, w1, w2, cv2.BORDER_CONSTANT, value=padding)) + if len(img.shape) > len(out_images[-1].shape): + out_images[-1] = out_images[-1][..., + np.newaxis] # single channel image + return out_images + + +class GroupCenterPad(object): + def __init__(self, size, padding): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + self.padding = padding + + def __call__(self, img_group): + assert (len(self.padding) == len(img_group)) + h, w = img_group[0].shape[0:2] + th, tw = self.size + + out_images = list() + h1 = max(0, int((th - h) / 2)) + w1 = max(0, int((tw - w) / 2)) + h2 = max(th - h - h1, 0) + w2 = max(tw - w - w1, 0) + + for img, padding in zip(img_group, self.padding): + assert (img.shape[0] == h and img.shape[1] == w) + out_images.append(cv2.copyMakeBorder( + img, h1, h2, w1, w2, cv2.BORDER_CONSTANT, value=padding)) + if len(img.shape) > len(out_images[-1].shape): + out_images[-1] = out_images[-1][..., + np.newaxis] # single channel image + return out_images + + +class GroupConcerPad(object): + def __init__(self, size, padding): + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + self.size = size + self.padding = padding + + def __call__(self, img_group): + assert (len(self.padding) == len(img_group)) + h, w = img_group[0].shape[0:2] + th, tw = self.size + + out_images = list() + h1 = 0 + w1 = 0 + h2 = max(th - h - h1, 0) + w2 = max(tw - w - w1, 0) + + for img, padding in zip(img_group, self.padding): + assert (img.shape[0] == h and img.shape[1] == w) + out_images.append(cv2.copyMakeBorder( + img, h1, h2, w1, w2, cv2.BORDER_CONSTANT, value=padding)) + if len(img.shape) > len(out_images[-1].shape): + out_images[-1] = out_images[-1][..., + np.newaxis] # single channel image + return out_images + + +class GroupRandomScaleNew(object): + def __init__(self, size=(976, 208), interpolation=(cv2.INTER_LINEAR, cv2.INTER_NEAREST)): + self.size = size + self.interpolation = interpolation + + def __call__(self, img_group): + assert (len(self.interpolation) == len(img_group)) + scale_w, scale_h = self.size[0] * 1.0 / 1640, self.size[1] * 1.0 / 590 + out_images = list() + for img, interpolation in zip(img_group, self.interpolation): + out_images.append(cv2.resize(img, None, fx=scale_w, + fy=scale_h, interpolation=interpolation)) + if len(img.shape) > len(out_images[-1].shape): + out_images[-1] = out_images[-1][..., + np.newaxis] # single channel image + return out_images + + +class GroupRandomScale(object): + def __init__(self, size=(0.5, 1.5), interpolation=(cv2.INTER_LINEAR, cv2.INTER_NEAREST)): + self.size = size + self.interpolation = interpolation + + def __call__(self, img_group): + assert (len(self.interpolation) == len(img_group)) + scale = random.uniform(self.size[0], self.size[1]) + out_images = list() + for img, interpolation in zip(img_group, self.interpolation): + out_images.append(cv2.resize(img, None, fx=scale, + fy=scale, interpolation=interpolation)) + if len(img.shape) > len(out_images[-1].shape): + out_images[-1] = out_images[-1][..., + np.newaxis] # single channel image + return out_images + + +class GroupRandomMultiScale(object): + def __init__(self, size=(0.5, 1.5), interpolation=(cv2.INTER_LINEAR, cv2.INTER_NEAREST)): + self.size = size + self.interpolation = interpolation + + def __call__(self, img_group): + assert (len(self.interpolation) == len(img_group)) + scales = [0.5, 1.0, 1.5] # random.uniform(self.size[0], self.size[1]) + out_images = list() + for scale in scales: + for img, interpolation in zip(img_group, self.interpolation): + out_images.append(cv2.resize( + img, None, fx=scale, fy=scale, interpolation=interpolation)) + if len(img.shape) > len(out_images[-1].shape): + out_images[-1] = out_images[-1][..., + np.newaxis] # single channel image + return out_images + + +class GroupRandomScaleRatio(object): + def __init__(self, size=(680, 762, 562, 592), interpolation=(cv2.INTER_LINEAR, cv2.INTER_NEAREST)): + self.size = size + self.interpolation = interpolation + self.origin_id = [0, 1360, 580, 768, 255, 300, 680, 710, 312, 1509, 800, 1377, 880, 910, 1188, 128, 960, 1784, + 1414, 1150, 512, 1162, 950, 750, 1575, 708, 2111, 1848, 1071, 1204, 892, 639, 2040, 1524, 832, 1122, 1224, 2295] + + def __call__(self, img_group): + assert (len(self.interpolation) == len(img_group)) + w_scale = random.randint(self.size[0], self.size[1]) + h_scale = random.randint(self.size[2], self.size[3]) + h, w, _ = img_group[0].shape + out_images = list() + out_images.append(cv2.resize(img_group[0], None, fx=w_scale*1.0/w, fy=h_scale*1.0/h, + interpolation=self.interpolation[0])) # fx=w_scale*1.0/w, fy=h_scale*1.0/h + ### process label map ### + origin_label = cv2.resize( + img_group[1], None, fx=w_scale*1.0/w, fy=h_scale*1.0/h, interpolation=self.interpolation[1]) + origin_label = origin_label.astype(int) + label = origin_label[:, :, 0] * 5 + \ + origin_label[:, :, 1] * 3 + origin_label[:, :, 2] + new_label = np.ones(label.shape) * 100 + new_label = new_label.astype(int) + for cnt in range(37): + new_label = ( + label == self.origin_id[cnt]) * (cnt - 100) + new_label + new_label = (label == self.origin_id[37]) * (36 - 100) + new_label + assert(100 not in np.unique(new_label)) + out_images.append(new_label) + return out_images + + +class GroupRandomRotation(object): + def __init__(self, degree=(-10, 10), interpolation=(cv2.INTER_LINEAR, cv2.INTER_NEAREST), padding=None): + self.degree = degree + self.interpolation = interpolation + self.padding = padding + if self.padding is None: + self.padding = [0, 0] + + def __call__(self, img_group): + assert (len(self.interpolation) == len(img_group)) + v = random.random() + if v < 0.5: + degree = random.uniform(self.degree[0], self.degree[1]) + h, w = img_group[0].shape[0:2] + center = (w / 2, h / 2) + map_matrix = cv2.getRotationMatrix2D(center, degree, 1.0) + out_images = list() + for img, interpolation, padding in zip(img_group, self.interpolation, self.padding): + out_images.append(cv2.warpAffine( + img, map_matrix, (w, h), flags=interpolation, borderMode=cv2.BORDER_CONSTANT, borderValue=padding)) + if len(img.shape) > len(out_images[-1].shape): + out_images[-1] = out_images[-1][..., + np.newaxis] # single channel image + return out_images + else: + return img_group + + +class GroupRandomBlur(object): + def __init__(self, applied): + self.applied = applied + + def __call__(self, img_group): + assert (len(self.applied) == len(img_group)) + v = random.random() + if v < 0.5: + out_images = [] + for img, a in zip(img_group, self.applied): + if a: + img = cv2.GaussianBlur( + img, (5, 5), random.uniform(1e-6, 0.6)) + out_images.append(img) + if len(img.shape) > len(out_images[-1].shape): + out_images[-1] = out_images[-1][..., + np.newaxis] # single channel image + return out_images + else: + return img_group + + +class GroupRandomHorizontalFlip(object): + """Randomly horizontally flips the given numpy Image with a probability of 0.5 + """ + + def __init__(self, is_flow=False): + self.is_flow = is_flow + + def __call__(self, img_group, is_flow=False): + v = random.random() + if v < 0.5: + out_images = [np.fliplr(img) for img in img_group] + if self.is_flow: + for i in range(0, len(out_images), 2): + # invert flow pixel values when flipping + out_images[i] = -out_images[i] + return out_images + else: + return img_group + + +class GroupNormalize(object): + def __init__(self, mean, std): + self.mean = mean + self.std = std + + def __call__(self, img_group): + out_images = list() + for img, m, s in zip(img_group, self.mean, self.std): + if len(m) == 1: + img = img - np.array(m) # single channel image + img = img / np.array(s) + else: + img = img - np.array(m)[np.newaxis, np.newaxis, ...] + img = img / np.array(s)[np.newaxis, np.newaxis, ...] + out_images.append(img) + + return out_images