feat: initial HSAP platform
Huaxu Sentinel Active Safety Platform with embedded algorithm code, Docker Compose setup, and vendored dataset scaffolds for clone-and-run. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
BIN
algorithms/lane_ufld/code/CLRNet-main/.github/arch.png
vendored
Normal file
BIN
algorithms/lane_ufld/code/CLRNet-main/.github/arch.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 152 KiB |
BIN
algorithms/lane_ufld/code/CLRNet-main/.github/latency_f1score.png
vendored
Normal file
BIN
algorithms/lane_ufld/code/CLRNet-main/.github/latency_f1score.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
25
algorithms/lane_ufld/code/CLRNet-main/.gitignore
vendored
Normal file
25
algorithms/lane_ufld/code/CLRNet-main/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
work_dirs
|
||||
data
|
||||
cache/
|
||||
|
||||
__pycache__/
|
||||
*/*.un~
|
||||
.*.swp
|
||||
|
||||
|
||||
logs/
|
||||
*.egg-info/
|
||||
*.egg
|
||||
*.eggs
|
||||
.ipynb_checkpoints/
|
||||
|
||||
output.txt
|
||||
.vscode/*
|
||||
.DS_Store
|
||||
tmp.*
|
||||
*.pt
|
||||
*.pth
|
||||
*.un~
|
||||
*.so
|
||||
build
|
||||
*.jpg
|
||||
201
algorithms/lane_ufld/code/CLRNet-main/LICENSE
Normal file
201
algorithms/lane_ufld/code/CLRNet-main/LICENSE
Normal file
@@ -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 [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
211
algorithms/lane_ufld/code/CLRNet-main/README.md
Normal file
211
algorithms/lane_ufld/code/CLRNet-main/README.md
Normal file
@@ -0,0 +1,211 @@
|
||||
<div align="center">
|
||||
|
||||
[](https://paperswithcode.com/sota/lane-detection-on-culane?p=clrnet-cross-layer-refinement-network-for)
|
||||
[](https://paperswithcode.com/sota/lane-detection-on-llamas?p=clrnet-cross-layer-refinement-network-for)
|
||||
[](https://paperswithcode.com/sota/lane-detection-on-tusimple?p=clrnet-cross-layer-refinement-network-for)
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div align="center">
|
||||
|
||||
# CLRNet: Cross Layer Refinement Network for Lane Detection
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
Pytorch implementation of the paper "[CLRNet: Cross Layer Refinement Network for Lane Detection](https://arxiv.org/abs/2203.10350)" (CVPR2022 Acceptance).
|
||||
|
||||
## Introduction
|
||||

|
||||
- CLRNet exploits more contextual information to detect lanes while leveraging local detailed lane features to improve localization accuracy.
|
||||
- CLRNet achieves SOTA result on CULane, Tusimple, and LLAMAS datasets.
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
Only test on Ubuntu18.04 and 20.04 with:
|
||||
- Python >= 3.8 (tested with Python3.8)
|
||||
- PyTorch >= 1.6 (tested with Pytorch1.6)
|
||||
- CUDA (tested with cuda10.2)
|
||||
- Other dependencies described in `requirements.txt`
|
||||
|
||||
### Clone this repository
|
||||
Clone this code to your workspace.
|
||||
We call this directory as `$CLRNET_ROOT`
|
||||
```Shell
|
||||
git clone https://github.com/Turoad/clrnet
|
||||
```
|
||||
|
||||
### Create a conda virtual environment and activate it (conda is optional)
|
||||
|
||||
```Shell
|
||||
conda create -n clrnet python=3.8 -y
|
||||
conda activate clrnet
|
||||
```
|
||||
|
||||
### Install dependencies
|
||||
|
||||
```Shell
|
||||
# Install pytorch firstly, the cudatoolkit version should be same in your system.
|
||||
|
||||
conda install pytorch torchvision cudatoolkit=10.1 -c pytorch
|
||||
|
||||
# Or you can install via pip
|
||||
pip install torch==1.8.0 torchvision==0.9.0
|
||||
|
||||
# Install python packages
|
||||
python setup.py build develop
|
||||
```
|
||||
|
||||
### Data preparation
|
||||
|
||||
#### CULane
|
||||
|
||||
Download [CULane](https://xingangpan.github.io/projects/CULane.html). Then extract them to `$CULANEROOT`. Create link to `data` directory.
|
||||
|
||||
```Shell
|
||||
cd $CLRNET_ROOT
|
||||
mkdir -p data
|
||||
ln -s $CULANEROOT data/CULane
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
#### Tusimple
|
||||
Download [Tusimple](https://github.com/TuSimple/tusimple-benchmark/issues/3). Then extract them to `$TUSIMPLEROOT`. Create link to `data` directory.
|
||||
|
||||
```Shell
|
||||
cd $CLRNET_ROOT
|
||||
mkdir -p data
|
||||
ln -s $TUSIMPLEROOT data/tusimple
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
#### LLAMAS
|
||||
Dowload [LLAMAS](https://unsupervised-llamas.com/llamas/). Then extract them to `$LLAMASROOT`. Create link to `data` directory.
|
||||
|
||||
```Shell
|
||||
cd $CLRNET_ROOT
|
||||
mkdir -p data
|
||||
ln -s $LLAMASROOT data/llamas
|
||||
```
|
||||
|
||||
Unzip both files (`color_images.zip` and `labels.zip`) into the same directory (e.g., `data/llamas/`), which will be the dataset's root. For LLAMAS, you should have structure like this:
|
||||
```
|
||||
$LLAMASROOT/color_images/train # data folders
|
||||
$LLAMASROOT/color_images/test # data folders
|
||||
$LLAMASROOT/color_images/valid # data folders
|
||||
$LLAMASROOT/labels/train # labels folders
|
||||
$LLAMASROOT/labels/valid # labels folders
|
||||
```
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Training
|
||||
For training, run
|
||||
```Shell
|
||||
python main.py [configs/path_to_your_config] --gpus [gpu_num]
|
||||
```
|
||||
|
||||
For example, run
|
||||
```Shell
|
||||
python main.py configs/clrnet/clr_resnet18_culane.py --gpus 0
|
||||
```
|
||||
|
||||
### Validation
|
||||
For testing, run
|
||||
```Shell
|
||||
python main.py [configs/path_to_your_config] --[test|validate] --load_from [path_to_your_model] --gpus [gpu_num]
|
||||
```
|
||||
|
||||
For example, run
|
||||
```Shell
|
||||
python main.py configs/clrnet/clr_dla34_culane.py --validate --load_from culane_dla34.pth --gpus 0
|
||||
```
|
||||
|
||||
Currently, this code can output the visualization result when testing, just add `--view`.
|
||||
We will get the visualization result in `work_dirs/xxx/xxx/visualization`.
|
||||
|
||||
|
||||
## Results
|
||||

|
||||
|
||||
[assets]: https://github.com/turoad/CLRNet/releases
|
||||
|
||||
### CULane
|
||||
|
||||
| Backbone | mF1 | F1@50 | F1@75 |
|
||||
| :--- | :---: | :---: | :---:|
|
||||
| [ResNet-18][assets] | 55.23 | 79.58 | 62.21 |
|
||||
| [ResNet-34][assets] | 55.14 | 79.73 | 62.11 |
|
||||
| [ResNet-101][assets] | 55.55| 80.13 | 62.96 |
|
||||
| [DLA-34][assets] | 55.64| 80.47 | 62.78 |
|
||||
|
||||
|
||||
|
||||
### TuSimple
|
||||
| Backbone | F1 | Acc | FDR | FNR |
|
||||
| :--- | ---: | ---: | ---: | ---: |
|
||||
| [ResNet-18][assets] | 97.89 | 96.84 | 2.28 | 1.92 |
|
||||
| [ResNet-34][assets] | 97.82 | 96.87 | 2.27 | 2.08 |
|
||||
| [ResNet-101][assets] | 97.62| 96.83 | 2.37 | 2.38 |
|
||||
|
||||
|
||||
|
||||
### LLAMAS
|
||||
| Backbone | <center> valid <br><center> mF1 F1@50 F1@75 | <center> test <br> F1@50 |
|
||||
| :---: | :---: | :---:|
|
||||
| [ResNet-18][assets] | <center> 70.83 96.93 85.23 | 96.00 |
|
||||
| [DLA-34][assets] | <center> 71.57 97.06 85.43 | 96.12 |
|
||||
|
||||
“F1@50” refers to the official metric, i.e., F1 score when IoU threshold is 0.5 between the gt and prediction. "F1@75" is the F1 score when IoU threshold is 0.75.
|
||||
|
||||
## Citation
|
||||
|
||||
If our paper and code are beneficial to your work, please consider citing:
|
||||
```
|
||||
@InProceedings{Zheng_2022_CVPR,
|
||||
author = {Zheng, Tu and Huang, Yifei and Liu, Yang and Tang, Wenjian and Yang, Zheng and Cai, Deng and He, Xiaofei},
|
||||
title = {CLRNet: Cross Layer Refinement Network for Lane Detection},
|
||||
booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
|
||||
month = {June},
|
||||
year = {2022},
|
||||
pages = {898-907}
|
||||
}
|
||||
```
|
||||
|
||||
## Acknowledgement
|
||||
<!--ts-->
|
||||
* [open-mmlab/mmdetection](https://github.com/open-mmlab/mmdetection)
|
||||
* [pytorch/vision](https://github.com/pytorch/vision)
|
||||
* [Turoad/lanedet](https://github.com/Turoad/lanedet)
|
||||
* [ZJULearning/resa](https://github.com/ZJULearning/resa)
|
||||
* [cfzd/Ultra-Fast-Lane-Detection](https://github.com/cfzd/Ultra-Fast-Lane-Detection)
|
||||
* [lucastabelini/LaneATT](https://github.com/lucastabelini/LaneATT)
|
||||
* [aliyun/conditional-lane-detection](https://github.com/aliyun/conditional-lane-detection)
|
||||
<!--te-->
|
||||
122
algorithms/lane_ufld/code/CLRNet-main/README_MUFLD.md
Normal file
122
algorithms/lane_ufld/code/CLRNet-main/README_MUFLD.md
Normal file
@@ -0,0 +1,122 @@
|
||||
CLRNet 使用说明(lane0_copy 数据)
|
||||
|
||||
代码目录:/home/chengfanglu/DATA/BK2/CLRNet-main
|
||||
数据目录:/home/chengfanglu/DATA/lane0_copy/(与 UFLD 相同多包布局)
|
||||
|
||||
上游论文原版说明见同目录 README.md。
|
||||
|
||||
|
||||
一、环境安装
|
||||
|
||||
source ~/miniconda3/etc/profile.d/conda.sh
|
||||
cd /home/chengfanglu/DATA/BK2/CLRNet-main
|
||||
bash scripts/setup_clrnet_env.sh
|
||||
conda activate clrnet_lane
|
||||
|
||||
说明:
|
||||
环境名默认 clrnet_lane
|
||||
需要 NVIDIA GPU(main.py 使用 .cuda())
|
||||
安装包含 PyTorch、mmcv-full、python setup.py develop(编译 NMS)
|
||||
|
||||
|
||||
二、数据目录
|
||||
|
||||
lane0_copy/
|
||||
DATASET/(images、annotations/segmentation_masks、list/)
|
||||
DATASET-AddBy-zhangsan-20260615/(增量包)
|
||||
lists_merged/(合并列表)
|
||||
datasets_registry.json(别名)
|
||||
|
||||
重要:dataset_path 填父目录 lane0_copy,不要只填 DATASET。
|
||||
|
||||
数据集类:clrnet/datasets/mufld.py(MufldLane),从 mask 提取车道折线。
|
||||
目录规范:/home/chengfanglu/DATA/lane0_copy/DATASETS_LAYOUT.md
|
||||
|
||||
|
||||
三、配置文件
|
||||
|
||||
configs/clrnet/clr_resnet18_mufld.py — 推荐,1280x720,多包
|
||||
configs/clrnet/clr_resnet18_mufld_smoke.py — 冒烟
|
||||
|
||||
请改 configs/clrnet/clr_resnet18_mufld.py:
|
||||
|
||||
dataset_path = '/home/chengfanglu/DATA/lane0_copy'
|
||||
train_packs = ['DATASET']
|
||||
(多包:train_packs = ['DATASET', 'DATASET-A'],别名见 datasets_registry.json)
|
||||
val_packs = ['DATASET']
|
||||
pack_list_name = 'list/train_gt.txt'
|
||||
remerge_lists = False
|
||||
|
||||
合并列表输出:lane0_copy/lists_merged/train__DATASET__....txt
|
||||
|
||||
图像与网络参数:
|
||||
原图 1280x720
|
||||
cut_height 160
|
||||
网络输入 800x320
|
||||
max_lanes 4
|
||||
|
||||
|
||||
四、训练
|
||||
|
||||
conda activate clrnet_lane
|
||||
cd /home/chengfanglu/DATA/BK2/CLRNet-main
|
||||
|
||||
首次冒烟前先建短列表:
|
||||
head -64 /home/chengfanglu/DATA/lane0_copy/DATASET/list/train_gt.txt > /home/chengfanglu/DATA/lane0_copy/DATASET/list/train_gt_smoke.txt
|
||||
|
||||
冒烟:
|
||||
python main.py configs/clrnet/clr_resnet18_mufld_smoke.py --gpus 0
|
||||
|
||||
正式:
|
||||
python main.py configs/clrnet/clr_resnet18_mufld.py --gpus 0
|
||||
|
||||
权重目录:work_dirs/clr/mufld_r18/(由 config 里 work_dirs 决定)
|
||||
多卡示例:--gpus 0 1
|
||||
|
||||
常用修改:
|
||||
batch_size = 16
|
||||
epochs = 15
|
||||
optimizer = dict(type='AdamW', lr=1.0e-3)
|
||||
换 train_packs 后请改 total_iter = (144117 // batch_size + 1) * epochs
|
||||
|
||||
|
||||
五、预生成车道线缓存(推荐)
|
||||
|
||||
首轮会从 mask 现场提线,较慢。可先执行:
|
||||
|
||||
python tools/generate_mufld_lines.py --data-root /home/chengfanglu/DATA/lane0_copy --list DATASET/list/train_gt.txt
|
||||
|
||||
缓存目录:lane0_copy/cache/mufld_lines/
|
||||
|
||||
|
||||
六、测试与推理
|
||||
|
||||
python main.py configs/clrnet/clr_resnet18_mufld.py --gpus 0 --test --load_from work_dirs/clr/mufld_r18/ckpt/best.pth
|
||||
|
||||
(--load_from 按实际 ckpt 路径填写)
|
||||
|
||||
说明:
|
||||
没有 CULane 官方评测
|
||||
预测会写成 lines.txt,日志 metric 为占位值
|
||||
|
||||
|
||||
七、增量数据
|
||||
|
||||
1)建包(与 UFLD 共用脚本):
|
||||
python /home/chengfanglu/DATA/lane0_copy/scripts/build_ufld_pack.py --src /path/to/archive --parent /home/chengfanglu/DATA/lane0_copy --engineer zhangsan --date 20260615
|
||||
|
||||
2)config 增加包名:
|
||||
train_packs = ['DATASET', 'DATASET-AddBy-zhangsan-20260615']
|
||||
remerge_lists = True
|
||||
|
||||
3)重新训练:
|
||||
python main.py configs/clrnet/clr_resnet18_mufld.py --gpus 0
|
||||
|
||||
|
||||
八、路径速查
|
||||
|
||||
代码:/home/chengfanglu/DATA/BK2/CLRNet-main
|
||||
数据父目录:/home/chengfanglu/DATA/lane0_copy
|
||||
训练配置:configs/clrnet/clr_resnet18_mufld.py
|
||||
数据集实现:clrnet/datasets/mufld.py
|
||||
安装脚本:scripts/setup_clrnet_env.sh
|
||||
1
algorithms/lane_ufld/code/CLRNet-main/clrnet/__init__.py
Normal file
1
algorithms/lane_ufld/code/CLRNet-main/clrnet/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .ops import *
|
||||
@@ -0,0 +1,7 @@
|
||||
from .registry import build_dataset, build_dataloader
|
||||
|
||||
from .tusimple import TuSimple
|
||||
from .culane import CULane
|
||||
from .llamas import LLAMAS
|
||||
from .mufld import MufldLane
|
||||
from .process import *
|
||||
@@ -0,0 +1,67 @@
|
||||
import os.path as osp
|
||||
import os
|
||||
import numpy as np
|
||||
import cv2
|
||||
import torch
|
||||
from torch.utils.data import Dataset
|
||||
import torchvision
|
||||
import logging
|
||||
from .registry import DATASETS
|
||||
from .process import Process
|
||||
from clrnet.utils.visualization import imshow_lanes
|
||||
from mmcv.parallel import DataContainer as DC
|
||||
|
||||
|
||||
@DATASETS.register_module
|
||||
class BaseDataset(Dataset):
|
||||
def __init__(self, data_root, split, processes=None, cfg=None):
|
||||
self.cfg = cfg
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.data_root = data_root
|
||||
self.training = 'train' in split
|
||||
self.processes = Process(processes, cfg)
|
||||
|
||||
def view(self, predictions, img_metas):
|
||||
img_metas = [item for img_meta in img_metas.data for item in img_meta]
|
||||
for lanes, img_meta in zip(predictions, img_metas):
|
||||
img_name = img_meta['img_name']
|
||||
img = cv2.imread(osp.join(self.data_root, img_name))
|
||||
out_file = osp.join(self.cfg.work_dir, 'visualization',
|
||||
img_name.replace('/', '_'))
|
||||
lanes = [lane.to_array(self.cfg) for lane in lanes]
|
||||
imshow_lanes(img, lanes, out_file=out_file)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data_infos)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
data_info = self.data_infos[idx]
|
||||
img = cv2.imread(data_info['img_path'])
|
||||
img = img[self.cfg.cut_height:, :, :]
|
||||
sample = data_info.copy()
|
||||
sample.update({'img': img})
|
||||
|
||||
if self.training:
|
||||
label = cv2.imread(sample['mask_path'], cv2.IMREAD_UNCHANGED)
|
||||
if len(label.shape) > 2:
|
||||
label = label[:, :, 0]
|
||||
label = label.squeeze()
|
||||
label = label[self.cfg.cut_height:, :]
|
||||
sample.update({'mask': label})
|
||||
|
||||
if self.cfg.cut_height != 0:
|
||||
new_lanes = []
|
||||
for i in sample['lanes']:
|
||||
lanes = []
|
||||
for p in i:
|
||||
lanes.append((p[0], p[1] - self.cfg.cut_height))
|
||||
new_lanes.append(lanes)
|
||||
sample.update({'lanes': new_lanes})
|
||||
|
||||
sample = self.processes(sample)
|
||||
meta = {'full_img_path': data_info['img_path'],
|
||||
'img_name': data_info['img_name']}
|
||||
meta = DC(meta, cpu_only=True)
|
||||
sample.update({'meta': meta})
|
||||
|
||||
return sample
|
||||
143
algorithms/lane_ufld/code/CLRNet-main/clrnet/datasets/culane.py
Normal file
143
algorithms/lane_ufld/code/CLRNet-main/clrnet/datasets/culane.py
Normal file
@@ -0,0 +1,143 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
from .base_dataset import BaseDataset
|
||||
from .registry import DATASETS
|
||||
import clrnet.utils.culane_metric as culane_metric
|
||||
import cv2
|
||||
from tqdm import tqdm
|
||||
import logging
|
||||
import pickle as pkl
|
||||
|
||||
LIST_FILE = {
|
||||
'train': 'list/train_gt.txt',
|
||||
'val': 'list/val.txt',
|
||||
'test': 'list/test.txt',
|
||||
}
|
||||
|
||||
CATEGORYS = {
|
||||
'normal': 'list/test_split/test0_normal.txt',
|
||||
'crowd': 'list/test_split/test1_crowd.txt',
|
||||
'hlight': 'list/test_split/test2_hlight.txt',
|
||||
'shadow': 'list/test_split/test3_shadow.txt',
|
||||
'noline': 'list/test_split/test4_noline.txt',
|
||||
'arrow': 'list/test_split/test5_arrow.txt',
|
||||
'curve': 'list/test_split/test6_curve.txt',
|
||||
'cross': 'list/test_split/test7_cross.txt',
|
||||
'night': 'list/test_split/test8_night.txt',
|
||||
}
|
||||
|
||||
|
||||
@DATASETS.register_module
|
||||
class CULane(BaseDataset):
|
||||
def __init__(self, data_root, split, processes=None, cfg=None):
|
||||
super().__init__(data_root, split, processes=processes, cfg=cfg)
|
||||
self.list_path = osp.join(data_root, LIST_FILE[split])
|
||||
self.split = split
|
||||
self.load_annotations()
|
||||
|
||||
def load_annotations(self):
|
||||
self.logger.info('Loading CULane annotations...')
|
||||
# Waiting for the dataset to load is tedious, let's cache it
|
||||
os.makedirs('cache', exist_ok=True)
|
||||
cache_path = 'cache/culane_{}.pkl'.format(self.split)
|
||||
if os.path.exists(cache_path):
|
||||
with open(cache_path, 'rb') as cache_file:
|
||||
self.data_infos = pkl.load(cache_file)
|
||||
self.max_lanes = max(
|
||||
len(anno['lanes']) for anno in self.data_infos)
|
||||
return
|
||||
|
||||
self.data_infos = []
|
||||
with open(self.list_path) as list_file:
|
||||
for line in list_file:
|
||||
infos = self.load_annotation(line.split())
|
||||
self.data_infos.append(infos)
|
||||
|
||||
# cache data infos to file
|
||||
with open(cache_path, 'wb') as cache_file:
|
||||
pkl.dump(self.data_infos, cache_file)
|
||||
|
||||
def load_annotation(self, line):
|
||||
infos = {}
|
||||
img_line = line[0]
|
||||
img_line = img_line[1 if img_line[0] == '/' else 0::]
|
||||
img_path = os.path.join(self.data_root, img_line)
|
||||
infos['img_name'] = img_line
|
||||
infos['img_path'] = img_path
|
||||
if len(line) > 1:
|
||||
mask_line = line[1]
|
||||
mask_line = mask_line[1 if mask_line[0] == '/' else 0::]
|
||||
mask_path = os.path.join(self.data_root, mask_line)
|
||||
infos['mask_path'] = mask_path
|
||||
|
||||
if len(line) > 2:
|
||||
exist_list = [int(l) for l in line[2:]]
|
||||
infos['lane_exist'] = np.array(exist_list)
|
||||
|
||||
anno_path = img_path[:-3] + 'lines.txt' # remove sufix jpg and add lines.txt
|
||||
with open(anno_path, 'r') as anno_file:
|
||||
data = [
|
||||
list(map(float, line.split()))
|
||||
for line in anno_file.readlines()
|
||||
]
|
||||
lanes = [[(lane[i], lane[i + 1]) for i in range(0, len(lane), 2)
|
||||
if lane[i] >= 0 and lane[i + 1] >= 0] for lane in data]
|
||||
lanes = [list(set(lane)) for lane in lanes] # remove duplicated points
|
||||
lanes = [lane for lane in lanes
|
||||
if len(lane) > 2] # remove lanes with less than 2 points
|
||||
|
||||
lanes = [sorted(lane, key=lambda x: x[1])
|
||||
for lane in lanes] # sort by y
|
||||
infos['lanes'] = lanes
|
||||
|
||||
return infos
|
||||
|
||||
def get_prediction_string(self, pred):
|
||||
ys = np.arange(270, 590, 8) / self.cfg.ori_img_h
|
||||
out = []
|
||||
for lane in pred:
|
||||
xs = lane(ys)
|
||||
valid_mask = (xs >= 0) & (xs < 1)
|
||||
xs = xs * self.cfg.ori_img_w
|
||||
lane_xs = xs[valid_mask]
|
||||
lane_ys = ys[valid_mask] * self.cfg.ori_img_h
|
||||
lane_xs, lane_ys = lane_xs[::-1], lane_ys[::-1]
|
||||
lane_str = ' '.join([
|
||||
'{:.5f} {:.5f}'.format(x, y) for x, y in zip(lane_xs, lane_ys)
|
||||
])
|
||||
if lane_str != '':
|
||||
out.append(lane_str)
|
||||
|
||||
return '\n'.join(out)
|
||||
|
||||
def evaluate(self, predictions, output_basedir):
|
||||
loss_lines = [[], [], [], []]
|
||||
print('Generating prediction output...')
|
||||
for idx, pred in enumerate(predictions):
|
||||
output_dir = os.path.join(
|
||||
output_basedir,
|
||||
os.path.dirname(self.data_infos[idx]['img_name']))
|
||||
output_filename = os.path.basename(
|
||||
self.data_infos[idx]['img_name'])[:-3] + 'lines.txt'
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
output = self.get_prediction_string(pred)
|
||||
|
||||
with open(os.path.join(output_dir, output_filename),
|
||||
'w') as out_file:
|
||||
out_file.write(output)
|
||||
|
||||
for cate, cate_file in CATEGORYS.items():
|
||||
result = culane_metric.eval_predictions(output_basedir,
|
||||
self.data_root,
|
||||
os.path.join(self.data_root, cate_file),
|
||||
iou_thresholds=[0.5],
|
||||
official=True)
|
||||
|
||||
result = culane_metric.eval_predictions(output_basedir,
|
||||
self.data_root,
|
||||
self.list_path,
|
||||
iou_thresholds=np.linspace(0.5, 0.95, 10),
|
||||
official=True)
|
||||
|
||||
return result[0.5]['F1']
|
||||
180
algorithms/lane_ufld/code/CLRNet-main/clrnet/datasets/llamas.py
Normal file
180
algorithms/lane_ufld/code/CLRNet-main/clrnet/datasets/llamas.py
Normal file
@@ -0,0 +1,180 @@
|
||||
import os
|
||||
import pickle as pkl
|
||||
import cv2
|
||||
|
||||
from .registry import DATASETS
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
from .base_dataset import BaseDataset
|
||||
|
||||
TRAIN_LABELS_DIR = 'labels/train'
|
||||
TEST_LABELS_DIR = 'labels/valid'
|
||||
TEST_IMGS_DIR = 'color_images/test'
|
||||
SPLIT_DIRECTORIES = {'train': 'labels/train', 'val': 'labels/valid'}
|
||||
from clrnet.utils.llamas_utils import get_horizontal_values_for_four_lanes
|
||||
import clrnet.utils.llamas_metric as llamas_metric
|
||||
|
||||
|
||||
@DATASETS.register_module
|
||||
class LLAMAS(BaseDataset):
|
||||
def __init__(self, data_root, split='train', processes=None, cfg=None):
|
||||
self.split = split
|
||||
self.data_root = data_root
|
||||
super().__init__(data_root, split, processes, cfg)
|
||||
if split != 'test' and split not in SPLIT_DIRECTORIES.keys():
|
||||
raise Exception('Split `{}` does not exist.'.format(split))
|
||||
if split != 'test':
|
||||
self.labels_dir = os.path.join(self.data_root,
|
||||
SPLIT_DIRECTORIES[split])
|
||||
|
||||
self.data_infos = []
|
||||
self.load_annotations()
|
||||
|
||||
def get_img_heigth(self, _):
|
||||
return self.cfg.ori_img_h
|
||||
|
||||
def get_img_width(self, _):
|
||||
return self.cfg.ori_img_w
|
||||
|
||||
def get_metrics(self, lanes, _):
|
||||
# Placeholders
|
||||
return [0] * len(lanes), [0] * len(lanes), [1] * len(lanes), [
|
||||
1
|
||||
] * len(lanes)
|
||||
|
||||
def get_img_path(self, json_path):
|
||||
# /foo/bar/test/folder/image_label.ext --> test/folder/image_label.ext
|
||||
base_name = '/'.join(json_path.split('/')[-3:])
|
||||
image_path = os.path.join(
|
||||
'color_images', base_name.replace('.json', '_color_rect.png'))
|
||||
return image_path
|
||||
|
||||
def get_img_name(self, json_path):
|
||||
base_name = (json_path.split('/')[-1]).replace('.json',
|
||||
'_color_rect.png')
|
||||
return base_name
|
||||
|
||||
def get_json_paths(self):
|
||||
json_paths = []
|
||||
for root, _, files in os.walk(self.labels_dir):
|
||||
for file in files:
|
||||
if file.endswith(".json"):
|
||||
json_paths.append(os.path.join(root, file))
|
||||
return json_paths
|
||||
|
||||
def load_annotations(self):
|
||||
# the labels are not public for the test set yet
|
||||
if self.split == 'test':
|
||||
imgs_dir = os.path.join(self.data_root, TEST_IMGS_DIR)
|
||||
self.data_infos = [{
|
||||
'img_path':
|
||||
os.path.join(root, file),
|
||||
'img_name':
|
||||
os.path.join(TEST_IMGS_DIR,
|
||||
root.split('/')[-1], file),
|
||||
'lanes': [],
|
||||
'relative_path':
|
||||
os.path.join(root.split('/')[-1], file)
|
||||
} for root, _, files in os.walk(imgs_dir) for file in files
|
||||
if file.endswith('.png')]
|
||||
self.data_infos = sorted(self.data_infos,
|
||||
key=lambda x: x['img_path'])
|
||||
return
|
||||
|
||||
# Waiting for the dataset to load is tedious, let's cache it
|
||||
os.makedirs('cache', exist_ok=True)
|
||||
cache_path = 'cache/llamas_{}.pkl'.format(self.split)
|
||||
if os.path.exists(cache_path):
|
||||
with open(cache_path, 'rb') as cache_file:
|
||||
self.data_infos = pkl.load(cache_file)
|
||||
self.max_lanes = max(
|
||||
len(anno['lanes']) for anno in self.data_infos)
|
||||
return
|
||||
|
||||
self.max_lanes = 0
|
||||
print("Searching annotation files...")
|
||||
json_paths = self.get_json_paths()
|
||||
print('{} annotations found.'.format(len(json_paths)))
|
||||
|
||||
for json_path in tqdm(json_paths):
|
||||
lanes = get_horizontal_values_for_four_lanes(json_path)
|
||||
lanes = [[(x, y) for x, y in zip(lane, range(self.cfg.ori_img_h))
|
||||
if x >= 0] for lane in lanes]
|
||||
lanes = [lane for lane in lanes if len(lane) > 0]
|
||||
lanes = [list(set(lane))
|
||||
for lane in lanes] # remove duplicated points
|
||||
lanes = [lane for lane in lanes
|
||||
if len(lane) > 2] # remove lanes with less than 2 points
|
||||
|
||||
lanes = [sorted(lane, key=lambda x: x[1])
|
||||
for lane in lanes] # sort by y
|
||||
lanes.sort(key=lambda lane: lane[0][0])
|
||||
mask_path = json_path.replace('.json', '.png')
|
||||
|
||||
# generate seg labels
|
||||
seg = np.zeros((717, 1276, 3))
|
||||
for i, lane in enumerate(lanes):
|
||||
for j in range(0, len(lane) - 1):
|
||||
cv2.line(seg, (round(lane[j][0]), lane[j][1]),
|
||||
(round(lane[j + 1][0]), lane[j + 1][1]),
|
||||
(i + 1, i + 1, i + 1),
|
||||
thickness=15)
|
||||
|
||||
cv2.imwrite(mask_path, seg)
|
||||
|
||||
relative_path = self.get_img_path(json_path)
|
||||
img_path = os.path.join(self.data_root, relative_path)
|
||||
self.max_lanes = max(self.max_lanes, len(lanes))
|
||||
self.data_infos.append({
|
||||
'img_path': img_path,
|
||||
'img_name': relative_path,
|
||||
'mask_path': mask_path,
|
||||
'lanes': lanes,
|
||||
'relative_path': relative_path
|
||||
})
|
||||
|
||||
with open(cache_path, 'wb') as cache_file:
|
||||
pkl.dump(self.data_infos, cache_file)
|
||||
|
||||
def assign_class_to_lanes(self, lanes):
|
||||
return {
|
||||
label: value
|
||||
for label, value in zip(['l0', 'l1', 'r0', 'r1'], lanes)
|
||||
}
|
||||
|
||||
def get_prediction_string(self, pred):
|
||||
ys = np.arange(300, 717, 1) / (self.cfg.ori_img_h - 1)
|
||||
out = []
|
||||
for lane in pred:
|
||||
xs = lane(ys)
|
||||
valid_mask = (xs >= 0) & (xs < 1)
|
||||
xs = xs * (self.cfg.ori_img_w - 1)
|
||||
lane_xs = xs[valid_mask]
|
||||
lane_ys = ys[valid_mask] * (self.cfg.ori_img_h - 1)
|
||||
lane_xs, lane_ys = lane_xs[::-1], lane_ys[::-1]
|
||||
lane_str = ' '.join([
|
||||
'{:.5f} {:.5f}'.format(x, y) for x, y in zip(lane_xs, lane_ys)
|
||||
])
|
||||
if lane_str != '':
|
||||
out.append(lane_str)
|
||||
|
||||
return '\n'.join(out)
|
||||
|
||||
def evaluate(self, predictions, output_basedir):
|
||||
print('Generating prediction output...')
|
||||
for idx, pred in enumerate(predictions):
|
||||
relative_path = self.data_infos[idx]['relative_path']
|
||||
output_filename = '/'.join(relative_path.split('/')[-2:]).replace(
|
||||
'_color_rect.png', '.lines.txt')
|
||||
output_filepath = os.path.join(output_basedir, output_filename)
|
||||
os.makedirs(os.path.dirname(output_filepath), exist_ok=True)
|
||||
output = self.get_prediction_string(pred)
|
||||
with open(output_filepath, 'w') as out_file:
|
||||
out_file.write(output)
|
||||
if self.split == 'test':
|
||||
return None
|
||||
result = llamas_metric.eval_predictions(output_basedir,
|
||||
self.labels_dir,
|
||||
iou_thresholds=np.linspace(0.5, 0.95, 10),
|
||||
unofficial=False)
|
||||
return result[0.5]['F1']
|
||||
188
algorithms/lane_ufld/code/CLRNet-main/clrnet/datasets/mufld.py
Normal file
188
algorithms/lane_ufld/code/CLRNet-main/clrnet/datasets/mufld.py
Normal file
@@ -0,0 +1,188 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import pickle as pkl
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from .base_dataset import BaseDataset
|
||||
from .registry import DATASETS
|
||||
from clrnet.utils.mask_to_lanes import lanes_from_mask, normalize_mask_labels
|
||||
from clrnet.utils.dataset_packs import resolve_list_file
|
||||
|
||||
DEFAULT_LIST = {
|
||||
"train": "list/train_gt.txt",
|
||||
"val": "list/val_gt.txt",
|
||||
"test": "list/test_gt.txt",
|
||||
}
|
||||
|
||||
|
||||
@DATASETS.register_module
|
||||
class MufldLane(BaseDataset):
|
||||
"""
|
||||
MUFLD / lane0_copy DATASET packs.
|
||||
list: <img_rel> <mask_rel> per line; lanes from mask or cached .lines.txt
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data_root,
|
||||
split,
|
||||
processes=None,
|
||||
cfg=None,
|
||||
list_file=None,
|
||||
):
|
||||
super().__init__(data_root, split, processes=processes, cfg=cfg)
|
||||
self.split = split
|
||||
if list_file is None:
|
||||
list_file = getattr(cfg, f"{split}_list_file", None)
|
||||
if list_file is None:
|
||||
list_file = resolve_list_file(cfg, split)
|
||||
if list_file is None:
|
||||
rel = DEFAULT_LIST.get(split, DEFAULT_LIST["train"])
|
||||
packs = getattr(cfg, "train_packs" if split == "train" else "val_packs", None)
|
||||
if packs:
|
||||
pack = packs[0] if isinstance(packs, (list, tuple)) else packs
|
||||
list_file = f"{pack}/{rel}"
|
||||
else:
|
||||
list_file = rel
|
||||
if osp.isabs(list_file):
|
||||
self.list_path = list_file
|
||||
else:
|
||||
self.list_path = osp.join(data_root, list_file)
|
||||
self.sample_ys = list(getattr(cfg, "sample_y", range(710, 150, -10)))
|
||||
self.num_lanes = getattr(cfg, "max_lanes", 4)
|
||||
self.lines_cache = getattr(cfg, "lines_cache_dir", "cache/mufld_lines")
|
||||
self.load_annotations()
|
||||
|
||||
def load_annotations(self):
|
||||
self.logger.info("Loading MufldLane annotations from %s", self.list_path)
|
||||
os.makedirs("cache", exist_ok=True)
|
||||
cache_key = self.list_path.replace("/", "_")
|
||||
cache_path = osp.join("cache", f"mufld_{self.split}_{cache_key}.pkl")
|
||||
if osp.exists(cache_path):
|
||||
with open(cache_path, "rb") as f:
|
||||
self.data_infos = pkl.load(f)
|
||||
self.max_lanes = max(len(a["lanes"]) for a in self.data_infos) if self.data_infos else self.num_lanes
|
||||
return
|
||||
|
||||
self.data_infos = []
|
||||
with open(self.list_path) as f:
|
||||
for line in f:
|
||||
parts = line.strip().split()
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
info = self.load_annotation(parts)
|
||||
if info and len(info.get("lanes", [])) > 0:
|
||||
self.data_infos.append(info)
|
||||
|
||||
with open(cache_path, "wb") as f:
|
||||
pkl.dump(self.data_infos, f)
|
||||
self.max_lanes = max(len(a["lanes"]) for a in self.data_infos) if self.data_infos else self.num_lanes
|
||||
self.logger.info("Loaded %d samples, max_lanes=%d", len(self.data_infos), self.max_lanes)
|
||||
|
||||
def _lines_path(self, img_path: str) -> str:
|
||||
base = img_path[:-4] if img_path.lower().endswith((".jpg", ".png")) else img_path
|
||||
cache_root = osp.join(self.data_root, self.lines_cache)
|
||||
rel = osp.relpath(base, self.data_root)
|
||||
return osp.join(cache_root, rel + ".lines.txt")
|
||||
|
||||
def load_annotation(self, line):
|
||||
img_line = line[0].lstrip("/")
|
||||
mask_line = line[1].lstrip("/")
|
||||
img_path = osp.join(self.data_root, img_line)
|
||||
mask_path = osp.join(self.data_root, mask_line)
|
||||
infos = {
|
||||
"img_name": img_line,
|
||||
"img_path": img_path,
|
||||
"mask_path": mask_path,
|
||||
}
|
||||
if len(line) > 2:
|
||||
infos["lane_exist"] = np.array([int(x) for x in line[2:]])
|
||||
|
||||
lines_path = self._lines_path(img_path)
|
||||
if osp.isfile(lines_path):
|
||||
with open(lines_path) as f:
|
||||
data = [list(map(float, ln.split())) for ln in f.readlines() if ln.strip()]
|
||||
lanes = [
|
||||
[(lane[i], lane[i + 1]) for i in range(0, len(lane), 2) if lane[i] >= 0 and lane[i + 1] >= 0]
|
||||
for lane in data
|
||||
]
|
||||
elif osp.isfile(mask_path):
|
||||
mask = cv2.imread(mask_path, cv2.IMREAD_UNCHANGED)
|
||||
if mask is None:
|
||||
return None
|
||||
if mask.ndim > 2:
|
||||
mask = mask[:, :, 0]
|
||||
lanes = lanes_from_mask(mask, self.sample_ys, self.num_lanes)
|
||||
if getattr(self.cfg, "write_lines_cache", False):
|
||||
os.makedirs(osp.dirname(lines_path), exist_ok=True)
|
||||
with open(lines_path, "w") as out:
|
||||
for lane in lanes:
|
||||
out.write(" ".join(f"{x:.5f} {y:.5f}" for x, y in lane) + "\n")
|
||||
else:
|
||||
return None
|
||||
|
||||
lanes = [lane for lane in lanes if len(lane) > 2]
|
||||
lanes = [sorted(lane, key=lambda x: x[1]) for lane in lanes]
|
||||
infos["lanes"] = lanes
|
||||
return infos
|
||||
|
||||
def __getitem__(self, idx):
|
||||
data_info = self.data_infos[idx]
|
||||
img = cv2.imread(data_info["img_path"])
|
||||
if img is None:
|
||||
raise FileNotFoundError(data_info["img_path"])
|
||||
img = img[self.cfg.cut_height :, :, :]
|
||||
sample = data_info.copy()
|
||||
sample.update({"img": img})
|
||||
|
||||
if self.training:
|
||||
label = cv2.imread(sample["mask_path"], cv2.IMREAD_UNCHANGED)
|
||||
if label is None:
|
||||
raise FileNotFoundError(sample["mask_path"])
|
||||
if label.ndim > 2:
|
||||
label = label[:, :, 0]
|
||||
label = normalize_mask_labels(label.squeeze(), self.num_lanes)
|
||||
label = label[self.cfg.cut_height :, :]
|
||||
sample.update({"mask": label})
|
||||
|
||||
if self.cfg.cut_height != 0:
|
||||
new_lanes = []
|
||||
for lane in sample["lanes"]:
|
||||
new_lanes.append([(p[0], p[1] - self.cfg.cut_height) for p in lane])
|
||||
sample.update({"lanes": new_lanes})
|
||||
|
||||
from mmcv.parallel import DataContainer as DC
|
||||
from clrnet.datasets.process import Process
|
||||
|
||||
sample = self.processes(sample)
|
||||
meta = {"full_img_path": data_info["img_path"], "img_name": data_info["img_name"]}
|
||||
sample.update({"meta": DC(meta, cpu_only=True)})
|
||||
return sample
|
||||
|
||||
def get_prediction_string(self, pred):
|
||||
ys = np.array(self.sample_ys) / self.cfg.ori_img_h
|
||||
out = []
|
||||
for lane in pred:
|
||||
xs = lane(ys)
|
||||
valid = (xs >= 0) & (xs < 1)
|
||||
xs = xs[valid] * self.cfg.ori_img_w
|
||||
lane_ys = ys[valid] * self.cfg.ori_img_h
|
||||
xs, lane_ys = xs[::-1], lane_ys[::-1]
|
||||
s = " ".join(f"{x:.5f} {y:.5f}" for x, y in zip(xs, lane_ys))
|
||||
if s:
|
||||
out.append(s)
|
||||
return "\n".join(out)
|
||||
|
||||
def evaluate(self, predictions, output_basedir):
|
||||
os.makedirs(output_basedir, exist_ok=True)
|
||||
for idx, pred in enumerate(predictions):
|
||||
rel = self.data_infos[idx]["img_name"]
|
||||
out_dir = osp.join(output_basedir, osp.dirname(rel))
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
out_file = osp.join(out_dir, osp.basename(rel)[:-4] + ".lines.txt")
|
||||
with open(out_file, "w") as f:
|
||||
f.write(self.get_prediction_string(pred))
|
||||
self.logger.info("Wrote predictions under %s (MUFLD: no CULane official eval)", output_basedir)
|
||||
return 0.0
|
||||
@@ -0,0 +1,21 @@
|
||||
from .transforms import (RandomLROffsetLABEL, RandomUDoffsetLABEL, Resize,
|
||||
RandomCrop, CenterCrop, RandomRotation, RandomBlur,
|
||||
RandomHorizontalFlip, Normalize, ToTensor)
|
||||
|
||||
from .generate_lane_line import GenerateLaneLine
|
||||
from .process import Process
|
||||
|
||||
__all__ = [
|
||||
'Process',
|
||||
'RandomLROffsetLABEL',
|
||||
'RandomUDoffsetLABEL',
|
||||
'Resize',
|
||||
'RandomCrop',
|
||||
'CenterCrop',
|
||||
'RandomRotation',
|
||||
'RandomBlur',
|
||||
'RandomHorizontalFlip',
|
||||
'Normalize',
|
||||
'ToTensor',
|
||||
'GenerateLaneLine',
|
||||
]
|
||||
@@ -0,0 +1,218 @@
|
||||
import math
|
||||
import numpy as np
|
||||
import cv2
|
||||
import imgaug.augmenters as iaa
|
||||
from imgaug.augmentables.lines import LineString, LineStringsOnImage
|
||||
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
|
||||
from scipy.interpolate import InterpolatedUnivariateSpline
|
||||
from clrnet.datasets.process.transforms import CLRTransforms
|
||||
|
||||
from ..registry import PROCESS
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class GenerateLaneLine(object):
|
||||
def __init__(self, transforms=None, cfg=None, training=True):
|
||||
self.transforms = transforms
|
||||
self.img_w, self.img_h = cfg.img_w, cfg.img_h
|
||||
self.num_points = cfg.num_points
|
||||
self.n_offsets = cfg.num_points
|
||||
self.n_strips = cfg.num_points - 1
|
||||
self.strip_size = self.img_h / self.n_strips
|
||||
self.max_lanes = cfg.max_lanes
|
||||
self.offsets_ys = np.arange(self.img_h, -1, -self.strip_size)
|
||||
self.training = training
|
||||
|
||||
if transforms is None:
|
||||
transforms = CLRTransforms(self.img_h, self.img_w)
|
||||
|
||||
if transforms is not None:
|
||||
img_transforms = []
|
||||
for aug in transforms:
|
||||
p = aug['p']
|
||||
if aug['name'] != 'OneOf':
|
||||
img_transforms.append(
|
||||
iaa.Sometimes(p=p,
|
||||
then_list=getattr(
|
||||
iaa,
|
||||
aug['name'])(**aug['parameters'])))
|
||||
else:
|
||||
img_transforms.append(
|
||||
iaa.Sometimes(
|
||||
p=p,
|
||||
then_list=iaa.OneOf([
|
||||
getattr(iaa,
|
||||
aug_['name'])(**aug_['parameters'])
|
||||
for aug_ in aug['transforms']
|
||||
])))
|
||||
else:
|
||||
img_transforms = []
|
||||
self.transform = iaa.Sequential(img_transforms)
|
||||
|
||||
def lane_to_linestrings(self, lanes):
|
||||
lines = []
|
||||
for lane in lanes:
|
||||
lines.append(LineString(lane))
|
||||
|
||||
return lines
|
||||
|
||||
def sample_lane(self, points, sample_ys):
|
||||
# this function expects the points to be sorted
|
||||
points = np.array(points)
|
||||
if not np.all(points[1:, 1] < points[:-1, 1]):
|
||||
raise Exception('Annotaion points have to be sorted')
|
||||
x, y = points[:, 0], points[:, 1]
|
||||
|
||||
# interpolate points inside domain
|
||||
assert len(points) > 1
|
||||
interp = InterpolatedUnivariateSpline(y[::-1],
|
||||
x[::-1],
|
||||
k=min(3,
|
||||
len(points) - 1))
|
||||
domain_min_y = y.min()
|
||||
domain_max_y = y.max()
|
||||
sample_ys_inside_domain = sample_ys[(sample_ys >= domain_min_y)
|
||||
& (sample_ys <= domain_max_y)]
|
||||
assert len(sample_ys_inside_domain) > 0
|
||||
interp_xs = interp(sample_ys_inside_domain)
|
||||
|
||||
# extrapolate lane to the bottom of the image with a straight line using the 2 points closest to the bottom
|
||||
two_closest_points = points[:2]
|
||||
extrap = np.polyfit(two_closest_points[:, 1],
|
||||
two_closest_points[:, 0],
|
||||
deg=1)
|
||||
extrap_ys = sample_ys[sample_ys > domain_max_y]
|
||||
extrap_xs = np.polyval(extrap, extrap_ys)
|
||||
all_xs = np.hstack((extrap_xs, interp_xs))
|
||||
|
||||
# separate between inside and outside points
|
||||
inside_mask = (all_xs >= 0) & (all_xs < self.img_w)
|
||||
xs_inside_image = all_xs[inside_mask]
|
||||
xs_outside_image = all_xs[~inside_mask]
|
||||
|
||||
return xs_outside_image, xs_inside_image
|
||||
|
||||
def filter_lane(self, lane):
|
||||
assert lane[-1][1] <= lane[0][1]
|
||||
filtered_lane = []
|
||||
used = set()
|
||||
for p in lane:
|
||||
if p[1] not in used:
|
||||
filtered_lane.append(p)
|
||||
used.add(p[1])
|
||||
|
||||
return filtered_lane
|
||||
|
||||
def transform_annotation(self, anno, img_wh=None):
|
||||
img_w, img_h = self.img_w, self.img_h
|
||||
|
||||
old_lanes = anno['lanes']
|
||||
|
||||
# removing lanes with less than 2 points
|
||||
old_lanes = filter(lambda x: len(x) > 1, old_lanes)
|
||||
# sort lane points by Y (bottom to top of the image)
|
||||
old_lanes = [sorted(lane, key=lambda x: -x[1]) for lane in old_lanes]
|
||||
# remove points with same Y (keep first occurrence)
|
||||
old_lanes = [self.filter_lane(lane) for lane in old_lanes]
|
||||
# normalize the annotation coordinates
|
||||
old_lanes = [[[
|
||||
x * self.img_w / float(img_w), y * self.img_h / float(img_h)
|
||||
] for x, y in lane] for lane in old_lanes]
|
||||
# create tranformed annotations
|
||||
lanes = np.ones(
|
||||
(self.max_lanes, 2 + 1 + 1 + 2 + self.n_offsets), dtype=np.float32
|
||||
) * -1e5 # 2 scores, 1 start_y, 1 start_x, 1 theta, 1 length, S+1 coordinates
|
||||
lanes_endpoints = np.ones((self.max_lanes, 2))
|
||||
# lanes are invalid by default
|
||||
lanes[:, 0] = 1
|
||||
lanes[:, 1] = 0
|
||||
for lane_idx, lane in enumerate(old_lanes):
|
||||
if lane_idx >= self.max_lanes:
|
||||
break
|
||||
|
||||
try:
|
||||
xs_outside_image, xs_inside_image = self.sample_lane(
|
||||
lane, self.offsets_ys)
|
||||
except AssertionError:
|
||||
continue
|
||||
if len(xs_inside_image) <= 1:
|
||||
continue
|
||||
all_xs = np.hstack((xs_outside_image, xs_inside_image))
|
||||
lanes[lane_idx, 0] = 0
|
||||
lanes[lane_idx, 1] = 1
|
||||
lanes[lane_idx, 2] = len(xs_outside_image) / self.n_strips
|
||||
lanes[lane_idx, 3] = xs_inside_image[0]
|
||||
|
||||
thetas = []
|
||||
for i in range(1, len(xs_inside_image)):
|
||||
theta = math.atan(
|
||||
i * self.strip_size /
|
||||
(xs_inside_image[i] - xs_inside_image[0] + 1e-5)) / math.pi
|
||||
theta = theta if theta > 0 else 1 - abs(theta)
|
||||
thetas.append(theta)
|
||||
|
||||
theta_far = sum(thetas) / len(thetas)
|
||||
|
||||
# lanes[lane_idx,
|
||||
# 4] = (theta_closest + theta_far) / 2 # averaged angle
|
||||
lanes[lane_idx, 4] = theta_far
|
||||
lanes[lane_idx, 5] = len(xs_inside_image)
|
||||
lanes[lane_idx, 6:6 + len(all_xs)] = all_xs
|
||||
lanes_endpoints[lane_idx, 0] = (len(all_xs) - 1) / self.n_strips
|
||||
lanes_endpoints[lane_idx, 1] = xs_inside_image[-1]
|
||||
|
||||
new_anno = {
|
||||
'label': lanes,
|
||||
'old_anno': anno,
|
||||
'lane_endpoints': lanes_endpoints
|
||||
}
|
||||
return new_anno
|
||||
|
||||
def linestrings_to_lanes(self, lines):
|
||||
lanes = []
|
||||
for line in lines:
|
||||
lanes.append(line.coords)
|
||||
|
||||
return lanes
|
||||
|
||||
def __call__(self, sample):
|
||||
img_org = sample['img']
|
||||
line_strings_org = self.lane_to_linestrings(sample['lanes'])
|
||||
line_strings_org = LineStringsOnImage(line_strings_org,
|
||||
shape=img_org.shape)
|
||||
|
||||
for i in range(30):
|
||||
if self.training:
|
||||
mask_org = SegmentationMapsOnImage(sample['mask'],
|
||||
shape=img_org.shape)
|
||||
img, line_strings, seg = self.transform(
|
||||
image=img_org.copy().astype(np.uint8),
|
||||
line_strings=line_strings_org,
|
||||
segmentation_maps=mask_org)
|
||||
else:
|
||||
img, line_strings = self.transform(
|
||||
image=img_org.copy().astype(np.uint8),
|
||||
line_strings=line_strings_org)
|
||||
line_strings.clip_out_of_image_()
|
||||
new_anno = {'lanes': self.linestrings_to_lanes(line_strings)}
|
||||
try:
|
||||
annos = self.transform_annotation(new_anno,
|
||||
img_wh=(self.img_w,
|
||||
self.img_h))
|
||||
label = annos['label']
|
||||
lane_endpoints = annos['lane_endpoints']
|
||||
break
|
||||
except:
|
||||
if (i + 1) == 30:
|
||||
self.logger.critical(
|
||||
'Transform annotation failed 30 times :(')
|
||||
exit()
|
||||
|
||||
sample['img'] = img.astype(np.float32) / 255.
|
||||
sample['lane_line'] = label
|
||||
sample['lanes_endpoints'] = lane_endpoints
|
||||
sample['gt_points'] = new_anno['lanes']
|
||||
sample['seg'] = seg.get_arr() if self.training else np.zeros(
|
||||
img_org.shape)
|
||||
|
||||
return sample
|
||||
@@ -0,0 +1,48 @@
|
||||
import collections
|
||||
|
||||
from clrnet.utils import build_from_cfg
|
||||
|
||||
from ..registry import PROCESS
|
||||
|
||||
|
||||
class Process(object):
|
||||
"""Compose multiple process sequentially.
|
||||
Args:
|
||||
process (Sequence[dict | callable]): Sequence of process object or
|
||||
config dict to be composed.
|
||||
"""
|
||||
def __init__(self, processes, cfg):
|
||||
assert isinstance(processes, collections.abc.Sequence)
|
||||
self.processes = []
|
||||
for process in processes:
|
||||
if isinstance(process, dict):
|
||||
process = build_from_cfg(process,
|
||||
PROCESS,
|
||||
default_args=dict(cfg=cfg))
|
||||
self.processes.append(process)
|
||||
elif callable(process):
|
||||
self.processes.append(process)
|
||||
else:
|
||||
raise TypeError('process must be callable or a dict')
|
||||
|
||||
def __call__(self, data):
|
||||
"""Call function to apply processes sequentially.
|
||||
Args:
|
||||
data (dict): A result dict contains the data to process.
|
||||
Returns:
|
||||
dict: Processed data.
|
||||
"""
|
||||
|
||||
for t in self.processes:
|
||||
data = t(data)
|
||||
if data is None:
|
||||
return None
|
||||
return data
|
||||
|
||||
def __repr__(self):
|
||||
format_string = self.__class__.__name__ + '('
|
||||
for t in self.processes:
|
||||
format_string += '\n'
|
||||
format_string += f' {t}'
|
||||
format_string += '\n)'
|
||||
return format_string
|
||||
@@ -0,0 +1,311 @@
|
||||
import random
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
import numbers
|
||||
import collections
|
||||
from PIL import Image
|
||||
|
||||
from ..registry import PROCESS
|
||||
|
||||
|
||||
def to_tensor(data):
|
||||
"""Convert objects of various python types to :obj:`torch.Tensor`.
|
||||
|
||||
Supported types are: :class:`numpy.ndarray`, :class:`torch.Tensor`,
|
||||
:class:`Sequence`, :class:`int` and :class:`float`.
|
||||
|
||||
Args:
|
||||
data (torch.Tensor | numpy.ndarray | Sequence | int | float): Data to
|
||||
be converted.
|
||||
"""
|
||||
|
||||
if isinstance(data, torch.Tensor):
|
||||
return data
|
||||
elif isinstance(data, np.ndarray):
|
||||
return torch.from_numpy(data)
|
||||
elif isinstance(data, int):
|
||||
return torch.LongTensor([data])
|
||||
elif isinstance(data, float):
|
||||
return torch.FloatTensor([data])
|
||||
else:
|
||||
raise TypeError(f'type {type(data)} cannot be converted to tensor.')
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class ToTensor(object):
|
||||
"""Convert some results to :obj:`torch.Tensor` by given keys.
|
||||
|
||||
Args:
|
||||
keys (Sequence[str]): Keys that need to be converted to Tensor.
|
||||
"""
|
||||
def __init__(self, keys=['img', 'mask'], cfg=None):
|
||||
self.keys = keys
|
||||
|
||||
def __call__(self, sample):
|
||||
data = {}
|
||||
if len(sample['img'].shape) < 3:
|
||||
sample['img'] = np.expand_dims(img, -1)
|
||||
for key in self.keys:
|
||||
if key == 'img_metas' or key == 'gt_masks' or key == 'lane_line':
|
||||
data[key] = sample[key]
|
||||
continue
|
||||
data[key] = to_tensor(sample[key])
|
||||
data['img'] = data['img'].permute(2, 0, 1)
|
||||
return data
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__ + f'(keys={self.keys})'
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class RandomLROffsetLABEL(object):
|
||||
def __init__(self, max_offset, cfg=None):
|
||||
self.max_offset = max_offset
|
||||
|
||||
def __call__(self, sample):
|
||||
img = sample['img']
|
||||
label = sample['mask']
|
||||
offset = np.random.randint(-self.max_offset, self.max_offset)
|
||||
h, w = img.shape[:2]
|
||||
|
||||
img = np.array(img)
|
||||
if offset > 0:
|
||||
img[:, offset:, :] = img[:, 0:w - offset, :]
|
||||
img[:, :offset, :] = 0
|
||||
if offset < 0:
|
||||
real_offset = -offset
|
||||
img[:, 0:w - real_offset, :] = img[:, real_offset:, :]
|
||||
img[:, w - real_offset:, :] = 0
|
||||
|
||||
label = np.array(label)
|
||||
if offset > 0:
|
||||
label[:, offset:] = label[:, 0:w - offset]
|
||||
label[:, :offset] = 0
|
||||
if offset < 0:
|
||||
offset = -offset
|
||||
label[:, 0:w - offset] = label[:, offset:]
|
||||
label[:, w - offset:] = 0
|
||||
sample['img'] = img
|
||||
sample['mask'] = label
|
||||
|
||||
return sample
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class RandomUDoffsetLABEL(object):
|
||||
def __init__(self, max_offset, cfg=None):
|
||||
self.max_offset = max_offset
|
||||
|
||||
def __call__(self, sample):
|
||||
img = sample['img']
|
||||
label = sample['mask']
|
||||
offset = np.random.randint(-self.max_offset, self.max_offset)
|
||||
h, w = img.shape[:2]
|
||||
|
||||
img = np.array(img)
|
||||
if offset > 0:
|
||||
img[offset:, :, :] = img[0:h - offset, :, :]
|
||||
img[:offset, :, :] = 0
|
||||
if offset < 0:
|
||||
real_offset = -offset
|
||||
img[0:h - real_offset, :, :] = img[real_offset:, :, :]
|
||||
img[h - real_offset:, :, :] = 0
|
||||
|
||||
label = np.array(label)
|
||||
if offset > 0:
|
||||
label[offset:, :] = label[0:h - offset, :]
|
||||
label[:offset, :] = 0
|
||||
if offset < 0:
|
||||
offset = -offset
|
||||
label[0:h - offset, :] = label[offset:, :]
|
||||
label[h - offset:, :] = 0
|
||||
sample['img'] = img
|
||||
sample['mask'] = label
|
||||
return sample
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class Resize(object):
|
||||
def __init__(self, size, cfg=None):
|
||||
assert (isinstance(size, collections.Iterable) and len(size) == 2)
|
||||
self.size = size
|
||||
|
||||
def __call__(self, sample):
|
||||
out = list()
|
||||
sample['img'] = cv2.resize(sample['img'],
|
||||
self.size,
|
||||
interpolation=cv2.INTER_CUBIC)
|
||||
if 'mask' in sample:
|
||||
sample['mask'] = cv2.resize(sample['mask'],
|
||||
self.size,
|
||||
interpolation=cv2.INTER_NEAREST)
|
||||
return sample
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class RandomCrop(object):
|
||||
def __init__(self, size, cfg=None):
|
||||
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
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class CenterCrop(object):
|
||||
def __init__(self, size, cfg=None):
|
||||
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
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class RandomRotation(object):
|
||||
def __init__(self,
|
||||
degree=(-10, 10),
|
||||
interpolation=(cv2.INTER_LINEAR, cv2.INTER_NEAREST),
|
||||
padding=None,
|
||||
cfg=None):
|
||||
self.degree = degree
|
||||
self.interpolation = interpolation
|
||||
self.padding = padding
|
||||
if self.padding is None:
|
||||
self.padding = [0, 0]
|
||||
|
||||
def _rotate_img(self, sample, map_matrix):
|
||||
h, w = sample['img'].shape[0:2]
|
||||
sample['img'] = cv2.warpAffine(sample['img'],
|
||||
map_matrix, (w, h),
|
||||
flags=cv2.INTER_LINEAR,
|
||||
borderMode=cv2.BORDER_CONSTANT,
|
||||
borderValue=self.padding)
|
||||
|
||||
def _rotate_mask(self, sample, map_matrix):
|
||||
if 'mask' not in sample:
|
||||
return
|
||||
h, w = sample['mask'].shape[0:2]
|
||||
sample['mask'] = cv2.warpAffine(sample['mask'],
|
||||
map_matrix, (w, h),
|
||||
flags=cv2.INTER_NEAREST,
|
||||
borderMode=cv2.BORDER_CONSTANT,
|
||||
borderValue=self.padding)
|
||||
|
||||
def __call__(self, sample):
|
||||
v = random.random()
|
||||
if v < 0.5:
|
||||
degree = random.uniform(self.degree[0], self.degree[1])
|
||||
h, w = sample['img'].shape[0:2]
|
||||
center = (w / 2, h / 2)
|
||||
map_matrix = cv2.getRotationMatrix2D(center, degree, 1.0)
|
||||
self._rotate_img(sample, map_matrix)
|
||||
self._rotate_mask(sample, map_matrix)
|
||||
return sample
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class RandomBlur(object):
|
||||
def __init__(self, applied, cfg=None):
|
||||
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
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class RandomHorizontalFlip(object):
|
||||
"""Randomly horizontally flips the given numpy Image with a probability of 0.5
|
||||
"""
|
||||
def __init__(self, cfg=None):
|
||||
pass
|
||||
|
||||
def __call__(self, sample):
|
||||
v = random.random()
|
||||
if v < 0.5:
|
||||
sample['img'] = np.fliplr(sample['img'])
|
||||
if 'mask' in sample: sample['mask'] = np.fliplr(sample['mask'])
|
||||
return sample
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class Normalize(object):
|
||||
def __init__(self, img_norm, cfg=None):
|
||||
self.mean = np.array(img_norm['mean'], dtype=np.float32)
|
||||
self.std = np.array(img_norm['std'], dtype=np.float32)
|
||||
|
||||
def __call__(self, sample):
|
||||
m = self.mean
|
||||
s = self.std
|
||||
img = sample['img']
|
||||
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, ...]
|
||||
sample['img'] = img
|
||||
|
||||
return sample
|
||||
|
||||
|
||||
def CLRTransforms(img_h, img_w):
|
||||
return [
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
dict(name='HorizontalFlip', parameters=dict(p=1.0), p=0.5),
|
||||
dict(name='Affine',
|
||||
parameters=dict(translate_percent=dict(x=(-0.1, 0.1),
|
||||
y=(-0.1, 0.1)),
|
||||
rotate=(-10, 10),
|
||||
scale=(0.8, 1.2)),
|
||||
p=0.7),
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
]
|
||||
@@ -0,0 +1,54 @@
|
||||
from clrnet.utils import Registry, build_from_cfg
|
||||
|
||||
import torch
|
||||
from functools import partial
|
||||
import numpy as np
|
||||
import random
|
||||
from mmcv.parallel import collate
|
||||
|
||||
DATASETS = Registry('datasets')
|
||||
PROCESS = Registry('process')
|
||||
|
||||
|
||||
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):
|
||||
return build(split_cfg, DATASETS, default_args=dict(cfg=cfg))
|
||||
|
||||
|
||||
def worker_init_fn(worker_id, seed):
|
||||
worker_seed = worker_id + seed
|
||||
np.random.seed(worker_seed)
|
||||
random.seed(worker_seed)
|
||||
|
||||
|
||||
def build_dataloader(split_cfg, cfg, is_train=True):
|
||||
if is_train:
|
||||
shuffle = True
|
||||
else:
|
||||
shuffle = False
|
||||
|
||||
dataset = build_dataset(split_cfg, cfg)
|
||||
|
||||
init_fn = partial(worker_init_fn, seed=cfg.seed)
|
||||
|
||||
samples_per_gpu = cfg.batch_size // cfg.gpus
|
||||
data_loader = torch.utils.data.DataLoader(
|
||||
dataset,
|
||||
batch_size=cfg.batch_size,
|
||||
shuffle=shuffle,
|
||||
num_workers=cfg.workers,
|
||||
pin_memory=False,
|
||||
drop_last=False,
|
||||
collate_fn=partial(collate, samples_per_gpu=samples_per_gpu),
|
||||
worker_init_fn=init_fn)
|
||||
|
||||
return data_loader
|
||||
@@ -0,0 +1,100 @@
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import cv2
|
||||
import os
|
||||
import json
|
||||
import torchvision
|
||||
from .base_dataset import BaseDataset
|
||||
from clrnet.utils.tusimple_metric import LaneEval
|
||||
from .registry import DATASETS
|
||||
import logging
|
||||
import random
|
||||
|
||||
SPLIT_FILES = {
|
||||
'trainval':
|
||||
['label_data_0313.json', 'label_data_0601.json', 'label_data_0531.json'],
|
||||
'train': ['label_data_0313.json', 'label_data_0601.json'],
|
||||
'val': ['label_data_0531.json'],
|
||||
'test': ['test_label.json'],
|
||||
}
|
||||
|
||||
|
||||
@DATASETS.register_module
|
||||
class TuSimple(BaseDataset):
|
||||
def __init__(self, data_root, split, processes=None, cfg=None):
|
||||
super().__init__(data_root, split, processes, cfg)
|
||||
self.anno_files = SPLIT_FILES[split]
|
||||
self.load_annotations()
|
||||
self.h_samples = list(range(160, 720, 10))
|
||||
|
||||
def load_annotations(self):
|
||||
self.logger.info('Loading TuSimple annotations...')
|
||||
self.data_infos = []
|
||||
max_lanes = 0
|
||||
for anno_file in self.anno_files:
|
||||
anno_file = osp.join(self.data_root, anno_file)
|
||||
with open(anno_file, 'r') as anno_obj:
|
||||
lines = anno_obj.readlines()
|
||||
for line in lines:
|
||||
data = json.loads(line)
|
||||
y_samples = data['h_samples']
|
||||
gt_lanes = data['lanes']
|
||||
mask_path = data['raw_file'].replace('clips',
|
||||
'seg_label')[:-3] + 'png'
|
||||
lanes = [[(x, y) for (x, y) in zip(lane, y_samples) if x >= 0]
|
||||
for lane in gt_lanes]
|
||||
lanes = [lane for lane in lanes if len(lane) > 0]
|
||||
max_lanes = max(max_lanes, len(lanes))
|
||||
self.data_infos.append({
|
||||
'img_path':
|
||||
osp.join(self.data_root, data['raw_file']),
|
||||
'img_name':
|
||||
data['raw_file'],
|
||||
'mask_path':
|
||||
osp.join(self.data_root, mask_path),
|
||||
'lanes':
|
||||
lanes,
|
||||
})
|
||||
|
||||
if self.training:
|
||||
random.shuffle(self.data_infos)
|
||||
self.max_lanes = max_lanes
|
||||
|
||||
def pred2lanes(self, pred):
|
||||
ys = np.array(self.h_samples) / self.cfg.ori_img_h
|
||||
lanes = []
|
||||
for lane in pred:
|
||||
xs = lane(ys)
|
||||
invalid_mask = xs < 0
|
||||
lane = (xs * self.cfg.ori_img_w).astype(int)
|
||||
lane[invalid_mask] = -2
|
||||
lanes.append(lane.tolist())
|
||||
|
||||
return lanes
|
||||
|
||||
def pred2tusimpleformat(self, idx, pred, runtime):
|
||||
runtime *= 1000. # s to ms
|
||||
img_name = self.data_infos[idx]['img_name']
|
||||
lanes = self.pred2lanes(pred)
|
||||
output = {'raw_file': img_name, 'lanes': lanes, 'run_time': runtime}
|
||||
return json.dumps(output)
|
||||
|
||||
def save_tusimple_predictions(self, predictions, filename, runtimes=None):
|
||||
if runtimes is None:
|
||||
runtimes = np.ones(len(predictions)) * 1.e-3
|
||||
lines = []
|
||||
for idx, (prediction, runtime) in enumerate(zip(predictions,
|
||||
runtimes)):
|
||||
line = self.pred2tusimpleformat(idx, prediction, runtime)
|
||||
lines.append(line)
|
||||
with open(filename, 'w') as output_file:
|
||||
output_file.write('\n'.join(lines))
|
||||
|
||||
def evaluate(self, predictions, output_basedir, runtimes=None):
|
||||
pred_filename = os.path.join(output_basedir,
|
||||
'tusimple_predictions.json')
|
||||
self.save_tusimple_predictions(predictions, pred_filename, runtimes)
|
||||
result, acc = LaneEval.bench_one_submit(pred_filename,
|
||||
self.cfg.test_json_file)
|
||||
self.logger.info(result)
|
||||
return acc
|
||||
@@ -0,0 +1,13 @@
|
||||
import torch
|
||||
|
||||
|
||||
def build_optimizer(cfg, net):
|
||||
params = []
|
||||
cfg_cp = cfg.optimizer.copy()
|
||||
cfg_type = cfg_cp.pop('type')
|
||||
|
||||
if cfg_type not in dir(torch.optim):
|
||||
raise ValueError("{} is not defined.".format(cfg_type))
|
||||
|
||||
_optim = getattr(torch.optim, cfg_type)
|
||||
return _optim(net.parameters(), **cfg_cp)
|
||||
@@ -0,0 +1,22 @@
|
||||
from clrnet.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))
|
||||
148
algorithms/lane_ufld/code/CLRNet-main/clrnet/engine/runner.py
Normal file
148
algorithms/lane_ufld/code/CLRNet-main/clrnet/engine/runner.py
Normal file
@@ -0,0 +1,148 @@
|
||||
import time
|
||||
import cv2
|
||||
import torch
|
||||
from tqdm import tqdm
|
||||
import pytorch_warmup as warmup
|
||||
import numpy as np
|
||||
import random
|
||||
import os
|
||||
|
||||
from clrnet.models.registry import build_net
|
||||
from .registry import build_trainer, build_evaluator
|
||||
from .optimizer import build_optimizer
|
||||
from .scheduler import build_scheduler
|
||||
from clrnet.datasets import build_dataloader
|
||||
from clrnet.utils.recorder import build_recorder
|
||||
from clrnet.utils.net_utils import save_model, load_network, resume_network
|
||||
from mmcv.parallel import MMDataParallel
|
||||
|
||||
|
||||
class Runner(object):
|
||||
def __init__(self, cfg):
|
||||
torch.manual_seed(cfg.seed)
|
||||
np.random.seed(cfg.seed)
|
||||
random.seed(cfg.seed)
|
||||
self.cfg = cfg
|
||||
self.recorder = build_recorder(self.cfg)
|
||||
self.net = build_net(self.cfg)
|
||||
self.net = MMDataParallel(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.metric = 0.
|
||||
self.val_loader = None
|
||||
self.test_loader = None
|
||||
|
||||
def to_cuda(self, batch):
|
||||
for k in batch:
|
||||
if not isinstance(batch[k], torch.Tensor):
|
||||
continue
|
||||
batch[k] = batch[k].cuda()
|
||||
return batch
|
||||
|
||||
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 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.net(data)
|
||||
self.optimizer.zero_grad()
|
||||
loss = output['loss'].sum()
|
||||
loss.backward()
|
||||
self.optimizer.step()
|
||||
if not self.cfg.lr_update_by_epoch:
|
||||
self.scheduler.step()
|
||||
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('Build train loader...')
|
||||
train_loader = build_dataloader(self.cfg.dataset.train,
|
||||
self.cfg,
|
||||
is_train=True)
|
||||
|
||||
self.recorder.logger.info('Start training...')
|
||||
start_epoch = 0
|
||||
if self.cfg.resume_from:
|
||||
start_epoch = resume_network(self.cfg.resume_from, self.net,
|
||||
self.optimizer, self.scheduler,
|
||||
self.recorder)
|
||||
for epoch in range(start_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()
|
||||
if self.recorder.step >= self.cfg.total_iter:
|
||||
break
|
||||
if self.cfg.lr_update_by_epoch:
|
||||
self.scheduler.step()
|
||||
|
||||
|
||||
def test(self):
|
||||
if not self.test_loader:
|
||||
self.test_loader = build_dataloader(self.cfg.dataset.test,
|
||||
self.cfg,
|
||||
is_train=False)
|
||||
self.net.eval()
|
||||
predictions = []
|
||||
for i, data in enumerate(tqdm(self.test_loader, desc=f'Testing')):
|
||||
data = self.to_cuda(data)
|
||||
with torch.no_grad():
|
||||
output = self.net(data)
|
||||
output = self.net.module.heads.get_lanes(output)
|
||||
predictions.extend(output)
|
||||
if self.cfg.view:
|
||||
self.test_loader.dataset.view(output, data['meta'])
|
||||
|
||||
metric = self.test_loader.dataset.evaluate(predictions,
|
||||
self.cfg.work_dir)
|
||||
if metric is not None:
|
||||
self.recorder.logger.info('metric: ' + str(metric))
|
||||
|
||||
def validate(self):
|
||||
if not self.val_loader:
|
||||
self.val_loader = build_dataloader(self.cfg.dataset.val,
|
||||
self.cfg,
|
||||
is_train=False)
|
||||
self.net.eval()
|
||||
predictions = []
|
||||
for i, data in enumerate(tqdm(self.val_loader, desc=f'Validate')):
|
||||
data = self.to_cuda(data)
|
||||
with torch.no_grad():
|
||||
output = self.net(data)
|
||||
output = self.net.module.heads.get_lanes(output)
|
||||
predictions.extend(output)
|
||||
if self.cfg.view:
|
||||
self.val_loader.dataset.view(output, data['meta'])
|
||||
|
||||
metric = self.val_loader.dataset.evaluate(predictions,
|
||||
self.cfg.work_dir)
|
||||
self.recorder.logger.info('metric: ' + str(metric))
|
||||
|
||||
def save_ckpt(self, is_best=False):
|
||||
save_model(self.net, self.optimizer, self.scheduler, self.recorder,
|
||||
is_best)
|
||||
@@ -0,0 +1,15 @@
|
||||
import torch
|
||||
import math
|
||||
|
||||
|
||||
def build_scheduler(cfg, optimizer):
|
||||
|
||||
cfg_cp = cfg.scheduler.copy()
|
||||
cfg_type = cfg_cp.pop('type')
|
||||
|
||||
if cfg_type not in dir(torch.optim.lr_scheduler):
|
||||
raise ValueError("{} is not defined.".format(cfg_type))
|
||||
|
||||
_scheduler = getattr(torch.optim.lr_scheduler, cfg_type)
|
||||
|
||||
return _scheduler(optimizer, **cfg_cp)
|
||||
@@ -0,0 +1,5 @@
|
||||
from .backbones import *
|
||||
from .heads import *
|
||||
from .nets import *
|
||||
from .necks import *
|
||||
from .registry import build_backbones
|
||||
@@ -0,0 +1,2 @@
|
||||
from .resnet import ResNet
|
||||
from .dla34 import DLA
|
||||
@@ -0,0 +1,460 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import math
|
||||
import logging
|
||||
import numpy as np
|
||||
from os.path import join
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
import torch.nn.functional as F
|
||||
import torch.utils.model_zoo as model_zoo
|
||||
from clrnet.models.registry import BACKBONES
|
||||
|
||||
BN_MOMENTUM = 0.1
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_model_url(data='imagenet', name='dla34', hash='ba72cf86'):
|
||||
return join('http://dl.yf.io/dla/models', data,
|
||||
'{}-{}.pth'.format(name, hash))
|
||||
|
||||
|
||||
def conv3x3(in_planes, out_planes, stride=1):
|
||||
"3x3 convolution with padding"
|
||||
return nn.Conv2d(in_planes,
|
||||
out_planes,
|
||||
kernel_size=3,
|
||||
stride=stride,
|
||||
padding=1,
|
||||
bias=False)
|
||||
|
||||
|
||||
class BasicBlock(nn.Module):
|
||||
def __init__(self, inplanes, planes, stride=1, dilation=1):
|
||||
super(BasicBlock, self).__init__()
|
||||
self.conv1 = nn.Conv2d(inplanes,
|
||||
planes,
|
||||
kernel_size=3,
|
||||
stride=stride,
|
||||
padding=dilation,
|
||||
bias=False,
|
||||
dilation=dilation)
|
||||
self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.conv2 = nn.Conv2d(planes,
|
||||
planes,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=dilation,
|
||||
bias=False,
|
||||
dilation=dilation)
|
||||
self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM)
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x, residual=None):
|
||||
if residual is None:
|
||||
residual = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.bn1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.bn2(out)
|
||||
|
||||
out += residual
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class Bottleneck(nn.Module):
|
||||
expansion = 2
|
||||
|
||||
def __init__(self, inplanes, planes, stride=1, dilation=1):
|
||||
super(Bottleneck, self).__init__()
|
||||
expansion = Bottleneck.expansion
|
||||
bottle_planes = planes // expansion
|
||||
self.conv1 = nn.Conv2d(inplanes,
|
||||
bottle_planes,
|
||||
kernel_size=1,
|
||||
bias=False)
|
||||
self.bn1 = nn.BatchNorm2d(bottle_planes, momentum=BN_MOMENTUM)
|
||||
self.conv2 = nn.Conv2d(bottle_planes,
|
||||
bottle_planes,
|
||||
kernel_size=3,
|
||||
stride=stride,
|
||||
padding=dilation,
|
||||
bias=False,
|
||||
dilation=dilation)
|
||||
self.bn2 = nn.BatchNorm2d(bottle_planes, momentum=BN_MOMENTUM)
|
||||
self.conv3 = nn.Conv2d(bottle_planes,
|
||||
planes,
|
||||
kernel_size=1,
|
||||
bias=False)
|
||||
self.bn3 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x, residual=None):
|
||||
if residual is None:
|
||||
residual = 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)
|
||||
|
||||
out += residual
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class BottleneckX(nn.Module):
|
||||
expansion = 2
|
||||
cardinality = 32
|
||||
|
||||
def __init__(self, inplanes, planes, stride=1, dilation=1):
|
||||
super(BottleneckX, self).__init__()
|
||||
cardinality = BottleneckX.cardinality
|
||||
# dim = int(math.floor(planes * (BottleneckV5.expansion / 64.0)))
|
||||
# bottle_planes = dim * cardinality
|
||||
bottle_planes = planes * cardinality // 32
|
||||
self.conv1 = nn.Conv2d(inplanes,
|
||||
bottle_planes,
|
||||
kernel_size=1,
|
||||
bias=False)
|
||||
self.bn1 = nn.BatchNorm2d(bottle_planes, momentum=BN_MOMENTUM)
|
||||
self.conv2 = nn.Conv2d(bottle_planes,
|
||||
bottle_planes,
|
||||
kernel_size=3,
|
||||
stride=stride,
|
||||
padding=dilation,
|
||||
bias=False,
|
||||
dilation=dilation,
|
||||
groups=cardinality)
|
||||
self.bn2 = nn.BatchNorm2d(bottle_planes, momentum=BN_MOMENTUM)
|
||||
self.conv3 = nn.Conv2d(bottle_planes,
|
||||
planes,
|
||||
kernel_size=1,
|
||||
bias=False)
|
||||
self.bn3 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x, residual=None):
|
||||
if residual is None:
|
||||
residual = 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)
|
||||
|
||||
out += residual
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class Root(nn.Module):
|
||||
def __init__(self, in_channels, out_channels, kernel_size, residual):
|
||||
super(Root, self).__init__()
|
||||
self.conv = nn.Conv2d(in_channels,
|
||||
out_channels,
|
||||
1,
|
||||
stride=1,
|
||||
bias=False,
|
||||
padding=(kernel_size - 1) // 2)
|
||||
self.bn = nn.BatchNorm2d(out_channels, momentum=BN_MOMENTUM)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.residual = residual
|
||||
|
||||
def forward(self, *x):
|
||||
children = x
|
||||
x = self.conv(torch.cat(x, 1))
|
||||
x = self.bn(x)
|
||||
if self.residual:
|
||||
x += children[0]
|
||||
x = self.relu(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class Tree(nn.Module):
|
||||
def __init__(self,
|
||||
levels,
|
||||
block,
|
||||
in_channels,
|
||||
out_channels,
|
||||
stride=1,
|
||||
level_root=False,
|
||||
root_dim=0,
|
||||
root_kernel_size=1,
|
||||
dilation=1,
|
||||
root_residual=False):
|
||||
super(Tree, self).__init__()
|
||||
if root_dim == 0:
|
||||
root_dim = 2 * out_channels
|
||||
if level_root:
|
||||
root_dim += in_channels
|
||||
if levels == 1:
|
||||
self.tree1 = block(in_channels,
|
||||
out_channels,
|
||||
stride,
|
||||
dilation=dilation)
|
||||
self.tree2 = block(out_channels,
|
||||
out_channels,
|
||||
1,
|
||||
dilation=dilation)
|
||||
else:
|
||||
self.tree1 = Tree(levels - 1,
|
||||
block,
|
||||
in_channels,
|
||||
out_channels,
|
||||
stride,
|
||||
root_dim=0,
|
||||
root_kernel_size=root_kernel_size,
|
||||
dilation=dilation,
|
||||
root_residual=root_residual)
|
||||
self.tree2 = Tree(levels - 1,
|
||||
block,
|
||||
out_channels,
|
||||
out_channels,
|
||||
root_dim=root_dim + out_channels,
|
||||
root_kernel_size=root_kernel_size,
|
||||
dilation=dilation,
|
||||
root_residual=root_residual)
|
||||
if levels == 1:
|
||||
self.root = Root(root_dim, out_channels, root_kernel_size,
|
||||
root_residual)
|
||||
self.level_root = level_root
|
||||
self.root_dim = root_dim
|
||||
self.downsample = None
|
||||
self.project = None
|
||||
self.levels = levels
|
||||
if stride > 1:
|
||||
self.downsample = nn.MaxPool2d(stride, stride=stride)
|
||||
# Match CLRerNet/official DLA: project only on leaf when channels differ.
|
||||
if levels == 1 and in_channels != out_channels:
|
||||
self.project = nn.Sequential(
|
||||
nn.Conv2d(in_channels,
|
||||
out_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
bias=False),
|
||||
nn.BatchNorm2d(out_channels, momentum=BN_MOMENTUM))
|
||||
|
||||
def forward(self, x, residual=None, children=None):
|
||||
children = [] if children is None else children
|
||||
bottom = self.downsample(x) if self.downsample else x
|
||||
residual = self.project(bottom) if self.project else bottom
|
||||
if self.level_root:
|
||||
children.append(bottom)
|
||||
x1 = self.tree1(x, residual)
|
||||
if self.levels == 1:
|
||||
x2 = self.tree2(x1)
|
||||
x = self.root(x2, x1, *children)
|
||||
else:
|
||||
children.append(x1)
|
||||
x = self.tree2(x1, children=children)
|
||||
return x
|
||||
|
||||
|
||||
class DLA(nn.Module):
|
||||
def __init__(self,
|
||||
levels,
|
||||
channels,
|
||||
num_classes=1000,
|
||||
block=BasicBlock,
|
||||
residual_root=False,
|
||||
linear_root=False):
|
||||
super(DLA, self).__init__()
|
||||
self.channels = channels
|
||||
self.num_classes = num_classes
|
||||
self.base_layer = nn.Sequential(
|
||||
nn.Conv2d(3,
|
||||
channels[0],
|
||||
kernel_size=7,
|
||||
stride=1,
|
||||
padding=3,
|
||||
bias=False),
|
||||
nn.BatchNorm2d(channels[0], momentum=BN_MOMENTUM),
|
||||
nn.ReLU(inplace=True))
|
||||
self.level0 = self._make_conv_level(channels[0], channels[0],
|
||||
levels[0])
|
||||
self.level1 = self._make_conv_level(channels[0],
|
||||
channels[1],
|
||||
levels[1],
|
||||
stride=2)
|
||||
self.level2 = Tree(levels[2],
|
||||
block,
|
||||
channels[1],
|
||||
channels[2],
|
||||
2,
|
||||
level_root=False,
|
||||
root_residual=residual_root)
|
||||
self.level3 = Tree(levels[3],
|
||||
block,
|
||||
channels[2],
|
||||
channels[3],
|
||||
2,
|
||||
level_root=True,
|
||||
root_residual=residual_root)
|
||||
self.level4 = Tree(levels[4],
|
||||
block,
|
||||
channels[3],
|
||||
channels[4],
|
||||
2,
|
||||
level_root=True,
|
||||
root_residual=residual_root)
|
||||
self.level5 = Tree(levels[5],
|
||||
block,
|
||||
channels[4],
|
||||
channels[5],
|
||||
2,
|
||||
level_root=True,
|
||||
root_residual=residual_root)
|
||||
|
||||
# for m in self.modules():
|
||||
# if isinstance(m, nn.Conv2d):
|
||||
# n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
|
||||
# m.weight.data.normal_(0, math.sqrt(2. / n))
|
||||
# elif isinstance(m, nn.BatchNorm2d):
|
||||
# m.weight.data.fill_(1)
|
||||
# m.bias.data.zero_()
|
||||
|
||||
def _make_level(self, block, inplanes, planes, blocks, stride=1):
|
||||
downsample = None
|
||||
if stride != 1 or inplanes != planes:
|
||||
downsample = nn.Sequential(
|
||||
nn.MaxPool2d(stride, stride=stride),
|
||||
nn.Conv2d(inplanes,
|
||||
planes,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
bias=False),
|
||||
nn.BatchNorm2d(planes, momentum=BN_MOMENTUM),
|
||||
)
|
||||
|
||||
layers = []
|
||||
layers.append(block(inplanes, planes, stride, downsample=downsample))
|
||||
for i in range(1, blocks):
|
||||
layers.append(block(inplanes, planes))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def _make_conv_level(self, inplanes, planes, convs, stride=1, dilation=1):
|
||||
modules = []
|
||||
for i in range(convs):
|
||||
modules.extend([
|
||||
nn.Conv2d(inplanes,
|
||||
planes,
|
||||
kernel_size=3,
|
||||
stride=stride if i == 0 else 1,
|
||||
padding=dilation,
|
||||
bias=False,
|
||||
dilation=dilation),
|
||||
nn.BatchNorm2d(planes, momentum=BN_MOMENTUM),
|
||||
nn.ReLU(inplace=True)
|
||||
])
|
||||
inplanes = planes
|
||||
return nn.Sequential(*modules)
|
||||
|
||||
def forward(self, x):
|
||||
y = []
|
||||
x = self.base_layer(x)
|
||||
for i in range(6):
|
||||
x = getattr(self, 'level{}'.format(i))(x)
|
||||
y.append(x)
|
||||
return y[2:]
|
||||
|
||||
def load_pretrained_model(self,
|
||||
data='imagenet',
|
||||
name='dla34',
|
||||
hash='ba72cf86'):
|
||||
# fc = self.fc
|
||||
if name.endswith('.pth'):
|
||||
model_weights = torch.load(data + name)
|
||||
else:
|
||||
model_url = get_model_url(data, name, hash)
|
||||
model_weights = model_zoo.load_url(model_url)
|
||||
self.load_state_dict(model_weights, strict=False)
|
||||
# self.fc = fc
|
||||
|
||||
|
||||
def dla34(pretrained=True, levels=None, in_channels=None, **kwargs): # DLA-34
|
||||
model = DLA(levels=levels,
|
||||
channels=in_channels,
|
||||
block=BasicBlock,
|
||||
**kwargs)
|
||||
if pretrained:
|
||||
model.load_pretrained_model(data='imagenet',
|
||||
name='dla34',
|
||||
hash='ba72cf86')
|
||||
return model
|
||||
|
||||
|
||||
@BACKBONES.register_module
|
||||
class DLAWrapper(nn.Module):
|
||||
def __init__(self,
|
||||
dla='dla34',
|
||||
pretrained=True,
|
||||
levels=[1, 1, 1, 2, 2, 1],
|
||||
in_channels=[16, 32, 64, 128, 256, 512],
|
||||
cfg=None):
|
||||
super(DLAWrapper, self).__init__()
|
||||
self.cfg = cfg
|
||||
self.in_channels = in_channels
|
||||
|
||||
self.model = eval(dla)(pretrained=pretrained,
|
||||
levels=levels,
|
||||
in_channels=in_channels)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.model(x)
|
||||
return x
|
||||
|
||||
|
||||
class Identity(nn.Module):
|
||||
def __init__(self):
|
||||
super(Identity, self).__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x
|
||||
|
||||
|
||||
def fill_fc_weights(layers):
|
||||
for m in layers.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
if m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
|
||||
|
||||
def fill_up_weights(up):
|
||||
w = up.weight.data
|
||||
f = math.ceil(w.size(2) / 2)
|
||||
c = (2 * f - 1 - f % 2) / (2. * f)
|
||||
for i in range(w.size(2)):
|
||||
for j in range(w.size(3)):
|
||||
w[0, 0, i, j] = \
|
||||
(1 - math.fabs(i / f - c)) * (1 - math.fabs(j / f - c))
|
||||
for c in range(1, w.size(0)):
|
||||
w[c, 0, :, :] = w[0, 0, :, :]
|
||||
@@ -0,0 +1,431 @@
|
||||
import torch
|
||||
from torch import nn
|
||||
import torch.nn.functional as F
|
||||
from torch.hub import load_state_dict_from_url
|
||||
|
||||
from clrnet.models.registry import BACKBONES
|
||||
|
||||
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
|
||||
|
||||
|
||||
@BACKBONES.register_module
|
||||
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):
|
||||
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" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
||||
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" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
||||
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" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
||||
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" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
||||
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" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
||||
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" <https://arxiv.org/pdf/1611.05431.pdf>`_
|
||||
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" <https://arxiv.org/pdf/1611.05431.pdf>`_
|
||||
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" <https://arxiv.org/pdf/1605.07146.pdf>`_
|
||||
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" <https://arxiv.org/pdf/1605.07146.pdf>`_
|
||||
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)
|
||||
@@ -0,0 +1 @@
|
||||
from .clr_head import CLRHead
|
||||
@@ -0,0 +1,492 @@
|
||||
import math
|
||||
|
||||
import cv2
|
||||
import torch
|
||||
import numpy as np
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import ConvModule
|
||||
|
||||
from clrnet.utils.lane import Lane
|
||||
from clrnet.models.losses.focal_loss import FocalLoss
|
||||
from clrnet.models.losses.accuracy import accuracy
|
||||
from clrnet.ops import nms
|
||||
|
||||
from clrnet.models.utils.roi_gather import ROIGather, LinearModule
|
||||
from clrnet.models.utils.seg_decoder import SegDecoder
|
||||
from clrnet.models.utils.dynamic_assign import assign
|
||||
from clrnet.models.losses.lineiou_loss import liou_loss
|
||||
from clrnet.utils.bilinear_grid_sample import bilinear_grid_sample
|
||||
from ..registry import HEADS
|
||||
|
||||
# Set True for ONNX/RKNN export (Gather+Pad instead of GridSample).
|
||||
USE_BILINEAR_GRID_SAMPLE = True
|
||||
|
||||
|
||||
@HEADS.register_module
|
||||
class CLRHead(nn.Module):
|
||||
def __init__(self,
|
||||
num_points=72,
|
||||
prior_feat_channels=64,
|
||||
fc_hidden_dim=64,
|
||||
num_priors=192,
|
||||
num_fc=2,
|
||||
refine_layers=3,
|
||||
sample_points=36,
|
||||
cfg=None):
|
||||
super(CLRHead, self).__init__()
|
||||
self.cfg = cfg
|
||||
self.img_w = self.cfg.img_w
|
||||
self.img_h = self.cfg.img_h
|
||||
self.n_strips = num_points - 1
|
||||
self.n_offsets = num_points
|
||||
self.num_priors = num_priors
|
||||
self.sample_points = sample_points
|
||||
self.refine_layers = refine_layers
|
||||
self.fc_hidden_dim = fc_hidden_dim
|
||||
|
||||
self.register_buffer(name='sample_x_indexs', tensor=(torch.linspace(
|
||||
0, 1, steps=self.sample_points, dtype=torch.float32) *
|
||||
self.n_strips).long())
|
||||
self.register_buffer(name='prior_feat_ys', tensor=torch.flip(
|
||||
(1 - self.sample_x_indexs.float() / self.n_strips), dims=[-1]))
|
||||
self.register_buffer(name='prior_ys', tensor=torch.linspace(1,
|
||||
0,
|
||||
steps=self.n_offsets,
|
||||
dtype=torch.float32))
|
||||
|
||||
self.prior_feat_channels = prior_feat_channels
|
||||
|
||||
self._init_prior_embeddings()
|
||||
init_priors, priors_on_featmap = self.generate_priors_from_embeddings() #None, None
|
||||
self.register_buffer(name='priors', tensor=init_priors)
|
||||
self.register_buffer(name='priors_on_featmap', tensor=priors_on_featmap)
|
||||
|
||||
# generate xys for feature map
|
||||
self.seg_decoder = SegDecoder(self.img_h, self.img_w,
|
||||
self.cfg.num_classes,
|
||||
self.prior_feat_channels,
|
||||
self.refine_layers)
|
||||
|
||||
reg_modules = list()
|
||||
cls_modules = list()
|
||||
for _ in range(num_fc):
|
||||
reg_modules += [*LinearModule(self.fc_hidden_dim)]
|
||||
cls_modules += [*LinearModule(self.fc_hidden_dim)]
|
||||
self.reg_modules = nn.ModuleList(reg_modules)
|
||||
self.cls_modules = nn.ModuleList(cls_modules)
|
||||
|
||||
self.roi_gather = ROIGather(self.prior_feat_channels, self.num_priors,
|
||||
self.sample_points, self.fc_hidden_dim,
|
||||
self.refine_layers)
|
||||
|
||||
self.reg_layers = nn.Linear(
|
||||
self.fc_hidden_dim, self.n_offsets + 1 + 2 +
|
||||
1) # n offsets + 1 length + start_x + start_y + theta
|
||||
self.cls_layers = nn.Linear(self.fc_hidden_dim, 2)
|
||||
|
||||
weights = torch.ones(self.cfg.num_classes)
|
||||
weights[0] = self.cfg.bg_weight
|
||||
self.criterion = torch.nn.NLLLoss(ignore_index=self.cfg.ignore_label,
|
||||
weight=weights)
|
||||
|
||||
# init the weights here
|
||||
self.init_weights()
|
||||
|
||||
# function to init layer weights
|
||||
def init_weights(self):
|
||||
# initialize heads
|
||||
for m in self.cls_layers.parameters():
|
||||
nn.init.normal_(m, mean=0., std=1e-3)
|
||||
|
||||
for m in self.reg_layers.parameters():
|
||||
nn.init.normal_(m, mean=0., std=1e-3)
|
||||
|
||||
def pool_prior_features(self, batch_features, num_priors, prior_xs):
|
||||
'''
|
||||
pool prior feature from feature map.
|
||||
Args:
|
||||
batch_features (Tensor): Input feature maps, shape: (B, C, H, W)
|
||||
'''
|
||||
|
||||
batch_size = batch_features.shape[0]
|
||||
|
||||
prior_xs = prior_xs.view(batch_size, num_priors, -1, 1)
|
||||
prior_ys = self.prior_feat_ys.repeat(batch_size * num_priors).view(
|
||||
batch_size, num_priors, -1, 1)
|
||||
|
||||
prior_xs = prior_xs * 2. - 1.
|
||||
prior_ys = prior_ys * 2. - 1.
|
||||
grid = torch.cat((prior_xs, prior_ys), dim=-1)
|
||||
if USE_BILINEAR_GRID_SAMPLE:
|
||||
feature = bilinear_grid_sample(batch_features, grid,
|
||||
align_corners=True).permute(0, 2, 1, 3)
|
||||
else:
|
||||
feature = F.grid_sample(batch_features, grid,
|
||||
align_corners=True).permute(0, 2, 1, 3)
|
||||
|
||||
feature = feature.reshape(batch_size * num_priors,
|
||||
self.prior_feat_channels, self.sample_points,
|
||||
1)
|
||||
return feature
|
||||
|
||||
def generate_priors_from_embeddings(self):
|
||||
predictions = self.prior_embeddings.weight # (num_prop, 3)
|
||||
|
||||
# 2 scores, 1 start_y, 1 start_x, 1 theta, 1 length, 72 coordinates, score[0] = negative prob, score[1] = positive prob
|
||||
priors = predictions.new_zeros(
|
||||
(self.num_priors, 2 + 2 + 2 + self.n_offsets), device=predictions.device)
|
||||
|
||||
priors[:, 2:5] = predictions.clone()
|
||||
priors[:, 6:] = (
|
||||
priors[:, 3].unsqueeze(1).clone().repeat(1, self.n_offsets) *
|
||||
(self.img_w - 1) +
|
||||
((1 - self.prior_ys.repeat(self.num_priors, 1) -
|
||||
priors[:, 2].unsqueeze(1).clone().repeat(1, self.n_offsets)) *
|
||||
self.img_h / torch.tan(priors[:, 4].unsqueeze(1).clone().repeat(
|
||||
1, self.n_offsets) * math.pi + 1e-5))) / (self.img_w - 1)
|
||||
|
||||
# init priors on feature map
|
||||
priors_on_featmap = priors.clone()[..., 6 + self.sample_x_indexs]
|
||||
|
||||
return priors, priors_on_featmap
|
||||
|
||||
def _init_prior_embeddings(self):
|
||||
# [start_y, start_x, theta] -> all normalize
|
||||
self.prior_embeddings = nn.Embedding(self.num_priors, 3)
|
||||
|
||||
bottom_priors_nums = self.num_priors * 3 // 4
|
||||
left_priors_nums, _ = self.num_priors // 8, self.num_priors // 8
|
||||
|
||||
strip_size = 0.5 / (left_priors_nums // 2 - 1)
|
||||
bottom_strip_size = 1 / (bottom_priors_nums // 4 + 1)
|
||||
for i in range(left_priors_nums):
|
||||
nn.init.constant_(self.prior_embeddings.weight[i, 0],
|
||||
(i // 2) * strip_size)
|
||||
nn.init.constant_(self.prior_embeddings.weight[i, 1], 0.)
|
||||
nn.init.constant_(self.prior_embeddings.weight[i, 2],
|
||||
0.16 if i % 2 == 0 else 0.32)
|
||||
|
||||
for i in range(left_priors_nums,
|
||||
left_priors_nums + bottom_priors_nums):
|
||||
nn.init.constant_(self.prior_embeddings.weight[i, 0], 0.)
|
||||
nn.init.constant_(self.prior_embeddings.weight[i, 1],
|
||||
((i - left_priors_nums) // 4 + 1) *
|
||||
bottom_strip_size)
|
||||
nn.init.constant_(self.prior_embeddings.weight[i, 2],
|
||||
0.2 * (i % 4 + 1))
|
||||
|
||||
for i in range(left_priors_nums + bottom_priors_nums, self.num_priors):
|
||||
nn.init.constant_(
|
||||
self.prior_embeddings.weight[i, 0],
|
||||
((i - left_priors_nums - bottom_priors_nums) // 2) *
|
||||
strip_size)
|
||||
nn.init.constant_(self.prior_embeddings.weight[i, 1], 1.)
|
||||
nn.init.constant_(self.prior_embeddings.weight[i, 2],
|
||||
0.68 if i % 2 == 0 else 0.84)
|
||||
|
||||
# forward function here
|
||||
def forward(self, x, **kwargs):
|
||||
'''
|
||||
Take pyramid features as input to perform Cross Layer Refinement and finally output the prediction lanes.
|
||||
Each feature is a 4D tensor.
|
||||
Args:
|
||||
x: input features (list[Tensor])
|
||||
Return:
|
||||
prediction_list: each layer's prediction result
|
||||
seg: segmentation result for auxiliary loss
|
||||
'''
|
||||
batch_features = list(x[len(x) - self.refine_layers:])
|
||||
batch_features.reverse()
|
||||
batch_size = batch_features[-1].shape[0]
|
||||
|
||||
if self.training:
|
||||
self.priors, self.priors_on_featmap = self.generate_priors_from_embeddings()
|
||||
|
||||
priors, priors_on_featmap = self.priors.repeat(batch_size, 1,
|
||||
1), self.priors_on_featmap.repeat(
|
||||
batch_size, 1, 1)
|
||||
|
||||
predictions_lists = []
|
||||
|
||||
# iterative refine
|
||||
prior_features_stages = []
|
||||
for stage in range(self.refine_layers):
|
||||
num_priors = priors_on_featmap.shape[1]
|
||||
prior_xs = torch.flip(priors_on_featmap, dims=[2])
|
||||
|
||||
batch_prior_features = self.pool_prior_features(
|
||||
batch_features[stage], num_priors, prior_xs)
|
||||
prior_features_stages.append(batch_prior_features)
|
||||
|
||||
fc_features = self.roi_gather(prior_features_stages,
|
||||
batch_features[stage], stage)
|
||||
|
||||
fc_features = fc_features.view(num_priors, batch_size,
|
||||
-1).reshape(batch_size * num_priors,
|
||||
self.fc_hidden_dim)
|
||||
|
||||
cls_features = fc_features.clone()
|
||||
reg_features = fc_features.clone()
|
||||
for cls_layer in self.cls_modules:
|
||||
cls_features = cls_layer(cls_features)
|
||||
for reg_layer in self.reg_modules:
|
||||
reg_features = reg_layer(reg_features)
|
||||
|
||||
cls_logits = self.cls_layers(cls_features)
|
||||
reg = self.reg_layers(reg_features)
|
||||
|
||||
cls_logits = cls_logits.reshape(
|
||||
batch_size, -1, cls_logits.shape[1]) # (B, num_priors, 2)
|
||||
reg = reg.reshape(batch_size, -1, reg.shape[1])
|
||||
|
||||
predictions = priors.clone()
|
||||
predictions[:, :, :2] = cls_logits
|
||||
|
||||
predictions[:, :,
|
||||
2:5] += reg[:, :, :3] # also reg theta angle here
|
||||
predictions[:, :, 5] = reg[:, :, 3] # length
|
||||
|
||||
def tran_tensor(t):
|
||||
return t.unsqueeze(2).clone().repeat(1, 1, self.n_offsets)
|
||||
|
||||
predictions[..., 6:] = (
|
||||
tran_tensor(predictions[..., 3]) * (self.img_w - 1) +
|
||||
((1 - self.prior_ys.repeat(batch_size, num_priors, 1) -
|
||||
tran_tensor(predictions[..., 2])) * self.img_h /
|
||||
torch.tan(tran_tensor(predictions[..., 4]) * math.pi + 1e-5))) / (self.img_w - 1)
|
||||
|
||||
prediction_lines = predictions.clone()
|
||||
predictions[..., 6:] += reg[..., 4:]
|
||||
|
||||
predictions_lists.append(predictions)
|
||||
|
||||
if stage != self.refine_layers - 1:
|
||||
priors = prediction_lines.detach().clone()
|
||||
priors_on_featmap = priors[..., 6 + self.sample_x_indexs]
|
||||
|
||||
if self.training:
|
||||
seg = None
|
||||
seg_features = torch.cat([
|
||||
F.interpolate(feature,
|
||||
size=[
|
||||
batch_features[-1].shape[2],
|
||||
batch_features[-1].shape[3]
|
||||
],
|
||||
mode='bilinear',
|
||||
align_corners=False)
|
||||
for feature in batch_features
|
||||
],
|
||||
dim=1)
|
||||
seg = self.seg_decoder(seg_features)
|
||||
output = {'predictions_lists': predictions_lists, 'seg': seg}
|
||||
return self.loss(output, kwargs['batch'])
|
||||
|
||||
return predictions_lists[-1]
|
||||
|
||||
def predictions_to_pred(self, predictions):
|
||||
'''
|
||||
Convert predictions to internal Lane structure for evaluation.
|
||||
'''
|
||||
self.prior_ys = self.prior_ys.to(predictions.device)
|
||||
self.prior_ys = self.prior_ys.double()
|
||||
lanes = []
|
||||
for lane in predictions:
|
||||
lane_xs = lane[6:] # normalized value
|
||||
start = min(max(0, int(round(lane[2].item() * self.n_strips))),
|
||||
self.n_strips)
|
||||
length = int(round(lane[5].item()))
|
||||
end = start + length - 1
|
||||
end = min(end, len(self.prior_ys) - 1)
|
||||
# end = label_end
|
||||
# if the prediction does not start at the bottom of the image,
|
||||
# extend its prediction until the x is outside the image
|
||||
mask = ~((((lane_xs[:start] >= 0.) & (lane_xs[:start] <= 1.)
|
||||
).cpu().numpy()[::-1].cumprod()[::-1]).astype(np.bool))
|
||||
lane_xs[end + 1:] = -2
|
||||
lane_xs[:start][mask] = -2
|
||||
lane_ys = self.prior_ys[lane_xs >= 0]
|
||||
lane_xs = lane_xs[lane_xs >= 0]
|
||||
lane_xs = lane_xs.flip(0).double()
|
||||
lane_ys = lane_ys.flip(0)
|
||||
|
||||
lane_ys = (lane_ys * (self.cfg.ori_img_h - self.cfg.cut_height) +
|
||||
self.cfg.cut_height) / self.cfg.ori_img_h
|
||||
if len(lane_xs) <= 1:
|
||||
continue
|
||||
points = torch.stack(
|
||||
(lane_xs.reshape(-1, 1), lane_ys.reshape(-1, 1)),
|
||||
dim=1).squeeze(2)
|
||||
lane = Lane(points=points.cpu().numpy(),
|
||||
metadata={
|
||||
'start_x': lane[3],
|
||||
'start_y': lane[2],
|
||||
'conf': lane[1]
|
||||
})
|
||||
lanes.append(lane)
|
||||
return lanes
|
||||
|
||||
def loss(self,
|
||||
output,
|
||||
batch,
|
||||
cls_loss_weight=2.,
|
||||
xyt_loss_weight=0.5,
|
||||
iou_loss_weight=2.,
|
||||
seg_loss_weight=1.):
|
||||
if self.cfg.haskey('cls_loss_weight'):
|
||||
cls_loss_weight = self.cfg.cls_loss_weight
|
||||
if self.cfg.haskey('xyt_loss_weight'):
|
||||
xyt_loss_weight = self.cfg.xyt_loss_weight
|
||||
if self.cfg.haskey('iou_loss_weight'):
|
||||
iou_loss_weight = self.cfg.iou_loss_weight
|
||||
if self.cfg.haskey('seg_loss_weight'):
|
||||
seg_loss_weight = self.cfg.seg_loss_weight
|
||||
|
||||
predictions_lists = output['predictions_lists']
|
||||
targets = batch['lane_line'].clone()
|
||||
cls_criterion = FocalLoss(alpha=0.25, gamma=2.)
|
||||
cls_loss = 0
|
||||
reg_xytl_loss = 0
|
||||
iou_loss = 0
|
||||
cls_acc = []
|
||||
|
||||
cls_acc_stage = []
|
||||
for stage in range(self.refine_layers):
|
||||
predictions_list = predictions_lists[stage]
|
||||
for predictions, target in zip(predictions_list, targets):
|
||||
target = target[target[:, 1] == 1]
|
||||
|
||||
if len(target) == 0:
|
||||
# If there are no targets, all predictions have to be negatives (i.e., 0 confidence)
|
||||
cls_target = predictions.new_zeros(predictions.shape[0]).long()
|
||||
cls_pred = predictions[:, :2]
|
||||
cls_loss = cls_loss + cls_criterion(
|
||||
cls_pred, cls_target).sum()
|
||||
continue
|
||||
|
||||
with torch.no_grad():
|
||||
matched_row_inds, matched_col_inds = assign(
|
||||
predictions, target, self.img_w, self.img_h)
|
||||
|
||||
# classification targets
|
||||
cls_target = predictions.new_zeros(predictions.shape[0]).long()
|
||||
cls_target[matched_row_inds] = 1
|
||||
cls_pred = predictions[:, :2]
|
||||
|
||||
# regression targets -> [start_y, start_x, theta] (all transformed to absolute values), only on matched pairs
|
||||
reg_yxtl = predictions[matched_row_inds, 2:6]
|
||||
reg_yxtl[:, 0] *= self.n_strips
|
||||
reg_yxtl[:, 1] *= (self.img_w - 1)
|
||||
reg_yxtl[:, 2] *= 180
|
||||
reg_yxtl[:, 3] *= self.n_strips
|
||||
|
||||
target_yxtl = target[matched_col_inds, 2:6].clone()
|
||||
|
||||
# regression targets -> S coordinates (all transformed to absolute values)
|
||||
reg_pred = predictions[matched_row_inds, 6:]
|
||||
reg_pred *= (self.img_w - 1)
|
||||
reg_targets = target[matched_col_inds, 6:].clone()
|
||||
|
||||
with torch.no_grad():
|
||||
predictions_starts = torch.clamp(
|
||||
(predictions[matched_row_inds, 2] *
|
||||
self.n_strips).round().long(), 0,
|
||||
self.n_strips) # ensure the predictions starts is valid
|
||||
target_starts = (target[matched_col_inds, 2] *
|
||||
self.n_strips).round().long()
|
||||
target_yxtl[:, -1] -= (predictions_starts - target_starts
|
||||
) # reg length
|
||||
|
||||
# Loss calculation
|
||||
cls_loss = cls_loss + cls_criterion(cls_pred, cls_target).sum(
|
||||
) / target.shape[0]
|
||||
|
||||
target_yxtl[:, 0] *= self.n_strips
|
||||
target_yxtl[:, 2] *= 180
|
||||
reg_xytl_loss = reg_xytl_loss + F.smooth_l1_loss(
|
||||
reg_yxtl, target_yxtl,
|
||||
reduction='none').mean()
|
||||
|
||||
iou_loss = iou_loss + liou_loss(
|
||||
reg_pred, reg_targets,
|
||||
self.img_w, length=15)
|
||||
|
||||
# calculate acc
|
||||
cls_accuracy = accuracy(cls_pred, cls_target)
|
||||
cls_acc_stage.append(cls_accuracy)
|
||||
|
||||
cls_acc.append(sum(cls_acc_stage) / len(cls_acc_stage))
|
||||
|
||||
# extra segmentation loss
|
||||
seg_loss = self.criterion(F.log_softmax(output['seg'], dim=1),
|
||||
batch['seg'].long())
|
||||
|
||||
cls_loss /= (len(targets) * self.refine_layers)
|
||||
reg_xytl_loss /= (len(targets) * self.refine_layers)
|
||||
iou_loss /= (len(targets) * self.refine_layers)
|
||||
|
||||
loss = cls_loss * cls_loss_weight + reg_xytl_loss * xyt_loss_weight \
|
||||
+ seg_loss * seg_loss_weight + iou_loss * iou_loss_weight
|
||||
|
||||
return_value = {
|
||||
'loss': loss,
|
||||
'loss_stats': {
|
||||
'loss': loss,
|
||||
'cls_loss': cls_loss * cls_loss_weight,
|
||||
'reg_xytl_loss': reg_xytl_loss * xyt_loss_weight,
|
||||
'seg_loss': seg_loss * seg_loss_weight,
|
||||
'iou_loss': iou_loss * iou_loss_weight
|
||||
}
|
||||
}
|
||||
|
||||
for i in range(self.refine_layers):
|
||||
return_value['loss_stats']['stage_{}_acc'.format(i)] = cls_acc[i]
|
||||
|
||||
return return_value
|
||||
|
||||
|
||||
def get_lanes(self, output, as_lanes=True):
|
||||
'''
|
||||
Convert model output to lanes.
|
||||
'''
|
||||
softmax = nn.Softmax(dim=1)
|
||||
|
||||
decoded = []
|
||||
for predictions in output:
|
||||
# filter out the conf lower than conf threshold
|
||||
threshold = self.cfg.test_parameters.conf_threshold
|
||||
scores = softmax(predictions[:, :2])[:, 1]
|
||||
keep_inds = scores >= threshold
|
||||
predictions = predictions[keep_inds]
|
||||
scores = scores[keep_inds]
|
||||
|
||||
if predictions.shape[0] == 0:
|
||||
decoded.append([])
|
||||
continue
|
||||
nms_predictions = predictions.detach().clone()
|
||||
nms_predictions = torch.cat(
|
||||
[nms_predictions[..., :4], nms_predictions[..., 5:]], dim=-1)
|
||||
nms_predictions[..., 4] = nms_predictions[..., 4] * self.n_strips
|
||||
nms_predictions[...,
|
||||
5:] = nms_predictions[..., 5:] * (self.img_w - 1)
|
||||
|
||||
keep, num_to_keep, _ = nms(
|
||||
nms_predictions,
|
||||
scores,
|
||||
overlap=self.cfg.test_parameters.nms_thres,
|
||||
top_k=self.cfg.max_lanes)
|
||||
keep = keep[:num_to_keep]
|
||||
predictions = predictions[keep]
|
||||
|
||||
if predictions.shape[0] == 0:
|
||||
decoded.append([])
|
||||
continue
|
||||
|
||||
predictions[:, 5] = torch.round(predictions[:, 5] * self.n_strips)
|
||||
if as_lanes:
|
||||
pred = self.predictions_to_pred(predictions)
|
||||
else:
|
||||
pred = predictions
|
||||
decoded.append(pred)
|
||||
|
||||
return decoded
|
||||
@@ -0,0 +1,77 @@
|
||||
import mmcv
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
@mmcv.jit(coderize=True)
|
||||
def accuracy(pred, target, topk=1, thresh=None):
|
||||
"""Calculate accuracy according to the prediction and target.
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): The model prediction, shape (N, num_class)
|
||||
target (torch.Tensor): The target of each prediction, shape (N, )
|
||||
topk (int | tuple[int], optional): If the predictions in ``topk``
|
||||
matches the target, the predictions will be regarded as
|
||||
correct ones. Defaults to 1.
|
||||
thresh (float, optional): If not None, predictions with scores under
|
||||
this threshold are considered incorrect. Default to None.
|
||||
|
||||
Returns:
|
||||
float | tuple[float]: If the input ``topk`` is a single integer,
|
||||
the function will return a single float as accuracy. If
|
||||
``topk`` is a tuple containing multiple integers, the
|
||||
function will return a tuple containing accuracies of
|
||||
each ``topk`` number.
|
||||
"""
|
||||
assert isinstance(topk, (int, tuple))
|
||||
if isinstance(topk, int):
|
||||
topk = (topk, )
|
||||
return_single = True
|
||||
else:
|
||||
return_single = False
|
||||
|
||||
maxk = max(topk)
|
||||
if pred.size(0) == 0:
|
||||
accu = [pred.new_tensor(0.) for i in range(len(topk))]
|
||||
return accu[0] if return_single else accu
|
||||
assert pred.ndim == 2 and target.ndim == 1
|
||||
assert pred.size(0) == target.size(0)
|
||||
assert maxk <= pred.size(1), \
|
||||
f'maxk {maxk} exceeds pred dimension {pred.size(1)}'
|
||||
pred_value, pred_label = pred.topk(maxk, dim=1)
|
||||
pred_label = pred_label.t() # transpose to shape (maxk, N)
|
||||
correct = pred_label.eq(target.view(1, -1).expand_as(pred_label))
|
||||
if thresh is not None:
|
||||
# Only prediction values larger than thresh are counted as correct
|
||||
correct = correct & (pred_value > thresh).t()
|
||||
res = []
|
||||
for k in topk:
|
||||
correct_k = correct[:k].reshape(-1).float().sum(0, keepdim=True)
|
||||
res.append(correct_k.mul_(100.0 / pred.size(0)))
|
||||
return res[0] if return_single else res
|
||||
|
||||
|
||||
class Accuracy(nn.Module):
|
||||
def __init__(self, topk=(1, ), thresh=None):
|
||||
"""Module to calculate the accuracy.
|
||||
|
||||
Args:
|
||||
topk (tuple, optional): The criterion used to calculate the
|
||||
accuracy. Defaults to (1,).
|
||||
thresh (float, optional): If not None, predictions with scores
|
||||
under this threshold are considered incorrect. Default to None.
|
||||
"""
|
||||
super().__init__()
|
||||
self.topk = topk
|
||||
self.thresh = thresh
|
||||
|
||||
def forward(self, pred, target):
|
||||
"""Forward function to calculate accuracy.
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): Prediction of models.
|
||||
target (torch.Tensor): Target for each prediction.
|
||||
|
||||
Returns:
|
||||
tuple[float]: The accuracies under different topk criterions.
|
||||
"""
|
||||
return accuracy(pred, target, self.topk, self.thresh)
|
||||
@@ -0,0 +1,191 @@
|
||||
# pylint: disable-all
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
# Source: https://github.com/kornia/kornia/blob/f4f70fefb63287f72bc80cd96df9c061b1cb60dd/kornia/losses/focal.py
|
||||
|
||||
|
||||
class SoftmaxFocalLoss(nn.Module):
|
||||
def __init__(self, gamma, ignore_lb=255, *args, **kwargs):
|
||||
super(SoftmaxFocalLoss, self).__init__()
|
||||
self.gamma = gamma
|
||||
self.nll = nn.NLLLoss(ignore_index=ignore_lb)
|
||||
|
||||
def forward(self, logits, labels):
|
||||
scores = F.softmax(logits, dim=1)
|
||||
factor = torch.pow(1. - scores, self.gamma)
|
||||
log_score = F.log_softmax(logits, dim=1)
|
||||
log_score = factor * log_score
|
||||
loss = self.nll(log_score, labels)
|
||||
return loss
|
||||
|
||||
|
||||
def one_hot(labels: torch.Tensor,
|
||||
num_classes: int,
|
||||
device: Optional[torch.device] = None,
|
||||
dtype: Optional[torch.dtype] = None,
|
||||
eps: Optional[float] = 1e-6) -> torch.Tensor:
|
||||
r"""Converts an integer label x-D tensor to a one-hot (x+1)-D tensor.
|
||||
|
||||
Args:
|
||||
labels (torch.Tensor) : tensor with labels of shape :math:`(N, *)`,
|
||||
where N is batch size. Each value is an integer
|
||||
representing correct classification.
|
||||
num_classes (int): number of classes in labels.
|
||||
device (Optional[torch.device]): the desired device of returned tensor.
|
||||
Default: if None, uses the current device for the default tensor type
|
||||
(see torch.set_default_tensor_type()). device will be the CPU for CPU
|
||||
tensor types and the current CUDA device for CUDA tensor types.
|
||||
dtype (Optional[torch.dtype]): the desired data type of returned
|
||||
tensor. Default: if None, infers data type from values.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: the labels in one hot tensor of shape :math:`(N, C, *)`,
|
||||
|
||||
Examples::
|
||||
>>> labels = torch.LongTensor([[[0, 1], [2, 0]]])
|
||||
>>> kornia.losses.one_hot(labels, num_classes=3)
|
||||
tensor([[[[1., 0.],
|
||||
[0., 1.]],
|
||||
[[0., 1.],
|
||||
[0., 0.]],
|
||||
[[0., 0.],
|
||||
[1., 0.]]]]
|
||||
"""
|
||||
if not torch.is_tensor(labels):
|
||||
raise TypeError(
|
||||
"Input labels type is not a torch.Tensor. Got {}".format(
|
||||
type(labels)))
|
||||
if not labels.dtype == torch.int64:
|
||||
raise ValueError(
|
||||
"labels must be of the same dtype torch.int64. Got: {}".format(
|
||||
labels.dtype))
|
||||
if num_classes < 1:
|
||||
raise ValueError("The number of classes must be bigger than one."
|
||||
" Got: {}".format(num_classes))
|
||||
shape = labels.shape
|
||||
one_hot = torch.zeros(shape[0],
|
||||
num_classes,
|
||||
*shape[1:],
|
||||
device=device,
|
||||
dtype=dtype)
|
||||
return one_hot.scatter_(1, labels.unsqueeze(1), 1.0) + eps
|
||||
|
||||
|
||||
def focal_loss(input: torch.Tensor,
|
||||
target: torch.Tensor,
|
||||
alpha: float,
|
||||
gamma: float = 2.0,
|
||||
reduction: str = 'none',
|
||||
eps: float = 1e-8) -> torch.Tensor:
|
||||
r"""Function that computes Focal loss.
|
||||
|
||||
See :class:`~kornia.losses.FocalLoss` for details.
|
||||
"""
|
||||
if not torch.is_tensor(input):
|
||||
raise TypeError("Input type is not a torch.Tensor. Got {}".format(
|
||||
type(input)))
|
||||
|
||||
if not len(input.shape) >= 2:
|
||||
raise ValueError(
|
||||
"Invalid input shape, we expect BxCx*. Got: {}".format(
|
||||
input.shape))
|
||||
|
||||
if input.size(0) != target.size(0):
|
||||
raise ValueError(
|
||||
'Expected input batch_size ({}) to match target batch_size ({}).'.
|
||||
format(input.size(0), target.size(0)))
|
||||
|
||||
n = input.size(0)
|
||||
out_size = (n, ) + input.size()[2:]
|
||||
if target.size()[1:] != input.size()[2:]:
|
||||
raise ValueError('Expected target size {}, got {}'.format(
|
||||
out_size, target.size()))
|
||||
|
||||
if not input.device == target.device:
|
||||
raise ValueError(
|
||||
"input and target must be in the same device. Got: {} and {}".
|
||||
format(input.device, target.device))
|
||||
|
||||
# compute softmax over the classes axis
|
||||
input_soft: torch.Tensor = F.softmax(input, dim=1) + eps
|
||||
|
||||
# create the labels one hot tensor
|
||||
target_one_hot: torch.Tensor = one_hot(target,
|
||||
num_classes=input.shape[1],
|
||||
device=input.device,
|
||||
dtype=input.dtype)
|
||||
|
||||
# compute the actual focal loss
|
||||
weight = torch.pow(-input_soft + 1., gamma)
|
||||
|
||||
focal = -alpha * weight * torch.log(input_soft)
|
||||
loss_tmp = torch.sum(target_one_hot * focal, dim=1)
|
||||
|
||||
if reduction == 'none':
|
||||
loss = loss_tmp
|
||||
elif reduction == 'mean':
|
||||
loss = torch.mean(loss_tmp)
|
||||
elif reduction == 'sum':
|
||||
loss = torch.sum(loss_tmp)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Invalid reduction mode: {}".format(reduction))
|
||||
return loss
|
||||
|
||||
|
||||
class FocalLoss(nn.Module):
|
||||
r"""Criterion that computes Focal loss.
|
||||
|
||||
According to [1], the Focal loss is computed as follows:
|
||||
|
||||
.. math::
|
||||
|
||||
\text{FL}(p_t) = -\alpha_t (1 - p_t)^{\gamma} \, \text{log}(p_t)
|
||||
|
||||
where:
|
||||
- :math:`p_t` is the model's estimated probability for each class.
|
||||
|
||||
|
||||
Arguments:
|
||||
alpha (float): Weighting factor :math:`\alpha \in [0, 1]`.
|
||||
gamma (float): Focusing parameter :math:`\gamma >= 0`.
|
||||
reduction (str, optional): Specifies the reduction to apply to the
|
||||
output: ‘none’ | ‘mean’ | ‘sum’. ‘none’: no reduction will be applied,
|
||||
‘mean’: the sum of the output will be divided by the number of elements
|
||||
in the output, ‘sum’: the output will be summed. Default: ‘none’.
|
||||
|
||||
Shape:
|
||||
- Input: :math:`(N, C, *)` where C = number of classes.
|
||||
- Target: :math:`(N, *)` where each value is
|
||||
:math:`0 ≤ targets[i] ≤ C−1`.
|
||||
|
||||
Examples:
|
||||
>>> N = 5 # num_classes
|
||||
>>> kwargs = {"alpha": 0.5, "gamma": 2.0, "reduction": 'mean'}
|
||||
>>> loss = kornia.losses.FocalLoss(**kwargs)
|
||||
>>> input = torch.randn(1, N, 3, 5, requires_grad=True)
|
||||
>>> target = torch.empty(1, 3, 5, dtype=torch.long).random_(N)
|
||||
>>> output = loss(input, target)
|
||||
>>> output.backward()
|
||||
|
||||
References:
|
||||
[1] https://arxiv.org/abs/1708.02002
|
||||
"""
|
||||
def __init__(self,
|
||||
alpha: float,
|
||||
gamma: float = 2.0,
|
||||
reduction: str = 'none') -> None:
|
||||
super(FocalLoss, self).__init__()
|
||||
self.alpha: float = alpha
|
||||
self.gamma: float = gamma
|
||||
self.reduction: str = reduction
|
||||
self.eps: float = 1e-6
|
||||
|
||||
def forward( # type: ignore
|
||||
self, input: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
|
||||
return focal_loss(input, target, self.alpha, self.gamma,
|
||||
self.reduction, self.eps)
|
||||
@@ -0,0 +1,38 @@
|
||||
import torch
|
||||
|
||||
|
||||
def line_iou(pred, target, img_w, length=15, aligned=True):
|
||||
'''
|
||||
Calculate the line iou value between predictions and targets
|
||||
Args:
|
||||
pred: lane predictions, shape: (num_pred, 72)
|
||||
target: ground truth, shape: (num_target, 72)
|
||||
img_w: image width
|
||||
length: extended radius
|
||||
aligned: True for iou loss calculation, False for pair-wise ious in assign
|
||||
'''
|
||||
px1 = pred - length
|
||||
px2 = pred + length
|
||||
tx1 = target - length
|
||||
tx2 = target + length
|
||||
if aligned:
|
||||
invalid_mask = target
|
||||
ovr = torch.min(px2, tx2) - torch.max(px1, tx1)
|
||||
union = torch.max(px2, tx2) - torch.min(px1, tx1)
|
||||
else:
|
||||
num_pred = pred.shape[0]
|
||||
invalid_mask = target.repeat(num_pred, 1, 1)
|
||||
ovr = (torch.min(px2[:, None, :], tx2[None, ...]) -
|
||||
torch.max(px1[:, None, :], tx1[None, ...]))
|
||||
union = (torch.max(px2[:, None, :], tx2[None, ...]) -
|
||||
torch.min(px1[:, None, :], tx1[None, ...]))
|
||||
|
||||
invalid_masks = (invalid_mask < 0) | (invalid_mask >= img_w)
|
||||
ovr[invalid_masks] = 0.
|
||||
union[invalid_masks] = 0.
|
||||
iou = ovr.sum(dim=-1) / (union.sum(dim=-1) + 1e-9)
|
||||
return iou
|
||||
|
||||
|
||||
def liou_loss(pred, target, img_w, length=15):
|
||||
return (1 - line_iou(pred, target, img_w, length)).mean()
|
||||
@@ -0,0 +1,2 @@
|
||||
from .fpn import FPN
|
||||
from .pafpn import PAFPN
|
||||
167
algorithms/lane_ufld/code/CLRNet-main/clrnet/models/necks/fpn.py
Normal file
167
algorithms/lane_ufld/code/CLRNet-main/clrnet/models/necks/fpn.py
Normal file
@@ -0,0 +1,167 @@
|
||||
import warnings
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from mmcv.cnn import ConvModule
|
||||
from ..registry import NECKS
|
||||
|
||||
|
||||
@NECKS.register_module
|
||||
class FPN(nn.Module):
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
num_outs,
|
||||
start_level=0,
|
||||
end_level=-1,
|
||||
add_extra_convs=False,
|
||||
extra_convs_on_inputs=True,
|
||||
relu_before_extra_convs=False,
|
||||
no_norm_on_lateral=False,
|
||||
conv_cfg=None,
|
||||
norm_cfg=None,
|
||||
attention=False,
|
||||
act_cfg=None,
|
||||
upsample_cfg=dict(mode='nearest'),
|
||||
init_cfg=dict(type='Xavier',
|
||||
layer='Conv2d',
|
||||
distribution='uniform'),
|
||||
cfg=None):
|
||||
super(FPN, self).__init__()
|
||||
assert isinstance(in_channels, list)
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
self.num_ins = len(in_channels)
|
||||
self.num_outs = num_outs
|
||||
self.attention = attention
|
||||
self.relu_before_extra_convs = relu_before_extra_convs
|
||||
self.no_norm_on_lateral = no_norm_on_lateral
|
||||
self.upsample_cfg = upsample_cfg.copy()
|
||||
|
||||
if end_level == -1:
|
||||
self.backbone_end_level = self.num_ins
|
||||
assert num_outs >= self.num_ins - start_level
|
||||
else:
|
||||
# if end_level < inputs, no extra level is allowed
|
||||
self.backbone_end_level = end_level
|
||||
assert end_level <= len(in_channels)
|
||||
assert num_outs == end_level - start_level
|
||||
self.start_level = start_level
|
||||
self.end_level = end_level
|
||||
self.add_extra_convs = add_extra_convs
|
||||
assert isinstance(add_extra_convs, (str, bool))
|
||||
if isinstance(add_extra_convs, str):
|
||||
# Extra_convs_source choices: 'on_input', 'on_lateral', 'on_output'
|
||||
assert add_extra_convs in ('on_input', 'on_lateral', 'on_output')
|
||||
elif add_extra_convs: # True
|
||||
if extra_convs_on_inputs:
|
||||
# TODO: deprecate `extra_convs_on_inputs`
|
||||
warnings.simplefilter('once')
|
||||
warnings.warn(
|
||||
'"extra_convs_on_inputs" will be deprecated in v2.9.0,'
|
||||
'Please use "add_extra_convs"', DeprecationWarning)
|
||||
self.add_extra_convs = 'on_input'
|
||||
else:
|
||||
self.add_extra_convs = 'on_output'
|
||||
|
||||
self.lateral_convs = nn.ModuleList()
|
||||
self.fpn_convs = nn.ModuleList()
|
||||
|
||||
for i in range(self.start_level, self.backbone_end_level):
|
||||
l_conv = ConvModule(
|
||||
in_channels[i],
|
||||
out_channels,
|
||||
1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg if not self.no_norm_on_lateral else None,
|
||||
act_cfg=act_cfg,
|
||||
inplace=False)
|
||||
fpn_conv = ConvModule(out_channels,
|
||||
out_channels,
|
||||
3,
|
||||
padding=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg,
|
||||
inplace=False)
|
||||
|
||||
self.lateral_convs.append(l_conv)
|
||||
self.fpn_convs.append(fpn_conv)
|
||||
|
||||
# add extra conv layers (e.g., RetinaNet)
|
||||
extra_levels = num_outs - self.backbone_end_level + self.start_level
|
||||
if self.add_extra_convs and extra_levels >= 1:
|
||||
for i in range(extra_levels):
|
||||
if i == 0 and self.add_extra_convs == 'on_input':
|
||||
in_channels = self.in_channels[self.backbone_end_level - 1]
|
||||
else:
|
||||
in_channels = out_channels
|
||||
extra_fpn_conv = ConvModule(in_channels,
|
||||
out_channels,
|
||||
3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg,
|
||||
inplace=False)
|
||||
self.fpn_convs.append(extra_fpn_conv)
|
||||
|
||||
def forward(self, inputs):
|
||||
"""Forward function."""
|
||||
assert len(inputs) >= len(self.in_channels)
|
||||
|
||||
if len(inputs) > len(self.in_channels):
|
||||
for _ in range(len(inputs) - len(self.in_channels)):
|
||||
del inputs[0]
|
||||
|
||||
# build laterals
|
||||
laterals = [
|
||||
lateral_conv(inputs[i + self.start_level])
|
||||
for i, lateral_conv in enumerate(self.lateral_convs)
|
||||
]
|
||||
|
||||
# build top-down path
|
||||
used_backbone_levels = len(laterals)
|
||||
for i in range(used_backbone_levels - 1, 0, -1):
|
||||
# In some cases, fixing `scale factor` (e.g. 2) is preferred, but
|
||||
# it cannot co-exist with `size` in `F.interpolate`.
|
||||
if 'scale_factor' in self.upsample_cfg:
|
||||
laterals[i - 1] += F.interpolate(laterals[i],
|
||||
**self.upsample_cfg)
|
||||
else:
|
||||
prev_shape = laterals[i - 1].shape[2:]
|
||||
laterals[i - 1] += F.interpolate(laterals[i],
|
||||
size=prev_shape,
|
||||
**self.upsample_cfg)
|
||||
|
||||
# build outputs
|
||||
# part 1: from original levels
|
||||
outs = [
|
||||
self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels)
|
||||
]
|
||||
# part 2: add extra levels
|
||||
if self.num_outs > len(outs):
|
||||
# use max pool to get more levels on top of outputs
|
||||
# (e.g., Faster R-CNN, Mask R-CNN)
|
||||
if not self.add_extra_convs:
|
||||
for i in range(self.num_outs - used_backbone_levels):
|
||||
outs.append(F.max_pool2d(outs[-1], 1, stride=2))
|
||||
# add conv layers on top of original feature maps (RetinaNet)
|
||||
else:
|
||||
if self.add_extra_convs == 'on_input':
|
||||
extra_source = inputs[self.backbone_end_level - 1]
|
||||
elif self.add_extra_convs == 'on_lateral':
|
||||
extra_source = laterals[-1]
|
||||
elif self.add_extra_convs == 'on_output':
|
||||
extra_source = outs[-1]
|
||||
else:
|
||||
raise NotImplementedError
|
||||
outs.append(self.fpn_convs[used_backbone_levels](extra_source))
|
||||
for i in range(used_backbone_levels + 1, self.num_outs):
|
||||
if self.relu_before_extra_convs:
|
||||
outs.append(self.fpn_convs[i](F.relu(outs[-1])))
|
||||
else:
|
||||
outs.append(self.fpn_convs[i](outs[-1]))
|
||||
return tuple(outs)
|
||||
@@ -0,0 +1,154 @@
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import ConvModule
|
||||
from mmcv.runner import auto_fp16
|
||||
|
||||
from ..registry import NECKS
|
||||
from .fpn import FPN
|
||||
|
||||
|
||||
@NECKS.register_module
|
||||
class PAFPN(FPN):
|
||||
"""Path Aggregation Network for Instance Segmentation.
|
||||
|
||||
This is an implementation of the `PAFPN in Path Aggregation Network
|
||||
<https://arxiv.org/abs/1803.01534>`_.
|
||||
|
||||
Args:
|
||||
in_channels (List[int]): Number of input channels per scale.
|
||||
out_channels (int): Number of output channels (used at each scale)
|
||||
num_outs (int): Number of output scales.
|
||||
start_level (int): Index of the start input backbone level used to
|
||||
build the feature pyramid. Default: 0.
|
||||
end_level (int): Index of the end input backbone level (exclusive) to
|
||||
build the feature pyramid. Default: -1, which means the last level.
|
||||
add_extra_convs (bool): Whether to add conv layers on top of the
|
||||
original feature maps. Default: False.
|
||||
extra_convs_on_inputs (bool): Whether to apply extra conv on
|
||||
the original feature from the backbone. Default: False.
|
||||
relu_before_extra_convs (bool): Whether to apply relu before the extra
|
||||
conv. Default: False.
|
||||
no_norm_on_lateral (bool): Whether to apply norm on lateral.
|
||||
Default: False.
|
||||
conv_cfg (dict): Config dict for convolution layer. Default: None.
|
||||
norm_cfg (dict): Config dict for normalization layer. Default: None.
|
||||
act_cfg (str): Config dict for activation layer in ConvModule.
|
||||
Default: None.
|
||||
"""
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
num_outs,
|
||||
start_level=0,
|
||||
end_level=-1,
|
||||
add_extra_convs=False,
|
||||
extra_convs_on_inputs=True,
|
||||
relu_before_extra_convs=False,
|
||||
no_norm_on_lateral=False,
|
||||
conv_cfg=None,
|
||||
norm_cfg=None,
|
||||
act_cfg=None,
|
||||
cfg=None,
|
||||
attention=False):
|
||||
super(PAFPN, self).__init__(in_channels,
|
||||
out_channels,
|
||||
num_outs,
|
||||
start_level,
|
||||
end_level,
|
||||
add_extra_convs,
|
||||
extra_convs_on_inputs,
|
||||
relu_before_extra_convs,
|
||||
no_norm_on_lateral,
|
||||
conv_cfg,
|
||||
norm_cfg,
|
||||
attention,
|
||||
act_cfg,
|
||||
cfg=cfg)
|
||||
# add extra bottom up pathway
|
||||
self.downsample_convs = nn.ModuleList()
|
||||
self.pafpn_convs = nn.ModuleList()
|
||||
for i in range(self.start_level + 1, self.backbone_end_level):
|
||||
d_conv = ConvModule(out_channels,
|
||||
out_channels,
|
||||
3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg,
|
||||
inplace=False)
|
||||
pafpn_conv = ConvModule(out_channels,
|
||||
out_channels,
|
||||
3,
|
||||
padding=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg,
|
||||
inplace=False)
|
||||
self.downsample_convs.append(d_conv)
|
||||
self.pafpn_convs.append(pafpn_conv)
|
||||
|
||||
def forward(self, inputs):
|
||||
"""Forward function."""
|
||||
assert len(inputs) >= len(self.in_channels)
|
||||
|
||||
if len(inputs) > len(self.in_channels):
|
||||
for _ in range(len(inputs) - len(self.in_channels)):
|
||||
del inputs[0]
|
||||
|
||||
# build laterals
|
||||
laterals = [
|
||||
lateral_conv(inputs[i + self.start_level])
|
||||
for i, lateral_conv in enumerate(self.lateral_convs)
|
||||
]
|
||||
|
||||
# build top-down path
|
||||
used_backbone_levels = len(laterals)
|
||||
for i in range(used_backbone_levels - 1, 0, -1):
|
||||
prev_shape = laterals[i - 1].shape[2:]
|
||||
laterals[i - 1] += F.interpolate(laterals[i],
|
||||
size=prev_shape,
|
||||
mode='nearest')
|
||||
|
||||
# build outputs
|
||||
# part 1: from original levels
|
||||
inter_outs = [
|
||||
self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels)
|
||||
]
|
||||
|
||||
# part 2: add bottom-up path
|
||||
for i in range(0, used_backbone_levels - 1):
|
||||
inter_outs[i + 1] += self.downsample_convs[i](inter_outs[i])
|
||||
|
||||
outs = []
|
||||
outs.append(inter_outs[0])
|
||||
outs.extend([
|
||||
self.pafpn_convs[i - 1](inter_outs[i])
|
||||
for i in range(1, used_backbone_levels)
|
||||
])
|
||||
|
||||
# part 3: add extra levels
|
||||
if self.num_outs > len(outs):
|
||||
# use max pool to get more levels on top of outputs
|
||||
# (e.g., Faster R-CNN, Mask R-CNN)
|
||||
if not self.add_extra_convs:
|
||||
for i in range(self.num_outs - used_backbone_levels):
|
||||
outs.append(F.max_pool2d(outs[-1], 1, stride=2))
|
||||
# add conv layers on top of original feature maps (RetinaNet)
|
||||
else:
|
||||
if self.add_extra_convs == 'on_input':
|
||||
orig = inputs[self.backbone_end_level - 1]
|
||||
outs.append(self.fpn_convs[used_backbone_levels](orig))
|
||||
elif self.add_extra_convs == 'on_lateral':
|
||||
outs.append(self.fpn_convs[used_backbone_levels](
|
||||
laterals[-1]))
|
||||
elif self.add_extra_convs == 'on_output':
|
||||
outs.append(self.fpn_convs[used_backbone_levels](outs[-1]))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
for i in range(used_backbone_levels + 1, self.num_outs):
|
||||
if self.relu_before_extra_convs:
|
||||
outs.append(self.fpn_convs[i](F.relu(outs[-1])))
|
||||
else:
|
||||
outs.append(self.fpn_convs[i](outs[-1]))
|
||||
return tuple(outs)
|
||||
@@ -0,0 +1 @@
|
||||
from .detector import Detector
|
||||
@@ -0,0 +1,36 @@
|
||||
import torch.nn as nn
|
||||
import torch
|
||||
|
||||
from clrnet.models.registry import NETS
|
||||
from ..registry import build_backbones, build_aggregator, build_heads, build_necks
|
||||
|
||||
|
||||
@NETS.register_module
|
||||
class Detector(nn.Module):
|
||||
def __init__(self, cfg):
|
||||
super(Detector, self).__init__()
|
||||
self.cfg = cfg
|
||||
self.backbone = build_backbones(cfg)
|
||||
self.aggregator = build_aggregator(cfg) if cfg.haskey('aggregator') else None
|
||||
self.neck = build_necks(cfg) if cfg.haskey('neck') else None
|
||||
self.heads = build_heads(cfg)
|
||||
|
||||
def get_lanes(self):
|
||||
return self.heads.get_lanes(output)
|
||||
|
||||
def forward(self, batch):
|
||||
output = {}
|
||||
fea = self.backbone(batch['img'] if isinstance(batch, dict) else batch)
|
||||
|
||||
if self.aggregator:
|
||||
fea[-1] = self.aggregator(fea[-1])
|
||||
|
||||
if self.neck:
|
||||
fea = self.neck(fea)
|
||||
|
||||
if self.training:
|
||||
output = self.heads(fea, batch=batch)
|
||||
else:
|
||||
output = self.heads(fea)
|
||||
|
||||
return output
|
||||
@@ -0,0 +1,45 @@
|
||||
from clrnet.utils import Registry, build_from_cfg
|
||||
import torch.nn as nn
|
||||
|
||||
BACKBONES = Registry('backbones')
|
||||
AGGREGATORS = Registry('aggregators')
|
||||
HEADS = Registry('heads')
|
||||
NECKS = Registry('necks')
|
||||
NETS = Registry('nets')
|
||||
|
||||
|
||||
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_backbones(cfg):
|
||||
return build(cfg.backbone, BACKBONES, default_args=dict(cfg=cfg))
|
||||
|
||||
|
||||
def build_necks(cfg):
|
||||
return build(cfg.necks, NECKS, default_args=dict(cfg=cfg))
|
||||
|
||||
|
||||
def build_aggregator(cfg):
|
||||
return build(cfg.aggregator, AGGREGATORS, default_args=dict(cfg=cfg))
|
||||
|
||||
|
||||
def build_heads(cfg):
|
||||
return build(cfg.heads, HEADS, default_args=dict(cfg=cfg))
|
||||
|
||||
|
||||
def build_head(split_cfg, cfg):
|
||||
return build(split_cfg, HEADS, default_args=dict(cfg=cfg))
|
||||
|
||||
|
||||
def build_net(cfg):
|
||||
return build(cfg.net, NETS, default_args=dict(cfg=cfg))
|
||||
|
||||
def build_necks(cfg):
|
||||
return build(cfg.neck, NECKS, default_args=dict(cfg=cfg))
|
||||
@@ -0,0 +1,140 @@
|
||||
import torch
|
||||
from clrnet.models.losses.lineiou_loss import line_iou
|
||||
|
||||
|
||||
def distance_cost(predictions, targets, img_w):
|
||||
"""
|
||||
repeat predictions and targets to generate all combinations
|
||||
use the abs distance as the new distance cost
|
||||
"""
|
||||
num_priors = predictions.shape[0]
|
||||
num_targets = targets.shape[0]
|
||||
|
||||
predictions = torch.repeat_interleave(
|
||||
predictions, num_targets, dim=0
|
||||
)[...,
|
||||
6:] # repeat_interleave'ing [a, b] 2 times gives [a, a, b, b] ((np + nt) * 78)
|
||||
|
||||
targets = torch.cat(
|
||||
num_priors *
|
||||
[targets])[...,
|
||||
6:] # applying this 2 times on [c, d] gives [c, d, c, d]
|
||||
|
||||
invalid_masks = (targets < 0) | (targets >= img_w)
|
||||
lengths = (~invalid_masks).sum(dim=1)
|
||||
distances = torch.abs((targets - predictions))
|
||||
distances[invalid_masks] = 0.
|
||||
distances = distances.sum(dim=1) / (lengths.float() + 1e-9)
|
||||
distances = distances.view(num_priors, num_targets)
|
||||
|
||||
return distances
|
||||
|
||||
|
||||
def focal_cost(cls_pred, gt_labels, alpha=0.25, gamma=2, eps=1e-12):
|
||||
"""
|
||||
Args:
|
||||
cls_pred (Tensor): Predicted classification logits, shape
|
||||
[num_query, num_class].
|
||||
gt_labels (Tensor): Label of `gt_bboxes`, shape (num_gt,).
|
||||
|
||||
Returns:
|
||||
torch.Tensor: cls_cost value
|
||||
"""
|
||||
cls_pred = cls_pred.sigmoid()
|
||||
neg_cost = -(1 - cls_pred + eps).log() * (1 - alpha) * cls_pred.pow(gamma)
|
||||
pos_cost = -(cls_pred + eps).log() * alpha * (1 - cls_pred).pow(gamma)
|
||||
cls_cost = pos_cost[:, gt_labels] - neg_cost[:, gt_labels]
|
||||
return cls_cost
|
||||
|
||||
|
||||
def dynamic_k_assign(cost, pair_wise_ious):
|
||||
"""
|
||||
Assign grouth truths with priors dynamically.
|
||||
|
||||
Args:
|
||||
cost: the assign cost.
|
||||
pair_wise_ious: iou of grouth truth and priors.
|
||||
|
||||
Returns:
|
||||
prior_idx: the index of assigned prior.
|
||||
gt_idx: the corresponding ground truth index.
|
||||
"""
|
||||
matching_matrix = torch.zeros_like(cost)
|
||||
ious_matrix = pair_wise_ious
|
||||
ious_matrix[ious_matrix < 0] = 0.
|
||||
n_candidate_k = 4
|
||||
topk_ious, _ = torch.topk(ious_matrix, n_candidate_k, dim=0)
|
||||
dynamic_ks = torch.clamp(topk_ious.sum(0).int(), min=1)
|
||||
num_gt = cost.shape[1]
|
||||
for gt_idx in range(num_gt):
|
||||
_, pos_idx = torch.topk(cost[:, gt_idx],
|
||||
k=dynamic_ks[gt_idx].item(),
|
||||
largest=False)
|
||||
matching_matrix[pos_idx, gt_idx] = 1.0
|
||||
del topk_ious, dynamic_ks, pos_idx
|
||||
|
||||
matched_gt = matching_matrix.sum(1)
|
||||
if (matched_gt > 1).sum() > 0:
|
||||
_, cost_argmin = torch.min(cost[matched_gt > 1, :], dim=1)
|
||||
matching_matrix[matched_gt > 1, 0] *= 0.0
|
||||
matching_matrix[matched_gt > 1, cost_argmin] = 1.0
|
||||
|
||||
prior_idx = matching_matrix.sum(1).nonzero()
|
||||
gt_idx = matching_matrix[prior_idx].argmax(-1)
|
||||
return prior_idx.flatten(), gt_idx.flatten()
|
||||
|
||||
|
||||
def assign(
|
||||
predictions,
|
||||
targets,
|
||||
img_w,
|
||||
img_h,
|
||||
distance_cost_weight=3.,
|
||||
cls_cost_weight=1.,
|
||||
):
|
||||
'''
|
||||
computes dynamicly matching based on the cost, including cls cost and lane similarity cost
|
||||
Args:
|
||||
predictions (Tensor): predictions predicted by each stage, shape: (num_priors, 78)
|
||||
targets (Tensor): lane targets, shape: (num_targets, 78)
|
||||
return:
|
||||
matched_row_inds (Tensor): matched predictions, shape: (num_targets)
|
||||
matched_col_inds (Tensor): matched targets, shape: (num_targets)
|
||||
'''
|
||||
predictions = predictions.detach().clone()
|
||||
predictions[:, 3] *= (img_w - 1)
|
||||
predictions[:, 6:] *= (img_w - 1)
|
||||
targets = targets.detach().clone()
|
||||
|
||||
# distances cost
|
||||
distances_score = distance_cost(predictions, targets, img_w)
|
||||
distances_score = 1 - (distances_score / torch.max(distances_score)
|
||||
) + 1e-2 # normalize the distance
|
||||
|
||||
# classification cost
|
||||
cls_score = focal_cost(predictions[:, :2], targets[:, 1].long())
|
||||
num_priors = predictions.shape[0]
|
||||
num_targets = targets.shape[0]
|
||||
|
||||
target_start_xys = targets[:, 2:4] # num_targets, 2
|
||||
target_start_xys[..., 0] *= (img_h - 1)
|
||||
prediction_start_xys = predictions[:, 2:4]
|
||||
prediction_start_xys[..., 0] *= (img_h - 1)
|
||||
|
||||
start_xys_score = torch.cdist(prediction_start_xys, target_start_xys,
|
||||
p=2).reshape(num_priors, num_targets)
|
||||
start_xys_score = (1 - start_xys_score / torch.max(start_xys_score)) + 1e-2
|
||||
|
||||
target_thetas = targets[:, 4].unsqueeze(-1)
|
||||
theta_score = torch.cdist(predictions[:, 4].unsqueeze(-1),
|
||||
target_thetas,
|
||||
p=1).reshape(num_priors, num_targets) * 180
|
||||
theta_score = (1 - theta_score / torch.max(theta_score)) + 1e-2
|
||||
|
||||
cost = -(distances_score * start_xys_score * theta_score
|
||||
)**2 * distance_cost_weight + cls_score * cls_cost_weight
|
||||
|
||||
iou = line_iou(predictions[..., 6:], targets[..., 6:], img_w, aligned=False)
|
||||
matched_row_inds, matched_col_inds = dynamic_k_assign(cost, iou)
|
||||
|
||||
return matched_row_inds, matched_col_inds
|
||||
@@ -0,0 +1,136 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import ConvModule
|
||||
|
||||
|
||||
def LinearModule(hidden_dim):
|
||||
return nn.ModuleList(
|
||||
[nn.Linear(hidden_dim, hidden_dim),
|
||||
nn.ReLU(inplace=True)])
|
||||
|
||||
|
||||
class FeatureResize(nn.Module):
|
||||
def __init__(self, size=(10, 25)):
|
||||
super(FeatureResize, self).__init__()
|
||||
self.size = size
|
||||
|
||||
def forward(self, x):
|
||||
x = F.interpolate(x, self.size)
|
||||
return x.flatten(2)
|
||||
|
||||
|
||||
class ROIGather(nn.Module):
|
||||
'''
|
||||
ROIGather module for gather global information
|
||||
Args:
|
||||
in_channels: prior feature channels
|
||||
num_priors: prior numbers we predefined
|
||||
sample_points: the number of sampled points when we extract feature from line
|
||||
fc_hidden_dim: the fc output channel
|
||||
refine_layers: the total number of layers to build refine
|
||||
'''
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
num_priors,
|
||||
sample_points,
|
||||
fc_hidden_dim,
|
||||
refine_layers,
|
||||
mid_channels=48):
|
||||
super(ROIGather, self).__init__()
|
||||
self.in_channels = in_channels
|
||||
self.num_priors = num_priors
|
||||
self.f_key = ConvModule(in_channels=self.in_channels,
|
||||
out_channels=self.in_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=dict(type='BN'))
|
||||
|
||||
self.f_query = nn.Sequential(
|
||||
nn.Conv1d(in_channels=num_priors,
|
||||
out_channels=num_priors,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
groups=num_priors),
|
||||
nn.ReLU(),
|
||||
)
|
||||
self.f_value = nn.Conv2d(in_channels=self.in_channels,
|
||||
out_channels=self.in_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0)
|
||||
self.W = nn.Conv1d(in_channels=num_priors,
|
||||
out_channels=num_priors,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
groups=num_priors)
|
||||
|
||||
self.resize = FeatureResize()
|
||||
nn.init.constant_(self.W.weight, 0)
|
||||
nn.init.constant_(self.W.bias, 0)
|
||||
|
||||
self.convs = nn.ModuleList()
|
||||
self.catconv = nn.ModuleList()
|
||||
for i in range(refine_layers):
|
||||
self.convs.append(
|
||||
ConvModule(in_channels,
|
||||
mid_channels, (9, 1),
|
||||
padding=(4, 0),
|
||||
bias=False,
|
||||
norm_cfg=dict(type='BN')))
|
||||
|
||||
self.catconv.append(
|
||||
ConvModule(mid_channels * (i + 1),
|
||||
in_channels, (9, 1),
|
||||
padding=(4, 0),
|
||||
bias=False,
|
||||
norm_cfg=dict(type='BN')))
|
||||
|
||||
self.fc = nn.Linear(sample_points * fc_hidden_dim, fc_hidden_dim)
|
||||
|
||||
self.fc_norm = nn.LayerNorm(fc_hidden_dim)
|
||||
|
||||
def roi_fea(self, x, layer_index):
|
||||
feats = []
|
||||
for i, feature in enumerate(x):
|
||||
feat_trans = self.convs[i](feature)
|
||||
feats.append(feat_trans)
|
||||
cat_feat = torch.cat(feats, dim=1)
|
||||
cat_feat = self.catconv[layer_index](cat_feat)
|
||||
return cat_feat
|
||||
|
||||
def forward(self, roi_features, x, layer_index):
|
||||
'''
|
||||
Args:
|
||||
roi_features: prior feature, shape: (Batch * num_priors, prior_feat_channel, sample_point, 1)
|
||||
x: feature map
|
||||
layer_index: currently on which layer to refine
|
||||
Return:
|
||||
roi: prior features with gathered global information, shape: (Batch, num_priors, fc_hidden_dim)
|
||||
'''
|
||||
roi = self.roi_fea(roi_features, layer_index)
|
||||
bs = x.size(0)
|
||||
roi = roi.contiguous().view(bs * self.num_priors, -1)
|
||||
|
||||
roi = F.relu(self.fc_norm(self.fc(roi)))
|
||||
roi = roi.view(bs, self.num_priors, -1)
|
||||
query = roi
|
||||
|
||||
value = self.resize(self.f_value(x))
|
||||
query = self.f_query(query)
|
||||
key = self.f_key(x)
|
||||
value = value.permute(0, 2, 1)
|
||||
key = self.resize(key)
|
||||
sim_map = torch.matmul(query, key)
|
||||
sim_map = (self.in_channels**-.5) * sim_map
|
||||
sim_map = F.softmax(sim_map, dim=-1)
|
||||
|
||||
context = torch.matmul(sim_map, value)
|
||||
context = self.W(context)
|
||||
|
||||
roi = roi + F.dropout(context, p=0.1, training=self.training)
|
||||
|
||||
return roi
|
||||
@@ -0,0 +1,29 @@
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
class SegDecoder(nn.Module):
|
||||
'''
|
||||
Optionaly seg decoder
|
||||
'''
|
||||
def __init__(self,
|
||||
image_height,
|
||||
image_width,
|
||||
num_class,
|
||||
prior_feat_channels=64,
|
||||
refine_layers=3):
|
||||
super().__init__()
|
||||
self.dropout = nn.Dropout2d(0.1)
|
||||
self.conv = nn.Conv2d(prior_feat_channels * refine_layers, num_class,
|
||||
1)
|
||||
self.image_height = image_height
|
||||
self.image_width = image_width
|
||||
|
||||
def forward(self, x):
|
||||
x = self.dropout(x)
|
||||
x = self.conv(x)
|
||||
x = F.interpolate(x,
|
||||
size=[self.image_height, self.image_width],
|
||||
mode='bilinear',
|
||||
align_corners=False)
|
||||
return x
|
||||
@@ -0,0 +1,3 @@
|
||||
from .nms import nms
|
||||
|
||||
__all__ = ['nms']
|
||||
@@ -0,0 +1,62 @@
|
||||
/* Copyright (c) 2018, Grégoire Payen de La Garanderie, Durham University
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <torch/extension.h>
|
||||
#include <torch/types.h>
|
||||
#include <iostream>
|
||||
|
||||
std::vector<at::Tensor> nms_cuda_forward(
|
||||
at::Tensor boxes,
|
||||
at::Tensor idx,
|
||||
float nms_overlap_thresh,
|
||||
unsigned long top_k);
|
||||
|
||||
#define CHECK_CUDA(x) AT_ASSERTM(x.type().is_cuda(), #x " must be a CUDA tensor")
|
||||
#define CHECK_CONTIGUOUS(x) AT_ASSERTM(x.is_contiguous(), #x " must be contiguous")
|
||||
#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x)
|
||||
|
||||
std::vector<at::Tensor> nms_forward(
|
||||
at::Tensor boxes,
|
||||
at::Tensor scores,
|
||||
float thresh,
|
||||
unsigned long top_k) {
|
||||
|
||||
|
||||
auto idx = std::get<1>(scores.sort(0,true));
|
||||
|
||||
CHECK_INPUT(boxes);
|
||||
CHECK_INPUT(idx);
|
||||
|
||||
return nms_cuda_forward(boxes, idx, thresh, top_k);
|
||||
}
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
||||
m.def("nms_forward", &nms_forward, "NMS");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
#include <torch/extension.h>
|
||||
#include <ATen/ATen.h>
|
||||
|
||||
#include <cuda.h>
|
||||
#include <cuda_runtime.h>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
// Hard-coded maximum. Increase if needed.
|
||||
#define MAX_COL_BLOCKS 1000
|
||||
#define STRIDE 4
|
||||
#define N_OFFSETS 72 // if you use more than 73 offsets you will have to adjust this value
|
||||
#define N_STRIPS (N_OFFSETS - 1)
|
||||
#define PROP_SIZE (5 + N_OFFSETS)
|
||||
#define DATASET_OFFSET 0
|
||||
|
||||
#define DIVUP(m,n) (((m)+(n)-1) / (n))
|
||||
int64_t const threadsPerBlock = sizeof(unsigned long long) * 8;
|
||||
|
||||
// The functions below originates from Fast R-CNN
|
||||
// See https://github.com/rbgirshick/py-faster-rcnn
|
||||
// Copyright (c) 2015 Microsoft
|
||||
// Licensed under The MIT License
|
||||
// Written by Shaoqing Ren
|
||||
|
||||
template <typename scalar_t>
|
||||
// __device__ inline scalar_t devIoU(scalar_t const * const a, scalar_t const * const b) {
|
||||
__device__ inline bool devIoU(scalar_t const * const a, scalar_t const * const b, const float threshold) {
|
||||
const int start_a = (int) (a[2] * N_STRIPS - DATASET_OFFSET + 0.5); // 0.5 rounding trick
|
||||
const int start_b = (int) (b[2] * N_STRIPS - DATASET_OFFSET + 0.5);
|
||||
const int start = max(start_a, start_b);
|
||||
const int end_a = start_a + a[4] - 1 + 0.5 - ((a[4] - 1) < 0); // - (x<0) trick to adjust for negative numbers (in case length is 0)
|
||||
const int end_b = start_b + b[4] - 1 + 0.5 - ((b[4] - 1) < 0);
|
||||
const int end = min(min(end_a, end_b), N_OFFSETS - 1);
|
||||
// if (end < start) return 1e9;
|
||||
if (end < start) return false;
|
||||
scalar_t dist = 0;
|
||||
for(unsigned char i = 5 + start; i <= 5 + end; ++i) {
|
||||
if (a[i] < b[i]) {
|
||||
dist += b[i] - a[i];
|
||||
} else {
|
||||
dist += a[i] - b[i];
|
||||
}
|
||||
}
|
||||
// return (dist / (end - start + 1)) < threshold;
|
||||
return dist < (threshold * (end - start + 1));
|
||||
// return dist / (end - start + 1);
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
__global__ void nms_kernel(const int64_t n_boxes, const scalar_t nms_overlap_thresh,
|
||||
const scalar_t *dev_boxes, const int64_t *idx, int64_t *dev_mask) {
|
||||
const int64_t row_start = blockIdx.y;
|
||||
const int64_t col_start = blockIdx.x;
|
||||
|
||||
if (row_start > col_start) return;
|
||||
|
||||
const int row_size =
|
||||
min(n_boxes - row_start * threadsPerBlock, threadsPerBlock);
|
||||
const int col_size =
|
||||
min(n_boxes - col_start * threadsPerBlock, threadsPerBlock);
|
||||
|
||||
__shared__ scalar_t block_boxes[threadsPerBlock * PROP_SIZE];
|
||||
if (threadIdx.x < col_size) {
|
||||
for (int i = 0; i < PROP_SIZE; ++i) {
|
||||
block_boxes[threadIdx.x * PROP_SIZE + i] = dev_boxes[idx[(threadsPerBlock * col_start + threadIdx.x)] * PROP_SIZE + i];
|
||||
}
|
||||
// block_boxes[threadIdx.x * 4 + 0] =
|
||||
// dev_boxes[idx[(threadsPerBlock * col_start + threadIdx.x)] * 4 + 0];
|
||||
// block_boxes[threadIdx.x * 4 + 1] =
|
||||
// dev_boxes[idx[(threadsPerBlock * col_start + threadIdx.x)] * 4 + 1];
|
||||
// block_boxes[threadIdx.x * 4 + 2] =
|
||||
// dev_boxes[idx[(threadsPerBlock * col_start + threadIdx.x)] * 4 + 2];
|
||||
// block_boxes[threadIdx.x * 4 + 3] =
|
||||
// dev_boxes[idx[(threadsPerBlock * col_start + threadIdx.x)] * 4 + 3];
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
if (threadIdx.x < row_size) {
|
||||
const int cur_box_idx = threadsPerBlock * row_start + threadIdx.x;
|
||||
const scalar_t *cur_box = dev_boxes + idx[cur_box_idx] * PROP_SIZE;
|
||||
int i = 0;
|
||||
unsigned long long t = 0;
|
||||
int start = 0;
|
||||
if (row_start == col_start) {
|
||||
start = threadIdx.x + 1;
|
||||
}
|
||||
for (i = start; i < col_size; i++) {
|
||||
if (devIoU(cur_box, block_boxes + i * PROP_SIZE, nms_overlap_thresh)) {
|
||||
t |= 1ULL << i;
|
||||
}
|
||||
}
|
||||
const int col_blocks = DIVUP(n_boxes, threadsPerBlock);
|
||||
dev_mask[cur_box_idx * col_blocks + col_start] = t;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
__global__ void nms_collect(const int64_t boxes_num, const int64_t col_blocks, int64_t top_k, const int64_t *idx, const int64_t *mask, int64_t *keep, int64_t *parent_object_index, int64_t *num_to_keep) {
|
||||
int64_t remv[MAX_COL_BLOCKS];
|
||||
int64_t num_to_keep_ = 0;
|
||||
|
||||
for (int i = 0; i < col_blocks; i++) {
|
||||
remv[i] = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < boxes_num; ++i) {
|
||||
parent_object_index[i] = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < boxes_num; i++) {
|
||||
int nblock = i / threadsPerBlock;
|
||||
int inblock = i % threadsPerBlock;
|
||||
|
||||
|
||||
if (!(remv[nblock] & (1ULL << inblock))) {
|
||||
int64_t idxi = idx[i];
|
||||
keep[num_to_keep_] = idxi;
|
||||
const int64_t *p = &mask[0] + i * col_blocks;
|
||||
for (int j = nblock; j < col_blocks; j++) {
|
||||
remv[j] |= p[j];
|
||||
}
|
||||
for (int j = i; j < boxes_num; j++) {
|
||||
int nblockj = j / threadsPerBlock;
|
||||
int inblockj = j % threadsPerBlock;
|
||||
if (p[nblockj] & (1ULL << inblockj))
|
||||
parent_object_index[idx[j]] = num_to_keep_+1;
|
||||
}
|
||||
parent_object_index[idx[i]] = num_to_keep_+1;
|
||||
|
||||
num_to_keep_++;
|
||||
|
||||
if (num_to_keep_==top_k)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the rest of the keep array to avoid uninitialized values.
|
||||
for (int i = num_to_keep_; i < boxes_num; ++i)
|
||||
keep[i] = 0;
|
||||
|
||||
*num_to_keep = min(top_k,num_to_keep_);
|
||||
}
|
||||
|
||||
#define CHECK_CONTIGUOUS(x) AT_ASSERTM(x.is_contiguous(), #x " must be contiguous")
|
||||
|
||||
std::vector<at::Tensor> nms_cuda_forward(
|
||||
at::Tensor boxes,
|
||||
at::Tensor idx,
|
||||
float nms_overlap_thresh,
|
||||
unsigned long top_k) {
|
||||
|
||||
const auto boxes_num = boxes.size(0);
|
||||
TORCH_CHECK(boxes.size(1) == PROP_SIZE, "Wrong number of offsets. Please adjust `PROP_SIZE`");
|
||||
|
||||
const int col_blocks = DIVUP(boxes_num, threadsPerBlock);
|
||||
|
||||
AT_ASSERTM (col_blocks < MAX_COL_BLOCKS, "The number of column blocks must be less than MAX_COL_BLOCKS. Increase the MAX_COL_BLOCKS constant if needed.");
|
||||
|
||||
auto longOptions = torch::TensorOptions().device(torch::kCUDA).dtype(torch::kLong);
|
||||
auto mask = at::empty({boxes_num * col_blocks}, longOptions);
|
||||
|
||||
dim3 blocks(DIVUP(boxes_num, threadsPerBlock),
|
||||
DIVUP(boxes_num, threadsPerBlock));
|
||||
dim3 threads(threadsPerBlock);
|
||||
|
||||
CHECK_CONTIGUOUS(boxes);
|
||||
CHECK_CONTIGUOUS(idx);
|
||||
CHECK_CONTIGUOUS(mask);
|
||||
|
||||
AT_DISPATCH_FLOATING_TYPES(boxes.type(), "nms_cuda_forward", ([&] {
|
||||
nms_kernel<<<blocks, threads>>>(boxes_num,
|
||||
(scalar_t)nms_overlap_thresh,
|
||||
boxes.data<scalar_t>(),
|
||||
idx.data<int64_t>(),
|
||||
mask.data<int64_t>());
|
||||
}));
|
||||
|
||||
auto keep = at::empty({boxes_num}, longOptions);
|
||||
auto parent_object_index = at::empty({boxes_num}, longOptions);
|
||||
auto num_to_keep = at::empty({}, longOptions);
|
||||
|
||||
nms_collect<<<1, 1>>>(boxes_num, col_blocks, top_k,
|
||||
idx.data<int64_t>(),
|
||||
mask.data<int64_t>(),
|
||||
keep.data<int64_t>(),
|
||||
parent_object_index.data<int64_t>(),
|
||||
num_to_keep.data<int64_t>());
|
||||
|
||||
|
||||
return {keep,num_to_keep,parent_object_index};
|
||||
}
|
||||
|
||||
33
algorithms/lane_ufld/code/CLRNet-main/clrnet/ops/nms.py
Normal file
33
algorithms/lane_ufld/code/CLRNet-main/clrnet/ops/nms.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# Copyright (c) 2018, Grégoire Payen de La Garanderie, Durham University
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from . import nms_impl
|
||||
|
||||
|
||||
def nms(boxes, scores, overlap, top_k):
|
||||
return nms_impl.nms_forward(boxes, scores, overlap, top_k)
|
||||
@@ -0,0 +1,2 @@
|
||||
from .config import Config
|
||||
from .registry import Registry, build_from_cfg
|
||||
@@ -0,0 +1,67 @@
|
||||
# Bilinear grid_sample using Pad + Gather (ONNX/RKNN friendly).
|
||||
# Ref: https://zenn.dev/pinto0309/scraps/7d4032067d0160
|
||||
# https://github.com/ibaiGorordo/CREStereo-Pytorch/.../nets/utils/utils.py
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
def bilinear_grid_sample(im, grid, align_corners=False):
|
||||
"""Drop-in replacement for F.grid_sample(..., mode='bilinear', padding_zeros).
|
||||
|
||||
Args:
|
||||
im: (N, C, H, W)
|
||||
grid: (N, Hg, Wg, 2), x/y in [-1, 1] (same as grid_sample)
|
||||
"""
|
||||
n, c, h, w = im.shape
|
||||
gn, gh, gw, _ = grid.shape
|
||||
assert n == gn
|
||||
|
||||
x = grid[..., 0]
|
||||
y = grid[..., 1]
|
||||
|
||||
if align_corners:
|
||||
x = ((x + 1) / 2) * (w - 1)
|
||||
y = ((y + 1) / 2) * (h - 1)
|
||||
else:
|
||||
x = ((x + 1) * w - 1) / 2
|
||||
y = ((y + 1) * h - 1) / 2
|
||||
|
||||
x = x.reshape(n, -1)
|
||||
y = y.reshape(n, -1)
|
||||
|
||||
x0 = torch.floor(x).long()
|
||||
y0 = torch.floor(y).long()
|
||||
x1 = x0 + 1
|
||||
y1 = y0 + 1
|
||||
|
||||
wa = ((x1 - x) * (y1 - y)).unsqueeze(1)
|
||||
wb = ((x1 - x) * (y - y0)).unsqueeze(1)
|
||||
wc = ((x - x0) * (y1 - y)).unsqueeze(1)
|
||||
wd = ((x - x0) * (y - y0)).unsqueeze(1)
|
||||
|
||||
im_padded = F.pad(im, pad=[1, 1, 1, 1], mode='constant', value=0)
|
||||
padded_h = h + 2
|
||||
padded_w = w + 2
|
||||
x0, x1, y0, y1 = x0 + 1, x1 + 1, y0 + 1, y1 + 1
|
||||
|
||||
# Clip in float so ONNX exports valid Clip (ORT rejects int64 Clip bounds).
|
||||
x0 = x0.float().clamp(0, padded_w - 1).long()
|
||||
x1 = x1.float().clamp(0, padded_w - 1).long()
|
||||
y0 = y0.float().clamp(0, padded_h - 1).long()
|
||||
y1 = y1.float().clamp(0, padded_h - 1).long()
|
||||
|
||||
im_flat = im_padded.reshape(n, c, -1)
|
||||
|
||||
x0_y0 = (x0 + y0 * padded_w).unsqueeze(1).expand(-1, c, -1)
|
||||
x0_y1 = (x0 + y1 * padded_w).unsqueeze(1).expand(-1, c, -1)
|
||||
x1_y0 = (x1 + y0 * padded_w).unsqueeze(1).expand(-1, c, -1)
|
||||
x1_y1 = (x1 + y1 * padded_w).unsqueeze(1).expand(-1, c, -1)
|
||||
|
||||
ia = torch.gather(im_flat, 2, x0_y0)
|
||||
ib = torch.gather(im_flat, 2, x0_y1)
|
||||
ic = torch.gather(im_flat, 2, x1_y0)
|
||||
id_ = torch.gather(im_flat, 2, x1_y1)
|
||||
|
||||
out = ia * wa + ib * wb + ic * wc + id_ * wd
|
||||
return out.reshape(n, c, gh, gw)
|
||||
418
algorithms/lane_ufld/code/CLRNet-main/clrnet/utils/config.py
Executable file
418
algorithms/lane_ufld/code/CLRNet-main/clrnet/utils/config.py
Executable file
@@ -0,0 +1,418 @@
|
||||
# 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 = ''
|
||||
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 haskey(self, name):
|
||||
return hasattr(self._cfg_dict, name)
|
||||
|
||||
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 has_attr_in_cfg(self, name):
|
||||
return hasattr(self._cfg_dict, name)
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,257 @@
|
||||
import os
|
||||
import argparse
|
||||
from functools import partial
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
from p_tqdm import t_map, p_map
|
||||
from scipy.interpolate import splprep, splev
|
||||
from scipy.optimize import linear_sum_assignment
|
||||
from shapely.geometry import LineString, Polygon
|
||||
|
||||
|
||||
def draw_lane(lane, img=None, img_shape=None, width=30):
|
||||
if img is None:
|
||||
img = np.zeros(img_shape, dtype=np.uint8)
|
||||
lane = lane.astype(np.int32)
|
||||
for p1, p2 in zip(lane[:-1], lane[1:]):
|
||||
cv2.line(img,
|
||||
tuple(p1),
|
||||
tuple(p2),
|
||||
color=(255, 255, 255),
|
||||
thickness=width)
|
||||
return img
|
||||
|
||||
|
||||
def discrete_cross_iou(xs, ys, width=30, img_shape=(590, 1640, 3)):
|
||||
xs = [draw_lane(lane, img_shape=img_shape, width=width) > 0 for lane in xs]
|
||||
ys = [draw_lane(lane, img_shape=img_shape, width=width) > 0 for lane in ys]
|
||||
|
||||
ious = np.zeros((len(xs), len(ys)))
|
||||
for i, x in enumerate(xs):
|
||||
for j, y in enumerate(ys):
|
||||
ious[i, j] = (x & y).sum() / (x | y).sum()
|
||||
return ious
|
||||
|
||||
|
||||
def continuous_cross_iou(xs, ys, width=30, img_shape=(590, 1640, 3)):
|
||||
h, w, _ = img_shape
|
||||
image = Polygon([(0, 0), (0, h - 1), (w - 1, h - 1), (w - 1, 0)])
|
||||
xs = [
|
||||
LineString(lane).buffer(distance=width / 2., cap_style=1,
|
||||
join_style=2).intersection(image)
|
||||
for lane in xs
|
||||
]
|
||||
ys = [
|
||||
LineString(lane).buffer(distance=width / 2., cap_style=1,
|
||||
join_style=2).intersection(image)
|
||||
for lane in ys
|
||||
]
|
||||
|
||||
ious = np.zeros((len(xs), len(ys)))
|
||||
for i, x in enumerate(xs):
|
||||
for j, y in enumerate(ys):
|
||||
ious[i, j] = x.intersection(y).area / x.union(y).area
|
||||
|
||||
return ious
|
||||
|
||||
|
||||
def interp(points, n=50):
|
||||
x = [x for x, _ in points]
|
||||
y = [y for _, y in points]
|
||||
tck, u = splprep([x, y], s=0, t=n, k=min(3, len(points) - 1))
|
||||
|
||||
u = np.linspace(0., 1., num=(len(u) - 1) * n + 1)
|
||||
return np.array(splev(u, tck)).T
|
||||
|
||||
|
||||
def culane_metric(pred,
|
||||
anno,
|
||||
width=30,
|
||||
iou_thresholds=[0.5],
|
||||
official=True,
|
||||
img_shape=(590, 1640, 3)):
|
||||
_metric = {}
|
||||
for thr in iou_thresholds:
|
||||
tp = 0
|
||||
fp = 0 if len(anno) != 0 else len(pred)
|
||||
fn = 0 if len(pred) != 0 else len(anno)
|
||||
_metric[thr] = [tp, fp, fn]
|
||||
|
||||
interp_pred = np.array([interp(pred_lane, n=5) for pred_lane in pred],
|
||||
dtype=object) # (4, 50, 2)
|
||||
interp_anno = np.array([interp(anno_lane, n=5) for anno_lane in anno],
|
||||
dtype=object) # (4, 50, 2)
|
||||
|
||||
if official:
|
||||
ious = discrete_cross_iou(interp_pred,
|
||||
interp_anno,
|
||||
width=width,
|
||||
img_shape=img_shape)
|
||||
else:
|
||||
ious = continuous_cross_iou(interp_pred,
|
||||
interp_anno,
|
||||
width=width,
|
||||
img_shape=img_shape)
|
||||
|
||||
row_ind, col_ind = linear_sum_assignment(1 - ious)
|
||||
|
||||
_metric = {}
|
||||
for thr in iou_thresholds:
|
||||
tp = int((ious[row_ind, col_ind] > thr).sum())
|
||||
fp = len(pred) - tp
|
||||
fn = len(anno) - tp
|
||||
_metric[thr] = [tp, fp, fn]
|
||||
return _metric
|
||||
|
||||
|
||||
def load_culane_img_data(path):
|
||||
with open(path, 'r') as data_file:
|
||||
img_data = data_file.readlines()
|
||||
img_data = [line.split() for line in img_data]
|
||||
img_data = [list(map(float, lane)) for lane in img_data]
|
||||
img_data = [[(lane[i], lane[i + 1]) for i in range(0, len(lane), 2)]
|
||||
for lane in img_data]
|
||||
img_data = [lane for lane in img_data if len(lane) >= 2]
|
||||
|
||||
return img_data
|
||||
|
||||
|
||||
def load_culane_data(data_dir, file_list_path):
|
||||
with open(file_list_path, 'r') as file_list:
|
||||
filepaths = [
|
||||
os.path.join(
|
||||
data_dir, line[1 if line[0] == '/' else 0:].rstrip().replace(
|
||||
'.jpg', '.lines.txt')) for line in file_list.readlines()
|
||||
]
|
||||
|
||||
data = []
|
||||
for path in filepaths:
|
||||
img_data = load_culane_img_data(path)
|
||||
data.append(img_data)
|
||||
|
||||
return data
|
||||
|
||||
def eval_predictions(pred_dir,
|
||||
anno_dir,
|
||||
list_path,
|
||||
iou_thresholds=[0.5],
|
||||
width=30,
|
||||
official=True,
|
||||
sequential=False):
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info('Calculating metric for List: {}'.format(list_path))
|
||||
predictions = load_culane_data(pred_dir, list_path)
|
||||
annotations = load_culane_data(anno_dir, list_path)
|
||||
img_shape = (590, 1640, 3)
|
||||
if sequential:
|
||||
results = map(
|
||||
partial(culane_metric,
|
||||
width=width,
|
||||
official=official,
|
||||
iou_thresholds=iou_thresholds,
|
||||
img_shape=img_shape), predictions, annotations)
|
||||
else:
|
||||
from multiprocessing import Pool, cpu_count
|
||||
from itertools import repeat
|
||||
with Pool(cpu_count()) as p:
|
||||
results = p.starmap(culane_metric, zip(predictions, annotations,
|
||||
repeat(width),
|
||||
repeat(iou_thresholds),
|
||||
repeat(official),
|
||||
repeat(img_shape)))
|
||||
|
||||
mean_f1, mean_prec, mean_recall, total_tp, total_fp, total_fn = 0, 0, 0, 0, 0, 0
|
||||
ret = {}
|
||||
for thr in iou_thresholds:
|
||||
tp = sum(m[thr][0] for m in results)
|
||||
fp = sum(m[thr][1] for m in results)
|
||||
fn = sum(m[thr][2] for m in results)
|
||||
precision = float(tp) / (tp + fp) if tp != 0 else 0
|
||||
recall = float(tp) / (tp + fn) if tp != 0 else 0
|
||||
f1 = 2 * precision * recall / (precision + recall) if tp !=0 else 0
|
||||
logger.info('iou thr: {:.2f}, tp: {}, fp: {}, fn: {},'
|
||||
'precision: {}, recall: {}, f1: {}'.format(
|
||||
thr, tp, fp, fn, precision, recall, f1))
|
||||
mean_f1 += f1 / len(iou_thresholds)
|
||||
mean_prec += precision / len(iou_thresholds)
|
||||
mean_recall += recall / len(iou_thresholds)
|
||||
total_tp += tp
|
||||
total_fp += fp
|
||||
total_fn += fn
|
||||
ret[thr] = {
|
||||
'TP': tp,
|
||||
'FP': fp,
|
||||
'FN': fn,
|
||||
'Precision': precision,
|
||||
'Recall': recall,
|
||||
'F1': f1
|
||||
}
|
||||
if len(iou_thresholds) > 2:
|
||||
logger.info('mean result, total_tp: {}, total_fp: {}, total_fn: {},'
|
||||
'precision: {}, recall: {}, f1: {}'.format(total_tp, total_fp,
|
||||
total_fn, mean_prec, mean_recall, mean_f1))
|
||||
ret['mean'] = {
|
||||
'TP': total_tp,
|
||||
'FP': total_fp,
|
||||
'FN': total_fn,
|
||||
'Precision': mean_prec,
|
||||
'Recall': mean_recall,
|
||||
'F1': mean_f1
|
||||
}
|
||||
return ret
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
for list_path in args.list:
|
||||
results = eval_predictions(args.pred_dir,
|
||||
args.anno_dir,
|
||||
list_path,
|
||||
width=args.width,
|
||||
official=args.official,
|
||||
sequential=args.sequential)
|
||||
|
||||
header = '=' * 20 + ' Results ({})'.format(
|
||||
os.path.basename(list_path)) + '=' * 20
|
||||
print(header)
|
||||
for metric, value in results.items():
|
||||
if isinstance(value, float):
|
||||
print('{}: {:.4f}'.format(metric, value))
|
||||
else:
|
||||
print('{}: {}'.format(metric, value))
|
||||
print('=' * len(header))
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Measure CULane's metric")
|
||||
parser.add_argument(
|
||||
"--pred_dir",
|
||||
help="Path to directory containing the predicted lanes",
|
||||
required=True)
|
||||
parser.add_argument(
|
||||
"--anno_dir",
|
||||
help="Path to directory containing the annotated lanes",
|
||||
required=True)
|
||||
parser.add_argument("--width",
|
||||
type=int,
|
||||
default=30,
|
||||
help="Width of the lane")
|
||||
parser.add_argument("--list",
|
||||
nargs='+',
|
||||
help="Path to txt file containing the list of files",
|
||||
required=True)
|
||||
parser.add_argument("--sequential",
|
||||
action='store_true',
|
||||
help="Run sequentially instead of in parallel")
|
||||
parser.add_argument("--official",
|
||||
action='store_true',
|
||||
help="Use official way to calculate the metric")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Merge train_packs for CLRNet (same layout as UFLD lane0_copy)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def parse_gt_line(line: str) -> tuple[str, str] | None:
|
||||
parts = line.strip().split()
|
||||
if len(parts) < 2:
|
||||
return None
|
||||
return parts[0].lstrip("/"), parts[1].lstrip("/")
|
||||
|
||||
|
||||
def load_registry(data_root: Path) -> dict:
|
||||
p = data_root / "datasets_registry.json"
|
||||
if p.is_file():
|
||||
return json.loads(p.read_text(encoding="utf-8"))
|
||||
return {}
|
||||
|
||||
|
||||
def resolve_pack_dir(pack: str, data_root: Path, registry: dict) -> str:
|
||||
aliases = registry.get("aliases", {})
|
||||
pack_dirs = registry.get("pack_dirs", {})
|
||||
if pack in aliases:
|
||||
pack = aliases[pack]
|
||||
if pack in pack_dirs:
|
||||
pack = pack_dirs[pack]
|
||||
if not (data_root / pack).is_dir():
|
||||
raise FileNotFoundError(f"pack not found: {data_root / pack} ({pack!r})")
|
||||
return pack
|
||||
|
||||
|
||||
def apply_pack_prefix(img: str, msk: str, prefix: str) -> tuple[str, str]:
|
||||
if not prefix:
|
||||
return img, msk
|
||||
if not img.startswith(prefix):
|
||||
img = prefix + img
|
||||
if not msk.startswith(prefix):
|
||||
msk = prefix + msk
|
||||
return img, msk
|
||||
|
||||
|
||||
def resolve_list_file(cfg, split: str = "train") -> str | None:
|
||||
"""Return list path relative to cfg.dataset_path, or None to use defaults."""
|
||||
packs_key = "train_packs" if split == "train" else "val_packs"
|
||||
packs = getattr(cfg, packs_key, None)
|
||||
if not packs:
|
||||
return getattr(cfg, f"{split}_list_file", None)
|
||||
|
||||
if isinstance(packs, str):
|
||||
packs = [p.strip() for p in packs.split(",") if p.strip()]
|
||||
else:
|
||||
packs = list(packs)
|
||||
|
||||
data_root = Path(cfg.dataset_path).resolve()
|
||||
registry = load_registry(data_root)
|
||||
pack_dirs = [resolve_pack_dir(p, data_root, registry) for p in packs]
|
||||
list_name = getattr(cfg, "pack_list_name", "list/train_gt.txt")
|
||||
if split == "val":
|
||||
list_name = getattr(cfg, "pack_val_list_name", "list/val_gt.txt")
|
||||
|
||||
merged_dir = Path(getattr(cfg, "merged_list_dir", "lists_merged"))
|
||||
safe = "__".join(pack_dirs)
|
||||
out_name = getattr(cfg, f"merged_{split}_list", None) or f"{split}__{safe}.txt"
|
||||
out_path = data_root / merged_dir / out_name
|
||||
out_rel = (merged_dir / out_name).as_posix()
|
||||
|
||||
if getattr(cfg, "remerge_lists", False) or not out_path.is_file():
|
||||
merged = []
|
||||
seen = set()
|
||||
for pack_dir in pack_dirs:
|
||||
prefix = f"{pack_dir}/"
|
||||
list_path = data_root / pack_dir / list_name
|
||||
if not list_path.is_file():
|
||||
raise FileNotFoundError(list_path)
|
||||
for line in list_path.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
p = parse_gt_line(line)
|
||||
if not p:
|
||||
continue
|
||||
img, msk = apply_pack_prefix(p[0], p[1], prefix)
|
||||
if img in seen:
|
||||
continue
|
||||
seen.add(img)
|
||||
merged.append(f"{img} {msk}")
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_path.write_text("\n".join(merged) + "\n", encoding="utf-8")
|
||||
|
||||
return out_rel
|
||||
50
algorithms/lane_ufld/code/CLRNet-main/clrnet/utils/lane.py
Normal file
50
algorithms/lane_ufld/code/CLRNet-main/clrnet/utils/lane.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from scipy.interpolate import InterpolatedUnivariateSpline
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Lane:
|
||||
def __init__(self, points=None, invalid_value=-2., metadata=None):
|
||||
super(Lane, self).__init__()
|
||||
self.curr_iter = 0
|
||||
self.points = points
|
||||
self.invalid_value = invalid_value
|
||||
self.function = InterpolatedUnivariateSpline(points[:, 1],
|
||||
points[:, 0],
|
||||
k=min(3,
|
||||
len(points) - 1))
|
||||
self.min_y = points[:, 1].min() - 0.01
|
||||
self.max_y = points[:, 1].max() + 0.01
|
||||
|
||||
self.metadata = metadata or {}
|
||||
|
||||
def __repr__(self):
|
||||
return '[Lane]\n' + str(self.points) + '\n[/Lane]'
|
||||
|
||||
def __call__(self, lane_ys):
|
||||
lane_xs = self.function(lane_ys)
|
||||
|
||||
lane_xs[(lane_ys < self.min_y) |
|
||||
(lane_ys > self.max_y)] = self.invalid_value
|
||||
return lane_xs
|
||||
|
||||
def to_array(self, cfg):
|
||||
sample_y = cfg.sample_y
|
||||
img_w, img_h = cfg.ori_img_w, cfg.ori_img_h
|
||||
ys = np.array(sample_y) / float(img_h)
|
||||
xs = self(ys)
|
||||
valid_mask = (xs >= 0) & (xs < 1)
|
||||
lane_xs = xs[valid_mask] * img_w
|
||||
lane_ys = ys[valid_mask] * img_h
|
||||
lane = np.concatenate((lane_xs.reshape(-1, 1), lane_ys.reshape(-1, 1)),
|
||||
axis=1)
|
||||
return lane
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
if self.curr_iter < len(self.points):
|
||||
self.curr_iter += 1
|
||||
return self.points[self.curr_iter - 1]
|
||||
self.curr_iter = 0
|
||||
raise StopIteration
|
||||
@@ -0,0 +1,315 @@
|
||||
""" Evaluation script for the CULane metric on the LLAMAS dataset.
|
||||
This script will compute the F1, precision and recall metrics as described in the CULane benchmark.
|
||||
The predictions format is the same one used in the CULane benchmark.
|
||||
In summary, for every annotation file:
|
||||
labels/a/b/c.json
|
||||
There should be a prediction file:
|
||||
predictions/a/b/c.lines.txt
|
||||
Inside each .lines.txt file each line will contain a sequence of points (x, y) separated by spaces.
|
||||
For more information, please see https://xingangpan.github.io/projects/CULane.html
|
||||
This script uses two methods to compute the IoU: one using an image to draw the lanes (named `discrete` here) and
|
||||
another one that uses shapes with the shapely library (named `continuous` here). The results achieved with the first
|
||||
method are very close to the official CULane implementation. Although the second should be a more exact method and is
|
||||
faster to compute, it deviates more from the official implementation. By default, the method closer to the official
|
||||
metric is used.
|
||||
"""
|
||||
|
||||
import os
|
||||
import argparse
|
||||
from functools import partial
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from p_tqdm import t_map, p_map
|
||||
from scipy.interpolate import splprep, splev
|
||||
from scipy.optimize import linear_sum_assignment
|
||||
from shapely.geometry import LineString, Polygon
|
||||
|
||||
import clrnet.utils.llamas_utils as llamas_utils
|
||||
|
||||
LLAMAS_IMG_RES = (717, 1276)
|
||||
|
||||
|
||||
def add_ys(xs):
|
||||
"""For each x in xs, make a tuple with x and its corresponding y."""
|
||||
xs = np.array(xs[300:])
|
||||
valid = xs >= 0
|
||||
xs = xs[valid]
|
||||
assert len(xs) > 1
|
||||
ys = np.arange(300, 717)[valid]
|
||||
return list(zip(xs, ys))
|
||||
|
||||
|
||||
def draw_lane(lane, img=None, img_shape=None, width=30):
|
||||
"""Draw a lane (a list of points) on an image by drawing a line with width `width` through each
|
||||
pair of points i and i+i"""
|
||||
if img is None:
|
||||
img = np.zeros(img_shape, dtype=np.uint8)
|
||||
lane = lane.astype(np.int32)
|
||||
for p1, p2 in zip(lane[:-1], lane[1:]):
|
||||
cv2.line(img, tuple(p1), tuple(p2), color=(1, ), thickness=width)
|
||||
return img
|
||||
|
||||
|
||||
def discrete_cross_iou(xs, ys, width=30, img_shape=LLAMAS_IMG_RES):
|
||||
"""For each lane in xs, compute its Intersection Over Union (IoU) with each lane in ys by drawing the lanes on
|
||||
an image"""
|
||||
xs = [draw_lane(lane, img_shape=img_shape, width=width) > 0 for lane in xs]
|
||||
ys = [draw_lane(lane, img_shape=img_shape, width=width) > 0 for lane in ys]
|
||||
|
||||
ious = np.zeros((len(xs), len(ys)))
|
||||
for i, x in enumerate(xs):
|
||||
for j, y in enumerate(ys):
|
||||
# IoU by the definition: sum all intersections (binary and) and divide by the sum of the union (binary or)
|
||||
ious[i, j] = (x & y).sum() / (x | y).sum()
|
||||
return ious
|
||||
|
||||
|
||||
def continuous_cross_iou(xs, ys, width=30, img_shape=LLAMAS_IMG_RES):
|
||||
"""For each lane in xs, compute its Intersection Over Union (IoU) with each lane in ys using the area between each
|
||||
pair of points"""
|
||||
h, w = img_shape
|
||||
image = Polygon([(0, 0), (0, h - 1), (w - 1, h - 1), (w - 1, 0)])
|
||||
xs = [
|
||||
LineString(lane).buffer(distance=width / 2., cap_style=1,
|
||||
join_style=2).intersection(image)
|
||||
for lane in xs
|
||||
]
|
||||
ys = [
|
||||
LineString(lane).buffer(distance=width / 2., cap_style=1,
|
||||
join_style=2).intersection(image)
|
||||
for lane in ys
|
||||
]
|
||||
|
||||
ious = np.zeros((len(xs), len(ys)))
|
||||
for i, x in enumerate(xs):
|
||||
for j, y in enumerate(ys):
|
||||
ious[i, j] = x.intersection(y).area / x.union(y).area
|
||||
|
||||
return ious
|
||||
|
||||
|
||||
def interpolate_lane(points, n=50):
|
||||
"""Spline interpolation of a lane. Used on the predictions"""
|
||||
x = [x for x, _ in points]
|
||||
y = [y for _, y in points]
|
||||
tck, _ = splprep([x, y], s=0, t=n, k=min(3, len(points) - 1))
|
||||
|
||||
u = np.linspace(0., 1., n)
|
||||
return np.array(splev(u, tck)).T
|
||||
|
||||
|
||||
def culane_metric(pred,
|
||||
anno,
|
||||
width=30,
|
||||
iou_thresholds=[0.5],
|
||||
unofficial=False,
|
||||
img_shape=LLAMAS_IMG_RES):
|
||||
_metric = {}
|
||||
for thr in iou_thresholds:
|
||||
tp = 0
|
||||
fp = 0 if len(anno) != 0 else len(pred)
|
||||
fn = 0 if len(pred) != 0 else len(anno)
|
||||
_metric[thr] = [tp, fp, fn]
|
||||
|
||||
"""Computes CULane's metric for a single image"""
|
||||
if len(pred) == 0:
|
||||
return 0, 0, len(anno), _metric
|
||||
if len(anno) == 0:
|
||||
return 0, len(pred), 0, _metric
|
||||
|
||||
interp_pred = np.array([
|
||||
interpolate_lane(pred_lane, n=50) for pred_lane in pred
|
||||
]) # (4, 50, 2)
|
||||
anno = np.array([np.array(anno_lane) for anno_lane in anno], dtype=object)
|
||||
|
||||
if unofficial:
|
||||
ious = continuous_cross_iou(interp_pred, anno, width=width)
|
||||
else:
|
||||
ious = discrete_cross_iou(interp_pred,
|
||||
anno,
|
||||
width=width,
|
||||
img_shape=img_shape)
|
||||
row_ind, col_ind = linear_sum_assignment(1 - ious)
|
||||
_metric = {}
|
||||
for thr in iou_thresholds:
|
||||
tp = int((ious[row_ind, col_ind] > thr).sum())
|
||||
fp = len(pred) - tp
|
||||
fn = len(anno) - tp
|
||||
_metric[thr] = [tp, fp, fn]
|
||||
|
||||
return _metric
|
||||
|
||||
|
||||
def load_prediction(path):
|
||||
"""Loads an image's predictions
|
||||
Returns a list of lanes, where each lane is a list of points (x,y)
|
||||
"""
|
||||
with open(path, 'r') as data_file:
|
||||
img_data = data_file.readlines()
|
||||
img_data = [line.split() for line in img_data]
|
||||
img_data = [list(map(float, lane)) for lane in img_data]
|
||||
img_data = [[(lane[i], lane[i + 1]) for i in range(0, len(lane), 2)]
|
||||
for lane in img_data]
|
||||
img_data = [lane for lane in img_data if len(lane) >= 2]
|
||||
|
||||
return img_data
|
||||
|
||||
|
||||
def load_prediction_list(label_paths, pred_dir):
|
||||
return [
|
||||
load_prediction(
|
||||
os.path.join(pred_dir, path.replace('.json', '.lines.txt')))
|
||||
for path in label_paths
|
||||
]
|
||||
|
||||
|
||||
def load_labels(label_dir):
|
||||
"""Loads the annotations and its paths
|
||||
Each annotation is converted to a list of points (x, y)
|
||||
"""
|
||||
label_paths = llamas_utils.get_files_from_folder(label_dir, '.json')
|
||||
annos = [
|
||||
[
|
||||
add_ys(xs) for xs in
|
||||
llamas_utils.get_horizontal_values_for_four_lanes(label_path)
|
||||
if (np.array(xs) >= 0).sum() > 1
|
||||
] # lanes annotated with a single point are ignored
|
||||
for label_path in label_paths
|
||||
]
|
||||
label_paths = [llamas_utils.get_label_base(p) for p in label_paths]
|
||||
return np.array(annos, dtype=object), np.array(label_paths, dtype=object)
|
||||
|
||||
|
||||
def eval_predictions(pred_dir,
|
||||
anno_dir,
|
||||
width=30,
|
||||
iou_thresholds=[0.5],
|
||||
unofficial=True,
|
||||
sequential=False):
|
||||
"""Evaluates the predictions in pred_dir and returns CULane's metrics (precision, recall, F1 and its components)"""
|
||||
print(f'Loading annotation data ({anno_dir})...')
|
||||
os.makedirs('cache', exist_ok=True)
|
||||
annotations_path = 'cache/llamas_annotations.pkl'
|
||||
label_path = 'cache/llamas_label_paths.pkl'
|
||||
import pickle as pkl
|
||||
if os.path.exists(annotations_path) and os.path.exists(label_path):
|
||||
with open(annotations_path, 'rb') as cache_file:
|
||||
annotations = pkl.load(cache_file)
|
||||
with open(label_path, 'rb') as cache_file:
|
||||
label_paths = pkl.load(cache_file)
|
||||
else:
|
||||
annotations, label_paths = load_labels(anno_dir)
|
||||
with open(annotations_path, 'wb') as cache_file:
|
||||
pkl.dump(annotations, cache_file)
|
||||
with open(label_path, 'wb') as cache_file:
|
||||
pkl.dump(label_paths, cache_file)
|
||||
|
||||
print(f'Loading prediction data ({pred_dir})...')
|
||||
predictions = load_prediction_list(label_paths, pred_dir)
|
||||
print('Calculating metric {}...'.format(
|
||||
'sequentially' if sequential else 'in parallel'))
|
||||
if sequential:
|
||||
results = map(
|
||||
partial(culane_metric,
|
||||
width=width,
|
||||
unofficial=unofficial,
|
||||
img_shape=LLAMAS_IMG_RES), predictions, annotations)
|
||||
else:
|
||||
from multiprocessing import Pool, cpu_count
|
||||
from itertools import repeat
|
||||
with Pool(cpu_count()) as p:
|
||||
results = p.starmap(culane_metric, zip(predictions, annotations,
|
||||
repeat(width),
|
||||
repeat(iou_thresholds),
|
||||
repeat(unofficial),
|
||||
repeat(LLAMAS_IMG_RES)))
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
mean_f1, mean_prec, mean_recall, total_tp, total_fp, total_fn = 0, 0, 0, 0, 0, 0
|
||||
ret = {}
|
||||
for thr in iou_thresholds:
|
||||
tp = sum(m[thr][0] for m in results)
|
||||
fp = sum(m[thr][1] for m in results)
|
||||
fn = sum(m[thr][2] for m in results)
|
||||
precision = float(tp) / (tp + fp) if tp != 0 else 0
|
||||
recall = float(tp) / (tp + fn) if tp != 0 else 0
|
||||
f1 = 2 * precision * recall / (precision + recall) if tp !=0 else 0
|
||||
logger.info('iou thr: {:.2f}, tp: {}, fp: {}, fn: {}, '
|
||||
'precision: {}, recall: {}, f1: {}'.format(
|
||||
thr, tp, fp, fn, precision, recall, f1))
|
||||
mean_f1 += f1 / len(iou_thresholds)
|
||||
mean_prec += precision / len(iou_thresholds)
|
||||
mean_recall += recall / len(iou_thresholds)
|
||||
total_tp += tp
|
||||
total_fp += fp
|
||||
total_fn += fn
|
||||
ret[thr] = {
|
||||
'TP': tp,
|
||||
'FP': fp,
|
||||
'FN': fn,
|
||||
'Precision': precision,
|
||||
'Recall': recall,
|
||||
'F1': f1
|
||||
}
|
||||
if len(iou_thresholds) > 2:
|
||||
logger.info('mean result, total_tp: {}, total_fp: {}, total_fn: {},'
|
||||
'precision: {}, recall: {}, f1: {}'.format(total_tp, total_fp,
|
||||
total_fn, mean_prec, mean_recall, mean_f1))
|
||||
ret['mean'] = {
|
||||
'TP': total_tp,
|
||||
'FP': total_fp,
|
||||
'FN': total_fn,
|
||||
'Precision': mean_prec,
|
||||
'Recall': mean_recall,
|
||||
'F1': mean_f1
|
||||
}
|
||||
return ret
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Measure CULane's metric on the LLAMAS dataset")
|
||||
parser.add_argument(
|
||||
"--pred_dir",
|
||||
help="Path to directory containing the predicted lanes",
|
||||
required=True)
|
||||
parser.add_argument(
|
||||
"--anno_dir",
|
||||
help="Path to directory containing the annotated lanes",
|
||||
required=True)
|
||||
parser.add_argument("--width",
|
||||
type=int,
|
||||
default=30,
|
||||
help="Width of the lane")
|
||||
parser.add_argument("--sequential",
|
||||
action='store_true',
|
||||
help="Run sequentially instead of in parallel")
|
||||
parser.add_argument("--unofficial",
|
||||
action='store_true',
|
||||
help="Use a faster but unofficial algorithm")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
results = eval_predictions(args.pred_dir,
|
||||
args.anno_dir,
|
||||
width=args.width,
|
||||
iou_thresholds=[0.5],
|
||||
unofficial=args.unofficial,
|
||||
sequential=args.sequential)
|
||||
|
||||
header = '=' * 20 + ' Results' + '=' * 20
|
||||
print(header)
|
||||
for metric, value in results.items():
|
||||
if isinstance(value, float):
|
||||
print('{}: {:.4f}'.format(metric, value))
|
||||
else:
|
||||
print('{}: {}'.format(metric, value))
|
||||
print('=' * len(header))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,389 @@
|
||||
# All following lines (which were slightly modified) were taken from: https://github.com/karstenBehrendt/unsupervised_llamas
|
||||
# Its license is copied here
|
||||
|
||||
# ##### Begin License ######
|
||||
# MIT License
|
||||
|
||||
# Copyright (c) 2019 Karsten Behrendt, Robert Bosch LLC
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# ##### End License ######
|
||||
|
||||
# Start code under the previous license
|
||||
|
||||
import os
|
||||
import json
|
||||
import numpy as np
|
||||
|
||||
|
||||
def get_files_from_folder(directory, extension=None):
|
||||
"""Get all files within a folder that fit the extension """
|
||||
# NOTE Can be replaced by glob for newer python versions
|
||||
label_files = []
|
||||
for root, _, files in os.walk(directory):
|
||||
for some_file in files:
|
||||
label_files.append(os.path.abspath(os.path.join(root, some_file)))
|
||||
if extension is not None:
|
||||
label_files = list(filter(lambda x: x.endswith(extension),
|
||||
label_files))
|
||||
return label_files
|
||||
|
||||
|
||||
def get_label_base(label_path):
|
||||
""" Gets directory independent label path """
|
||||
return '/'.join(label_path.split('/')[-2:])
|
||||
|
||||
|
||||
def get_labels(dataset_root, split='test'):
|
||||
""" Gets label files of specified dataset split """
|
||||
label_paths = get_files_from_folder(os.path.join(dataset_root, split),
|
||||
'.json')
|
||||
return label_paths
|
||||
|
||||
|
||||
def _extend_lane(lane, projection_matrix):
|
||||
"""Extends marker closest to the camera
|
||||
Adds an extra marker that reaches the end of the image
|
||||
Parameters
|
||||
----------
|
||||
lane : iterable of markers
|
||||
projection_matrix : 3x3 projection matrix
|
||||
"""
|
||||
# Unfortunately, we did not store markers beyond the image plane. That hurts us now
|
||||
# z is the orthongal distance to the car. It's good enough
|
||||
|
||||
# The markers are automatically detected, mapped, and labeled. There exist faulty ones,
|
||||
# e.g., horizontal markers which need to be filtered
|
||||
filtered_markers = filter(
|
||||
lambda x: (x['pixel_start']['y'] != x['pixel_end']['y'] and x[
|
||||
'pixel_start']['x'] != x['pixel_end']['x']), lane['markers'])
|
||||
# might be the first marker in the list but not guaranteed
|
||||
closest_marker = min(filtered_markers, key=lambda x: x['world_start']['z'])
|
||||
|
||||
if closest_marker['world_start'][
|
||||
'z'] < 0: # This one likely equals "if False"
|
||||
return lane
|
||||
|
||||
# World marker extension approximation
|
||||
x_gradient = (closest_marker['world_end']['x'] - closest_marker['world_start']['x']) /\
|
||||
(closest_marker['world_end']['z'] - closest_marker['world_start']['z'])
|
||||
y_gradient = (closest_marker['world_end']['y'] - closest_marker['world_start']['y']) /\
|
||||
(closest_marker['world_end']['z'] - closest_marker['world_start']['z'])
|
||||
|
||||
zero_x = closest_marker['world_start']['x'] - (
|
||||
closest_marker['world_start']['z'] - 1) * x_gradient
|
||||
zero_y = closest_marker['world_start']['y'] - (
|
||||
closest_marker['world_start']['z'] - 1) * y_gradient
|
||||
|
||||
# Pixel marker extension approximation
|
||||
pixel_x_gradient = (closest_marker['pixel_end']['x'] - closest_marker['pixel_start']['x']) /\
|
||||
(closest_marker['pixel_end']['y'] - closest_marker['pixel_start']['y'])
|
||||
pixel_y_gradient = (closest_marker['pixel_end']['y'] - closest_marker['pixel_start']['y']) /\
|
||||
(closest_marker['pixel_end']['x'] - closest_marker['pixel_start']['x'])
|
||||
|
||||
pixel_zero_x = closest_marker['pixel_start']['x'] + (
|
||||
716 - closest_marker['pixel_start']['y']) * pixel_x_gradient
|
||||
if pixel_zero_x < 0:
|
||||
left_y = closest_marker['pixel_start'][
|
||||
'y'] - closest_marker['pixel_start']['x'] * pixel_y_gradient
|
||||
new_pixel_point = (0, left_y)
|
||||
elif pixel_zero_x > 1276:
|
||||
right_y = closest_marker['pixel_start']['y'] + (
|
||||
1276 - closest_marker['pixel_start']['x']) * pixel_y_gradient
|
||||
new_pixel_point = (1276, right_y)
|
||||
else:
|
||||
new_pixel_point = (pixel_zero_x, 716)
|
||||
|
||||
new_marker = {
|
||||
'lane_marker_id': 'FAKE',
|
||||
'world_end': {
|
||||
'x': closest_marker['world_start']['x'],
|
||||
'y': closest_marker['world_start']['y'],
|
||||
'z': closest_marker['world_start']['z']
|
||||
},
|
||||
'world_start': {
|
||||
'x': zero_x,
|
||||
'y': zero_y,
|
||||
'z': 1
|
||||
},
|
||||
'pixel_end': {
|
||||
'x': closest_marker['pixel_start']['x'],
|
||||
'y': closest_marker['pixel_start']['y']
|
||||
},
|
||||
'pixel_start': {
|
||||
'x': ir(new_pixel_point[0]),
|
||||
'y': ir(new_pixel_point[1])
|
||||
}
|
||||
}
|
||||
lane['markers'].insert(0, new_marker)
|
||||
|
||||
return lane
|
||||
|
||||
|
||||
class SplineCreator():
|
||||
"""
|
||||
For each lane divder
|
||||
- all lines are projected
|
||||
- linearly interpolated to limit oscillations
|
||||
- interpolated by a spline
|
||||
- subsampled to receive individual pixel values
|
||||
The spline creation can be optimized!
|
||||
- Better spline parameters
|
||||
- Extend lowest marker to reach bottom of image would also help
|
||||
- Extending last marker may in some cases be interesting too
|
||||
Any help is welcome.
|
||||
Call create_all_points and get the points in self.sampled_points
|
||||
It has an x coordinate for each value for each lane
|
||||
"""
|
||||
def __init__(self, json_path):
|
||||
self.json_path = json_path
|
||||
self.json_content = read_json(json_path)
|
||||
self.lanes = self.json_content['lanes']
|
||||
self.lane_marker_points = {}
|
||||
self.sampled_points = {} # <--- the interesting part
|
||||
self.debug_image = np.zeros((717, 1276, 3), dtype=np.uint8)
|
||||
|
||||
def _sample_points(self, lane, ypp=5, between_markers=True):
|
||||
""" Markers are given by start and endpoint. This one adds extra points
|
||||
which need to be considered for the interpolation. Otherwise the spline
|
||||
could arbitrarily oscillate between start and end of the individual markers
|
||||
Parameters
|
||||
----------
|
||||
lane: polyline, in theory but there are artifacts which lead to inconsistencies
|
||||
in ordering. There may be parallel lines. The lines may be dashed. It's messy.
|
||||
ypp: y-pixels per point, e.g. 10 leads to a point every ten pixels
|
||||
between_markers : bool, interpolates inbetween dashes
|
||||
Notes
|
||||
-----
|
||||
Especially, adding points in the lower parts of the image (high y-values) because
|
||||
the start and end points are too sparse.
|
||||
Removing upper lane markers that have starting and end points mapped into the same pixel.
|
||||
"""
|
||||
|
||||
# Collect all x values from all markers along a given line. There may be multiple
|
||||
# intersecting markers, i.e., multiple entries for some y values
|
||||
x_values = [[] for i in range(717)]
|
||||
for marker in lane['markers']:
|
||||
x_values[marker['pixel_start']['y']].append(
|
||||
marker['pixel_start']['x'])
|
||||
|
||||
height = marker['pixel_start']['y'] - marker['pixel_end']['y']
|
||||
if height > 2:
|
||||
slope = (marker['pixel_end']['x'] -
|
||||
marker['pixel_start']['x']) / height
|
||||
step_size = (marker['pixel_start']['y'] -
|
||||
marker['pixel_end']['y']) / float(height)
|
||||
for i in range(height + 1):
|
||||
x = marker['pixel_start']['x'] + slope * step_size * i
|
||||
y = marker['pixel_start']['y'] - step_size * i
|
||||
x_values[ir(y)].append(ir(x))
|
||||
|
||||
# Calculate average x values for each y value
|
||||
for y, xs in enumerate(x_values):
|
||||
if not xs:
|
||||
x_values[y] = -1
|
||||
else:
|
||||
x_values[y] = sum(xs) / float(len(xs))
|
||||
|
||||
# In the following, we will only interpolate between markers if needed
|
||||
if not between_markers:
|
||||
return x_values # TODO ypp
|
||||
|
||||
# # interpolate between markers
|
||||
current_y = 0
|
||||
while x_values[current_y] == -1: # skip missing first entries
|
||||
current_y += 1
|
||||
|
||||
# Also possible using numpy.interp when accounting for beginning and end
|
||||
next_set_y = 0
|
||||
try:
|
||||
while current_y < 717:
|
||||
if x_values[current_y] != -1: # set. Nothing to be done
|
||||
current_y += 1
|
||||
continue
|
||||
|
||||
# Finds target x value for interpolation
|
||||
while next_set_y <= current_y or x_values[next_set_y] == -1:
|
||||
next_set_y += 1
|
||||
if next_set_y >= 717:
|
||||
raise StopIteration
|
||||
|
||||
x_values[current_y] = x_values[current_y - 1] + (x_values[next_set_y] - x_values[current_y - 1]) /\
|
||||
(next_set_y - current_y + 1)
|
||||
current_y += 1
|
||||
|
||||
except StopIteration:
|
||||
pass # Done with lane
|
||||
|
||||
return x_values
|
||||
|
||||
def _lane_points_fit(self, lane):
|
||||
# TODO name and docstring
|
||||
""" Fits spline in image space for the markers of a single lane (side)
|
||||
Parameters
|
||||
----------
|
||||
lane: dict as specified in label
|
||||
Returns
|
||||
-------
|
||||
Pixel level values for curve along the y-axis
|
||||
Notes
|
||||
-----
|
||||
This one can be drastically improved. Probably fairly easy as well.
|
||||
"""
|
||||
# NOTE all variable names represent image coordinates, interpolation coordinates are swapped!
|
||||
lane = _extend_lane(lane, self.json_content['projection_matrix'])
|
||||
sampled_points = self._sample_points(lane, ypp=1)
|
||||
self.sampled_points[lane['lane_id']] = sampled_points
|
||||
|
||||
return sampled_points
|
||||
|
||||
def create_all_points(self, ):
|
||||
""" Creates splines for given label """
|
||||
for lane in self.lanes:
|
||||
self._lane_points_fit(lane)
|
||||
|
||||
|
||||
def get_horizontal_values_for_four_lanes(json_path):
|
||||
""" Gets an x value for every y coordinate for l1, l0, r0, r1
|
||||
This allows to easily train a direct curve approximation. For each value along
|
||||
the y-axis, the respective x-values can be compared, e.g. squared distance.
|
||||
Missing values are filled with -1. Missing values are values missing from the spline.
|
||||
There is no extrapolation to the image start/end (yet).
|
||||
But values are interpolated between markers. Space between dashed markers is not missing.
|
||||
Parameters
|
||||
----------
|
||||
json_path: str
|
||||
path to label-file
|
||||
Returns
|
||||
-------
|
||||
List of [l1, l0, r0, r1], each of which represents a list of ints the length of
|
||||
the number of vertical pixels of the image
|
||||
Notes
|
||||
-----
|
||||
The points are currently based on the splines. The splines are interpolated based on the
|
||||
segmentation values. The spline interpolation has lots of room for improvement, e.g.
|
||||
the lines could be interpolated in 3D, a better approach to spline interpolation could
|
||||
be used, there is barely any error checking, sometimes the splines oscillate too much.
|
||||
This was used for a quick poly-line regression training only.
|
||||
"""
|
||||
|
||||
sc = SplineCreator(json_path)
|
||||
sc.create_all_points()
|
||||
|
||||
l1 = sc.sampled_points.get('l1', [-1] * 717)
|
||||
l0 = sc.sampled_points.get('l0', [-1] * 717)
|
||||
r0 = sc.sampled_points.get('r0', [-1] * 717)
|
||||
r1 = sc.sampled_points.get('r1', [-1] * 717)
|
||||
|
||||
lanes = [l1, l0, r0, r1]
|
||||
return lanes
|
||||
|
||||
|
||||
def _filter_lanes_by_size(label, min_height=40):
|
||||
""" May need some tuning """
|
||||
filtered_lanes = []
|
||||
for lane in label['lanes']:
|
||||
lane_start = min(
|
||||
[int(marker['pixel_start']['y']) for marker in lane['markers']])
|
||||
lane_end = max(
|
||||
[int(marker['pixel_start']['y']) for marker in lane['markers']])
|
||||
if (lane_end - lane_start) < min_height:
|
||||
continue
|
||||
filtered_lanes.append(lane)
|
||||
label['lanes'] = filtered_lanes
|
||||
|
||||
|
||||
def _filter_few_markers(label, min_markers=2):
|
||||
"""Filter lines that consist of only few markers"""
|
||||
filtered_lanes = []
|
||||
for lane in label['lanes']:
|
||||
if len(lane['markers']) >= min_markers:
|
||||
filtered_lanes.append(lane)
|
||||
label['lanes'] = filtered_lanes
|
||||
|
||||
|
||||
def _fix_lane_names(label):
|
||||
""" Given keys ['l3', 'l2', 'l0', 'r0', 'r2'] returns ['l2', 'l1', 'l0', 'r0', 'r1']"""
|
||||
|
||||
# Create mapping
|
||||
l_counter = 0
|
||||
r_counter = 0
|
||||
mapping = {}
|
||||
lane_ids = [lane['lane_id'] for lane in label['lanes']]
|
||||
for key in sorted(lane_ids):
|
||||
if key[0] == 'l':
|
||||
mapping[key] = 'l' + str(l_counter)
|
||||
l_counter += 1
|
||||
if key[0] == 'r':
|
||||
mapping[key] = 'r' + str(r_counter)
|
||||
r_counter += 1
|
||||
for lane in label['lanes']:
|
||||
lane['lane_id'] = mapping[lane['lane_id']]
|
||||
|
||||
|
||||
def read_json(json_path, min_lane_height=20):
|
||||
""" Reads and cleans label file information by path"""
|
||||
with open(json_path, 'r') as jf:
|
||||
label_content = json.load(jf)
|
||||
|
||||
_filter_lanes_by_size(label_content, min_height=min_lane_height)
|
||||
_filter_few_markers(label_content, min_markers=2)
|
||||
_fix_lane_names(label_content)
|
||||
|
||||
content = {
|
||||
'projection_matrix': label_content['projection_matrix'],
|
||||
'lanes': label_content['lanes']
|
||||
}
|
||||
|
||||
for lane in content['lanes']:
|
||||
for marker in lane['markers']:
|
||||
for pixel_key in marker['pixel_start'].keys():
|
||||
marker['pixel_start'][pixel_key] = int(
|
||||
marker['pixel_start'][pixel_key])
|
||||
for pixel_key in marker['pixel_end'].keys():
|
||||
marker['pixel_end'][pixel_key] = int(
|
||||
marker['pixel_end'][pixel_key])
|
||||
for pixel_key in marker['world_start'].keys():
|
||||
marker['world_start'][pixel_key] = float(
|
||||
marker['world_start'][pixel_key])
|
||||
for pixel_key in marker['world_end'].keys():
|
||||
marker['world_end'][pixel_key] = float(
|
||||
marker['world_end'][pixel_key])
|
||||
return content
|
||||
|
||||
|
||||
def ir(some_value):
|
||||
""" Rounds and casts to int
|
||||
Useful for pixel values that cannot be floats
|
||||
Parameters
|
||||
----------
|
||||
some_value : float
|
||||
numeric value
|
||||
Returns
|
||||
--------
|
||||
Rounded integer
|
||||
Raises
|
||||
------
|
||||
ValueError for non scalar types
|
||||
"""
|
||||
return int(round(some_value))
|
||||
|
||||
|
||||
# End code under the previous license
|
||||
18
algorithms/lane_ufld/code/CLRNet-main/clrnet/utils/logger.py
Normal file
18
algorithms/lane_ufld/code/CLRNet-main/clrnet/utils/logger.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import logging
|
||||
|
||||
|
||||
def init_logger(log_file=None, log_level=logging.INFO):
|
||||
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)
|
||||
|
||||
logging.basicConfig(level=log_level, handlers=handlers)
|
||||
@@ -0,0 +1,60 @@
|
||||
"""Extract lane polylines from MUFLD/CULane-style segmentation masks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def normalize_mask_labels(arr: np.ndarray, num_lanes: int = 4) -> np.ndarray:
|
||||
"""Map MUFLD ids 0,2,3,4,5 -> 0,1,2,3,4 for seg training."""
|
||||
if arr.max() <= num_lanes:
|
||||
return arr.astype(np.uint8)
|
||||
out = np.zeros_like(arr, dtype=np.uint8)
|
||||
for lane_idx in range(1, num_lanes + 1):
|
||||
out[arr == (lane_idx + 1)] = lane_idx
|
||||
return out
|
||||
|
||||
|
||||
def lane_pixel_id(lane_idx: int, mask_max: int) -> int:
|
||||
if mask_max == 2:
|
||||
return lane_idx
|
||||
return lane_idx + 1
|
||||
|
||||
|
||||
def lanes_from_mask(
|
||||
mask: np.ndarray,
|
||||
sample_ys: list | range,
|
||||
num_lanes: int = 4,
|
||||
) -> list[list[tuple[float, float]]]:
|
||||
"""Return list of lanes; each lane is [(x,y), ...] sorted by y descending."""
|
||||
if mask.ndim > 2:
|
||||
mask = mask[:, :, 0]
|
||||
mask = normalize_mask_labels(mask.astype(np.uint8), num_lanes)
|
||||
mx = int(mask.max())
|
||||
lanes = []
|
||||
for lane_idx in range(1, num_lanes + 1):
|
||||
vid = lane_pixel_id(lane_idx, mx)
|
||||
pts = []
|
||||
for y in sample_ys:
|
||||
yi = int(round(y))
|
||||
if yi < 0 or yi >= mask.shape[0]:
|
||||
continue
|
||||
xs = np.where(mask[yi] == vid)[0]
|
||||
if len(xs) == 0:
|
||||
continue
|
||||
pts.append((float(np.mean(xs)), float(yi)))
|
||||
if len(pts) >= 2:
|
||||
pts = sorted(pts, key=lambda p: -p[1])
|
||||
lanes.append(pts)
|
||||
return lanes
|
||||
|
||||
|
||||
def lanes_to_lines_txt(lanes: list[list[tuple[float, float]]]) -> str:
|
||||
lines = []
|
||||
for lane in lanes:
|
||||
parts = []
|
||||
for x, y in lane:
|
||||
parts.append(f"{x:.5f} {y:.5f}")
|
||||
if parts:
|
||||
lines.append(" ".join(parts))
|
||||
return "\n".join(lines) + ("\n" if lines else "")
|
||||
@@ -0,0 +1,58 @@
|
||||
import torch
|
||||
import os
|
||||
from torch import nn
|
||||
import numpy as np
|
||||
import torch.nn.functional
|
||||
|
||||
|
||||
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=False)
|
||||
|
||||
|
||||
def resume_network(model_dir, net, optim, scheduler, recorder):
|
||||
if not os.path.exists(model_dir):
|
||||
print('WARNING: NO MODEL LOADED !!!', 'red')
|
||||
return 0
|
||||
|
||||
print('resume model: {}'.format(model_dir))
|
||||
|
||||
pretrained_model = torch.load(model_dir)
|
||||
net.load_state_dict(pretrained_model['net'])
|
||||
optim.load_state_dict(pretrained_model['optim'])
|
||||
scheduler.load_state_dict(pretrained_model['scheduler'])
|
||||
recorder.load_state_dict(pretrained_model['recorder'])
|
||||
return pretrained_model['epoch'] + 1
|
||||
135
algorithms/lane_ufld/code/CLRNet-main/clrnet/utils/recorder.py
Normal file
135
algorithms/lane_ufld/code/CLRNet-main/clrnet/utils/recorder.py
Normal file
@@ -0,0 +1,135 @@
|
||||
from collections import deque, defaultdict
|
||||
import torch
|
||||
import os
|
||||
import datetime
|
||||
from .logger import init_logger
|
||||
import logging
|
||||
import pathspec
|
||||
|
||||
|
||||
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')
|
||||
|
||||
init_logger(self.log_path)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.logger.info('Config: \n' + cfg.text)
|
||||
|
||||
self.save_cfg(cfg)
|
||||
self.cp_projects(self.work_dir)
|
||||
|
||||
# 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 save_cfg(self, cfg):
|
||||
cfg_path = os.path.join(self.work_dir, 'config.py')
|
||||
with open(cfg_path, 'w') as cfg_file:
|
||||
cfg_file.write(cfg.text)
|
||||
|
||||
def cp_projects(self, to_path):
|
||||
with open('./.gitignore', 'r') as fp:
|
||||
ign = fp.read()
|
||||
ign += '\n.git'
|
||||
spec = pathspec.PathSpec.from_lines(
|
||||
pathspec.patterns.GitWildMatchPattern, ign.splitlines())
|
||||
all_files = {
|
||||
os.path.join(root, name)
|
||||
for root, dirs, files in os.walk('./') for name in files
|
||||
}
|
||||
matches = spec.match_files(all_files)
|
||||
matches = set(matches)
|
||||
to_cp_files = all_files - matches
|
||||
for f in to_cp_files:
|
||||
dirs = os.path.join(to_path, 'code', os.path.split(f[2:])[0])
|
||||
if not os.path.exists(dirs):
|
||||
os.makedirs(dirs)
|
||||
os.system('cp %s %s' % (f, os.path.join(to_path, 'code', f[2:])))
|
||||
|
||||
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():
|
||||
if not isinstance(v, torch.Tensor): continue
|
||||
self.loss_stats[k].update(v.detach().mean().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: {:.6f}', '{}', '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)
|
||||
@@ -0,0 +1,82 @@
|
||||
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 = cfg.copy()
|
||||
obj_type = args.pop('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)
|
||||
@@ -0,0 +1,136 @@
|
||||
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
|
||||
|
||||
fp = fp / num
|
||||
fn = fn / num
|
||||
tp = 1 - fp
|
||||
precision = tp / (tp + fp)
|
||||
recall = tp / (tp + fn)
|
||||
f1 = 2 * precision * recall / (precision + recall)
|
||||
|
||||
return json.dumps([{
|
||||
'name': 'Accuracy',
|
||||
'value': accuracy / num,
|
||||
'order': 'desc'
|
||||
}, {
|
||||
'name': 'F1_score',
|
||||
'value': f1,
|
||||
'order': 'desc'
|
||||
}, {
|
||||
'name': 'FP',
|
||||
'value': fp,
|
||||
'order': 'asc'
|
||||
}, {
|
||||
'name': 'FN',
|
||||
'value': fn,
|
||||
'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)
|
||||
@@ -0,0 +1,64 @@
|
||||
import cv2
|
||||
import os
|
||||
import os.path as osp
|
||||
|
||||
|
||||
COLORS = [
|
||||
(255, 0, 0),
|
||||
(0, 255, 0),
|
||||
(0, 0, 255),
|
||||
(255, 255, 0),
|
||||
(255, 0, 255),
|
||||
(0, 255, 255),
|
||||
(128, 255, 0),
|
||||
(255, 128, 0),
|
||||
(128, 0, 255),
|
||||
(255, 0, 128),
|
||||
(0, 128, 255),
|
||||
(0, 255, 128),
|
||||
(128, 255, 255),
|
||||
(255, 128, 255),
|
||||
(255, 255, 128),
|
||||
(60, 180, 0),
|
||||
(180, 60, 0),
|
||||
(0, 60, 180),
|
||||
(0, 180, 60),
|
||||
(60, 0, 180),
|
||||
(180, 0, 60),
|
||||
(255, 0, 0),
|
||||
(0, 255, 0),
|
||||
(0, 0, 255),
|
||||
(255, 255, 0),
|
||||
(255, 0, 255),
|
||||
(0, 255, 255),
|
||||
(128, 255, 0),
|
||||
(255, 128, 0),
|
||||
(128, 0, 255),
|
||||
]
|
||||
|
||||
|
||||
def imshow_lanes(img, lanes, show=False, out_file=None, width=4):
|
||||
lanes_xys = []
|
||||
for _, lane in enumerate(lanes):
|
||||
xys = []
|
||||
for x, y in lane:
|
||||
if x <= 0 or y <= 0:
|
||||
continue
|
||||
x, y = int(x), int(y)
|
||||
xys.append((x, y))
|
||||
lanes_xys.append(xys)
|
||||
lanes_xys.sort(key=lambda xys : xys[0][0])
|
||||
|
||||
for idx, xys in enumerate(lanes_xys):
|
||||
for i in range(1, len(xys)):
|
||||
cv2.line(img, xys[i - 1], xys[i], COLORS[idx], thickness=width)
|
||||
|
||||
|
||||
if show:
|
||||
cv2.imshow('view', img)
|
||||
cv2.waitKey(0)
|
||||
|
||||
if out_file:
|
||||
if not osp.exists(osp.dirname(out_file)):
|
||||
os.makedirs(osp.dirname(out_file))
|
||||
cv2.imwrite(out_file, img)
|
||||
@@ -0,0 +1,124 @@
|
||||
net = dict(type='Detector', )
|
||||
|
||||
backbone = dict(
|
||||
type='DLAWrapper',
|
||||
dla='dla34',
|
||||
pretrained=True,
|
||||
)
|
||||
|
||||
num_points = 72
|
||||
max_lanes = 4
|
||||
sample_y = range(589, 230, -20)
|
||||
|
||||
heads = dict(type='CLRHead',
|
||||
num_priors=192,
|
||||
refine_layers=3,
|
||||
fc_hidden_dim=64,
|
||||
sample_points=36)
|
||||
|
||||
iou_loss_weight = 2.
|
||||
cls_loss_weight = 2.
|
||||
xyt_loss_weight = 0.2
|
||||
seg_loss_weight = 1.0
|
||||
|
||||
work_dirs = "work_dirs/clr/dla34_culane"
|
||||
|
||||
neck = dict(type='FPN',
|
||||
in_channels=[128, 256, 512],
|
||||
out_channels=64,
|
||||
num_outs=3,
|
||||
attention=False)
|
||||
|
||||
test_parameters = dict(conf_threshold=0.4, nms_thres=50, nms_topk=max_lanes)
|
||||
|
||||
epochs = 15
|
||||
batch_size = 24
|
||||
|
||||
optimizer = dict(type='AdamW', lr=0.6e-3) # 3e-4 for batchsize 8
|
||||
total_iter = (88880 // batch_size) * epochs
|
||||
scheduler = dict(type='CosineAnnealingLR', T_max=total_iter)
|
||||
|
||||
eval_ep = 3
|
||||
save_ep = 10
|
||||
|
||||
img_norm = dict(mean=[103.939, 116.779, 123.68], std=[1., 1., 1.])
|
||||
ori_img_w = 1640
|
||||
ori_img_h = 590
|
||||
img_w = 800
|
||||
img_h = 320
|
||||
cut_height = 270
|
||||
|
||||
train_process = [
|
||||
dict(
|
||||
type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
dict(name='HorizontalFlip', parameters=dict(p=1.0), p=0.5),
|
||||
dict(name='ChannelShuffle', parameters=dict(p=1.0), p=0.1),
|
||||
dict(name='MultiplyAndAddToBrightness',
|
||||
parameters=dict(mul=(0.85, 1.15), add=(-10, 10)),
|
||||
p=0.6),
|
||||
dict(name='AddToHueAndSaturation',
|
||||
parameters=dict(value=(-10, 10)),
|
||||
p=0.7),
|
||||
dict(name='OneOf',
|
||||
transforms=[
|
||||
dict(name='MotionBlur', parameters=dict(k=(3, 5))),
|
||||
dict(name='MedianBlur', parameters=dict(k=(3, 5)))
|
||||
],
|
||||
p=0.2),
|
||||
dict(name='Affine',
|
||||
parameters=dict(translate_percent=dict(x=(-0.1, 0.1),
|
||||
y=(-0.1, 0.1)),
|
||||
rotate=(-10, 10),
|
||||
scale=(0.8, 1.2)),
|
||||
p=0.7),
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
),
|
||||
dict(type='ToTensor', keys=['img', 'lane_line', 'seg']),
|
||||
]
|
||||
|
||||
val_process = [
|
||||
dict(type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
training=False),
|
||||
dict(type='ToTensor', keys=['img']),
|
||||
]
|
||||
|
||||
dataset_path = './data/CULane'
|
||||
dataset_type = 'CULane'
|
||||
dataset = dict(train=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='train',
|
||||
processes=train_process,
|
||||
),
|
||||
val=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
),
|
||||
test=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
))
|
||||
|
||||
workers = 10
|
||||
log_interval = 500
|
||||
# seed = 0
|
||||
num_classes = 4 + 1
|
||||
ignore_label = 255
|
||||
bg_weight = 0.4
|
||||
lr_update_by_epoch = False
|
||||
@@ -0,0 +1,124 @@
|
||||
net = dict(type='Detector', )
|
||||
|
||||
backbone = dict(
|
||||
type='DLAWrapper',
|
||||
dla='dla34',
|
||||
pretrained=True,
|
||||
)
|
||||
|
||||
num_points = 72
|
||||
max_lanes = 4
|
||||
sample_y = range(717, 300, -20)
|
||||
|
||||
heads = dict(type='CLRHead',
|
||||
num_priors=192,
|
||||
refine_layers=3,
|
||||
fc_hidden_dim=64,
|
||||
sample_points=36)
|
||||
|
||||
iou_loss_weight = 2.
|
||||
cls_loss_weight = 4.
|
||||
xyt_loss_weight = 0.5
|
||||
seg_loss_weight = 1.0
|
||||
|
||||
work_dirs = "work_dirs/clr/dla34_llamas"
|
||||
|
||||
neck = dict(type='FPN',
|
||||
in_channels=[128, 256, 512],
|
||||
out_channels=64,
|
||||
num_outs=3,
|
||||
attention=False)
|
||||
|
||||
test_parameters = dict(conf_threshold=0.45, nms_thres=50, nms_topk=max_lanes)
|
||||
|
||||
epochs = 20
|
||||
batch_size = 24
|
||||
|
||||
optimizer = dict(type='AdamW', lr=0.6e-3) # 3e-4 for batchsize 8
|
||||
total_iter = (58272 // batch_size) * epochs
|
||||
scheduler = dict(type='CosineAnnealingLR', T_max=total_iter)
|
||||
|
||||
eval_ep = 3
|
||||
save_ep = 5
|
||||
|
||||
img_norm = dict(mean=[103.939, 116.779, 123.68], std=[1., 1., 1.])
|
||||
ori_img_w = 1276
|
||||
ori_img_h = 717
|
||||
img_w = 800
|
||||
img_h = 320
|
||||
cut_height = 300
|
||||
|
||||
train_process = [
|
||||
dict(
|
||||
type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
dict(name='HorizontalFlip', parameters=dict(p=1.0), p=0.5),
|
||||
dict(name='ChannelShuffle', parameters=dict(p=1.0), p=0.1),
|
||||
# dict(name='MultiplyAndAddToBrightness',
|
||||
# parameters=dict(mul=(0.85, 1.15), add=(-10, 10)),
|
||||
# p=0.6),
|
||||
# dict(name='AddToHueAndSaturation',
|
||||
# parameters=dict(value=(-10, 10)),
|
||||
# p=0.7),
|
||||
# dict(name='OneOf',
|
||||
# transforms=[
|
||||
# dict(name='MotionBlur', parameters=dict(k=(3, 5))),
|
||||
# dict(name='MedianBlur', parameters=dict(k=(3, 5)))
|
||||
# ],
|
||||
# p=0.2),
|
||||
dict(name='Affine',
|
||||
parameters=dict(translate_percent=dict(x=(-0.1, 0.1),
|
||||
y=(-0.1, 0.1)),
|
||||
rotate=(-10, 10),
|
||||
scale=(0.8, 1.2)),
|
||||
p=0.7),
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
),
|
||||
dict(type='ToTensor', keys=['img', 'lane_line', 'seg']),
|
||||
]
|
||||
|
||||
val_process = [
|
||||
dict(type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
training=False),
|
||||
dict(type='ToTensor', keys=['img']),
|
||||
]
|
||||
|
||||
dataset_path = './data/llamas'
|
||||
dataset_type = 'LLAMAS'
|
||||
dataset = dict(train=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='train',
|
||||
processes=train_process,
|
||||
),
|
||||
val=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='val',
|
||||
processes=val_process,
|
||||
),
|
||||
test=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
))
|
||||
|
||||
workers = 10
|
||||
log_interval = 500
|
||||
# seed = 0
|
||||
num_classes = 4 + 1
|
||||
ignore_label = 255
|
||||
bg_weight = 0.4
|
||||
lr_update_by_epoch = False
|
||||
@@ -0,0 +1,126 @@
|
||||
net = dict(type='Detector', )
|
||||
|
||||
backbone = dict(
|
||||
type='ResNetWrapper',
|
||||
resnet='resnet101',
|
||||
pretrained=True,
|
||||
replace_stride_with_dilation=[False, False, False],
|
||||
out_conv=False,
|
||||
)
|
||||
|
||||
num_points = 72
|
||||
max_lanes = 4
|
||||
sample_y = range(589, 230, -20)
|
||||
|
||||
heads = dict(type='CLRHead',
|
||||
num_priors=192,
|
||||
refine_layers=3,
|
||||
fc_hidden_dim=64,
|
||||
sample_points=36)
|
||||
|
||||
iou_loss_weight = 2.
|
||||
cls_loss_weight = 2.
|
||||
xyt_loss_weight = 0.2
|
||||
seg_loss_weight = 1.0
|
||||
|
||||
work_dirs = "work_dirs/clr/r101_culane"
|
||||
|
||||
neck = dict(type='FPN',
|
||||
in_channels=[512, 1024, 2048],
|
||||
out_channels=64,
|
||||
num_outs=3,
|
||||
attention=False)
|
||||
|
||||
test_parameters = dict(conf_threshold=0.4, nms_thres=50, nms_topk=max_lanes)
|
||||
|
||||
epochs = 20
|
||||
batch_size = 12
|
||||
|
||||
optimizer = dict(type='AdamW', lr=0.3e-3) # 3e-4 for batchsize 8
|
||||
total_iter = (88880 // batch_size) * epochs
|
||||
scheduler = dict(type='CosineAnnealingLR', T_max=total_iter)
|
||||
|
||||
eval_ep = 3
|
||||
save_ep = 10
|
||||
|
||||
img_norm = dict(mean=[103.939, 116.779, 123.68], std=[1., 1., 1.])
|
||||
ori_img_w = 1640
|
||||
ori_img_h = 590
|
||||
img_w = 800
|
||||
img_h = 320
|
||||
cut_height = 270
|
||||
|
||||
train_process = [
|
||||
dict(
|
||||
type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
dict(name='HorizontalFlip', parameters=dict(p=1.0), p=0.5),
|
||||
dict(name='ChannelShuffle', parameters=dict(p=1.0), p=0.1),
|
||||
dict(name='MultiplyAndAddToBrightness',
|
||||
parameters=dict(mul=(0.85, 1.15), add=(-10, 10)),
|
||||
p=0.6),
|
||||
dict(name='AddToHueAndSaturation',
|
||||
parameters=dict(value=(-10, 10)),
|
||||
p=0.7),
|
||||
dict(name='OneOf',
|
||||
transforms=[
|
||||
dict(name='MotionBlur', parameters=dict(k=(3, 5))),
|
||||
dict(name='MedianBlur', parameters=dict(k=(3, 5)))
|
||||
],
|
||||
p=0.2),
|
||||
dict(name='Affine',
|
||||
parameters=dict(translate_percent=dict(x=(-0.1, 0.1),
|
||||
y=(-0.1, 0.1)),
|
||||
rotate=(-10, 10),
|
||||
scale=(0.8, 1.2)),
|
||||
p=0.7),
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
),
|
||||
dict(type='ToTensor', keys=['img', 'lane_line', 'seg']),
|
||||
]
|
||||
|
||||
val_process = [
|
||||
dict(type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
training=False),
|
||||
dict(type='ToTensor', keys=['img']),
|
||||
]
|
||||
|
||||
dataset_path = './data/CULane'
|
||||
dataset_type = 'CULane'
|
||||
dataset = dict(train=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='train',
|
||||
processes=train_process,
|
||||
),
|
||||
val=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
),
|
||||
test=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
))
|
||||
|
||||
workers = 10
|
||||
log_interval = 500
|
||||
# seed = 0
|
||||
num_classes = 4 + 1
|
||||
ignore_label = 255
|
||||
bg_weight = 0.4
|
||||
lr_update_by_epoch = False
|
||||
@@ -0,0 +1,127 @@
|
||||
net = dict(type='Detector', )
|
||||
|
||||
backbone = dict(
|
||||
type='ResNetWrapper',
|
||||
resnet='resnet101',
|
||||
pretrained=True,
|
||||
replace_stride_with_dilation=[False, False, False],
|
||||
out_conv=False,
|
||||
)
|
||||
|
||||
num_points = 72
|
||||
max_lanes = 5
|
||||
sample_y = range(710, 150, -10)
|
||||
|
||||
heads = dict(type='CLRHead',
|
||||
num_priors=192,
|
||||
refine_layers=3,
|
||||
fc_hidden_dim=64,
|
||||
sample_points=36)
|
||||
|
||||
iou_loss_weight = 2.
|
||||
cls_loss_weight = 6.
|
||||
xyt_loss_weight = 0.5
|
||||
seg_loss_weight = 1.0
|
||||
|
||||
work_dirs = "work_dirs/clr/r101_tusimple"
|
||||
|
||||
neck = dict(type='FPN',
|
||||
in_channels=[512, 1024, 2048],
|
||||
out_channels=64,
|
||||
num_outs=3,
|
||||
attention=False)
|
||||
|
||||
test_parameters = dict(conf_threshold=0.40, nms_thres=50, nms_topk=max_lanes)
|
||||
|
||||
epochs = 70
|
||||
batch_size = 10
|
||||
|
||||
optimizer = dict(type='AdamW', lr=0.3e-3) # 3e-4 for batchsize 8
|
||||
total_iter = (3616 // batch_size + 1) * epochs
|
||||
scheduler = dict(type='CosineAnnealingLR', T_max=total_iter)
|
||||
|
||||
eval_ep = 1
|
||||
save_ep = epochs
|
||||
|
||||
img_norm = dict(mean=[103.939, 116.779, 123.68], std=[1., 1., 1.])
|
||||
ori_img_w = 1280
|
||||
ori_img_h = 720
|
||||
img_h = 320
|
||||
img_w = 800
|
||||
cut_height = 160
|
||||
|
||||
train_process = [
|
||||
dict(
|
||||
type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
dict(name='HorizontalFlip', parameters=dict(p=1.0), p=0.5),
|
||||
dict(name='ChannelShuffle', parameters=dict(p=1.0), p=0.1),
|
||||
dict(name='MultiplyAndAddToBrightness',
|
||||
parameters=dict(mul=(0.85, 1.15), add=(-10, 10)),
|
||||
p=0.6),
|
||||
dict(name='AddToHueAndSaturation',
|
||||
parameters=dict(value=(-10, 10)),
|
||||
p=0.7),
|
||||
dict(name='OneOf',
|
||||
transforms=[
|
||||
dict(name='MotionBlur', parameters=dict(k=(3, 5))),
|
||||
dict(name='MedianBlur', parameters=dict(k=(3, 5)))
|
||||
],
|
||||
p=0.2),
|
||||
dict(name='Affine',
|
||||
parameters=dict(translate_percent=dict(x=(-0.1, 0.1),
|
||||
y=(-0.1, 0.1)),
|
||||
rotate=(-10, 10),
|
||||
scale=(0.8, 1.2)),
|
||||
p=0.7),
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
),
|
||||
dict(type='ToTensor', keys=['img', 'lane_line', 'seg']),
|
||||
]
|
||||
|
||||
val_process = [
|
||||
dict(type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
training=False),
|
||||
dict(type='ToTensor', keys=['img']),
|
||||
]
|
||||
|
||||
dataset_path = './data/tusimple'
|
||||
dataset_type = 'TuSimple'
|
||||
test_json_file = 'data/tusimple/test_label.json'
|
||||
dataset = dict(train=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='trainval',
|
||||
processes=train_process,
|
||||
),
|
||||
val=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
),
|
||||
test=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
))
|
||||
|
||||
workers = 10
|
||||
log_interval = 100
|
||||
# seed = 0
|
||||
num_classes = 6 + 1
|
||||
ignore_label = 255
|
||||
bg_weight = 0.4
|
||||
lr_update_by_epoch = False
|
||||
@@ -0,0 +1,126 @@
|
||||
net = dict(type='Detector', )
|
||||
|
||||
backbone = dict(
|
||||
type='ResNetWrapper',
|
||||
resnet='resnet18',
|
||||
pretrained=True,
|
||||
replace_stride_with_dilation=[False, False, False],
|
||||
out_conv=False,
|
||||
)
|
||||
|
||||
num_points = 72
|
||||
max_lanes = 4
|
||||
sample_y = range(589, 230, -20)
|
||||
|
||||
heads = dict(type='CLRHead',
|
||||
num_priors=192,
|
||||
refine_layers=3,
|
||||
fc_hidden_dim=64,
|
||||
sample_points=36)
|
||||
|
||||
iou_loss_weight = 2.
|
||||
cls_loss_weight = 2.
|
||||
xyt_loss_weight = 0.2
|
||||
seg_loss_weight = 1.0
|
||||
|
||||
work_dirs = "work_dirs/clr/r18_culane"
|
||||
|
||||
neck = dict(type='FPN',
|
||||
in_channels=[128, 256, 512],
|
||||
out_channels=64,
|
||||
num_outs=3,
|
||||
attention=False)
|
||||
|
||||
test_parameters = dict(conf_threshold=0.4, nms_thres=50, nms_topk=max_lanes)
|
||||
|
||||
epochs = 15
|
||||
batch_size = 24
|
||||
|
||||
optimizer = dict(type='AdamW', lr=0.6e-3) # 3e-4 for batchsize 8
|
||||
total_iter = (88880 // batch_size) * epochs
|
||||
scheduler = dict(type='CosineAnnealingLR', T_max=total_iter)
|
||||
|
||||
eval_ep = 1
|
||||
save_ep = 10
|
||||
|
||||
img_norm = dict(mean=[103.939, 116.779, 123.68], std=[1., 1., 1.])
|
||||
ori_img_w = 1640
|
||||
ori_img_h = 590
|
||||
img_w = 800
|
||||
img_h = 320
|
||||
cut_height = 270
|
||||
|
||||
train_process = [
|
||||
dict(
|
||||
type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
dict(name='HorizontalFlip', parameters=dict(p=1.0), p=0.5),
|
||||
dict(name='ChannelShuffle', parameters=dict(p=1.0), p=0.1),
|
||||
dict(name='MultiplyAndAddToBrightness',
|
||||
parameters=dict(mul=(0.85, 1.15), add=(-10, 10)),
|
||||
p=0.6),
|
||||
dict(name='AddToHueAndSaturation',
|
||||
parameters=dict(value=(-10, 10)),
|
||||
p=0.7),
|
||||
dict(name='OneOf',
|
||||
transforms=[
|
||||
dict(name='MotionBlur', parameters=dict(k=(3, 5))),
|
||||
dict(name='MedianBlur', parameters=dict(k=(3, 5)))
|
||||
],
|
||||
p=0.2),
|
||||
dict(name='Affine',
|
||||
parameters=dict(translate_percent=dict(x=(-0.1, 0.1),
|
||||
y=(-0.1, 0.1)),
|
||||
rotate=(-10, 10),
|
||||
scale=(0.8, 1.2)),
|
||||
p=0.7),
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
),
|
||||
dict(type='ToTensor', keys=['img', 'lane_line', 'seg']),
|
||||
]
|
||||
|
||||
val_process = [
|
||||
dict(type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
training=False),
|
||||
dict(type='ToTensor', keys=['img']),
|
||||
]
|
||||
|
||||
dataset_path = './data/CULane'
|
||||
dataset_type = 'CULane'
|
||||
dataset = dict(train=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='train',
|
||||
processes=train_process,
|
||||
),
|
||||
val=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
),
|
||||
test=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
))
|
||||
|
||||
workers = 10
|
||||
log_interval = 1000
|
||||
# seed = 0
|
||||
num_classes = 4 + 1
|
||||
ignore_label = 255
|
||||
bg_weight = 0.4
|
||||
lr_update_by_epoch = False
|
||||
@@ -0,0 +1,126 @@
|
||||
net = dict(type='Detector', )
|
||||
|
||||
backbone = dict(
|
||||
type='ResNetWrapper',
|
||||
resnet='resnet18',
|
||||
pretrained=True,
|
||||
replace_stride_with_dilation=[False, False, False],
|
||||
out_conv=False,
|
||||
)
|
||||
|
||||
num_points = 72
|
||||
max_lanes = 4
|
||||
sample_y = range(717, 300, -20)
|
||||
|
||||
heads = dict(type='CLRHead',
|
||||
num_priors=192,
|
||||
refine_layers=3,
|
||||
fc_hidden_dim=64,
|
||||
sample_points=36)
|
||||
|
||||
iou_loss_weight = 8.
|
||||
cls_loss_weight = 2.
|
||||
xyt_loss_weight = 0.5
|
||||
seg_loss_weight = 1.0
|
||||
|
||||
work_dirs = "work_dirs/clr/resnet18_llamas"
|
||||
|
||||
neck = dict(type='FPN',
|
||||
in_channels=[128, 256, 512],
|
||||
out_channels=64,
|
||||
num_outs=3,
|
||||
attention=False)
|
||||
|
||||
test_parameters = dict(conf_threshold=0.45, nms_thres=60, nms_topk=max_lanes)
|
||||
|
||||
epochs = 20
|
||||
batch_size = 24
|
||||
|
||||
optimizer = dict(type='AdamW', lr=0.6e-3) # 3e-4 for batchsize 8
|
||||
total_iter = (58272 // batch_size) * epochs
|
||||
scheduler = dict(type='CosineAnnealingLR', T_max=total_iter)
|
||||
|
||||
eval_ep = 3
|
||||
save_ep = 5
|
||||
|
||||
img_norm = dict(mean=[103.939, 116.779, 123.68], std=[1., 1., 1.])
|
||||
ori_img_w = 1276
|
||||
ori_img_h = 717
|
||||
img_w = 800
|
||||
img_h = 320
|
||||
cut_height = 300
|
||||
|
||||
train_process = [
|
||||
dict(
|
||||
type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
dict(name='HorizontalFlip', parameters=dict(p=1.0), p=0.5),
|
||||
dict(name='ChannelShuffle', parameters=dict(p=1.0), p=0.1),
|
||||
dict(name='MultiplyAndAddToBrightness',
|
||||
parameters=dict(mul=(0.85, 1.15), add=(-10, 10)),
|
||||
p=0.6),
|
||||
dict(name='AddToHueAndSaturation',
|
||||
parameters=dict(value=(-10, 10)),
|
||||
p=0.7),
|
||||
dict(name='OneOf',
|
||||
transforms=[
|
||||
dict(name='MotionBlur', parameters=dict(k=(3, 5))),
|
||||
dict(name='MedianBlur', parameters=dict(k=(3, 5)))
|
||||
],
|
||||
p=0.2),
|
||||
dict(name='Affine',
|
||||
parameters=dict(translate_percent=dict(x=(-0.1, 0.1),
|
||||
y=(-0.1, 0.1)),
|
||||
rotate=(-10, 10),
|
||||
scale=(0.8, 1.2)),
|
||||
p=0.7),
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
),
|
||||
dict(type='ToTensor', keys=['img', 'lane_line', 'seg']),
|
||||
]
|
||||
|
||||
val_process = [
|
||||
dict(type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
training=False),
|
||||
dict(type='ToTensor', keys=['img']),
|
||||
]
|
||||
|
||||
dataset_path = './data/llamas'
|
||||
dataset_type = 'LLAMAS'
|
||||
dataset = dict(train=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='train',
|
||||
processes=train_process,
|
||||
),
|
||||
val=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='val',
|
||||
processes=val_process,
|
||||
),
|
||||
test=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
))
|
||||
|
||||
workers = 10
|
||||
log_interval = 100
|
||||
# seed = 0
|
||||
num_classes = 4 + 1
|
||||
ignore_label = 255
|
||||
bg_weight = 0.4
|
||||
lr_update_by_epoch = False
|
||||
@@ -0,0 +1,127 @@
|
||||
# CLRNet on lane0_copy MUFLD packs (1280x720)
|
||||
# data_root = parent of DATASET / DATASET-AddBy-*
|
||||
|
||||
net = dict(type='Detector', )
|
||||
|
||||
backbone = dict(
|
||||
type='ResNetWrapper',
|
||||
resnet='resnet18',
|
||||
pretrained=True,
|
||||
replace_stride_with_dilation=[False, False, False],
|
||||
out_conv=False,
|
||||
)
|
||||
|
||||
num_points = 72
|
||||
max_lanes = 4
|
||||
sample_y = range(710, 150, -10)
|
||||
|
||||
heads = dict(type='CLRHead',
|
||||
num_priors=192,
|
||||
refine_layers=3,
|
||||
fc_hidden_dim=64,
|
||||
sample_points=36)
|
||||
|
||||
iou_loss_weight = 2.
|
||||
cls_loss_weight = 6.
|
||||
xyt_loss_weight = 0.5
|
||||
seg_loss_weight = 1.0
|
||||
|
||||
work_dirs = "work_dirs/clr/mufld_r18"
|
||||
|
||||
neck = dict(type='FPN',
|
||||
in_channels=[128, 256, 512],
|
||||
out_channels=64,
|
||||
num_outs=3,
|
||||
attention=False)
|
||||
|
||||
test_parameters = dict(conf_threshold=0.40, nms_thres=50, nms_topk=max_lanes)
|
||||
|
||||
epochs = 15
|
||||
batch_size = 16
|
||||
|
||||
optimizer = dict(type='AdamW', lr=1.0e-3)
|
||||
# ~144k / 16 * 15 — adjust after changing train_packs
|
||||
total_iter = (144117 // batch_size + 1) * epochs
|
||||
scheduler = dict(type='CosineAnnealingLR', T_max=total_iter)
|
||||
|
||||
eval_ep = 3
|
||||
save_ep = 5
|
||||
|
||||
img_norm = dict(mean=[103.939, 116.779, 123.68], std=[1., 1., 1.])
|
||||
ori_img_w = 1280
|
||||
ori_img_h = 720
|
||||
img_w = 800
|
||||
img_h = 320
|
||||
cut_height = 160
|
||||
|
||||
train_process = [
|
||||
dict(
|
||||
type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
dict(name='HorizontalFlip', parameters=dict(p=1.0), p=0.5),
|
||||
dict(name='Affine',
|
||||
parameters=dict(translate_percent=dict(x=(-0.1, 0.1),
|
||||
y=(-0.1, 0.1)),
|
||||
rotate=(-10, 10),
|
||||
scale=(0.8, 1.2)),
|
||||
p=0.7),
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
),
|
||||
dict(type='ToTensor', keys=['img', 'lane_line', 'seg']),
|
||||
]
|
||||
|
||||
val_process = [
|
||||
dict(type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
training=False),
|
||||
dict(type='ToTensor', keys=['img']),
|
||||
]
|
||||
|
||||
# --- MUFLD multi-pack (same as UFLD) ---
|
||||
dataset_path = '/home/chengfanglu/DATA/lane0_copy'
|
||||
train_packs = ['DATASET']
|
||||
# train_packs = ['DATASET', 'DATASET-A'] # alias in datasets_registry.json
|
||||
val_packs = ['DATASET']
|
||||
pack_list_name = 'list/train_gt.txt'
|
||||
pack_val_list_name = 'list/val_gt.txt'
|
||||
merged_list_dir = 'lists_merged'
|
||||
remerge_lists = False
|
||||
write_lines_cache = True
|
||||
lines_cache_dir = 'cache/mufld_lines'
|
||||
|
||||
dataset_type = 'MufldLane'
|
||||
dataset = dict(
|
||||
train=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='train',
|
||||
),
|
||||
val=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='val',
|
||||
),
|
||||
test=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
list_file='DATASET/list/test_gt.txt',
|
||||
),
|
||||
)
|
||||
|
||||
workers = 8
|
||||
log_interval = 500
|
||||
num_classes = max_lanes + 1
|
||||
ignore_label = 255
|
||||
bg_weight = 0.4
|
||||
lr_update_by_epoch = False
|
||||
@@ -0,0 +1,60 @@
|
||||
# Smoke: copy fields from clr_resnet18_mufld.py and override below
|
||||
# (CLRNet Config does not use _base_ inheritance)
|
||||
|
||||
net = dict(type='Detector', )
|
||||
backbone = dict(type='ResNetWrapper', resnet='resnet18', pretrained=True,
|
||||
replace_stride_with_dilation=[False, False, False], out_conv=False)
|
||||
num_points = 72
|
||||
max_lanes = 4
|
||||
sample_y = range(710, 150, -10)
|
||||
heads = dict(type='CLRHead', num_priors=192, refine_layers=3, fc_hidden_dim=64, sample_points=36)
|
||||
iou_loss_weight = 2.
|
||||
cls_loss_weight = 6.
|
||||
xyt_loss_weight = 0.5
|
||||
seg_loss_weight = 1.0
|
||||
work_dirs = "work_dirs/clr/mufld_r18_smoke"
|
||||
neck = dict(type='FPN', in_channels=[128, 256, 512], out_channels=64, num_outs=3, attention=False)
|
||||
test_parameters = dict(conf_threshold=0.40, nms_thres=50, nms_topk=max_lanes)
|
||||
epochs = 1
|
||||
batch_size = 4
|
||||
optimizer = dict(type='AdamW', lr=1.0e-3)
|
||||
total_iter = 32
|
||||
scheduler = dict(type='CosineAnnealingLR', T_max=total_iter)
|
||||
eval_ep = 1
|
||||
save_ep = 1
|
||||
img_norm = dict(mean=[103.939, 116.779, 123.68], std=[1., 1., 1.])
|
||||
ori_img_w = 1280
|
||||
ori_img_h = 720
|
||||
img_w = 800
|
||||
img_h = 320
|
||||
cut_height = 160
|
||||
train_process = [
|
||||
dict(type='GenerateLaneLine',
|
||||
transforms=[dict(name='Resize', parameters=dict(size=dict(height=img_h, width=img_w)), p=1.0)],
|
||||
training=True),
|
||||
dict(type='ToTensor', keys=['img', 'lane_line', 'seg']),
|
||||
]
|
||||
val_process = [
|
||||
dict(type='GenerateLaneLine',
|
||||
transforms=[dict(name='Resize', parameters=dict(size=dict(height=img_h, width=img_w)), p=1.0)],
|
||||
training=False),
|
||||
dict(type='ToTensor', keys=['img']),
|
||||
]
|
||||
dataset_path = '/home/chengfanglu/DATA/lane0_copy'
|
||||
train_list_file = 'DATASET/list/train_gt_smoke.txt'
|
||||
val_list_file = 'DATASET/list/train_gt_smoke.txt'
|
||||
write_lines_cache = True
|
||||
lines_cache_dir = 'cache/mufld_lines'
|
||||
dataset_type = 'MufldLane'
|
||||
dataset = dict(
|
||||
train=dict(type=dataset_type, data_root=dataset_path, split='train'),
|
||||
val=dict(type=dataset_type, data_root=dataset_path, split='val'),
|
||||
test=dict(type=dataset_type, data_root=dataset_path, split='test',
|
||||
list_file='DATASET/list/train_gt_smoke.txt'),
|
||||
)
|
||||
workers = 2
|
||||
log_interval = 8
|
||||
num_classes = max_lanes + 1
|
||||
ignore_label = 255
|
||||
bg_weight = 0.4
|
||||
lr_update_by_epoch = False
|
||||
@@ -0,0 +1,127 @@
|
||||
net = dict(type='Detector', )
|
||||
|
||||
backbone = dict(
|
||||
type='ResNetWrapper',
|
||||
resnet='resnet18',
|
||||
pretrained=True,
|
||||
replace_stride_with_dilation=[False, False, False],
|
||||
out_conv=False,
|
||||
)
|
||||
|
||||
num_points = 72
|
||||
max_lanes = 5
|
||||
sample_y = range(710, 150, -10)
|
||||
|
||||
heads = dict(type='CLRHead',
|
||||
num_priors=192,
|
||||
refine_layers=3,
|
||||
fc_hidden_dim=64,
|
||||
sample_points=36)
|
||||
|
||||
iou_loss_weight = 2.
|
||||
cls_loss_weight = 6.
|
||||
xyt_loss_weight = 0.5
|
||||
seg_loss_weight = 1.0
|
||||
|
||||
work_dirs = "work_dirs/clr/r18_tusimple"
|
||||
|
||||
neck = dict(type='FPN',
|
||||
in_channels=[128, 256, 512],
|
||||
out_channels=64,
|
||||
num_outs=3,
|
||||
attention=False)
|
||||
|
||||
test_parameters = dict(conf_threshold=0.40, nms_thres=50, nms_topk=max_lanes)
|
||||
|
||||
epochs = 70
|
||||
batch_size = 40
|
||||
|
||||
optimizer = dict(type='AdamW', lr=1.0e-3) # 3e-4 for batchsize 8
|
||||
total_iter = (3616 // batch_size + 1) * epochs
|
||||
scheduler = dict(type='CosineAnnealingLR', T_max=total_iter)
|
||||
|
||||
eval_ep = 3
|
||||
save_ep = epochs
|
||||
|
||||
img_norm = dict(mean=[103.939, 116.779, 123.68], std=[1., 1., 1.])
|
||||
ori_img_w = 1280
|
||||
ori_img_h = 720
|
||||
img_h = 320
|
||||
img_w = 800
|
||||
cut_height = 160
|
||||
|
||||
train_process = [
|
||||
dict(
|
||||
type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
dict(name='HorizontalFlip', parameters=dict(p=1.0), p=0.5),
|
||||
dict(name='ChannelShuffle', parameters=dict(p=1.0), p=0.1),
|
||||
dict(name='MultiplyAndAddToBrightness',
|
||||
parameters=dict(mul=(0.85, 1.15), add=(-10, 10)),
|
||||
p=0.6),
|
||||
dict(name='AddToHueAndSaturation',
|
||||
parameters=dict(value=(-10, 10)),
|
||||
p=0.7),
|
||||
dict(name='OneOf',
|
||||
transforms=[
|
||||
dict(name='MotionBlur', parameters=dict(k=(3, 5))),
|
||||
dict(name='MedianBlur', parameters=dict(k=(3, 5)))
|
||||
],
|
||||
p=0.2),
|
||||
dict(name='Affine',
|
||||
parameters=dict(translate_percent=dict(x=(-0.1, 0.1),
|
||||
y=(-0.1, 0.1)),
|
||||
rotate=(-10, 10),
|
||||
scale=(0.8, 1.2)),
|
||||
p=0.7),
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
),
|
||||
dict(type='ToTensor', keys=['img', 'lane_line', 'seg']),
|
||||
]
|
||||
|
||||
val_process = [
|
||||
dict(type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
training=False),
|
||||
dict(type='ToTensor', keys=['img']),
|
||||
]
|
||||
|
||||
dataset_path = './data/tusimple'
|
||||
dataset_type = 'TuSimple'
|
||||
test_json_file = 'data/tusimple/test_label.json'
|
||||
dataset = dict(train=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='trainval',
|
||||
processes=train_process,
|
||||
),
|
||||
val=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
),
|
||||
test=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
))
|
||||
|
||||
workers = 10
|
||||
log_interval = 100
|
||||
# seed = 0
|
||||
num_classes = 6 + 1
|
||||
ignore_label = 255
|
||||
bg_weight = 0.4
|
||||
lr_update_by_epoch = False
|
||||
@@ -0,0 +1,126 @@
|
||||
net = dict(type='Detector', )
|
||||
|
||||
backbone = dict(
|
||||
type='ResNetWrapper',
|
||||
resnet='resnet34',
|
||||
pretrained=True,
|
||||
replace_stride_with_dilation=[False, False, False],
|
||||
out_conv=False,
|
||||
)
|
||||
|
||||
num_points = 72
|
||||
max_lanes = 4
|
||||
sample_y = range(589, 230, -20)
|
||||
|
||||
heads = dict(type='CLRHead',
|
||||
num_priors=192,
|
||||
refine_layers=3,
|
||||
fc_hidden_dim=64,
|
||||
sample_points=36)
|
||||
|
||||
iou_loss_weight = 2.
|
||||
cls_loss_weight = 2.
|
||||
xyt_loss_weight = 0.2
|
||||
seg_loss_weight = 1.0
|
||||
|
||||
work_dirs = "work_dirs/clr/r34_culane"
|
||||
|
||||
neck = dict(type='FPN',
|
||||
in_channels=[128, 256, 512],
|
||||
out_channels=64,
|
||||
num_outs=3,
|
||||
attention=False)
|
||||
|
||||
test_parameters = dict(conf_threshold=0.4, nms_thres=50, nms_topk=max_lanes)
|
||||
|
||||
epochs = 15
|
||||
batch_size = 24
|
||||
|
||||
optimizer = dict(type='AdamW', lr=0.6e-3) # 3e-4 for batchsize 8
|
||||
total_iter = (88880 // batch_size) * epochs
|
||||
scheduler = dict(type='CosineAnnealingLR', T_max=total_iter)
|
||||
|
||||
eval_ep = 3
|
||||
save_ep = 10
|
||||
|
||||
img_norm = dict(mean=[103.939, 116.779, 123.68], std=[1., 1., 1.])
|
||||
ori_img_w = 1640
|
||||
ori_img_h = 590
|
||||
img_w = 800
|
||||
img_h = 320
|
||||
cut_height = 270
|
||||
|
||||
train_process = [
|
||||
dict(
|
||||
type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
dict(name='HorizontalFlip', parameters=dict(p=1.0), p=0.5),
|
||||
dict(name='ChannelShuffle', parameters=dict(p=1.0), p=0.1),
|
||||
dict(name='MultiplyAndAddToBrightness',
|
||||
parameters=dict(mul=(0.85, 1.15), add=(-10, 10)),
|
||||
p=0.6),
|
||||
dict(name='AddToHueAndSaturation',
|
||||
parameters=dict(value=(-10, 10)),
|
||||
p=0.7),
|
||||
dict(name='OneOf',
|
||||
transforms=[
|
||||
dict(name='MotionBlur', parameters=dict(k=(3, 5))),
|
||||
dict(name='MedianBlur', parameters=dict(k=(3, 5)))
|
||||
],
|
||||
p=0.2),
|
||||
dict(name='Affine',
|
||||
parameters=dict(translate_percent=dict(x=(-0.1, 0.1),
|
||||
y=(-0.1, 0.1)),
|
||||
rotate=(-10, 10),
|
||||
scale=(0.8, 1.2)),
|
||||
p=0.7),
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
),
|
||||
dict(type='ToTensor', keys=['img', 'lane_line', 'seg']),
|
||||
]
|
||||
|
||||
val_process = [
|
||||
dict(type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
training=False),
|
||||
dict(type='ToTensor', keys=['img']),
|
||||
]
|
||||
|
||||
dataset_path = './data/CULane'
|
||||
dataset_type = 'CULane'
|
||||
dataset = dict(train=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='train',
|
||||
processes=train_process,
|
||||
),
|
||||
val=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
),
|
||||
test=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
))
|
||||
|
||||
workers = 10
|
||||
log_interval = 500
|
||||
# seed = 0
|
||||
num_classes = 4 + 1
|
||||
ignore_label = 255
|
||||
bg_weight = 0.4
|
||||
lr_update_by_epoch = False
|
||||
@@ -0,0 +1,127 @@
|
||||
net = dict(type='Detector', )
|
||||
|
||||
backbone = dict(
|
||||
type='ResNetWrapper',
|
||||
resnet='resnet34',
|
||||
pretrained=True,
|
||||
replace_stride_with_dilation=[False, False, False],
|
||||
out_conv=False,
|
||||
)
|
||||
|
||||
num_points = 72
|
||||
max_lanes = 5
|
||||
sample_y = range(710, 150, -10)
|
||||
|
||||
heads = dict(type='CLRHead',
|
||||
num_priors=192,
|
||||
refine_layers=3,
|
||||
fc_hidden_dim=64,
|
||||
sample_points=36)
|
||||
|
||||
iou_loss_weight = 2.
|
||||
cls_loss_weight = 6.
|
||||
xyt_loss_weight = 0.5
|
||||
seg_loss_weight = 1.0
|
||||
|
||||
work_dirs = "work_dirs/clr/r34_tusimple"
|
||||
|
||||
neck = dict(type='FPN',
|
||||
in_channels=[128, 256, 512],
|
||||
out_channels=64,
|
||||
num_outs=3,
|
||||
attention=False)
|
||||
|
||||
test_parameters = dict(conf_threshold=0.40, nms_thres=50, nms_topk=max_lanes)
|
||||
|
||||
epochs = 70
|
||||
batch_size = 32
|
||||
|
||||
optimizer = dict(type='AdamW', lr=0.8e-3) # 3e-4 for batchsize 8
|
||||
total_iter = (3616 // batch_size + 1) * epochs
|
||||
scheduler = dict(type='CosineAnnealingLR', T_max=total_iter)
|
||||
|
||||
eval_ep = 3
|
||||
save_ep = epochs
|
||||
|
||||
img_norm = dict(mean=[103.939, 116.779, 123.68], std=[1., 1., 1.])
|
||||
ori_img_w = 1280
|
||||
ori_img_h = 720
|
||||
img_h = 320
|
||||
img_w = 800
|
||||
cut_height = 160
|
||||
|
||||
train_process = [
|
||||
dict(
|
||||
type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
dict(name='HorizontalFlip', parameters=dict(p=1.0), p=0.5),
|
||||
dict(name='ChannelShuffle', parameters=dict(p=1.0), p=0.1),
|
||||
dict(name='MultiplyAndAddToBrightness',
|
||||
parameters=dict(mul=(0.85, 1.15), add=(-10, 10)),
|
||||
p=0.6),
|
||||
dict(name='AddToHueAndSaturation',
|
||||
parameters=dict(value=(-10, 10)),
|
||||
p=0.7),
|
||||
dict(name='OneOf',
|
||||
transforms=[
|
||||
dict(name='MotionBlur', parameters=dict(k=(3, 5))),
|
||||
dict(name='MedianBlur', parameters=dict(k=(3, 5)))
|
||||
],
|
||||
p=0.2),
|
||||
dict(name='Affine',
|
||||
parameters=dict(translate_percent=dict(x=(-0.1, 0.1),
|
||||
y=(-0.1, 0.1)),
|
||||
rotate=(-10, 10),
|
||||
scale=(0.8, 1.2)),
|
||||
p=0.7),
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
),
|
||||
dict(type='ToTensor', keys=['img', 'lane_line', 'seg']),
|
||||
]
|
||||
|
||||
val_process = [
|
||||
dict(type='GenerateLaneLine',
|
||||
transforms=[
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
],
|
||||
training=False),
|
||||
dict(type='ToTensor', keys=['img']),
|
||||
]
|
||||
|
||||
dataset_path = './data/tusimple'
|
||||
dataset_type = 'TuSimple'
|
||||
test_json_file = 'data/tusimple/test_label.json'
|
||||
dataset = dict(train=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='trainval',
|
||||
processes=train_process,
|
||||
),
|
||||
val=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
),
|
||||
test=dict(
|
||||
type=dataset_type,
|
||||
data_root=dataset_path,
|
||||
split='test',
|
||||
processes=val_process,
|
||||
))
|
||||
|
||||
workers = 10
|
||||
log_interval = 100
|
||||
# seed = 0
|
||||
num_classes = 6 + 1
|
||||
ignore_label = 255
|
||||
bg_weight = 0.4
|
||||
lr_update_by_epoch = False
|
||||
75
algorithms/lane_ufld/code/CLRNet-main/main.py
Normal file
75
algorithms/lane_ufld/code/CLRNet-main/main.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import os
|
||||
import cv2
|
||||
import torch
|
||||
import torch.nn.parallel
|
||||
import torch.backends.cudnn as cudnn
|
||||
import argparse
|
||||
import numpy as np
|
||||
import random
|
||||
from clrnet.utils.config import Config
|
||||
from clrnet.engine.runner import Runner
|
||||
from clrnet.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.resume_from = args.resume_from
|
||||
cfg.finetune_from = args.finetune_from
|
||||
cfg.view = args.view
|
||||
cfg.seed = args.seed
|
||||
|
||||
cfg.work_dirs = args.work_dirs if args.work_dirs else cfg.work_dirs
|
||||
|
||||
cudnn.benchmark = True
|
||||
|
||||
runner = Runner(cfg)
|
||||
|
||||
if args.validate:
|
||||
runner.validate()
|
||||
elif args.test:
|
||||
runner.test()
|
||||
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=None,
|
||||
help='work dirs')
|
||||
parser.add_argument('--load_from',
|
||||
default=None,
|
||||
help='the checkpoint file to load from')
|
||||
parser.add_argument('--resume_from',
|
||||
default=None,
|
||||
help='the checkpoint file to resume from')
|
||||
parser.add_argument('--finetune_from',
|
||||
default=None,
|
||||
help='the checkpoint file to resume from')
|
||||
parser.add_argument('--view', action='store_true', help='whether to view')
|
||||
parser.add_argument(
|
||||
'--validate',
|
||||
action='store_true',
|
||||
help='whether to evaluate the checkpoint during training')
|
||||
parser.add_argument(
|
||||
'--test',
|
||||
action='store_true',
|
||||
help='whether to test the checkpoint on testing set')
|
||||
parser.add_argument('--gpus', nargs='+', type=int, default='0')
|
||||
parser.add_argument('--seed', type=int, default=0, help='random seed')
|
||||
args = parser.parse_args()
|
||||
|
||||
return args
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,2 @@
|
||||
/home/chengfanglu/DATA/BK2/archive/yolo26_rknn_ultralytics-main/ultralytics/assets/bus.jpg
|
||||
/home/chengfanglu/DATA/BK2/archive/yolo26_rknn_ultralytics-main/ultralytics/assets/zidane.jpg
|
||||
20
algorithms/lane_ufld/code/CLRNet-main/requirements.txt
Normal file
20
algorithms/lane_ufld/code/CLRNet-main/requirements.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
torch==1.8.0
|
||||
torchvision==0.9.0
|
||||
pandas
|
||||
addict
|
||||
sklearn
|
||||
opencv-python
|
||||
pytorch_warmup
|
||||
scikit-image
|
||||
tqdm
|
||||
p_tqdm
|
||||
imgaug>=0.4.0
|
||||
Shapely==1.7.0
|
||||
ujson==1.35
|
||||
yapf
|
||||
pathspec
|
||||
timm
|
||||
mmcv==1.2.5
|
||||
albumentations==0.4.6
|
||||
pathspec
|
||||
ptflops
|
||||
32
algorithms/lane_ufld/code/CLRNet-main/scripts/setup_clrnet_env.sh
Executable file
32
algorithms/lane_ufld/code/CLRNet-main/scripts/setup_clrnet_env.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# Create conda env for CLRNet + MUFLD dataset
|
||||
set -euo pipefail
|
||||
|
||||
ENV_NAME="${CLRNET_ENV_NAME:-clrnet_lane}"
|
||||
CLRNET_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
source "$(conda info --base)/etc/profile.d/conda.sh"
|
||||
|
||||
if conda env list | awk '{print $1}' | grep -qx "$ENV_NAME"; then
|
||||
echo "Env $ENV_NAME exists, activate: conda activate $ENV_NAME"
|
||||
else
|
||||
conda create -n "$ENV_NAME" python=3.8 -y
|
||||
fi
|
||||
conda activate "$ENV_NAME"
|
||||
|
||||
# PyTorch (adjust CUDA version to match your driver)
|
||||
pip install torch==1.10.2+cu113 torchvision==0.11.3+cu113 \
|
||||
-f https://download.pytorch.org/whl/cu113/torch_stable.html
|
||||
|
||||
pip install mmcv-full==1.4.0 -f https://download.openmmlab.com/mmcv/dist/cu113/torch1.10/index.html
|
||||
|
||||
pip install -r "$CLRNET_ROOT/requirements.txt"
|
||||
|
||||
cd "$CLRNET_ROOT"
|
||||
python setup.py develop
|
||||
|
||||
echo ""
|
||||
echo "Done. Usage:"
|
||||
echo " conda activate $ENV_NAME"
|
||||
echo " cd $CLRNET_ROOT"
|
||||
echo " python main.py configs/clrnet/clr_resnet18_mufld_smoke.py --gpus 0"
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# CPU-friendly env for CLRNet ONNX export (no CUDA required for export).
|
||||
set -euo pipefail
|
||||
|
||||
ENV_NAME="${CLRNET_EXPORT_ENV:-clrnet_export}"
|
||||
CLRNET_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
source "$(conda info --base)/etc/profile.d/conda.sh"
|
||||
|
||||
if ! conda env list | awk '{print $1}' | grep -qx "$ENV_NAME"; then
|
||||
conda create -n "$ENV_NAME" python=3.8 -y
|
||||
fi
|
||||
conda activate "$ENV_NAME"
|
||||
|
||||
pip install --upgrade pip
|
||||
pip install torch==1.10.2+cpu torchvision==0.11.3+cpu \
|
||||
-f https://download.pytorch.org/whl/cpu/torch_stable.html
|
||||
pip install mmcv-full==1.4.0 -f https://download.openmmlab.com/mmcv/dist/cpu/torch1.10/index.html
|
||||
pip install onnx onnxruntime onnxsim
|
||||
pip install -r "$CLRNET_ROOT/requirements.txt"
|
||||
cd "$CLRNET_ROOT"
|
||||
pip install -e .
|
||||
|
||||
echo ""
|
||||
echo "Export ONNX:"
|
||||
echo " conda activate $ENV_NAME"
|
||||
echo " cd $CLRNET_ROOT"
|
||||
echo " python tools/export_onnx.py --check"
|
||||
116
algorithms/lane_ufld/code/CLRNet-main/setup.py
Normal file
116
algorithms/lane_ufld/code/CLRNet-main/setup.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
from setuptools import find_packages, setup
|
||||
from torch.utils.cpp_extension import CUDAExtension, BuildExtension
|
||||
|
||||
|
||||
def parse_requirements(fname='requirements.txt', with_version=True):
|
||||
"""Parse the package dependencies listed in a requirements file but strips
|
||||
specific versioning information.
|
||||
Args:
|
||||
fname (str): path to requirements file
|
||||
with_version (bool, default=False): if True include version specs
|
||||
Returns:
|
||||
List[str]: list of requirements items
|
||||
CommandLine:
|
||||
python -c "import setup; print(setup.parse_requirements())"
|
||||
"""
|
||||
import sys
|
||||
from os.path import exists
|
||||
require_fpath = fname
|
||||
|
||||
def parse_line(line):
|
||||
"""Parse information from a line in a requirements text file."""
|
||||
if line.startswith('-r '):
|
||||
# Allow specifying requirements in other files
|
||||
target = line.split(' ')[1]
|
||||
for info in parse_require_file(target):
|
||||
yield info
|
||||
else:
|
||||
info = {'line': line}
|
||||
if line.startswith('-e '):
|
||||
info['package'] = line.split('#egg=')[1]
|
||||
else:
|
||||
# Remove versioning from the package
|
||||
pat = '(' + '|'.join(['>=', '==', '>']) + ')'
|
||||
parts = re.split(pat, line, maxsplit=1)
|
||||
parts = [p.strip() for p in parts]
|
||||
|
||||
info['package'] = parts[0]
|
||||
if len(parts) > 1:
|
||||
op, rest = parts[1:]
|
||||
if ';' in rest:
|
||||
# Handle platform specific dependencies
|
||||
# http://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-platform-specific-dependencies
|
||||
version, platform_deps = map(str.strip,
|
||||
rest.split(';'))
|
||||
info['platform_deps'] = platform_deps
|
||||
else:
|
||||
version = rest # NOQA
|
||||
info['version'] = (op, version)
|
||||
yield info
|
||||
|
||||
def parse_require_file(fpath):
|
||||
with open(fpath, 'r') as f:
|
||||
for line in f.readlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
for info in parse_line(line):
|
||||
yield info
|
||||
|
||||
def gen_packages_items():
|
||||
if exists(require_fpath):
|
||||
for info in parse_require_file(require_fpath):
|
||||
parts = [info['package']]
|
||||
if with_version and 'version' in info:
|
||||
parts.extend(info['version'])
|
||||
if not sys.version.startswith('3.4'):
|
||||
# apparently package_deps are broken in 3.4
|
||||
platform_deps = info.get('platform_deps')
|
||||
if platform_deps is not None:
|
||||
parts.append(';' + platform_deps)
|
||||
item = ''.join(parts)
|
||||
yield item
|
||||
|
||||
packages = list(gen_packages_items())
|
||||
return packages
|
||||
|
||||
|
||||
install_requires = parse_requirements()
|
||||
|
||||
|
||||
def get_extensions():
|
||||
extensions = []
|
||||
|
||||
op_files = glob.glob('./clrnet/ops/csrc/*.c*')
|
||||
extension = CUDAExtension
|
||||
ext_name = 'clrnet.ops.nms_impl'
|
||||
|
||||
ext_ops = extension(
|
||||
name=ext_name,
|
||||
sources=op_files,
|
||||
)
|
||||
|
||||
extensions.append(ext_ops)
|
||||
|
||||
return extensions
|
||||
|
||||
|
||||
setup(name='clrnet',
|
||||
version="1.0",
|
||||
keywords='computer vision & lane detection',
|
||||
classifiers=[
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Intended Audience :: Developers',
|
||||
'Operating System :: OS Independent'
|
||||
],
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
setup_requires=['pytest-runner'],
|
||||
tests_require=['pytest'],
|
||||
install_requires=install_requires,
|
||||
ext_modules=get_extensions(),
|
||||
cmdclass={'build_ext': BuildExtension},
|
||||
zip_safe=False)
|
||||
155
algorithms/lane_ufld/code/CLRNet-main/tools/export_onnx.py
Normal file
155
algorithms/lane_ufld/code/CLRNet-main/tools/export_onnx.py
Normal file
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Export CLRNet to ONNX (bilinear_grid_sample, no GridSample)."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import types
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
|
||||
def _stub_nms_cuda_ext():
|
||||
"""Export does not need CUDA NMS; stub so clrnet.ops imports on CPU-only hosts."""
|
||||
if 'clrnet.ops.nms_impl' in sys.modules:
|
||||
return
|
||||
stub = types.ModuleType('clrnet.ops.nms_impl')
|
||||
|
||||
def _nms_forward(boxes, scores, overlap, top_k):
|
||||
raise NotImplementedError('NMS runs on CPU after RKNN inference, not in exported graph')
|
||||
|
||||
stub.nms_forward = _nms_forward
|
||||
sys.modules['clrnet.ops.nms_impl'] = stub
|
||||
|
||||
|
||||
_stub_nms_cuda_ext()
|
||||
|
||||
from clrnet.utils.config import Config # noqa: E402
|
||||
from clrnet.models.registry import build_net # noqa: E402
|
||||
|
||||
|
||||
class CLRNetOnnxWrapper(nn.Module):
|
||||
def __init__(self, net):
|
||||
super().__init__()
|
||||
self.net = net
|
||||
|
||||
def forward(self, img):
|
||||
# Detector returns (B, num_priors, 77) in eval mode
|
||||
return self.net({'img': img})
|
||||
|
||||
|
||||
def _remap_clrernet_keys(key):
|
||||
"""Map CLRerNet (mmdet) checkpoint keys to Turoad CLRNet-main module names."""
|
||||
key = key.replace('bbox_head.', 'heads.')
|
||||
key = key.replace('heads.sample_x_indices', 'heads.sample_x_indexs')
|
||||
key = key.replace('heads.anchor_generator.prior_embeddings',
|
||||
'heads.prior_embeddings')
|
||||
key = key.replace('heads.attention.attention.', 'heads.roi_gather.')
|
||||
key = key.replace('heads.attention.', 'heads.roi_gather.')
|
||||
return key
|
||||
|
||||
|
||||
def load_weights(net, path):
|
||||
ckpt = torch.load(path, map_location='cpu')
|
||||
if isinstance(ckpt, dict):
|
||||
if 'net' in ckpt:
|
||||
state = ckpt['net']
|
||||
elif 'state_dict' in ckpt:
|
||||
state = ckpt['state_dict']
|
||||
else:
|
||||
state = ckpt
|
||||
else:
|
||||
state = ckpt
|
||||
remapped = {_remap_clrernet_keys(k): v for k, v in state.items()}
|
||||
missing, unexpected = net.load_state_dict(remapped, strict=False)
|
||||
print('load_state_dict: missing', len(missing), 'unexpected', len(unexpected))
|
||||
if missing:
|
||||
print(' missing sample:', missing[:8])
|
||||
if unexpected:
|
||||
print(' unexpected sample:', unexpected[:8])
|
||||
|
||||
|
||||
def compare_outputs(wrapper, dummy, onnx_path):
|
||||
try:
|
||||
import onnxruntime as ort
|
||||
except ImportError:
|
||||
print('onnxruntime not installed, skip numeric check')
|
||||
return
|
||||
|
||||
wrapper.eval()
|
||||
with torch.no_grad():
|
||||
pt_out = wrapper(dummy).numpy()
|
||||
|
||||
sess = ort.InferenceSession(onnx_path, providers=['CPUExecutionProvider'])
|
||||
ort_out = sess.run(None, {'img': dummy.numpy()})[0]
|
||||
diff = np.abs(pt_out - ort_out).max()
|
||||
print(f'PyTorch vs ONNX max diff: {diff:.6f}')
|
||||
print(f'output shape: {pt_out.shape}')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--config', default='configs/clrnet/clr_dla34_culane.py')
|
||||
parser.add_argument('--checkpoint', default='models/clrernet_culane_dla34.pth')
|
||||
parser.add_argument('--output', default='models/clrernet_culane_dla34.onnx')
|
||||
parser.add_argument('--opset', type=int, default=11)
|
||||
parser.add_argument('--height', type=int, default=None)
|
||||
parser.add_argument('--width', type=int, default=None)
|
||||
parser.add_argument('--check', action='store_true', help='run onnxruntime compare')
|
||||
args = parser.parse_args()
|
||||
|
||||
cfg = Config.fromfile(os.path.join(ROOT, args.config))
|
||||
h = args.height or cfg.img_h
|
||||
w = args.width or cfg.img_w
|
||||
|
||||
# Avoid downloading ImageNet weights; we load the CLRNet checkpoint below.
|
||||
if cfg.get('backbone', None) is not None:
|
||||
cfg.backbone.pretrained = False
|
||||
|
||||
net = build_net(cfg)
|
||||
load_weights(net, os.path.join(ROOT, args.checkpoint))
|
||||
net.eval()
|
||||
|
||||
wrapper = CLRNetOnnxWrapper(net)
|
||||
dummy = torch.randn(1, 3, h, w)
|
||||
out_path = os.path.join(ROOT, args.output)
|
||||
os.makedirs(os.path.dirname(out_path) or '.', exist_ok=True)
|
||||
|
||||
print(f'export ONNX: {out_path}')
|
||||
print(f'input: (1, 3, {h}, {w})')
|
||||
with torch.no_grad():
|
||||
test_out = wrapper(dummy)
|
||||
print(f'output: {tuple(test_out.shape)}')
|
||||
|
||||
torch.onnx.export(
|
||||
wrapper,
|
||||
dummy,
|
||||
out_path,
|
||||
opset_version=args.opset,
|
||||
input_names=['img'],
|
||||
output_names=['predictions'],
|
||||
dynamic_axes={'img': {0: 'batch'}, 'predictions': {0: 'batch'}},
|
||||
do_constant_folding=True,
|
||||
)
|
||||
print('saved:', out_path)
|
||||
|
||||
try:
|
||||
import onnx
|
||||
model = onnx.load(out_path)
|
||||
ops = {n.op_type for n in model.graph.node}
|
||||
print('GridSample in graph:', 'GridSample' in ops)
|
||||
print('node op types (sample):', sorted(ops)[:20], '...')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if args.check:
|
||||
compare_outputs(wrapper, dummy, out_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pre-generate .lines.txt from masks for faster CLRNet training."""
|
||||
import argparse
|
||||
import os
|
||||
import os.path as osp
|
||||
import sys
|
||||
|
||||
import cv2
|
||||
from tqdm import tqdm
|
||||
|
||||
ROOT = osp.dirname(osp.dirname(osp.abspath(__file__)))
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
from clrnet.utils.mask_to_lanes import lanes_from_mask, lanes_to_lines_txt
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--data-root", required=True)
|
||||
ap.add_argument("--list", required=True, help="train_gt.txt path")
|
||||
ap.add_argument("--sample-y", type=int, nargs="+", default=list(range(710, 150, -10)))
|
||||
ap.add_argument("--num-lanes", type=int, default=4)
|
||||
ap.add_argument("--cache-dir", default="cache/mufld_lines")
|
||||
args = ap.parse_args()
|
||||
|
||||
list_path = args.list if osp.isabs(args.list) else osp.join(args.data_root, args.list)
|
||||
n_ok = 0
|
||||
with open(list_path) as f:
|
||||
lines = [ln.strip() for ln in f if ln.strip()]
|
||||
for line in tqdm(lines):
|
||||
parts = line.split()
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
img_rel, _ = parts[0].lstrip("/"), parts[1].lstrip("/")
|
||||
img_path = osp.join(args.data_root, img_rel)
|
||||
base = img_path[:-4]
|
||||
out = osp.join(args.data_root, args.cache_dir, osp.relpath(base, args.data_root) + ".lines.txt")
|
||||
mask_path = osp.join(args.data_root, parts[1].lstrip("/"))
|
||||
mask = cv2.imread(mask_path, cv2.IMREAD_UNCHANGED)
|
||||
if mask is None:
|
||||
continue
|
||||
if mask.ndim > 2:
|
||||
mask = mask[:, :, 0]
|
||||
lanes = lanes_from_mask(mask, args.sample_y, args.num_lanes)
|
||||
os.makedirs(osp.dirname(out), exist_ok=True)
|
||||
with open(out, "w") as fp:
|
||||
fp.write(lanes_to_lines_txt(lanes))
|
||||
n_ok += 1
|
||||
print("wrote", n_ok, "lines files under", args.cache_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,126 @@
|
||||
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)
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Write a calibration image list for RKNN (one absolute path per line)."""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--glob', default='**/*.jpg', help='glob under --root')
|
||||
parser.add_argument('--root', default='.', help='search root')
|
||||
parser.add_argument('--out', default='models/calib_images.txt')
|
||||
parser.add_argument('--max', type=int, default=20)
|
||||
args = parser.parse_args()
|
||||
|
||||
root = os.path.abspath(args.root)
|
||||
paths = []
|
||||
for p in sorted(glob.glob(os.path.join(root, args.glob), recursive=True)):
|
||||
if os.path.isfile(p):
|
||||
paths.append(os.path.abspath(p))
|
||||
if len(paths) >= args.max:
|
||||
break
|
||||
|
||||
if not paths:
|
||||
raise SystemExit(f'no images under {root} with {args.glob}')
|
||||
|
||||
out = os.path.abspath(args.out)
|
||||
os.makedirs(os.path.dirname(out) or '.', exist_ok=True)
|
||||
with open(out, 'w') as f:
|
||||
f.write('\n'.join(paths) + '\n')
|
||||
print(f'wrote {len(paths)} paths -> {out}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
56
algorithms/lane_ufld/code/CLRNet-main/tools/onnx_to_rknn.py
Normal file
56
algorithms/lane_ufld/code/CLRNet-main/tools/onnx_to_rknn.py
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ONNX -> RKNN for CLRNet (platform rk3576). Requires rknn-toolkit2 on x86 Linux."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--onnx', default='models/clrernet_culane_dla34.onnx')
|
||||
parser.add_argument('--rknn', default='models/clrernet_culane_dla34_rk3576.rknn')
|
||||
parser.add_argument('--platform', default='rk3576')
|
||||
parser.add_argument('--dtype', default='i8', choices=['i8', 'fp'])
|
||||
parser.add_argument('--dataset', default=None,
|
||||
help='txt list of calibration images (one path per line)')
|
||||
parser.add_argument('--height', type=int, default=320)
|
||||
parser.add_argument('--width', type=int, default=800)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
from rknn.api import RKNN
|
||||
except ImportError as e:
|
||||
raise SystemExit(
|
||||
'Install rknn-toolkit2 in a separate env: pip install rknn-toolkit2\n' + str(e)
|
||||
) from e
|
||||
|
||||
if not args.dataset:
|
||||
raise SystemExit(
|
||||
'Provide --dataset with a txt of RGB image paths for INT8 calibration.'
|
||||
)
|
||||
|
||||
rknn = RKNN(verbose=True)
|
||||
print('config:', args.platform, args.dtype)
|
||||
rknn.config(mean_values=[[103.939, 116.779, 123.68]],
|
||||
std_values=[[1, 1, 1]],
|
||||
target_platform=args.platform,
|
||||
quantized_dtype='asymmetric_quantized-8' if args.dtype == 'i8' else 'float16')
|
||||
|
||||
ret = rknn.load_onnx(model=args.onnx)
|
||||
if ret != 0:
|
||||
raise SystemExit('load_onnx failed')
|
||||
|
||||
ret = rknn.build(do_quantization=(args.dtype == 'i8'), dataset=args.dataset)
|
||||
if ret != 0:
|
||||
raise SystemExit('build failed')
|
||||
|
||||
os.makedirs(os.path.dirname(args.rknn) or '.', exist_ok=True)
|
||||
ret = rknn.export_rknn(args.rknn)
|
||||
if ret != 0:
|
||||
raise SystemExit('export_rknn failed')
|
||||
print('saved:', os.path.abspath(args.rknn))
|
||||
rknn.release()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
11
algorithms/lane_ufld/code/README.md
Normal file
11
algorithms/lane_ufld/code/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# LaneDection 代码
|
||||
|
||||
**ML 入口**:`ML/Lane/code`(软链到本目录)
|
||||
|
||||
| 子目录 | 用途 |
|
||||
|--------|------|
|
||||
| `UFLD/` | 车道线分割训练(`ml.py train lane` 默认) |
|
||||
| `CLRNet-main/` | CLRNet |
|
||||
| `pytorch-auto-drive-master/` | 其他车道线模型 |
|
||||
|
||||
数据在 `ML/Lane/dataset`(= `/DATA/lane`)。
|
||||
138
algorithms/lane_ufld/code/UFLD/README.md
Normal file
138
algorithms/lane_ufld/code/UFLD/README.md
Normal file
@@ -0,0 +1,138 @@
|
||||
UFLD 使用说明(lane0_copy 数据)
|
||||
|
||||
代码目录:/home/chengfanglu/DATA/BK2/UFLD
|
||||
数据目录:/home/chengfanglu/DATA/lane0_copy/
|
||||
|
||||
|
||||
一、环境
|
||||
|
||||
source ~/miniconda3/etc/profile.d/conda.sh
|
||||
conda activate lane_light
|
||||
cd /home/chengfanglu/DATA/BK2/UFLD
|
||||
|
||||
|
||||
二、数据目录
|
||||
|
||||
lane0_copy/
|
||||
DATASET/(基线包,勿改 list/train_gt.txt)
|
||||
images/
|
||||
annotations/segmentation_masks/
|
||||
list/train_gt.txt(训练:图 + mask 两列)
|
||||
list/val_gt.txt
|
||||
list/test_gt.txt
|
||||
list/test.txt(仅图片)
|
||||
DATASET-AddBy-zhangsan-20260615/(增量包,结构同上)
|
||||
lists_merged/(多包合并列表,训练时自动生成)
|
||||
datasets_registry.json(短名别名)
|
||||
|
||||
增量包命名:DATASET-AddBy-姓名-YYYYMMDD(日期 8 位,如 20260615)
|
||||
目录规范详见:/home/chengfanglu/DATA/lane0_copy/DATASETS_LAYOUT.md
|
||||
|
||||
新建增量包命令:
|
||||
|
||||
python /home/chengfanglu/DATA/lane0_copy/scripts/build_ufld_pack.py --src /path/to/archive --parent /home/chengfanglu/DATA/lane0_copy --engineer zhangsan --date 20260615
|
||||
|
||||
别名(config 里可写 DATASET-A):编辑 lane0_copy/datasets_registry.json,例如:
|
||||
{"aliases": {"DATASET-A": "DATASET-AddBy-zhangsan-20260615"}}
|
||||
|
||||
|
||||
三、配置文件
|
||||
|
||||
configs/mufld_lane_multi_pack.py — 推荐,多包训练,用 train_packs 控制合并
|
||||
configs/mufld_lane_culane.py — 单包,data_root 指向 DATASET 目录本身
|
||||
configs/mufld_lane_smoke.py — 冒烟(少量样本)
|
||||
configs/tusimple_res18_4lane_v1.py — 对接旧权重 best.pth(griding_num=100)
|
||||
|
||||
多包训练请改 configs/mufld_lane_multi_pack.py:
|
||||
|
||||
data_root = '/home/chengfanglu/DATA/lane0_copy'
|
||||
train_packs = ['DATASET']
|
||||
(多包示例:train_packs = ['DATASET', 'DATASET-A'])
|
||||
pack_list_name = 'list/train_gt.txt'
|
||||
remerge_train_list = False(增删包后改为 True,强制重建 lists_merged)
|
||||
|
||||
训练时自动合并列表到:lane0_copy/lists_merged/train__DATASET__....txt
|
||||
|
||||
|
||||
四、训练
|
||||
|
||||
conda activate lane_light
|
||||
cd /home/chengfanglu/DATA/BK2/UFLD
|
||||
|
||||
冒烟:
|
||||
UFLD_NUM_WORKERS=0 python train.py configs/mufld_lane_smoke.py
|
||||
|
||||
正式(多包):
|
||||
python train.py configs/mufld_lane_multi_pack.py
|
||||
|
||||
断点续训:
|
||||
python train.py configs/mufld_lane_multi_pack.py --resume log/你的实验目录/best.pth
|
||||
|
||||
日志与权重在:log/时间_lr_.../best.pth
|
||||
无 GPU 时可设 UFLD_NUM_WORKERS=0
|
||||
|
||||
常用 config 项:
|
||||
batch_size = 16
|
||||
learning_rate = 0.1
|
||||
use_aux = True(False 与旧 best.pth 一致,更省显存)
|
||||
griding_num = 200(旧权重用 100)
|
||||
num_lanes = 4
|
||||
|
||||
|
||||
五、推理与测试
|
||||
|
||||
【5.1 可视化 demo】
|
||||
|
||||
先准备 test3.txt(示例取 3 张):
|
||||
awk '{print $1}' /home/chengfanglu/DATA/lane0_copy/DATASET/list/test_gt.txt | head -3 > /home/chengfanglu/DATA/lane0_copy/DATASET/test3.txt
|
||||
|
||||
python demo.py configs/tusimple_res18_4lane_v1.py --test_model log/20250702_165153_lr_1e-05_b_32_ufld_2lanes_res18/best.pth --data_root /home/chengfanglu/DATA/lane0_copy/DATASET
|
||||
|
||||
【5.2 批量测试】
|
||||
|
||||
python test.py configs/tusimple_res18_4lane_v1.py --test_model log/20250702_165153_lr_1e-05_b_32_ufld_2lanes_res18/best.pth --data_root /home/chengfanglu/DATA/lane0_copy/DATASET --test_list list/test_gt.txt
|
||||
|
||||
多包时 data_root 用 lane0_copy,例如:
|
||||
python test.py configs/mufld_lane_multi_pack.py --test_model log/xxx/best.pth --data_root /home/chengfanglu/DATA/lane0_copy --test_list lists_merged/train__DATASET.txt
|
||||
|
||||
无 test_label.json 时只出预测,不算 TuSimple 官方指标。
|
||||
|
||||
【5.3 预测画到图上】
|
||||
|
||||
python vis_tusimple_pred.py --pred tmp/tusimple_eval_tmp.0.txt --data_root /home/chengfanglu/DATA/lane0_copy/DATASET --out_dir tmp/vis_pred
|
||||
|
||||
|
||||
六、导出 ONNX
|
||||
|
||||
python pth_to_onnx.py --model_path log/20250702_165153_lr_1e-05_b_32_ufld_2lanes_res18/best.pth --output log/20250702_165153_lr_1e-05_b_32_ufld_2lanes_res18/best.onnx
|
||||
|
||||
需与训练时 backbone、griding_num、num_lanes 一致。
|
||||
|
||||
|
||||
【6.1 VoVNet backbone】
|
||||
|
||||
已从 `BK2/archive/vovnet-detectron2-master` 移植 OSA+eSE 结构(无 detectron2 依赖),与 ResNet 相同接口。
|
||||
|
||||
| config `backbone` | 说明 |
|
||||
|-------------------|------|
|
||||
| `vov19slim` | V-19-slim-eSE,约 52.7M 参数(288×800) |
|
||||
| `vov19slim_dw` | slim + depthwise |
|
||||
| `vov19` / `vov39` / `vov57` / `vov99` | 更大变体 |
|
||||
|
||||
示例配置:`configs/tusimple_vov19slim_4lane_v1.py`
|
||||
|
||||
```bash
|
||||
python train.py configs/tusimple_vov19slim_4lane_v1.py
|
||||
python profile_model.py --backbone vov19slim --griding_num 100 --num_lanes 4
|
||||
```
|
||||
|
||||
VoVNet **无** torchvision 预训练权重,需从头训或自行转换 detectron2 权重。旧 ResNet 的 `best.pth` **不能**直接用于 VoVNet。
|
||||
|
||||
|
||||
七、路径速查
|
||||
|
||||
代码:/home/chengfanglu/DATA/BK2/UFLD
|
||||
数据父目录:/home/chengfanglu/DATA/lane0_copy
|
||||
基线数据:/home/chengfanglu/DATA/lane0_copy/DATASET
|
||||
多包配置:configs/mufld_lane_multi_pack.py
|
||||
已有权重:log/20250702_165153_lr_1e-05_b_32_ufld_2lanes_res18/best.pth
|
||||
46
algorithms/lane_ufld/code/UFLD/TRAIN_ENV_CPU.md
Normal file
46
algorithms/lane_ufld/code/UFLD/TRAIN_ENV_CPU.md
Normal file
@@ -0,0 +1,46 @@
|
||||
## lane_light + CPU PyTorch(UFLD 训练)
|
||||
|
||||
### 1. 激活环境
|
||||
|
||||
```bash
|
||||
source /home/chengfanglu/miniconda3/etc/profile.d/conda.sh
|
||||
conda activate lane_light
|
||||
```
|
||||
|
||||
### 2. 已安装依赖(`lane_light`)
|
||||
|
||||
- `torch` / `torchvision`(**CPU 轮子**,来自 `https://download.pytorch.org/whl/cpu`)
|
||||
- `opencv-python`, `tqdm`, `tensorboard`, `addict`, `scikit-learn`, `pathspec`(与 `requirements.txt` 对齐;`sklearn` 包名在 pip 中为 `scikit-learn`)
|
||||
|
||||
自检:
|
||||
|
||||
```bash
|
||||
python -c "import torch; print('torch', torch.__version__, 'cuda=', torch.cuda.is_available())"
|
||||
```
|
||||
|
||||
### 3. 数据与配置
|
||||
|
||||
- 默认数据根仍指向 `lane0_reorganized/lane_training_pack`(见 `configs/mufld_lane_culane.py`)。
|
||||
- **CPU 建议**使用 `configs/mufld_lane_culane_cpu.py`(`batch_size=4`,学习率与 warmup 已按 batch 相对 16 做了粗略缩放)。内存不够可改配置或命令行覆盖:
|
||||
|
||||
```bash
|
||||
cd /home/chengfanglu/DATA/BK2/UFLD
|
||||
python train.py configs/mufld_lane_culane_cpu.py --batch_size 2
|
||||
```
|
||||
|
||||
### 4. 运行训练
|
||||
|
||||
```bash
|
||||
cd /home/chengfanglu/DATA/BK2/UFLD
|
||||
python train.py configs/mufld_lane_culane_cpu.py
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `train.py` 已改为在 **无 CUDA** 时使用 `cpu`;原仓库中写死的 `CUDA_VISIBLE_DEVICES=1,2` 与 `.cuda()` 已去掉,避免 CPU 机直接报错。
|
||||
- 首次 `pretrained=True` 会下载 ResNet 骨干权重,需联网。
|
||||
- CPU 训练很慢,建议先用小 `epoch` / 小 `batch_size` 做通路测试。
|
||||
|
||||
### 5. 可选:DataLoader `num_workers`
|
||||
|
||||
当前 `data/dataloader.py` 里 `num_workers=8`。若 CPU 内存紧张或不想多进程读盘,可自行把该值改小(例如 `0` 或 `2`)。
|
||||
45
algorithms/lane_ufld/code/UFLD/configs/culane.py
Executable file
45
algorithms/lane_ufld/code/UFLD/configs/culane.py
Executable file
@@ -0,0 +1,45 @@
|
||||
# DATA
|
||||
dataset = 'CULane'
|
||||
data_root = 'C:\\data\\Tusimple\\test_set'
|
||||
|
||||
# TRAIN
|
||||
epoch = 50
|
||||
batch_size = 32
|
||||
optimizer = 'SGD' #['SGD','Adam']
|
||||
learning_rate = 0.1
|
||||
weight_decay = 1e-4
|
||||
momentum = 0.9
|
||||
|
||||
scheduler = 'multi' #['multi', 'cos']
|
||||
steps = [25,38]
|
||||
gamma = 0.1
|
||||
warmup = 'linear'
|
||||
warmup_iters = 695
|
||||
|
||||
# NETWORK
|
||||
use_aux = True
|
||||
griding_num = 200
|
||||
backbone = '18'
|
||||
|
||||
# LOSS
|
||||
sim_loss_w = 0.0
|
||||
shp_loss_w = 0.0
|
||||
|
||||
# EXP
|
||||
note = ''
|
||||
|
||||
log_path = None
|
||||
|
||||
# FINETUNE or RESUME MODEL PATH
|
||||
finetune = None
|
||||
resume = None
|
||||
|
||||
# TEST
|
||||
test_model = './model/culane_18.pth'
|
||||
test_work_dir = './tmp'
|
||||
|
||||
num_lanes = 4
|
||||
|
||||
|
||||
|
||||
|
||||
41
algorithms/lane_ufld/code/UFLD/configs/mufld_lane_culane.py
Normal file
41
algorithms/lane_ufld/code/UFLD/configs/mufld_lane_culane.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# MUFLD lane pack in CULane-style layout for UFLD training.
|
||||
# Data root layout:
|
||||
# <data_root>/images/...
|
||||
# <data_root>/annotations/segmentation_masks/...
|
||||
# <data_root>/list/train_gt.txt (two columns: training split only)
|
||||
# <data_root>/list/val_gt.txt (validation pairs, optional custom loop)
|
||||
# <data_root>/list/test.txt (held-out test images, one per line)
|
||||
|
||||
dataset = 'CULane'
|
||||
data_root = '/home/chengfanglu/DATA/lane0_copy/DATASET'
|
||||
|
||||
epoch = 50
|
||||
batch_size = 16
|
||||
optimizer = 'SGD'
|
||||
learning_rate = 0.1
|
||||
weight_decay = 1e-4
|
||||
momentum = 0.9
|
||||
|
||||
scheduler = 'multi'
|
||||
steps = [25, 38]
|
||||
gamma = 0.1
|
||||
warmup = 'linear'
|
||||
warmup_iters = 695
|
||||
|
||||
use_aux = True
|
||||
griding_num = 200
|
||||
backbone = '18'
|
||||
|
||||
sim_loss_w = 0.0
|
||||
shp_loss_w = 0.0
|
||||
|
||||
note = 'lane_training_pack_v1'
|
||||
log_path = './log'
|
||||
|
||||
finetune = None
|
||||
resume = None
|
||||
|
||||
test_model = './model/culane_18.pth'
|
||||
test_work_dir = './tmp'
|
||||
|
||||
num_lanes = 4
|
||||
@@ -0,0 +1,40 @@
|
||||
# CPU 单机训练示例:batch 需显著减小;学习率可按 batch 相对 16 做线性缩放(可选)。
|
||||
#
|
||||
# layout 同 configs/mufld_lane_culane.py;
|
||||
# lane_light 环境与安装说明见 TRAIN_ENV_CPU.md
|
||||
|
||||
dataset = "CULane"
|
||||
data_root = "/home/chengfanglu/DATA/lane0_copy/DATASET"
|
||||
|
||||
epoch = 50
|
||||
batch_size = 4
|
||||
optimizer = "SGD"
|
||||
# 若在 CPU 上不收敛可先试更小 lr,例如 batch=4 时约 0.1 * (4 / 16) = 0.025
|
||||
learning_rate = 0.025
|
||||
weight_decay = 1e-4
|
||||
momentum = 0.9
|
||||
|
||||
scheduler = "multi"
|
||||
steps = [25, 38]
|
||||
gamma = 0.1
|
||||
warmup = "linear"
|
||||
# warmup 与原配置按 batch 比例对齐(原为 695 @ bs=16)
|
||||
warmup_iters = 174
|
||||
|
||||
use_aux = True
|
||||
griding_num = 200
|
||||
backbone = "18"
|
||||
|
||||
sim_loss_w = 0.0
|
||||
shp_loss_w = 0.0
|
||||
|
||||
note = "lane_training_pack_cpu_bs4"
|
||||
log_path = None
|
||||
|
||||
finetune = None
|
||||
resume = None
|
||||
|
||||
test_model = "./model/culane_18.pth"
|
||||
test_work_dir = "./tmp"
|
||||
|
||||
num_lanes = 4
|
||||
@@ -0,0 +1,54 @@
|
||||
# Multi-pack training — control merged packs in this config.
|
||||
# data_root = parent of DATASET / DATASET-AddBy-* / DATASET-A (alias).
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
dataset = 'CULane'
|
||||
data_root = str(Path(__file__).resolve().parents[5] / "datasets" / "lane")
|
||||
|
||||
# Pack names: directory under data_root, or alias from datasets_registry.json
|
||||
train_packs = [
|
||||
'lane_v1',
|
||||
]
|
||||
|
||||
# Default list inside each pack (relative to pack root)
|
||||
pack_list_name = 'list/train_gt.txt'
|
||||
|
||||
# Cached merged list (auto filename from pack names if merged_train_list is None)
|
||||
merged_list_dir = 'lists_merged'
|
||||
merged_train_list = None # e.g. 'lists_merged/train_all_v2.txt'
|
||||
remerge_train_list = False # True to rebuild merged list every run
|
||||
|
||||
# Single-pack fallback (ignored when train_packs is set)
|
||||
train_list = 'list/train_gt.txt'
|
||||
|
||||
epoch = 50
|
||||
batch_size = 16
|
||||
optimizer = 'SGD'
|
||||
learning_rate = 0.1
|
||||
weight_decay = 1e-4
|
||||
momentum = 0.9
|
||||
|
||||
scheduler = 'multi'
|
||||
steps = [25, 38]
|
||||
gamma = 0.1
|
||||
warmup = 'linear'
|
||||
warmup_iters = 695
|
||||
|
||||
use_aux = True
|
||||
griding_num = 200
|
||||
backbone = '18'
|
||||
|
||||
sim_loss_w = 0.0
|
||||
shp_loss_w = 0.0
|
||||
|
||||
note = 'multi_pack_v2'
|
||||
log_path = './log'
|
||||
|
||||
finetune = None
|
||||
resume = None
|
||||
|
||||
test_model = './model/culane_18.pth'
|
||||
test_work_dir = './tmp'
|
||||
|
||||
num_lanes = 4
|
||||
35
algorithms/lane_ufld/code/UFLD/configs/mufld_lane_smoke.py
Normal file
35
algorithms/lane_ufld/code/UFLD/configs/mufld_lane_smoke.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Smoke test: few samples, 1 epoch, small batch (CPU or GPU).
|
||||
dataset = "CULane"
|
||||
data_root = "/home/chengfanglu/DATA/lane0_copy/DATASET"
|
||||
train_list = "list/train_gt_smoke.txt"
|
||||
|
||||
epoch = 1
|
||||
batch_size = 2
|
||||
optimizer = "SGD"
|
||||
learning_rate = 0.025
|
||||
weight_decay = 1e-4
|
||||
momentum = 0.9
|
||||
|
||||
scheduler = "multi"
|
||||
steps = [1]
|
||||
gamma = 0.1
|
||||
warmup = "linear"
|
||||
warmup_iters = 10
|
||||
|
||||
use_aux = True
|
||||
griding_num = 200
|
||||
backbone = "18"
|
||||
|
||||
sim_loss_w = 0.0
|
||||
shp_loss_w = 0.0
|
||||
|
||||
note = "dataset_smoke_test"
|
||||
log_path = "./log"
|
||||
|
||||
finetune = None
|
||||
resume = None
|
||||
|
||||
test_model = "./model/culane_18.pth"
|
||||
test_work_dir = "./tmp"
|
||||
|
||||
num_lanes = 4
|
||||
41
algorithms/lane_ufld/code/UFLD/configs/tusimple_res18.py
Executable file
41
algorithms/lane_ufld/code/UFLD/configs/tusimple_res18.py
Executable file
@@ -0,0 +1,41 @@
|
||||
# DATA
|
||||
dataset = 'Tusimple'
|
||||
data_root = '/mnt/HDisk2T/liuxy51/ganxian/data_luojk'
|
||||
|
||||
# TRAIN
|
||||
epoch = 500 # 10
|
||||
batch_size = 32 # 4
|
||||
optimizer = 'Adam' #['SGD','Adam']
|
||||
learning_rate = 1e-5
|
||||
weight_decay = 1e-4
|
||||
momentum = 0.9
|
||||
scheduler = 'cos' #['multi', 'cos']
|
||||
|
||||
# steps = [50,75]
|
||||
gamma = 0.1
|
||||
warmup = 'linear'
|
||||
warmup_iters = 100
|
||||
|
||||
# NETWORK
|
||||
backbone = '18'
|
||||
griding_num = 100
|
||||
use_aux = False
|
||||
|
||||
# LOSS
|
||||
sim_loss_w = 1.0
|
||||
shp_loss_w = 0.0
|
||||
|
||||
# EXP
|
||||
note = '_ufld_2lanes_res18'
|
||||
|
||||
log_path = './log'
|
||||
|
||||
# FINETUNE or RESUME MODEL PATH
|
||||
finetune = None
|
||||
resume = None
|
||||
|
||||
# TESTNone
|
||||
test_model = './model/lane_m599_all.pth'
|
||||
test_work_dir = './tmp'
|
||||
|
||||
num_lanes = 2
|
||||
41
algorithms/lane_ufld/code/UFLD/configs/tusimple_res18_4lane_v1.py
Executable file
41
algorithms/lane_ufld/code/UFLD/configs/tusimple_res18_4lane_v1.py
Executable file
@@ -0,0 +1,41 @@
|
||||
# DATA
|
||||
dataset = 'Tusimple'
|
||||
data_root = '/mnt/HDisk2T/liuxy51/ganxian/train_2025_03_13_mufld' # 针对clrnet数据集8.1w帧多车道数据
|
||||
|
||||
# TRAIN
|
||||
epoch = 500 # 10
|
||||
batch_size = 32 # 4
|
||||
optimizer = 'Adam' #['SGD','Adam']
|
||||
learning_rate = 1e-5
|
||||
weight_decay = 1e-4
|
||||
momentum = 0.9
|
||||
scheduler = 'cos' #['multi', 'cos']
|
||||
|
||||
# steps = [50,75]
|
||||
gamma = 0.1
|
||||
warmup = 'linear'
|
||||
warmup_iters = 100
|
||||
|
||||
# NETWORK
|
||||
backbone = '18'
|
||||
griding_num = 100
|
||||
use_aux = False
|
||||
|
||||
# LOSS
|
||||
sim_loss_w = 1.0
|
||||
shp_loss_w = 0.0
|
||||
|
||||
# EXP
|
||||
note = '_ufld_2lanes_res18'
|
||||
|
||||
log_path = './log'
|
||||
|
||||
# FINETUNE or RESUME MODEL PATH
|
||||
finetune = None
|
||||
resume = None
|
||||
|
||||
# TESTNone
|
||||
test_model = './model/lane_m599_all.pth'
|
||||
test_work_dir = './tmp'
|
||||
|
||||
num_lanes = 4
|
||||
41
algorithms/lane_ufld/code/UFLD/configs/tusimple_res18_nbg.py
Executable file
41
algorithms/lane_ufld/code/UFLD/configs/tusimple_res18_nbg.py
Executable file
@@ -0,0 +1,41 @@
|
||||
# DATA
|
||||
dataset = 'Tusimple'
|
||||
data_root = '/mnt/HDisk2T/liuxy51/ganxian/train_2024_03_06'
|
||||
|
||||
# TRAIN
|
||||
epoch = 500 # 10
|
||||
batch_size = 32 # 4
|
||||
optimizer = 'Adam' #['SGD','Adam']
|
||||
learning_rate = 1e-5
|
||||
weight_decay = 1e-4
|
||||
momentum = 0.9
|
||||
scheduler = 'cos' #['multi', 'cos']
|
||||
|
||||
# steps = [50,75]
|
||||
gamma = 0.1
|
||||
warmup = 'linear'
|
||||
warmup_iters = 100
|
||||
|
||||
# NETWORK
|
||||
backbone = '18'
|
||||
griding_num = 100
|
||||
use_aux = False
|
||||
|
||||
# LOSS
|
||||
sim_loss_w = 1.0
|
||||
shp_loss_w = 0.0
|
||||
|
||||
# EXP
|
||||
note = '_ufld_2lanes_res18'
|
||||
|
||||
log_path = './log'
|
||||
|
||||
# FINETUNE or RESUME MODEL PATH
|
||||
finetune = None
|
||||
resume = '/mnt/HDisk2T/liuxy51/ganxian/UFLD/log/20240607_162111_lr_1e-05_b_32_ufld_2lanes_res18/ep304.pth' # None
|
||||
|
||||
# TESTNone
|
||||
test_model = './model/lane_m599_all.pth'
|
||||
test_work_dir = './tmp'
|
||||
|
||||
num_lanes = 2
|
||||
41
algorithms/lane_ufld/code/UFLD/configs/tusimple_res18_nbg2.py
Executable file
41
algorithms/lane_ufld/code/UFLD/configs/tusimple_res18_nbg2.py
Executable file
@@ -0,0 +1,41 @@
|
||||
# DATA
|
||||
dataset = 'Tusimple'
|
||||
data_root = '/mnt/HDisk2T/liuxy51/ganxian/train_2024_03_06_1'
|
||||
|
||||
# TRAIN
|
||||
epoch = 500 # 10
|
||||
batch_size = 32 # 4
|
||||
optimizer = 'Adam' #['SGD','Adam']
|
||||
learning_rate = 1e-5
|
||||
weight_decay = 1e-4
|
||||
momentum = 0.9
|
||||
scheduler = 'cos' #['multi', 'cos']
|
||||
|
||||
# steps = [50,75]
|
||||
gamma = 0.1
|
||||
warmup = 'linear'
|
||||
warmup_iters = 100
|
||||
|
||||
# NETWORK
|
||||
backbone = '18'
|
||||
griding_num = 100
|
||||
use_aux = False
|
||||
|
||||
# LOSS
|
||||
sim_loss_w = 1.0
|
||||
shp_loss_w = 0.0
|
||||
|
||||
# EXP
|
||||
note = '_ufld_2lanes_res18'
|
||||
|
||||
log_path = './log'
|
||||
|
||||
# FINETUNE or RESUME MODEL PATH
|
||||
finetune = None
|
||||
resume = None
|
||||
|
||||
# TESTNone
|
||||
test_model = './model/lane_m599_all.pth'
|
||||
test_work_dir = './tmp'
|
||||
|
||||
num_lanes = 2
|
||||
41
algorithms/lane_ufld/code/UFLD/configs/tusimple_res18_nbg3.py
Executable file
41
algorithms/lane_ufld/code/UFLD/configs/tusimple_res18_nbg3.py
Executable file
@@ -0,0 +1,41 @@
|
||||
# DATA
|
||||
dataset = 'Tusimple'
|
||||
data_root = '/mnt/HDisk2T/liuxy51/ganxian/train_2024_03_06_2'
|
||||
|
||||
# TRAIN
|
||||
epoch = 500 # 10
|
||||
batch_size = 32 # 4
|
||||
optimizer = 'Adam' #['SGD','Adam']
|
||||
learning_rate = 1e-5
|
||||
weight_decay = 1e-4
|
||||
momentum = 0.9
|
||||
scheduler = 'cos' #['multi', 'cos']
|
||||
|
||||
# steps = [50,75]
|
||||
gamma = 0.1
|
||||
warmup = 'linear'
|
||||
warmup_iters = 100
|
||||
|
||||
# NETWORK
|
||||
backbone = '18'
|
||||
griding_num = 100
|
||||
use_aux = False
|
||||
|
||||
# LOSS
|
||||
sim_loss_w = 1.0
|
||||
shp_loss_w = 0.0
|
||||
|
||||
# EXP
|
||||
note = '_ufld_2lanes_res18'
|
||||
|
||||
log_path = './log'
|
||||
|
||||
# FINETUNE or RESUME MODEL PATH
|
||||
finetune = None
|
||||
resume = None
|
||||
|
||||
# TESTNone
|
||||
test_model = './model/lane_m599_all.pth'
|
||||
test_work_dir = './tmp'
|
||||
|
||||
num_lanes = 2
|
||||
44
algorithms/lane_ufld/code/UFLD/configs/tusimple_res34.py
Executable file
44
algorithms/lane_ufld/code/UFLD/configs/tusimple_res34.py
Executable file
@@ -0,0 +1,44 @@
|
||||
# DATA
|
||||
dataset = 'Tusimple'
|
||||
# data_root = 'C:\\data\\Tusimple\\test_set'
|
||||
# data_root = 'C:\\data\\anno\\324lane'
|
||||
# data_root = 'C:\\data\\Tusimple\\train_set'
|
||||
data_root = '/data/panh28/yk_syj/data/train_0306'
|
||||
# TRAIN
|
||||
epoch = 600
|
||||
batch_size = 64
|
||||
optimizer = 'Adam' #['SGD','Adam']
|
||||
# learning_rate = 0.1
|
||||
learning_rate = 1e-5
|
||||
weight_decay = 1e-4
|
||||
momentum = 0.9
|
||||
|
||||
scheduler = 'cos' #['multi', 'cos']
|
||||
# steps = [50,75]
|
||||
gamma = 0.1
|
||||
warmup = 'linear'
|
||||
warmup_iters = 100
|
||||
|
||||
# NETWORK
|
||||
backbone = '34'
|
||||
griding_num = 100
|
||||
use_aux = False
|
||||
|
||||
# LOSS
|
||||
sim_loss_w = 1.0
|
||||
shp_loss_w = 0.0
|
||||
|
||||
# EXP
|
||||
note = 'lane_res34_2ch_syj_0906_minilearn'
|
||||
|
||||
log_path = './log'
|
||||
|
||||
# FINETUNE or RESUME MODEL PATH
|
||||
finetune = None
|
||||
resume = "/data/panh28/yk_syj/code/UFLD/log/20230906_161808_lr_1e-04_b_64lane_res34_2ch_syj_0906/ep068.pth"
|
||||
# TESTNone
|
||||
test_model = './model/lane_m599_all.pth'
|
||||
# test_model = './model/tusimple_18.pth'
|
||||
test_work_dir = './tmp'
|
||||
|
||||
num_lanes = 2
|
||||
@@ -0,0 +1,6 @@
|
||||
# UFLD + VoVNet-19-slim-eSE backbone (train from scratch; no torchvision weights).
|
||||
|
||||
from configs.tusimple_res18_4lane_v1 import *
|
||||
|
||||
backbone = 'vov19slim'
|
||||
note = '_ufld_4lanes_vov19slim'
|
||||
45
algorithms/lane_ufld/code/UFLD/curve_fit.py
Executable file
45
algorithms/lane_ufld/code/UFLD/curve_fit.py
Executable file
@@ -0,0 +1,45 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from scipy.optimize import curve_fit
|
||||
|
||||
|
||||
# 自定义函数 e指数形式
|
||||
def func(x, a, b, c):
|
||||
return a * np.sqrt(x) * (b * np.square(x) + c)
|
||||
|
||||
|
||||
# 定义x、y散点坐标
|
||||
x = [20, 30, 40, 50, 60, 70]
|
||||
x = np.array(x)
|
||||
num = [453, 482, 503, 508, 498, 479]
|
||||
y = np.array(num)
|
||||
|
||||
|
||||
def get_curve_fit(x, y):
|
||||
# 非线性最小二乘法拟合
|
||||
popt, pcov = curve_fit(func, x, y)
|
||||
# 获取popt里面是拟合系数
|
||||
# print(popt)
|
||||
a = popt[0]
|
||||
b = popt[1]
|
||||
c = popt[2]
|
||||
yvals = func(x, a, b, c) # 拟合y值
|
||||
# print('popt:', popt)
|
||||
# print('系数a:', a)
|
||||
# print('系数b:', b)
|
||||
# print('系数c:', c)
|
||||
# print('系数pcov:', pcov)
|
||||
# print('系数yvals:', yvals)
|
||||
return yvals
|
||||
|
||||
|
||||
yvals = get_curve_fit(x, y)
|
||||
print(yvals)
|
||||
# 绘图
|
||||
plot1 = plt.plot(x, y, 's', label='original values')
|
||||
plot2 = plt.plot(x, yvals, 'r', label='polyfit values')
|
||||
plt.xlabel('x')
|
||||
plt.ylabel('y')
|
||||
plt.legend(loc=4) # 指定legend的位置右下角
|
||||
plt.title('curve_fit')
|
||||
plt.show()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user