Voidljc commited on
Commit ·
aa24fe8
1
Parent(s): 3eca65b
Your commit message
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- data.yaml +8 -0
- faster-rcnn-pytorch-master/.gitignore +140 -0
- faster-rcnn-pytorch-master/LICENSE +21 -0
- faster-rcnn-pytorch-master/README.md +150 -0
- faster-rcnn-pytorch-master/frcnn.py +334 -0
- faster-rcnn-pytorch-master/get_map.py +138 -0
- faster-rcnn-pytorch-master/img/street.jpg +3 -0
- faster-rcnn-pytorch-master/nets/__init__.py +1 -0
- faster-rcnn-pytorch-master/nets/classifier.py +119 -0
- faster-rcnn-pytorch-master/nets/frcnn.py +110 -0
- faster-rcnn-pytorch-master/nets/frcnn_training.py +393 -0
- faster-rcnn-pytorch-master/nets/resnet50.py +130 -0
- faster-rcnn-pytorch-master/nets/rpn.py +191 -0
- faster-rcnn-pytorch-master/nets/vgg16.py +110 -0
- faster-rcnn-pytorch-master/predict.py +139 -0
- faster-rcnn-pytorch-master/requirements.txt +10 -0
- faster-rcnn-pytorch-master/summary.py +29 -0
- faster-rcnn-pytorch-master/test.py +3 -0
- faster-rcnn-pytorch-master/train.py +453 -0
- faster-rcnn-pytorch-master/utils/__init__.py +1 -0
- faster-rcnn-pytorch-master/utils/anchors.py +67 -0
- faster-rcnn-pytorch-master/utils/callbacks.py +237 -0
- faster-rcnn-pytorch-master/utils/dataloader.py +165 -0
- faster-rcnn-pytorch-master/utils/utils.py +86 -0
- faster-rcnn-pytorch-master/utils/utils_bbox.py +131 -0
- faster-rcnn-pytorch-master/utils/utils_fit.py +76 -0
- faster-rcnn-pytorch-master/utils/utils_map.py +923 -0
- faster-rcnn-pytorch-master/voc_annotation.py +153 -0
- faster-rcnn-pytorch-master/常见问题汇总.md +554 -0
- run.py +31 -0
- ssd-pytorch-master/.gitignore +140 -0
- ssd-pytorch-master/2007_train.txt +0 -0
- ssd-pytorch-master/2007_val.txt +42 -0
- ssd-pytorch-master/LICENSE +21 -0
- ssd-pytorch-master/README.md +157 -0
- ssd-pytorch-master/get_map.py +138 -0
- ssd-pytorch-master/img/street.jpg +3 -0
- ssd-pytorch-master/nets/__init__.py +1 -0
- ssd-pytorch-master/nets/mobilenetv2.py +117 -0
- ssd-pytorch-master/nets/resnet.py +174 -0
- ssd-pytorch-master/nets/ssd.py +211 -0
- ssd-pytorch-master/nets/ssd_training.py +174 -0
- ssd-pytorch-master/nets/vgg.py +50 -0
- ssd-pytorch-master/predict.py +158 -0
- ssd-pytorch-master/requirements.txt +10 -0
- ssd-pytorch-master/ssd.py +390 -0
- ssd-pytorch-master/summary.py +31 -0
- ssd-pytorch-master/train.py +540 -0
- ssd-pytorch-master/utils/__init__.py +1 -0
- ssd-pytorch-master/utils/anchors.py +281 -0
data.yaml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# moncake
|
| 2 |
+
train: /home/lab/LJ/wampee/WampeeDataSets/train # train images (relative to 'path') 128 images
|
| 3 |
+
val: /home/lab/LJ/wampee/WampeeDataSets/valid # val images (relative to 'path') 128 images
|
| 4 |
+
test: /home/lab/LJ/wampee/WampeeDataSets/test # test images (optional)
|
| 5 |
+
|
| 6 |
+
# Classes
|
| 7 |
+
names:
|
| 8 |
+
0: wampee
|
faster-rcnn-pytorch-master/.gitignore
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ignore map, miou, datasets
|
| 2 |
+
map_out/
|
| 3 |
+
miou_out/
|
| 4 |
+
VOCdevkit/
|
| 5 |
+
datasets/
|
| 6 |
+
Medical_Datasets/
|
| 7 |
+
lfw/
|
| 8 |
+
logs/
|
| 9 |
+
model_data/
|
| 10 |
+
.temp_map_out/
|
| 11 |
+
|
| 12 |
+
# Byte-compiled / optimized / DLL files
|
| 13 |
+
__pycache__/
|
| 14 |
+
*.py[cod]
|
| 15 |
+
*$py.class
|
| 16 |
+
|
| 17 |
+
# C extensions
|
| 18 |
+
*.so
|
| 19 |
+
|
| 20 |
+
# Distribution / packaging
|
| 21 |
+
.Python
|
| 22 |
+
build/
|
| 23 |
+
develop-eggs/
|
| 24 |
+
dist/
|
| 25 |
+
downloads/
|
| 26 |
+
eggs/
|
| 27 |
+
.eggs/
|
| 28 |
+
lib/
|
| 29 |
+
lib64/
|
| 30 |
+
parts/
|
| 31 |
+
sdist/
|
| 32 |
+
var/
|
| 33 |
+
wheels/
|
| 34 |
+
pip-wheel-metadata/
|
| 35 |
+
share/python-wheels/
|
| 36 |
+
*.egg-info/
|
| 37 |
+
.installed.cfg
|
| 38 |
+
*.egg
|
| 39 |
+
MANIFEST
|
| 40 |
+
|
| 41 |
+
# PyInstaller
|
| 42 |
+
# Usually these files are written by a python script from a template
|
| 43 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 44 |
+
*.manifest
|
| 45 |
+
*.spec
|
| 46 |
+
|
| 47 |
+
# Installer logs
|
| 48 |
+
pip-log.txt
|
| 49 |
+
pip-delete-this-directory.txt
|
| 50 |
+
|
| 51 |
+
# Unit test / coverage reports
|
| 52 |
+
htmlcov/
|
| 53 |
+
.tox/
|
| 54 |
+
.nox/
|
| 55 |
+
.coverage
|
| 56 |
+
.coverage.*
|
| 57 |
+
.cache
|
| 58 |
+
nosetests.xml
|
| 59 |
+
coverage.xml
|
| 60 |
+
*.cover
|
| 61 |
+
*.py,cover
|
| 62 |
+
.hypothesis/
|
| 63 |
+
.pytest_cache/
|
| 64 |
+
|
| 65 |
+
# Translations
|
| 66 |
+
*.mo
|
| 67 |
+
*.pot
|
| 68 |
+
|
| 69 |
+
# Django stuff:
|
| 70 |
+
*.log
|
| 71 |
+
local_settings.py
|
| 72 |
+
db.sqlite3
|
| 73 |
+
db.sqlite3-journal
|
| 74 |
+
|
| 75 |
+
# Flask stuff:
|
| 76 |
+
instance/
|
| 77 |
+
.webassets-cache
|
| 78 |
+
|
| 79 |
+
# Scrapy stuff:
|
| 80 |
+
.scrapy
|
| 81 |
+
|
| 82 |
+
# Sphinx documentation
|
| 83 |
+
docs/_build/
|
| 84 |
+
|
| 85 |
+
# PyBuilder
|
| 86 |
+
target/
|
| 87 |
+
|
| 88 |
+
# Jupyter Notebook
|
| 89 |
+
.ipynb_checkpoints
|
| 90 |
+
|
| 91 |
+
# IPython
|
| 92 |
+
profile_default/
|
| 93 |
+
ipython_config.py
|
| 94 |
+
|
| 95 |
+
# pyenv
|
| 96 |
+
.python-version
|
| 97 |
+
|
| 98 |
+
# pipenv
|
| 99 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 100 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 101 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 102 |
+
# install all needed dependencies.
|
| 103 |
+
#Pipfile.lock
|
| 104 |
+
|
| 105 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
| 106 |
+
__pypackages__/
|
| 107 |
+
|
| 108 |
+
# Celery stuff
|
| 109 |
+
celerybeat-schedule
|
| 110 |
+
celerybeat.pid
|
| 111 |
+
|
| 112 |
+
# SageMath parsed files
|
| 113 |
+
*.sage.py
|
| 114 |
+
|
| 115 |
+
# Environments
|
| 116 |
+
.env
|
| 117 |
+
.venv
|
| 118 |
+
env/
|
| 119 |
+
venv/
|
| 120 |
+
ENV/
|
| 121 |
+
env.bak/
|
| 122 |
+
venv.bak/
|
| 123 |
+
|
| 124 |
+
# Spyder project settings
|
| 125 |
+
.spyderproject
|
| 126 |
+
.spyproject
|
| 127 |
+
|
| 128 |
+
# Rope project settings
|
| 129 |
+
.ropeproject
|
| 130 |
+
|
| 131 |
+
# mkdocs documentation
|
| 132 |
+
/site
|
| 133 |
+
|
| 134 |
+
# mypy
|
| 135 |
+
.mypy_cache/
|
| 136 |
+
.dmypy.json
|
| 137 |
+
dmypy.json
|
| 138 |
+
|
| 139 |
+
# Pyre type checker
|
| 140 |
+
.pyre/
|
faster-rcnn-pytorch-master/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2020 JiaQi Xu
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
faster-rcnn-pytorch-master/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Faster-Rcnn:Two-Stage目标检测模型在Pytorch当中的实现
|
| 2 |
+
---
|
| 3 |
+
|
| 4 |
+
## 目录
|
| 5 |
+
1. [仓库更新 Top News](#仓库更新)
|
| 6 |
+
2. [性能情况 Performance](#性能情况)
|
| 7 |
+
3. [所需环境 Environment](#所需环境)
|
| 8 |
+
4. [文件下载 Download](#文件下载)
|
| 9 |
+
5. [预测步骤 How2predict](#预测步骤)
|
| 10 |
+
6. [训练步骤 How2train](#训练步骤)
|
| 11 |
+
7. [评估步骤 How2eval](#评估步骤)
|
| 12 |
+
8. [参考资料 Reference](#Reference)
|
| 13 |
+
|
| 14 |
+
## Top News
|
| 15 |
+
**`2022-04`**:**进行了大幅度的更新,支持step、cos学习率下降法、支持adam、sgd优化器选择、支持学习率根据batch_size自适应调整、新增图片裁剪。**
|
| 16 |
+
BiliBili视频中的原仓库地址为:https://github.com/bubbliiiing/faster-rcnn-pytorch/tree/bilibili
|
| 17 |
+
|
| 18 |
+
**`2021-10`**:**进行了大幅度的更新,增加了大量注释、增加了大量可调整参数、对代码的组成模块进行修改、增加fps、视频预测、批量预测等功能。**
|
| 19 |
+
|
| 20 |
+
## 性能情况
|
| 21 |
+
| 训练数据集 | 权值文件名称 | 测试数据集 | 输入图片大小 | mAP 0.5:0.95 | mAP 0.5 |
|
| 22 |
+
| :-----: | :-----: | :------: | :------: | :------: | :-----: |
|
| 23 |
+
| VOC07+12 | [voc_weights_resnet.pth](https://github.com/bubbliiiing/faster-rcnn-pytorch/releases/download/v1.0/voc_weights_resnet.pth) | VOC-Test07 | - | - | 80.36
|
| 24 |
+
| VOC07+12 | [voc_weights_vgg.pth](https://github.com/bubbliiiing/faster-rcnn-pytorch/releases/download/v1.0/voc_weights_vgg.pth) | VOC-Test07 | - | - | 77.46
|
| 25 |
+
|
| 26 |
+
## 所需环境
|
| 27 |
+
torch == 1.2.0
|
| 28 |
+
|
| 29 |
+
## 文件下载
|
| 30 |
+
训练所需的voc_weights_resnet.pth或者voc_weights_vgg.pth以及主干的网络权重可以在百度云下载。
|
| 31 |
+
voc_weights_resnet.pth是resnet为主干特征提取网络用到的;
|
| 32 |
+
voc_weights_vgg.pth是vgg为主干特征提取网络用到的;
|
| 33 |
+
链接: https://pan.baidu.com/s/1S6wG8sEXBeoSec95NZxmlQ
|
| 34 |
+
提取码: 8mgp
|
| 35 |
+
|
| 36 |
+
VOC数据集下载地址如下,里面已经包括了训练集、测试集、验证集(与测试集一样),无需再次划分:
|
| 37 |
+
链接: https://pan.baidu.com/s/1-1Ej6dayrx3g0iAA88uY5A
|
| 38 |
+
提取码: ph32
|
| 39 |
+
|
| 40 |
+
## 训练步骤
|
| 41 |
+
### a、训练VOC07+12数据集
|
| 42 |
+
1. 数据集的准备
|
| 43 |
+
**本文使用VOC格式进行训练,训练前需要下载好VOC07+12的数据集,解压后放在根目录**
|
| 44 |
+
|
| 45 |
+
2. 数据集的处理
|
| 46 |
+
修改voc_annotation.py里面的annotation_mode=2,运行voc_annotation.py生成根目录下的2007_train.txt和2007_val.txt。
|
| 47 |
+
|
| 48 |
+
3. 开始网络训练
|
| 49 |
+
train.py的默认参数用于训练VOC数据集,直接运行train.py即可开始训练。
|
| 50 |
+
|
| 51 |
+
4. 训练结果预测
|
| 52 |
+
训练结果预测需要用到两个文件,分别是frcnn.py和predict.py。我们首先需要去frcnn.py里面修改model_path以及classes_path,这两个参数必须要修改。
|
| 53 |
+
**model_path指向训练好的权值文件,在logs文件夹里。
|
| 54 |
+
classes_path指向检测类别所对应的txt。**
|
| 55 |
+
完成修改后就可以运行predict.py进行检测了。运行后输入图片路径即可检测。
|
| 56 |
+
|
| 57 |
+
### b、训练自己的数据集
|
| 58 |
+
1. 数据集的准备
|
| 59 |
+
**本文使用VOC格式进行训练,训练前需要自己制作好数据集,**
|
| 60 |
+
训练前将标签文件放在VOCdevkit文件夹下的VOC2007文件夹下的Annotation中。
|
| 61 |
+
训练前将图片文件放在VOCdevkit文件夹下的VOC2007文件夹下的JPEGImages中。
|
| 62 |
+
|
| 63 |
+
2. 数据集的处理
|
| 64 |
+
在完成数据集的摆放之后,我们需要利用voc_annotation.py获得训练用的2007_train.txt和2007_val.txt。
|
| 65 |
+
修改voc_annotation.py里面的参数。第一次训练可以仅修改classes_path,classes_path用于指向检测类别所对应的txt。
|
| 66 |
+
训练自己的数据集时,可以自己建立一个cls_classes.txt,里面写自己所需要区分的类别。
|
| 67 |
+
model_data/cls_classes.txt文件内容为:
|
| 68 |
+
```python
|
| 69 |
+
cat
|
| 70 |
+
dog
|
| 71 |
+
...
|
| 72 |
+
```
|
| 73 |
+
修改voc_annotation.py中的classes_path,使其对应cls_classes.txt,并运行voc_annotation.py。
|
| 74 |
+
|
| 75 |
+
3. 开始网络训练
|
| 76 |
+
**训练的参数较多,均在train.py中,大家可以在下载库后仔细看注释,其中最重要的部分依然是train.py里的classes_path。**
|
| 77 |
+
**classes_path用于指向检测类别所对应的txt,这个txt和voc_annotation.py里面的txt一样!训练自己的数据集必须要修改!**
|
| 78 |
+
修改完classes_path后就可以运行train.py开始训练了,在训练多个epoch后,权值会生成在logs文件夹中。
|
| 79 |
+
|
| 80 |
+
4. 训练结果预测
|
| 81 |
+
训练结果预测需要用到两个文件,分别是frcnn.py和predict.py。在frcnn.py里面修改model_path以及classes_path。
|
| 82 |
+
**model_path指向训练好的权值文件,在logs文件夹里。
|
| 83 |
+
classes_path指向检测类别所对应的txt。**
|
| 84 |
+
完成修改后就可以运行predict.py进行检测了。运行后输入图片路径即可检测。
|
| 85 |
+
|
| 86 |
+
## 预测步骤
|
| 87 |
+
### a、使用预训练权重
|
| 88 |
+
1. 下载完库后解压,在百度网盘下载frcnn_weights.pth,放入model_data,运行predict.py,输入
|
| 89 |
+
```python
|
| 90 |
+
img/street.jpg
|
| 91 |
+
```
|
| 92 |
+
2. 在predict.py里面进行设置可以进行fps测试和video���频检测。
|
| 93 |
+
### b、使用自己训练的权重
|
| 94 |
+
1. 按照训练步骤训练。
|
| 95 |
+
2. 在frcnn.py文件里面,在如下部分修改model_path和classes_path使其对应训练好的文件;**model_path对应logs文件夹下面的权值文件,classes_path是model_path对应分的类**。
|
| 96 |
+
```python
|
| 97 |
+
_defaults = {
|
| 98 |
+
#--------------------------------------------------------------------------#
|
| 99 |
+
# 使用自己训练好的模型进行预测一定要修改model_path和classes_path!
|
| 100 |
+
# model_path指向logs文件夹下的权值文件,classes_path指向model_data下的txt
|
| 101 |
+
# 如果出现shape不匹配,同时要注意训练时的model_path和classes_path参数的修改
|
| 102 |
+
#--------------------------------------------------------------------------#
|
| 103 |
+
"model_path" : 'model_data/voc_weights_resnet.pth',
|
| 104 |
+
"classes_path" : 'model_data/voc_classes.txt',
|
| 105 |
+
#---------------------------------------------------------------------#
|
| 106 |
+
# 网络的主干特征提取网络,resnet50或者vgg
|
| 107 |
+
#---------------------------------------------------------------------#
|
| 108 |
+
"backbone" : "resnet50",
|
| 109 |
+
#---------------------------------------------------------------------#
|
| 110 |
+
# 只有得分大于置信度的预测框会被保留下来
|
| 111 |
+
#---------------------------------------------------------------------#
|
| 112 |
+
"confidence" : 0.5,
|
| 113 |
+
#---------------------------------------------------------------------#
|
| 114 |
+
# 非极大抑制所用到的nms_iou大小
|
| 115 |
+
#---------------------------------------------------------------------#
|
| 116 |
+
"nms_iou" : 0.3,
|
| 117 |
+
#---------------------------------------------------------------------#
|
| 118 |
+
# 用于指定先验框的大小
|
| 119 |
+
#---------------------------------------------------------------------#
|
| 120 |
+
'anchors_size' : [8, 16, 32],
|
| 121 |
+
#-------------------------------#
|
| 122 |
+
# 是否使用Cuda
|
| 123 |
+
# 没有GPU可以设置成False
|
| 124 |
+
#-------------------------------#
|
| 125 |
+
"cuda" : True,
|
| 126 |
+
}
|
| 127 |
+
```
|
| 128 |
+
3. 运行predict.py,输入
|
| 129 |
+
```python
|
| 130 |
+
img/street.jpg
|
| 131 |
+
```
|
| 132 |
+
4. 在predict.py里面进行设置可以进行fps测试和video视频检测。
|
| 133 |
+
|
| 134 |
+
## 评估步骤
|
| 135 |
+
### a、评估VOC07+12的测试集
|
| 136 |
+
1. 本文使用VOC格式进行评估。VOC07+12已经划分好了测试集,无需利用voc_annotation.py生成ImageSets文件夹下的txt。
|
| 137 |
+
2. 在frcnn.py里面修改model_path以及classes_path。**model_path指向训练好的权值文件,在logs文件夹里。classes_path指向检测类别所对应的txt。**
|
| 138 |
+
3. 运行get_map.py即可获得评估结果,评估结果会保存在map_out文件夹中。
|
| 139 |
+
|
| 140 |
+
### b、评估自己的数据集
|
| 141 |
+
1. 本文使用VOC格式进行评估。
|
| 142 |
+
2. 如果在训练前已经运行过voc_annotation.py文件,代码会自动将数据集划分成训练集、验证集和测试集。如果想要修改测试集的比例,可以修改voc_annotation.py文件下的trainval_percent。trainval_percent用于指定(训练集+验证集)与测试集的比例,默认情况下 (训练集+验证集):测试集 = 9:1。train_percent用于指定(训练集+验证集)中训练集与验证集的比例,默认情况下 训练集:验证集 = 9:1。
|
| 143 |
+
3. 利用voc_annotation.py划分测试集后,前往get_map.py文件修改classes_path,classes_path用于指向检测类别所对应的txt,这个txt和训练时的txt一样。评估自己的数据集必须要修改。
|
| 144 |
+
4. 在frcnn.py里面修改model_path以及classes_path。**model_path指向训练好的权值文件,在logs文件夹里。classes_path指向检测类别所对应的txt。**
|
| 145 |
+
5. 运行get_map.py即可获得评估结果,评估结果会保存在map_out文件夹中。
|
| 146 |
+
|
| 147 |
+
## Reference
|
| 148 |
+
https://github.com/chenyuntc/simple-faster-rcnn-pytorch
|
| 149 |
+
https://github.com/eriklindernoren/PyTorch-YOLOv3
|
| 150 |
+
https://github.com/BobLiu20/YOLOv3_PyTorch
|
faster-rcnn-pytorch-master/frcnn.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import colorsys
|
| 2 |
+
import os
|
| 3 |
+
import time
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 9 |
+
|
| 10 |
+
from nets.frcnn import FasterRCNN
|
| 11 |
+
from utils.utils import (cvtColor, get_classes, get_new_img_size, resize_image,
|
| 12 |
+
preprocess_input, show_config)
|
| 13 |
+
from utils.utils_bbox import DecodeBox
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
#--------------------------------------------#
|
| 17 |
+
# 使用自己训练好的模型预测需要修改2个参数
|
| 18 |
+
# model_path和classes_path都需要修改!
|
| 19 |
+
# 如果出现shape不匹配
|
| 20 |
+
# 一定要注意训练时的NUM_CLASSES、
|
| 21 |
+
# model_path和classes_path参数的修改
|
| 22 |
+
#--------------------------------------------#
|
| 23 |
+
class FRCNN(object):
|
| 24 |
+
_defaults = {
|
| 25 |
+
#--------------------------------------------------------------------------#
|
| 26 |
+
# 使用自己训练好的模型进行预测一定要修改model_path和classes_path!
|
| 27 |
+
# model_path指向logs文件夹下的权值文件,classes_path指向model_data下的txt
|
| 28 |
+
#
|
| 29 |
+
# 训练好后logs文件夹下存在多个权值文件,选择验证集损失较低的即可。
|
| 30 |
+
# 验证集损失较低不代表mAP较高,仅代表该权值在验证集上泛化性能较好。
|
| 31 |
+
# 如果出现shape不匹配,同时要注意训练时的model_path和classes_path参数的修改
|
| 32 |
+
#--------------------------------------------------------------------------#
|
| 33 |
+
"model_path" : 'model_data/voc_weights_resnet.pth',
|
| 34 |
+
"classes_path": '/home/lab/FH_Banana/faster-rcnn-pytorch-master/model_data/class.txt',
|
| 35 |
+
#---------------------------------------------------------------------#
|
| 36 |
+
# 网络的主干特征提取网络,resnet50或者vgg
|
| 37 |
+
#---------------------------------------------------------------------#
|
| 38 |
+
"backbone" : "resnet50",
|
| 39 |
+
#---------------------------------------------------------------------#
|
| 40 |
+
# 只有得分大于置信度的预测框会被保留下来
|
| 41 |
+
#---------------------------------------------------------------------#
|
| 42 |
+
"confidence" : 0.5,
|
| 43 |
+
#---------------------------------------------------------------------#
|
| 44 |
+
# 非极大抑制所用到的nms_iou大小
|
| 45 |
+
#---------------------------------------------------------------------#
|
| 46 |
+
"nms_iou" : 0.3,
|
| 47 |
+
#---------------------------------------------------------------------#
|
| 48 |
+
# 用于指定先验框的大小
|
| 49 |
+
#---------------------------------------------------------------------#
|
| 50 |
+
'anchors_size' : [8, 16, 32],
|
| 51 |
+
#-------------------------------#
|
| 52 |
+
# 是否使用Cuda
|
| 53 |
+
# 没有GPU可以设置成False
|
| 54 |
+
#-------------------------------#
|
| 55 |
+
"cuda" : True,
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
@classmethod
|
| 59 |
+
def get_defaults(cls, n):
|
| 60 |
+
if n in cls._defaults:
|
| 61 |
+
return cls._defaults[n]
|
| 62 |
+
else:
|
| 63 |
+
return "Unrecognized attribute name '" + n + "'"
|
| 64 |
+
|
| 65 |
+
#---------------------------------------------------#
|
| 66 |
+
# 初始化faster RCNN
|
| 67 |
+
#---------------------------------------------------#
|
| 68 |
+
def __init__(self, **kwargs):
|
| 69 |
+
self.__dict__.update(self._defaults)
|
| 70 |
+
for name, value in kwargs.items():
|
| 71 |
+
setattr(self, name, value)
|
| 72 |
+
self._defaults[name] = value
|
| 73 |
+
#---------------------------------------------------#
|
| 74 |
+
# 获得种类和先验框的数量
|
| 75 |
+
#---------------------------------------------------#
|
| 76 |
+
self.class_names, self.num_classes = get_classes(self.classes_path)
|
| 77 |
+
|
| 78 |
+
self.std = torch.Tensor([0.1, 0.1, 0.2, 0.2]).repeat(self.num_classes + 1)[None]
|
| 79 |
+
if self.cuda:
|
| 80 |
+
self.std = self.std.cuda()
|
| 81 |
+
self.bbox_util = DecodeBox(self.std, self.num_classes)
|
| 82 |
+
|
| 83 |
+
#---------------------------------------------------#
|
| 84 |
+
# 画框设置不同的颜色
|
| 85 |
+
#---------------------------------------------------#
|
| 86 |
+
hsv_tuples = [(x / self.num_classes, 1., 1.) for x in range(self.num_classes)]
|
| 87 |
+
self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
|
| 88 |
+
self.colors = list(map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), self.colors))
|
| 89 |
+
self.generate()
|
| 90 |
+
|
| 91 |
+
show_config(**self._defaults)
|
| 92 |
+
|
| 93 |
+
#---------------------------------------------------#
|
| 94 |
+
# 载入模型
|
| 95 |
+
#---------------------------------------------------#
|
| 96 |
+
def generate(self):
|
| 97 |
+
#-------------------------------#
|
| 98 |
+
# 载入模型与权值
|
| 99 |
+
#-------------------------------#
|
| 100 |
+
self.net = FasterRCNN(self.num_classes, "predict", anchor_scales = self.anchors_size, backbone = self.backbone)
|
| 101 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 102 |
+
self.net.load_state_dict(torch.load(self.model_path, map_location=device))
|
| 103 |
+
self.net = self.net.eval()
|
| 104 |
+
print('{} model, anchors, and classes loaded.'.format(self.model_path))
|
| 105 |
+
|
| 106 |
+
if self.cuda:
|
| 107 |
+
self.net = nn.DataParallel(self.net)
|
| 108 |
+
self.net = self.net.cuda()
|
| 109 |
+
|
| 110 |
+
#---------------------------------------------------#
|
| 111 |
+
# 检测图片
|
| 112 |
+
#---------------------------------------------------#
|
| 113 |
+
def detect_image(self, image, crop = False, count = False):
|
| 114 |
+
#---------------------------------------------------#
|
| 115 |
+
# 计算输入图片的高和宽
|
| 116 |
+
#---------------------------------------------------#
|
| 117 |
+
image_shape = np.array(np.shape(image)[0:2])
|
| 118 |
+
#---------------------------------------------------#
|
| 119 |
+
# 计算resize后的图片的大小,resize后的图片短边为600
|
| 120 |
+
#---------------------------------------------------#
|
| 121 |
+
input_shape = get_new_img_size(image_shape[0], image_shape[1])
|
| 122 |
+
#---------------------------------------------------------#
|
| 123 |
+
# 在这里将图像转换成RGB图像,防止灰度图在预测时报错。
|
| 124 |
+
# 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
|
| 125 |
+
#---------------------------------------------------------#
|
| 126 |
+
image = cvtColor(image)
|
| 127 |
+
#---------------------------------------------------------#
|
| 128 |
+
# 给原图像进行resize,resize到短边为600的大小上
|
| 129 |
+
#---------------------------------------------------------#
|
| 130 |
+
image_data = resize_image(image, [input_shape[1], input_shape[0]])
|
| 131 |
+
#---------------------------------------------------------#
|
| 132 |
+
# 添加上batch_size维度
|
| 133 |
+
#---------------------------------------------------------#
|
| 134 |
+
image_data = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)
|
| 135 |
+
|
| 136 |
+
with torch.no_grad():
|
| 137 |
+
images = torch.from_numpy(image_data)
|
| 138 |
+
if self.cuda:
|
| 139 |
+
images = images.cuda()
|
| 140 |
+
|
| 141 |
+
#-------------------------------------------------------------#
|
| 142 |
+
# roi_cls_locs 建议框的调整参数
|
| 143 |
+
# roi_scores 建议框的种类得分
|
| 144 |
+
# rois 建议框的坐标
|
| 145 |
+
#-------------------------------------------------------------#
|
| 146 |
+
roi_cls_locs, roi_scores, rois, _ = self.net(images)
|
| 147 |
+
#-------------------------------------------------------------#
|
| 148 |
+
# 利用classifier的预测结果对建议框进行解码,获得预测框
|
| 149 |
+
#-------------------------------------------------------------#
|
| 150 |
+
results = self.bbox_util.forward(roi_cls_locs, roi_scores, rois, image_shape, input_shape,
|
| 151 |
+
nms_iou = self.nms_iou, confidence = self.confidence)
|
| 152 |
+
#---------------------------------------------------------#
|
| 153 |
+
# 如果没有检测出物体,返回原图
|
| 154 |
+
#---------------------------------------------------------#
|
| 155 |
+
if len(results[0]) <= 0:
|
| 156 |
+
return image
|
| 157 |
+
|
| 158 |
+
top_label = np.array(results[0][:, 5], dtype = 'int32')
|
| 159 |
+
top_conf = results[0][:, 4]
|
| 160 |
+
top_boxes = results[0][:, :4]
|
| 161 |
+
|
| 162 |
+
#---------------------------------------------------------#
|
| 163 |
+
# 设置字体与边框厚度
|
| 164 |
+
#---------------------------------------------------------#
|
| 165 |
+
font = ImageFont.truetype(font='model_data/simhei.ttf', size=np.floor(3e-2 * image.size[1] + 0.5).astype('int32'))
|
| 166 |
+
thickness = int(max((image.size[0] + image.size[1]) // np.mean(input_shape), 1))
|
| 167 |
+
#---------------------------------------------------------#
|
| 168 |
+
# 计数
|
| 169 |
+
#---------------------------------------------------------#
|
| 170 |
+
if count:
|
| 171 |
+
print("top_label:", top_label)
|
| 172 |
+
classes_nums = np.zeros([self.num_classes])
|
| 173 |
+
for i in range(self.num_classes):
|
| 174 |
+
num = np.sum(top_label == i)
|
| 175 |
+
if num > 0:
|
| 176 |
+
print(self.class_names[i], " : ", num)
|
| 177 |
+
classes_nums[i] = num
|
| 178 |
+
print("classes_nums:", classes_nums)
|
| 179 |
+
#---------------------------------------------------------#
|
| 180 |
+
# 是否进行目标的裁剪
|
| 181 |
+
#---------------------------------------------------------#
|
| 182 |
+
if crop:
|
| 183 |
+
for i, c in list(enumerate(top_label)):
|
| 184 |
+
top, left, bottom, right = top_boxes[i]
|
| 185 |
+
top = max(0, np.floor(top).astype('int32'))
|
| 186 |
+
left = max(0, np.floor(left).astype('int32'))
|
| 187 |
+
bottom = min(image.size[1], np.floor(bottom).astype('int32'))
|
| 188 |
+
right = min(image.size[0], np.floor(right).astype('int32'))
|
| 189 |
+
|
| 190 |
+
dir_save_path = "img_crop"
|
| 191 |
+
if not os.path.exists(dir_save_path):
|
| 192 |
+
os.makedirs(dir_save_path)
|
| 193 |
+
crop_image = image.crop([left, top, right, bottom])
|
| 194 |
+
crop_image.save(os.path.join(dir_save_path, "crop_" + str(i) + ".png"), quality=95, subsampling=0)
|
| 195 |
+
print("save crop_" + str(i) + ".png to " + dir_save_path)
|
| 196 |
+
#---------------------------------------------------------#
|
| 197 |
+
# 图像绘制
|
| 198 |
+
#---------------------------------------------------------#
|
| 199 |
+
for i, c in list(enumerate(top_label)):
|
| 200 |
+
predicted_class = self.class_names[int(c)]
|
| 201 |
+
box = top_boxes[i]
|
| 202 |
+
score = top_conf[i]
|
| 203 |
+
|
| 204 |
+
top, left, bottom, right = box
|
| 205 |
+
|
| 206 |
+
top = max(0, np.floor(top).astype('int32'))
|
| 207 |
+
left = max(0, np.floor(left).astype('int32'))
|
| 208 |
+
bottom = min(image.size[1], np.floor(bottom).astype('int32'))
|
| 209 |
+
right = min(image.size[0], np.floor(right).astype('int32'))
|
| 210 |
+
|
| 211 |
+
label = '{} {:.2f}'.format(predicted_class, score)
|
| 212 |
+
draw = ImageDraw.Draw(image)
|
| 213 |
+
label_size = draw.textsize(label, font)
|
| 214 |
+
label = label.encode('utf-8')
|
| 215 |
+
# print(label, top, left, bottom, right)
|
| 216 |
+
|
| 217 |
+
if top - label_size[1] >= 0:
|
| 218 |
+
text_origin = np.array([left, top - label_size[1]])
|
| 219 |
+
else:
|
| 220 |
+
text_origin = np.array([left, top + 1])
|
| 221 |
+
|
| 222 |
+
for i in range(thickness):
|
| 223 |
+
draw.rectangle([left + i, top + i, right - i, bottom - i], outline=self.colors[c])
|
| 224 |
+
draw.rectangle([tuple(text_origin), tuple(text_origin + label_size)], fill=self.colors[c])
|
| 225 |
+
draw.text(text_origin, str(label,'UTF-8'), fill=(0, 0, 0), font=font)
|
| 226 |
+
del draw
|
| 227 |
+
|
| 228 |
+
return image
|
| 229 |
+
|
| 230 |
+
def get_FPS(self, image, test_interval):
|
| 231 |
+
#---------------------------------------------------#
|
| 232 |
+
# 计算输入图片的高和宽
|
| 233 |
+
#---------------------------------------------------#
|
| 234 |
+
image_shape = np.array(np.shape(image)[0:2])
|
| 235 |
+
input_shape = get_new_img_size(image_shape[0], image_shape[1])
|
| 236 |
+
#---------------------------------------------------------#
|
| 237 |
+
# 在这里将图像转换成RGB图像,防止灰度图在预测时报错。
|
| 238 |
+
# 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
|
| 239 |
+
#---------------------------------------------------------#
|
| 240 |
+
image = cvtColor(image)
|
| 241 |
+
|
| 242 |
+
#---------------------------------------------------------#
|
| 243 |
+
# 给原图像进行resize,resize到短边为600的大小上
|
| 244 |
+
#---------------------------------------------------------#
|
| 245 |
+
image_data = resize_image(image, [input_shape[1], input_shape[0]])
|
| 246 |
+
#---------------------------------------------------------#
|
| 247 |
+
# 添加上batch_size维度
|
| 248 |
+
#---------------------------------------------------------#
|
| 249 |
+
image_data = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)
|
| 250 |
+
|
| 251 |
+
with torch.no_grad():
|
| 252 |
+
images = torch.from_numpy(image_data)
|
| 253 |
+
if self.cuda:
|
| 254 |
+
images = images.cuda()
|
| 255 |
+
|
| 256 |
+
roi_cls_locs, roi_scores, rois, _ = self.net(images)
|
| 257 |
+
#-------------------------------------------------------------#
|
| 258 |
+
# 利用classifier的预测结果对建议框进行解码,获得预测框
|
| 259 |
+
#-------------------------------------------------------------#
|
| 260 |
+
results = self.bbox_util.forward(roi_cls_locs, roi_scores, rois, image_shape, input_shape,
|
| 261 |
+
nms_iou = self.nms_iou, confidence = self.confidence)
|
| 262 |
+
t1 = time.time()
|
| 263 |
+
for _ in range(test_interval):
|
| 264 |
+
with torch.no_grad():
|
| 265 |
+
roi_cls_locs, roi_scores, rois, _ = self.net(images)
|
| 266 |
+
#-------------------------------------------------------------#
|
| 267 |
+
# 利用classifier的预测结果对建议框进行解码,获得预测框
|
| 268 |
+
#-------------------------------------------------------------#
|
| 269 |
+
results = self.bbox_util.forward(roi_cls_locs, roi_scores, rois, image_shape, input_shape,
|
| 270 |
+
nms_iou = self.nms_iou, confidence = self.confidence)
|
| 271 |
+
|
| 272 |
+
t2 = time.time()
|
| 273 |
+
tact_time = (t2 - t1) / test_interval
|
| 274 |
+
return tact_time
|
| 275 |
+
|
| 276 |
+
#---------------------------------------------------#
|
| 277 |
+
# 检测图片
|
| 278 |
+
#---------------------------------------------------#
|
| 279 |
+
def get_map_txt(self, image_id, image, class_names, map_out_path):
|
| 280 |
+
f = open(os.path.join(map_out_path, "detection-results/"+image_id+".txt"),"w")
|
| 281 |
+
#---------------------------------------------------#
|
| 282 |
+
# 计算输入图片的高和宽
|
| 283 |
+
#---------------------------------------------------#
|
| 284 |
+
image_shape = np.array(np.shape(image)[0:2])
|
| 285 |
+
input_shape = get_new_img_size(image_shape[0], image_shape[1])
|
| 286 |
+
#---------------------------------------------------------#
|
| 287 |
+
# 在这里将图像转换成RGB图像,防止灰度图在预测时报错。
|
| 288 |
+
# 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
|
| 289 |
+
#---------------------------------------------------------#
|
| 290 |
+
image = cvtColor(image)
|
| 291 |
+
|
| 292 |
+
#---------------------------------------------------------#
|
| 293 |
+
# 给原图像进行resize,resize到短边为600的大小上
|
| 294 |
+
#---------------------------------------------------------#
|
| 295 |
+
image_data = resize_image(image, [input_shape[1], input_shape[0]])
|
| 296 |
+
#---------------------------------------------------------#
|
| 297 |
+
# 添加上batch_size维度
|
| 298 |
+
#---------------------------------------------------------#
|
| 299 |
+
image_data = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)
|
| 300 |
+
|
| 301 |
+
with torch.no_grad():
|
| 302 |
+
images = torch.from_numpy(image_data)
|
| 303 |
+
if self.cuda:
|
| 304 |
+
images = images.cuda()
|
| 305 |
+
|
| 306 |
+
roi_cls_locs, roi_scores, rois, _ = self.net(images)
|
| 307 |
+
#-------------------------------------------------------------#
|
| 308 |
+
# 利用classifier的预测结果对建议框进行解码,获得预测框
|
| 309 |
+
#-------------------------------------------------------------#
|
| 310 |
+
results = self.bbox_util.forward(roi_cls_locs, roi_scores, rois, image_shape, input_shape,
|
| 311 |
+
nms_iou = self.nms_iou, confidence = self.confidence)
|
| 312 |
+
#--------------------------------------#
|
| 313 |
+
# 如果没有检测到物体,则返回原图
|
| 314 |
+
#--------------------------------------#
|
| 315 |
+
if len(results[0]) <= 0:
|
| 316 |
+
return
|
| 317 |
+
|
| 318 |
+
top_label = np.array(results[0][:, 5], dtype = 'int32')
|
| 319 |
+
top_conf = results[0][:, 4]
|
| 320 |
+
top_boxes = results[0][:, :4]
|
| 321 |
+
|
| 322 |
+
for i, c in list(enumerate(top_label)):
|
| 323 |
+
predicted_class = self.class_names[int(c)]
|
| 324 |
+
box = top_boxes[i]
|
| 325 |
+
score = str(top_conf[i])
|
| 326 |
+
|
| 327 |
+
top, left, bottom, right = box
|
| 328 |
+
if predicted_class not in class_names:
|
| 329 |
+
continue
|
| 330 |
+
|
| 331 |
+
f.write("%s %s %s %s %s %s\n" % (predicted_class, score[:6], str(int(left)), str(int(top)), str(int(right)),str(int(bottom))))
|
| 332 |
+
|
| 333 |
+
f.close()
|
| 334 |
+
return
|
faster-rcnn-pytorch-master/get_map.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import xml.etree.ElementTree as ET
|
| 3 |
+
|
| 4 |
+
from PIL import Image
|
| 5 |
+
from tqdm import tqdm
|
| 6 |
+
|
| 7 |
+
from utils.utils import get_classes
|
| 8 |
+
from utils.utils_map import get_coco_map, get_map
|
| 9 |
+
from frcnn import FRCNN
|
| 10 |
+
|
| 11 |
+
if __name__ == "__main__":
|
| 12 |
+
'''
|
| 13 |
+
Recall和Precision不像AP是一个面积的概念,因此在门限值(Confidence)不同时,网络的Recall和Precision值是不同的。
|
| 14 |
+
默认情况下,本代码计算的Recall和Precision代表的是当门限值(Confidence)为0.5时,所对应的Recall和Precision值。
|
| 15 |
+
|
| 16 |
+
受到mAP计算原理的限制,网络在计算mAP时需要获得近乎所有的预测框,这样才可以计算不同门限条件下的Recall和Precision值
|
| 17 |
+
因此,本代码获得的map_out/detection-results/里面的txt的框的数量一般会比直接predict多一些,目的是列出所有可能的预测框,
|
| 18 |
+
'''
|
| 19 |
+
#------------------------------------------------------------------------------------------------------------------#
|
| 20 |
+
# map_mode用于指定该文件运行时计算的内容
|
| 21 |
+
# map_mode为0代表整个map计算流程,包括获得预测结果、获得真实框、计算VOC_map。
|
| 22 |
+
# map_mode为1代表仅仅获得预测结果。
|
| 23 |
+
# map_mode为2代表仅仅获得真实框。
|
| 24 |
+
# map_mode为3代表仅仅计算VOC_map。
|
| 25 |
+
# map_mode为4代表利用COCO工具箱计算当前数据集的0.50:0.95map。需要获得预测结果、获得真实框后并安装pycocotools才行
|
| 26 |
+
#-------------------------------------------------------------------------------------------------------------------#
|
| 27 |
+
map_mode = 0
|
| 28 |
+
#--------------------------------------------------------------------------------------#
|
| 29 |
+
# 此处的classes_path用于指定需要测量VOC_map的类别
|
| 30 |
+
# 一般情况下与训练和预测所用的classes_path一致即可
|
| 31 |
+
#--------------------------------------------------------------------------------------#
|
| 32 |
+
classes_path = 'model_data/voc_classes.txt'
|
| 33 |
+
#--------------------------------------------------------------------------------------#
|
| 34 |
+
# MINOVERLAP用于指定想要获得的mAP0.x,mAP0.x的意义是什么请同学们百度一下。
|
| 35 |
+
# 比如计算mAP0.75,可以设定MINOVERLAP = 0.75。
|
| 36 |
+
#
|
| 37 |
+
# 当某一预测框与真实框重合度大于MINOVERLAP时,该预测框被认为是正样本,否则为负样本。
|
| 38 |
+
# 因此MINOVERLAP的值越大,预测框要预测的越准确才能被认为是正样本,此时算出来的mAP值越低,
|
| 39 |
+
#--------------------------------------------------------------------------------------#
|
| 40 |
+
MINOVERLAP = 0.5
|
| 41 |
+
#--------------------------------------------------------------------------------------#
|
| 42 |
+
# 受到mAP计算原理的限制,网络在计算mAP时需要获得近乎所有的预测框,这样才可以计算mAP
|
| 43 |
+
# 因此,confidence的值应当设置的尽量小进而获得全部可能的预测框。
|
| 44 |
+
#
|
| 45 |
+
# 该值一般不调整。因为计算mAP需要获得近乎所有的预测框,此处的confidence不能随便更改。
|
| 46 |
+
# 想要获得不同门限值下的Recall和Precision值,请修改下方的score_threhold。
|
| 47 |
+
#--------------------------------------------------------------------------------------#
|
| 48 |
+
confidence = 0.02
|
| 49 |
+
#--------------------------------------------------------------------------------------#
|
| 50 |
+
# 预测时使用到的非极大抑制值的大小,越大表示非极大抑制越不严格。
|
| 51 |
+
#
|
| 52 |
+
# 该值一般不调整。
|
| 53 |
+
#--------------------------------------------------------------------------------------#
|
| 54 |
+
nms_iou = 0.5
|
| 55 |
+
#---------------------------------------------------------------------------------------------------------------#
|
| 56 |
+
# Recall和Precision不像AP是一个面积的概念,因此在门限值不同时,网络的Recall和Precision值是不同的。
|
| 57 |
+
#
|
| 58 |
+
# 默认情况下,本代码计算的Recall和Precision代表的是当门限值为0.5(此处定义为score_threhold)时所对应的Recall和Precision值。
|
| 59 |
+
# 因为计算mAP需要获得近乎所有的预测框,上面定义的confidence不能随便更改。
|
| 60 |
+
# 这里专门定义一个score_threhold用于代表门限值,进而在计算mAP时找到门限值对应的Recall和Precision值。
|
| 61 |
+
#---------------------------------------------------------------------------------------------------------------#
|
| 62 |
+
score_threhold = 0.5
|
| 63 |
+
#-------------------------------------------------------#
|
| 64 |
+
# map_vis用于指定是否开启VOC_map计算的可视化
|
| 65 |
+
#-------------------------------------------------------#
|
| 66 |
+
map_vis = False
|
| 67 |
+
#-------------------------------------------------------#
|
| 68 |
+
# 指向VOC数据集所在的文件夹
|
| 69 |
+
# 默认指向根目录下的VOC数据集
|
| 70 |
+
#-------------------------------------------------------#
|
| 71 |
+
VOCdevkit_path = 'VOCdevkit'
|
| 72 |
+
#-------------------------------------------------------#
|
| 73 |
+
# 结果输出的文件夹,默认为map_out
|
| 74 |
+
#-------------------------------------------------------#
|
| 75 |
+
map_out_path = 'map_out'
|
| 76 |
+
|
| 77 |
+
image_ids = open(os.path.join(VOCdevkit_path, "VOC2007/ImageSets/Main/test.txt")).read().strip().split()
|
| 78 |
+
|
| 79 |
+
if not os.path.exists(map_out_path):
|
| 80 |
+
os.makedirs(map_out_path)
|
| 81 |
+
if not os.path.exists(os.path.join(map_out_path, 'ground-truth')):
|
| 82 |
+
os.makedirs(os.path.join(map_out_path, 'ground-truth'))
|
| 83 |
+
if not os.path.exists(os.path.join(map_out_path, 'detection-results')):
|
| 84 |
+
os.makedirs(os.path.join(map_out_path, 'detection-results'))
|
| 85 |
+
if not os.path.exists(os.path.join(map_out_path, 'images-optional')):
|
| 86 |
+
os.makedirs(os.path.join(map_out_path, 'images-optional'))
|
| 87 |
+
|
| 88 |
+
class_names, _ = get_classes(classes_path)
|
| 89 |
+
|
| 90 |
+
if map_mode == 0 or map_mode == 1:
|
| 91 |
+
print("Load model.")
|
| 92 |
+
frcnn = FRCNN(confidence = confidence, nms_iou = nms_iou)
|
| 93 |
+
print("Load model done.")
|
| 94 |
+
|
| 95 |
+
print("Get predict result.")
|
| 96 |
+
for image_id in tqdm(image_ids):
|
| 97 |
+
image_path = os.path.join(VOCdevkit_path, "VOC2007/JPEGImages/"+image_id+".jpg")
|
| 98 |
+
image = Image.open(image_path)
|
| 99 |
+
if map_vis:
|
| 100 |
+
image.save(os.path.join(map_out_path, "images-optional/" + image_id + ".jpg"))
|
| 101 |
+
frcnn.get_map_txt(image_id, image, class_names, map_out_path)
|
| 102 |
+
print("Get predict result done.")
|
| 103 |
+
|
| 104 |
+
if map_mode == 0 or map_mode == 2:
|
| 105 |
+
print("Get ground truth result.")
|
| 106 |
+
for image_id in tqdm(image_ids):
|
| 107 |
+
with open(os.path.join(map_out_path, "ground-truth/"+image_id+".txt"), "w") as new_f:
|
| 108 |
+
root = ET.parse(os.path.join(VOCdevkit_path, "VOC2007/Annotations/"+image_id+".xml")).getroot()
|
| 109 |
+
for obj in root.findall('object'):
|
| 110 |
+
difficult_flag = False
|
| 111 |
+
if obj.find('difficult')!=None:
|
| 112 |
+
difficult = obj.find('difficult').text
|
| 113 |
+
if int(difficult)==1:
|
| 114 |
+
difficult_flag = True
|
| 115 |
+
obj_name = obj.find('name').text
|
| 116 |
+
if obj_name not in class_names:
|
| 117 |
+
continue
|
| 118 |
+
bndbox = obj.find('bndbox')
|
| 119 |
+
left = bndbox.find('xmin').text
|
| 120 |
+
top = bndbox.find('ymin').text
|
| 121 |
+
right = bndbox.find('xmax').text
|
| 122 |
+
bottom = bndbox.find('ymax').text
|
| 123 |
+
|
| 124 |
+
if difficult_flag:
|
| 125 |
+
new_f.write("%s %s %s %s %s difficult\n" % (obj_name, left, top, right, bottom))
|
| 126 |
+
else:
|
| 127 |
+
new_f.write("%s %s %s %s %s\n" % (obj_name, left, top, right, bottom))
|
| 128 |
+
print("Get ground truth result done.")
|
| 129 |
+
|
| 130 |
+
if map_mode == 0 or map_mode == 3:
|
| 131 |
+
print("Get map.")
|
| 132 |
+
get_map(MINOVERLAP, True, score_threhold = score_threhold, path = map_out_path)
|
| 133 |
+
print("Get map done.")
|
| 134 |
+
|
| 135 |
+
if map_mode == 4:
|
| 136 |
+
print("Get map.")
|
| 137 |
+
get_coco_map(class_names = class_names, path = map_out_path)
|
| 138 |
+
print("Get map done.")
|
faster-rcnn-pytorch-master/img/street.jpg
ADDED
|
Git LFS Details
|
faster-rcnn-pytorch-master/nets/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
#
|
faster-rcnn-pytorch-master/nets/classifier.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import warnings
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from torch import nn
|
| 5 |
+
from torchvision.ops import RoIPool
|
| 6 |
+
|
| 7 |
+
warnings.filterwarnings("ignore")
|
| 8 |
+
|
| 9 |
+
class VGG16RoIHead(nn.Module):
|
| 10 |
+
def __init__(self, n_class, roi_size, spatial_scale, classifier):
|
| 11 |
+
super(VGG16RoIHead, self).__init__()
|
| 12 |
+
self.classifier = classifier
|
| 13 |
+
#--------------------------------------#
|
| 14 |
+
# 对ROIPooling后的的结果进行回归预测
|
| 15 |
+
#--------------------------------------#
|
| 16 |
+
self.cls_loc = nn.Linear(4096, n_class * 4)
|
| 17 |
+
#-----------------------------------#
|
| 18 |
+
# 对ROIPooling后的的结果进行分类
|
| 19 |
+
#-----------------------------------#
|
| 20 |
+
self.score = nn.Linear(4096, n_class)
|
| 21 |
+
#-----------------------------------#
|
| 22 |
+
# 权值初始化
|
| 23 |
+
#-----------------------------------#
|
| 24 |
+
normal_init(self.cls_loc, 0, 0.001)
|
| 25 |
+
normal_init(self.score, 0, 0.01)
|
| 26 |
+
|
| 27 |
+
self.roi = RoIPool((roi_size, roi_size), spatial_scale)
|
| 28 |
+
|
| 29 |
+
def forward(self, x, rois, roi_indices, img_size):
|
| 30 |
+
n, _, _, _ = x.shape
|
| 31 |
+
if x.is_cuda:
|
| 32 |
+
roi_indices = roi_indices.cuda()
|
| 33 |
+
rois = rois.cuda()
|
| 34 |
+
rois = torch.flatten(rois, 0, 1)
|
| 35 |
+
roi_indices = torch.flatten(roi_indices, 0, 1)
|
| 36 |
+
|
| 37 |
+
rois_feature_map = torch.zeros_like(rois)
|
| 38 |
+
rois_feature_map[:, [0,2]] = rois[:, [0,2]] / img_size[1] * x.size()[3]
|
| 39 |
+
rois_feature_map[:, [1,3]] = rois[:, [1,3]] / img_size[0] * x.size()[2]
|
| 40 |
+
|
| 41 |
+
indices_and_rois = torch.cat([roi_indices[:, None], rois_feature_map], dim=1)
|
| 42 |
+
#-----------------------------------#
|
| 43 |
+
# 利用建议框对公用特征层进行截取
|
| 44 |
+
#-----------------------------------#
|
| 45 |
+
pool = self.roi(x, indices_and_rois)
|
| 46 |
+
#-----------------------------------#
|
| 47 |
+
# 利用classifier网络进行特征提取
|
| 48 |
+
#-----------------------------------#
|
| 49 |
+
pool = pool.view(pool.size(0), -1)
|
| 50 |
+
#--------------------------------------------------------------#
|
| 51 |
+
# 当输入为一张图片的时候,这里获得的f7的shape为[300, 4096]
|
| 52 |
+
#--------------------------------------------------------------#
|
| 53 |
+
fc7 = self.classifier(pool)
|
| 54 |
+
|
| 55 |
+
roi_cls_locs = self.cls_loc(fc7)
|
| 56 |
+
roi_scores = self.score(fc7)
|
| 57 |
+
|
| 58 |
+
roi_cls_locs = roi_cls_locs.view(n, -1, roi_cls_locs.size(1))
|
| 59 |
+
roi_scores = roi_scores.view(n, -1, roi_scores.size(1))
|
| 60 |
+
return roi_cls_locs, roi_scores
|
| 61 |
+
|
| 62 |
+
class Resnet50RoIHead(nn.Module):
|
| 63 |
+
def __init__(self, n_class, roi_size, spatial_scale, classifier):
|
| 64 |
+
super(Resnet50RoIHead, self).__init__()
|
| 65 |
+
self.classifier = classifier
|
| 66 |
+
#--------------------------------------#
|
| 67 |
+
# 对ROIPooling后的的结果进行回归预测
|
| 68 |
+
#--------------------------------------#
|
| 69 |
+
self.cls_loc = nn.Linear(2048, n_class * 4)
|
| 70 |
+
#-----------------------------------#
|
| 71 |
+
# 对ROIPooling后的的结果进行分类
|
| 72 |
+
#-----------------------------------#
|
| 73 |
+
self.score = nn.Linear(2048, n_class)
|
| 74 |
+
#-----------------------------------#
|
| 75 |
+
# 权值初始化
|
| 76 |
+
#-----------------------------------#
|
| 77 |
+
normal_init(self.cls_loc, 0, 0.001)
|
| 78 |
+
normal_init(self.score, 0, 0.01)
|
| 79 |
+
|
| 80 |
+
self.roi = RoIPool((roi_size, roi_size), spatial_scale)
|
| 81 |
+
|
| 82 |
+
def forward(self, x, rois, roi_indices, img_size):
|
| 83 |
+
n, _, _, _ = x.shape
|
| 84 |
+
if x.is_cuda:
|
| 85 |
+
roi_indices = roi_indices.cuda()
|
| 86 |
+
rois = rois.cuda()
|
| 87 |
+
rois = torch.flatten(rois, 0, 1)
|
| 88 |
+
roi_indices = torch.flatten(roi_indices, 0, 1)
|
| 89 |
+
|
| 90 |
+
rois_feature_map = torch.zeros_like(rois)
|
| 91 |
+
rois_feature_map[:, [0,2]] = rois[:, [0,2]] / img_size[1] * x.size()[3]
|
| 92 |
+
rois_feature_map[:, [1,3]] = rois[:, [1,3]] / img_size[0] * x.size()[2]
|
| 93 |
+
|
| 94 |
+
indices_and_rois = torch.cat([roi_indices[:, None], rois_feature_map], dim=1)
|
| 95 |
+
#-----------------------------------#
|
| 96 |
+
# 利用建议框对公用特征层进行截取
|
| 97 |
+
#-----------------------------------#
|
| 98 |
+
pool = self.roi(x, indices_and_rois)
|
| 99 |
+
#-----------------------------------#
|
| 100 |
+
# 利用classifier网络进行特征提取
|
| 101 |
+
#-----------------------------------#
|
| 102 |
+
fc7 = self.classifier(pool)
|
| 103 |
+
#--------------------------------------------------------------#
|
| 104 |
+
# 当输入为一张图片的时候,这里获得的f7的shape为[300, 2048]
|
| 105 |
+
#--------------------------------------------------------------#
|
| 106 |
+
fc7 = fc7.view(fc7.size(0), -1)
|
| 107 |
+
|
| 108 |
+
roi_cls_locs = self.cls_loc(fc7)
|
| 109 |
+
roi_scores = self.score(fc7)
|
| 110 |
+
roi_cls_locs = roi_cls_locs.view(n, -1, roi_cls_locs.size(1))
|
| 111 |
+
roi_scores = roi_scores.view(n, -1, roi_scores.size(1))
|
| 112 |
+
return roi_cls_locs, roi_scores
|
| 113 |
+
|
| 114 |
+
def normal_init(m, mean, stddev, truncated=False):
|
| 115 |
+
if truncated:
|
| 116 |
+
m.weight.data.normal_().fmod_(2).mul_(stddev).add_(mean) # not a perfect approximation
|
| 117 |
+
else:
|
| 118 |
+
m.weight.data.normal_(mean, stddev)
|
| 119 |
+
m.bias.data.zero_()
|
faster-rcnn-pytorch-master/nets/frcnn.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn as nn
|
| 2 |
+
|
| 3 |
+
from nets.classifier import Resnet50RoIHead, VGG16RoIHead
|
| 4 |
+
from nets.resnet50 import resnet50
|
| 5 |
+
from nets.rpn import RegionProposalNetwork
|
| 6 |
+
from nets.vgg16 import decom_vgg16
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class FasterRCNN(nn.Module):
|
| 10 |
+
def __init__(self, num_classes,
|
| 11 |
+
mode = "training",
|
| 12 |
+
feat_stride = 16,
|
| 13 |
+
anchor_scales = [8, 16, 32],
|
| 14 |
+
ratios = [0.5, 1, 2],
|
| 15 |
+
backbone = 'vgg',
|
| 16 |
+
pretrained = False):
|
| 17 |
+
super(FasterRCNN, self).__init__()
|
| 18 |
+
self.feat_stride = feat_stride
|
| 19 |
+
#---------------------------------#
|
| 20 |
+
# 一共存在两个主干
|
| 21 |
+
# vgg和resnet50
|
| 22 |
+
#---------------------------------#
|
| 23 |
+
if backbone == 'vgg':
|
| 24 |
+
self.extractor, classifier = decom_vgg16(pretrained)
|
| 25 |
+
#---------------------------------#
|
| 26 |
+
# 构建建议框网络
|
| 27 |
+
#---------------------------------#
|
| 28 |
+
self.rpn = RegionProposalNetwork(
|
| 29 |
+
512, 512,
|
| 30 |
+
ratios = ratios,
|
| 31 |
+
anchor_scales = anchor_scales,
|
| 32 |
+
feat_stride = self.feat_stride,
|
| 33 |
+
mode = mode
|
| 34 |
+
)
|
| 35 |
+
#---------------------------------#
|
| 36 |
+
# 构建分类器网络
|
| 37 |
+
#---------------------------------#
|
| 38 |
+
self.head = VGG16RoIHead(
|
| 39 |
+
n_class = num_classes + 1,
|
| 40 |
+
roi_size = 7,
|
| 41 |
+
spatial_scale = 1,
|
| 42 |
+
classifier = classifier
|
| 43 |
+
)
|
| 44 |
+
elif backbone == 'resnet50':
|
| 45 |
+
self.extractor, classifier = resnet50(pretrained)
|
| 46 |
+
#---------------------------------#
|
| 47 |
+
# 构建classifier网络
|
| 48 |
+
#---------------------------------#
|
| 49 |
+
self.rpn = RegionProposalNetwork(
|
| 50 |
+
1024, 512,
|
| 51 |
+
ratios = ratios,
|
| 52 |
+
anchor_scales = anchor_scales,
|
| 53 |
+
feat_stride = self.feat_stride,
|
| 54 |
+
mode = mode
|
| 55 |
+
)
|
| 56 |
+
#---------------------------------#
|
| 57 |
+
# 构建classifier网络
|
| 58 |
+
#---------------------------------#
|
| 59 |
+
self.head = Resnet50RoIHead(
|
| 60 |
+
n_class = num_classes + 1,
|
| 61 |
+
roi_size = 14,
|
| 62 |
+
spatial_scale = 1,
|
| 63 |
+
classifier = classifier
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
def forward(self, x, scale=1., mode="forward"):
|
| 67 |
+
if mode == "forward":
|
| 68 |
+
#---------------------------------#
|
| 69 |
+
# 计算输入图片的大小
|
| 70 |
+
#---------------------------------#
|
| 71 |
+
img_size = x.shape[2:]
|
| 72 |
+
#---------------------------------#
|
| 73 |
+
# 利用主干网络提取特征
|
| 74 |
+
#---------------------------------#
|
| 75 |
+
base_feature = self.extractor.forward(x)
|
| 76 |
+
|
| 77 |
+
#---------------------------------#
|
| 78 |
+
# 获得建议框
|
| 79 |
+
#---------------------------------#
|
| 80 |
+
_, _, rois, roi_indices, _ = self.rpn.forward(base_feature, img_size, scale)
|
| 81 |
+
#---------------------------------------#
|
| 82 |
+
# 获得classifier的分类结果和回归结果
|
| 83 |
+
#---------------------------------------#
|
| 84 |
+
roi_cls_locs, roi_scores = self.head.forward(base_feature, rois, roi_indices, img_size)
|
| 85 |
+
return roi_cls_locs, roi_scores, rois, roi_indices
|
| 86 |
+
elif mode == "extractor":
|
| 87 |
+
#---------------------------------#
|
| 88 |
+
# 利用主干网络提取特征
|
| 89 |
+
#---------------------------------#
|
| 90 |
+
base_feature = self.extractor.forward(x)
|
| 91 |
+
return base_feature
|
| 92 |
+
elif mode == "rpn":
|
| 93 |
+
base_feature, img_size = x
|
| 94 |
+
#---------------------------------#
|
| 95 |
+
# 获得建议框
|
| 96 |
+
#---------------------------------#
|
| 97 |
+
rpn_locs, rpn_scores, rois, roi_indices, anchor = self.rpn.forward(base_feature, img_size, scale)
|
| 98 |
+
return rpn_locs, rpn_scores, rois, roi_indices, anchor
|
| 99 |
+
elif mode == "head":
|
| 100 |
+
base_feature, rois, roi_indices, img_size = x
|
| 101 |
+
#---------------------------------------#
|
| 102 |
+
# 获得classifier的分类结果和回归结果
|
| 103 |
+
#---------------------------------------#
|
| 104 |
+
roi_cls_locs, roi_scores = self.head.forward(base_feature, rois, roi_indices, img_size)
|
| 105 |
+
return roi_cls_locs, roi_scores
|
| 106 |
+
|
| 107 |
+
def freeze_bn(self):
|
| 108 |
+
for m in self.modules():
|
| 109 |
+
if isinstance(m, nn.BatchNorm2d):
|
| 110 |
+
m.eval()
|
faster-rcnn-pytorch-master/nets/frcnn_training.py
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from functools import partial
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
from torch.nn import functional as F
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def bbox_iou(bbox_a, bbox_b):
|
| 11 |
+
if bbox_a.shape[1] != 4 or bbox_b.shape[1] != 4:
|
| 12 |
+
print(bbox_a, bbox_b)
|
| 13 |
+
raise IndexError
|
| 14 |
+
tl = np.maximum(bbox_a[:, None, :2], bbox_b[:, :2])
|
| 15 |
+
br = np.minimum(bbox_a[:, None, 2:], bbox_b[:, 2:])
|
| 16 |
+
area_i = np.prod(br - tl, axis=2) * (tl < br).all(axis=2)
|
| 17 |
+
area_a = np.prod(bbox_a[:, 2:] - bbox_a[:, :2], axis=1)
|
| 18 |
+
area_b = np.prod(bbox_b[:, 2:] - bbox_b[:, :2], axis=1)
|
| 19 |
+
return area_i / (area_a[:, None] + area_b - area_i)
|
| 20 |
+
|
| 21 |
+
def bbox2loc(src_bbox, dst_bbox):
|
| 22 |
+
width = src_bbox[:, 2] - src_bbox[:, 0]
|
| 23 |
+
height = src_bbox[:, 3] - src_bbox[:, 1]
|
| 24 |
+
ctr_x = src_bbox[:, 0] + 0.5 * width
|
| 25 |
+
ctr_y = src_bbox[:, 1] + 0.5 * height
|
| 26 |
+
|
| 27 |
+
base_width = dst_bbox[:, 2] - dst_bbox[:, 0]
|
| 28 |
+
base_height = dst_bbox[:, 3] - dst_bbox[:, 1]
|
| 29 |
+
base_ctr_x = dst_bbox[:, 0] + 0.5 * base_width
|
| 30 |
+
base_ctr_y = dst_bbox[:, 1] + 0.5 * base_height
|
| 31 |
+
|
| 32 |
+
eps = np.finfo(height.dtype).eps
|
| 33 |
+
width = np.maximum(width, eps)
|
| 34 |
+
height = np.maximum(height, eps)
|
| 35 |
+
|
| 36 |
+
dx = (base_ctr_x - ctr_x) / width
|
| 37 |
+
dy = (base_ctr_y - ctr_y) / height
|
| 38 |
+
dw = np.log(base_width / width)
|
| 39 |
+
dh = np.log(base_height / height)
|
| 40 |
+
|
| 41 |
+
loc = np.vstack((dx, dy, dw, dh)).transpose()
|
| 42 |
+
return loc
|
| 43 |
+
|
| 44 |
+
class AnchorTargetCreator(object):
|
| 45 |
+
def __init__(self, n_sample=256, pos_iou_thresh=0.7, neg_iou_thresh=0.3, pos_ratio=0.5):
|
| 46 |
+
self.n_sample = n_sample
|
| 47 |
+
self.pos_iou_thresh = pos_iou_thresh
|
| 48 |
+
self.neg_iou_thresh = neg_iou_thresh
|
| 49 |
+
self.pos_ratio = pos_ratio
|
| 50 |
+
|
| 51 |
+
def __call__(self, bbox, anchor):
|
| 52 |
+
argmax_ious, label = self._create_label(anchor, bbox)
|
| 53 |
+
if (label > 0).any():
|
| 54 |
+
loc = bbox2loc(anchor, bbox[argmax_ious])
|
| 55 |
+
return loc, label
|
| 56 |
+
else:
|
| 57 |
+
return np.zeros_like(anchor), label
|
| 58 |
+
|
| 59 |
+
def _calc_ious(self, anchor, bbox):
|
| 60 |
+
#----------------------------------------------#
|
| 61 |
+
# anchor和bbox的iou
|
| 62 |
+
# 获得的ious的shape为[num_anchors, num_gt]
|
| 63 |
+
#----------------------------------------------#
|
| 64 |
+
ious = bbox_iou(anchor, bbox)
|
| 65 |
+
|
| 66 |
+
if len(bbox)==0:
|
| 67 |
+
return np.zeros(len(anchor), np.int32), np.zeros(len(anchor)), np.zeros(len(bbox))
|
| 68 |
+
#---------------------------------------------------------#
|
| 69 |
+
# 获得每一个先验框最对应的真实框 [num_anchors, ]
|
| 70 |
+
#---------------------------------------------------------#
|
| 71 |
+
argmax_ious = ious.argmax(axis=1)
|
| 72 |
+
#---------------------------------------------------------#
|
| 73 |
+
# 找出每一个先验框最对应的真实框的iou [num_anchors, ]
|
| 74 |
+
#---------------------------------------------------------#
|
| 75 |
+
max_ious = np.max(ious, axis=1)
|
| 76 |
+
#---------------------------------------------------------#
|
| 77 |
+
# 获得每一个真实框最对应的先验框 [num_gt, ]
|
| 78 |
+
#---------------------------------------------------------#
|
| 79 |
+
gt_argmax_ious = ious.argmax(axis=0)
|
| 80 |
+
#---------------------------------------------------------#
|
| 81 |
+
# 保证每一个真实框都存在对应的先验框
|
| 82 |
+
#---------------------------------------------------------#
|
| 83 |
+
for i in range(len(gt_argmax_ious)):
|
| 84 |
+
argmax_ious[gt_argmax_ious[i]] = i
|
| 85 |
+
|
| 86 |
+
return argmax_ious, max_ious, gt_argmax_ious
|
| 87 |
+
|
| 88 |
+
def _create_label(self, anchor, bbox):
|
| 89 |
+
# ------------------------------------------ #
|
| 90 |
+
# 1是正样本,0是负样本,-1忽略
|
| 91 |
+
# 初始化的时候全部设置为-1
|
| 92 |
+
# ------------------------------------------ #
|
| 93 |
+
label = np.empty((len(anchor),), dtype=np.int32)
|
| 94 |
+
label.fill(-1)
|
| 95 |
+
|
| 96 |
+
# ------------------------------------------------------------------------ #
|
| 97 |
+
# argmax_ious为每个先验框对应的最大的真实框的序号 [num_anchors, ]
|
| 98 |
+
# max_ious为每个真实框对应的最大的真实框的iou [num_anchors, ]
|
| 99 |
+
# gt_argmax_ious为每一个真实框对应的最大的先验框的序号 [num_gt, ]
|
| 100 |
+
# ------------------------------------------------------------------------ #
|
| 101 |
+
argmax_ious, max_ious, gt_argmax_ious = self._calc_ious(anchor, bbox)
|
| 102 |
+
|
| 103 |
+
# ----------------------------------------------------- #
|
| 104 |
+
# 如果小于门限值则设置为负样本
|
| 105 |
+
# 如果大于门限值则设置为正样本
|
| 106 |
+
# 每个真实框至少对应一个先验框
|
| 107 |
+
# ----------------------------------------------------- #
|
| 108 |
+
label[max_ious < self.neg_iou_thresh] = 0
|
| 109 |
+
label[max_ious >= self.pos_iou_thresh] = 1
|
| 110 |
+
if len(gt_argmax_ious)>0:
|
| 111 |
+
label[gt_argmax_ious] = 1
|
| 112 |
+
|
| 113 |
+
# ----------------------------------------------------- #
|
| 114 |
+
# 判断正样本数量是否大于128,如果大于则限制在128
|
| 115 |
+
# ----------------------------------------------------- #
|
| 116 |
+
n_pos = int(self.pos_ratio * self.n_sample)
|
| 117 |
+
pos_index = np.where(label == 1)[0]
|
| 118 |
+
if len(pos_index) > n_pos:
|
| 119 |
+
disable_index = np.random.choice(pos_index, size=(len(pos_index) - n_pos), replace=False)
|
| 120 |
+
label[disable_index] = -1
|
| 121 |
+
|
| 122 |
+
# ----------------------------------------------------- #
|
| 123 |
+
# 平衡正负样本,保持总数量为256
|
| 124 |
+
# ----------------------------------------------------- #
|
| 125 |
+
n_neg = self.n_sample - np.sum(label == 1)
|
| 126 |
+
neg_index = np.where(label == 0)[0]
|
| 127 |
+
if len(neg_index) > n_neg:
|
| 128 |
+
disable_index = np.random.choice(neg_index, size=(len(neg_index) - n_neg), replace=False)
|
| 129 |
+
label[disable_index] = -1
|
| 130 |
+
|
| 131 |
+
return argmax_ious, label
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
class ProposalTargetCreator(object):
|
| 135 |
+
def __init__(self, n_sample=128, pos_ratio=0.5, pos_iou_thresh=0.5, neg_iou_thresh_high=0.5, neg_iou_thresh_low=0):
|
| 136 |
+
self.n_sample = n_sample
|
| 137 |
+
self.pos_ratio = pos_ratio
|
| 138 |
+
self.pos_roi_per_image = np.round(self.n_sample * self.pos_ratio)
|
| 139 |
+
self.pos_iou_thresh = pos_iou_thresh
|
| 140 |
+
self.neg_iou_thresh_high = neg_iou_thresh_high
|
| 141 |
+
self.neg_iou_thresh_low = neg_iou_thresh_low
|
| 142 |
+
|
| 143 |
+
def __call__(self, roi, bbox, label, loc_normalize_std=(0.1, 0.1, 0.2, 0.2)):
|
| 144 |
+
roi = np.concatenate((roi.detach().cpu().numpy(), bbox), axis=0)
|
| 145 |
+
# ----------------------------------------------------- #
|
| 146 |
+
# 计算建议框和真实框的重合程度
|
| 147 |
+
# ----------------------------------------------------- #
|
| 148 |
+
iou = bbox_iou(roi, bbox)
|
| 149 |
+
|
| 150 |
+
if len(bbox)==0:
|
| 151 |
+
gt_assignment = np.zeros(len(roi), np.int32)
|
| 152 |
+
max_iou = np.zeros(len(roi))
|
| 153 |
+
gt_roi_label = np.zeros(len(roi))
|
| 154 |
+
else:
|
| 155 |
+
#---------------------------------------------------------#
|
| 156 |
+
# 获得每一个建议框最对应的真实框 [num_roi, ]
|
| 157 |
+
#---------------------------------------------------------#
|
| 158 |
+
gt_assignment = iou.argmax(axis=1)
|
| 159 |
+
#---------------------------------------------------------#
|
| 160 |
+
# 获得每一个建议框最对应的真实框的iou [num_roi, ]
|
| 161 |
+
#---------------------------------------------------------#
|
| 162 |
+
max_iou = iou.max(axis=1)
|
| 163 |
+
#---------------------------------------------------------#
|
| 164 |
+
# 真实框的标签要+1因为有背景的存在
|
| 165 |
+
#---------------------------------------------------------#
|
| 166 |
+
gt_roi_label = label[gt_assignment] + 1
|
| 167 |
+
|
| 168 |
+
#----------------------------------------------------------------#
|
| 169 |
+
# 满足建议框和真实框重合程度大于neg_iou_thresh_high的作为负样本
|
| 170 |
+
# 将正样本的数量限制在self.pos_roi_per_image以内
|
| 171 |
+
#----------------------------------------------------------------#
|
| 172 |
+
pos_index = np.where(max_iou >= self.pos_iou_thresh)[0]
|
| 173 |
+
pos_roi_per_this_image = int(min(self.pos_roi_per_image, pos_index.size))
|
| 174 |
+
if pos_index.size > 0:
|
| 175 |
+
pos_index = np.random.choice(pos_index, size=pos_roi_per_this_image, replace=False)
|
| 176 |
+
|
| 177 |
+
#-----------------------------------------------------------------------------------------------------#
|
| 178 |
+
# 满足建议框和真实框重合程度小于neg_iou_thresh_high大于neg_iou_thresh_low作为负样本
|
| 179 |
+
# 将正样本的数量和负样本的数量的总和固定成self.n_sample
|
| 180 |
+
#-----------------------------------------------------------------------------------------------------#
|
| 181 |
+
neg_index = np.where((max_iou < self.neg_iou_thresh_high) & (max_iou >= self.neg_iou_thresh_low))[0]
|
| 182 |
+
neg_roi_per_this_image = self.n_sample - pos_roi_per_this_image
|
| 183 |
+
neg_roi_per_this_image = int(min(neg_roi_per_this_image, neg_index.size))
|
| 184 |
+
if neg_index.size > 0:
|
| 185 |
+
neg_index = np.random.choice(neg_index, size=neg_roi_per_this_image, replace=False)
|
| 186 |
+
|
| 187 |
+
#---------------------------------------------------------#
|
| 188 |
+
# sample_roi [n_sample, ]
|
| 189 |
+
# gt_roi_loc [n_sample, 4]
|
| 190 |
+
# gt_roi_label [n_sample, ]
|
| 191 |
+
#---------------------------------------------------------#
|
| 192 |
+
keep_index = np.append(pos_index, neg_index)
|
| 193 |
+
|
| 194 |
+
sample_roi = roi[keep_index]
|
| 195 |
+
if len(bbox)==0:
|
| 196 |
+
return sample_roi, np.zeros_like(sample_roi), gt_roi_label[keep_index]
|
| 197 |
+
|
| 198 |
+
gt_roi_loc = bbox2loc(sample_roi, bbox[gt_assignment[keep_index]])
|
| 199 |
+
gt_roi_loc = (gt_roi_loc / np.array(loc_normalize_std, np.float32))
|
| 200 |
+
|
| 201 |
+
gt_roi_label = gt_roi_label[keep_index]
|
| 202 |
+
gt_roi_label[pos_roi_per_this_image:] = 0
|
| 203 |
+
return sample_roi, gt_roi_loc, gt_roi_label
|
| 204 |
+
|
| 205 |
+
class FasterRCNNTrainer(nn.Module):
|
| 206 |
+
def __init__(self, model_train, optimizer):
|
| 207 |
+
super(FasterRCNNTrainer, self).__init__()
|
| 208 |
+
self.model_train = model_train
|
| 209 |
+
self.optimizer = optimizer
|
| 210 |
+
|
| 211 |
+
self.rpn_sigma = 1
|
| 212 |
+
self.roi_sigma = 1
|
| 213 |
+
|
| 214 |
+
self.anchor_target_creator = AnchorTargetCreator()
|
| 215 |
+
self.proposal_target_creator = ProposalTargetCreator()
|
| 216 |
+
|
| 217 |
+
self.loc_normalize_std = [0.1, 0.1, 0.2, 0.2]
|
| 218 |
+
|
| 219 |
+
def _fast_rcnn_loc_loss(self, pred_loc, gt_loc, gt_label, sigma):
|
| 220 |
+
pred_loc = pred_loc[gt_label > 0]
|
| 221 |
+
gt_loc = gt_loc[gt_label > 0]
|
| 222 |
+
|
| 223 |
+
sigma_squared = sigma ** 2
|
| 224 |
+
regression_diff = (gt_loc - pred_loc)
|
| 225 |
+
regression_diff = regression_diff.abs().float()
|
| 226 |
+
regression_loss = torch.where(
|
| 227 |
+
regression_diff < (1. / sigma_squared),
|
| 228 |
+
0.5 * sigma_squared * regression_diff ** 2,
|
| 229 |
+
regression_diff - 0.5 / sigma_squared
|
| 230 |
+
)
|
| 231 |
+
regression_loss = regression_loss.sum()
|
| 232 |
+
num_pos = (gt_label > 0).sum().float()
|
| 233 |
+
|
| 234 |
+
regression_loss /= torch.max(num_pos, torch.ones_like(num_pos))
|
| 235 |
+
return regression_loss
|
| 236 |
+
|
| 237 |
+
def forward(self, imgs, bboxes, labels, scale):
|
| 238 |
+
n = imgs.shape[0]
|
| 239 |
+
img_size = imgs.shape[2:]
|
| 240 |
+
#-------------------------------#
|
| 241 |
+
# 获取公用特征层
|
| 242 |
+
#-------------------------------#
|
| 243 |
+
base_feature = self.model_train(imgs, mode = 'extractor')
|
| 244 |
+
|
| 245 |
+
# -------------------------------------------------- #
|
| 246 |
+
# 利用rpn网络获得调整参数、得分、建议框、先验框
|
| 247 |
+
# -------------------------------------------------- #
|
| 248 |
+
rpn_locs, rpn_scores, rois, roi_indices, anchor = self.model_train(x = [base_feature, img_size], scale = scale, mode = 'rpn')
|
| 249 |
+
|
| 250 |
+
rpn_loc_loss_all, rpn_cls_loss_all, roi_loc_loss_all, roi_cls_loss_all = 0, 0, 0, 0
|
| 251 |
+
sample_rois, sample_indexes, gt_roi_locs, gt_roi_labels = [], [], [], []
|
| 252 |
+
for i in range(n):
|
| 253 |
+
bbox = bboxes[i]
|
| 254 |
+
label = labels[i]
|
| 255 |
+
rpn_loc = rpn_locs[i]
|
| 256 |
+
rpn_score = rpn_scores[i]
|
| 257 |
+
roi = rois[i]
|
| 258 |
+
# -------------------------------------------------- #
|
| 259 |
+
# 利用真实框和先验框获得建议框网络应该有的预测结果
|
| 260 |
+
# 给每个先验框都打上标签
|
| 261 |
+
# gt_rpn_loc [num_anchors, 4]
|
| 262 |
+
# gt_rpn_label [num_anchors, ]
|
| 263 |
+
# -------------------------------------------------- #
|
| 264 |
+
gt_rpn_loc, gt_rpn_label = self.anchor_target_creator(bbox, anchor[0].cpu().numpy())
|
| 265 |
+
gt_rpn_loc = torch.Tensor(gt_rpn_loc).type_as(rpn_locs)
|
| 266 |
+
gt_rpn_label = torch.Tensor(gt_rpn_label).type_as(rpn_locs).long()
|
| 267 |
+
# -------------------------------------------------- #
|
| 268 |
+
# 分别计算建议框网络的回归损失和分类损失
|
| 269 |
+
# -------------------------------------------------- #
|
| 270 |
+
rpn_loc_loss = self._fast_rcnn_loc_loss(rpn_loc, gt_rpn_loc, gt_rpn_label, self.rpn_sigma)
|
| 271 |
+
rpn_cls_loss = F.cross_entropy(rpn_score, gt_rpn_label, ignore_index=-1)
|
| 272 |
+
|
| 273 |
+
rpn_loc_loss_all += rpn_loc_loss
|
| 274 |
+
rpn_cls_loss_all += rpn_cls_loss
|
| 275 |
+
# ------------------------------------------------------ #
|
| 276 |
+
# 利用真实框和建议框获得classifier网络应该有的预测结果
|
| 277 |
+
# 获得三个变量,分别是sample_roi, gt_roi_loc, gt_roi_label
|
| 278 |
+
# sample_roi [n_sample, ]
|
| 279 |
+
# gt_roi_loc [n_sample, 4]
|
| 280 |
+
# gt_roi_label [n_sample, ]
|
| 281 |
+
# ------------------------------------------------------ #
|
| 282 |
+
sample_roi, gt_roi_loc, gt_roi_label = self.proposal_target_creator(roi, bbox, label, self.loc_normalize_std)
|
| 283 |
+
sample_rois.append(torch.Tensor(sample_roi).type_as(rpn_locs))
|
| 284 |
+
sample_indexes.append(torch.ones(len(sample_roi)).type_as(rpn_locs) * roi_indices[i][0])
|
| 285 |
+
gt_roi_locs.append(torch.Tensor(gt_roi_loc).type_as(rpn_locs))
|
| 286 |
+
gt_roi_labels.append(torch.Tensor(gt_roi_label).type_as(rpn_locs).long())
|
| 287 |
+
|
| 288 |
+
sample_rois = torch.stack(sample_rois, dim=0)
|
| 289 |
+
sample_indexes = torch.stack(sample_indexes, dim=0)
|
| 290 |
+
roi_cls_locs, roi_scores = self.model_train([base_feature, sample_rois, sample_indexes, img_size], mode = 'head')
|
| 291 |
+
for i in range(n):
|
| 292 |
+
# ------------------------------------------------------ #
|
| 293 |
+
# 根据建议框的种类,取出对应的回归预测结果
|
| 294 |
+
# ------------------------------------------------------ #
|
| 295 |
+
n_sample = roi_cls_locs.size()[1]
|
| 296 |
+
|
| 297 |
+
roi_cls_loc = roi_cls_locs[i]
|
| 298 |
+
roi_score = roi_scores[i]
|
| 299 |
+
gt_roi_loc = gt_roi_locs[i]
|
| 300 |
+
gt_roi_label = gt_roi_labels[i]
|
| 301 |
+
|
| 302 |
+
roi_cls_loc = roi_cls_loc.view(n_sample, -1, 4)
|
| 303 |
+
roi_loc = roi_cls_loc[torch.arange(0, n_sample), gt_roi_label]
|
| 304 |
+
|
| 305 |
+
# -------------------------------------------------- #
|
| 306 |
+
# 分别计算Classifier网络的回归损失和分类损失
|
| 307 |
+
# -------------------------------------------------- #
|
| 308 |
+
roi_loc_loss = self._fast_rcnn_loc_loss(roi_loc, gt_roi_loc, gt_roi_label.data, self.roi_sigma)
|
| 309 |
+
roi_cls_loss = nn.CrossEntropyLoss()(roi_score, gt_roi_label)
|
| 310 |
+
|
| 311 |
+
roi_loc_loss_all += roi_loc_loss
|
| 312 |
+
roi_cls_loss_all += roi_cls_loss
|
| 313 |
+
|
| 314 |
+
losses = [rpn_loc_loss_all/n, rpn_cls_loss_all/n, roi_loc_loss_all/n, roi_cls_loss_all/n]
|
| 315 |
+
losses = losses + [sum(losses)]
|
| 316 |
+
return losses
|
| 317 |
+
|
| 318 |
+
def train_step(self, imgs, bboxes, labels, scale, fp16=False, scaler=None):
|
| 319 |
+
self.optimizer.zero_grad()
|
| 320 |
+
if not fp16:
|
| 321 |
+
losses = self.forward(imgs, bboxes, labels, scale)
|
| 322 |
+
losses[-1].backward()
|
| 323 |
+
self.optimizer.step()
|
| 324 |
+
else:
|
| 325 |
+
from torch.cuda.amp import autocast
|
| 326 |
+
with autocast():
|
| 327 |
+
losses = self.forward(imgs, bboxes, labels, scale)
|
| 328 |
+
|
| 329 |
+
#----------------------#
|
| 330 |
+
# 反向传播
|
| 331 |
+
#----------------------#
|
| 332 |
+
scaler.scale(losses[-1]).backward()
|
| 333 |
+
scaler.step(self.optimizer)
|
| 334 |
+
scaler.update()
|
| 335 |
+
|
| 336 |
+
return losses
|
| 337 |
+
|
| 338 |
+
def weights_init(net, init_type='normal', init_gain=0.02):
|
| 339 |
+
def init_func(m):
|
| 340 |
+
classname = m.__class__.__name__
|
| 341 |
+
if hasattr(m, 'weight') and classname.find('Conv') != -1:
|
| 342 |
+
if init_type == 'normal':
|
| 343 |
+
torch.nn.init.normal_(m.weight.data, 0.0, init_gain)
|
| 344 |
+
elif init_type == 'xavier':
|
| 345 |
+
torch.nn.init.xavier_normal_(m.weight.data, gain=init_gain)
|
| 346 |
+
elif init_type == 'kaiming':
|
| 347 |
+
torch.nn.init.kaiming_normal_(m.weight.data, a=0, mode='fan_in')
|
| 348 |
+
elif init_type == 'orthogonal':
|
| 349 |
+
torch.nn.init.orthogonal_(m.weight.data, gain=init_gain)
|
| 350 |
+
else:
|
| 351 |
+
raise NotImplementedError('initialization method [%s] is not implemented' % init_type)
|
| 352 |
+
elif classname.find('BatchNorm2d') != -1:
|
| 353 |
+
torch.nn.init.normal_(m.weight.data, 1.0, 0.02)
|
| 354 |
+
torch.nn.init.constant_(m.bias.data, 0.0)
|
| 355 |
+
print('initialize network with %s type' % init_type)
|
| 356 |
+
net.apply(init_func)
|
| 357 |
+
|
| 358 |
+
def get_lr_scheduler(lr_decay_type, lr, min_lr, total_iters, warmup_iters_ratio = 0.05, warmup_lr_ratio = 0.1, no_aug_iter_ratio = 0.05, step_num = 10):
|
| 359 |
+
def yolox_warm_cos_lr(lr, min_lr, total_iters, warmup_total_iters, warmup_lr_start, no_aug_iter, iters):
|
| 360 |
+
if iters <= warmup_total_iters:
|
| 361 |
+
# lr = (lr - warmup_lr_start) * iters / float(warmup_total_iters) + warmup_lr_start
|
| 362 |
+
lr = (lr - warmup_lr_start) * pow(iters / float(warmup_total_iters), 2) + warmup_lr_start
|
| 363 |
+
elif iters >= total_iters - no_aug_iter:
|
| 364 |
+
lr = min_lr
|
| 365 |
+
else:
|
| 366 |
+
lr = min_lr + 0.5 * (lr - min_lr) * (
|
| 367 |
+
1.0 + math.cos(math.pi* (iters - warmup_total_iters) / (total_iters - warmup_total_iters - no_aug_iter))
|
| 368 |
+
)
|
| 369 |
+
return lr
|
| 370 |
+
|
| 371 |
+
def step_lr(lr, decay_rate, step_size, iters):
|
| 372 |
+
if step_size < 1:
|
| 373 |
+
raise ValueError("step_size must above 1.")
|
| 374 |
+
n = iters // step_size
|
| 375 |
+
out_lr = lr * decay_rate ** n
|
| 376 |
+
return out_lr
|
| 377 |
+
|
| 378 |
+
if lr_decay_type == "cos":
|
| 379 |
+
warmup_total_iters = min(max(warmup_iters_ratio * total_iters, 1), 3)
|
| 380 |
+
warmup_lr_start = max(warmup_lr_ratio * lr, 1e-6)
|
| 381 |
+
no_aug_iter = min(max(no_aug_iter_ratio * total_iters, 1), 15)
|
| 382 |
+
func = partial(yolox_warm_cos_lr ,lr, min_lr, total_iters, warmup_total_iters, warmup_lr_start, no_aug_iter)
|
| 383 |
+
else:
|
| 384 |
+
decay_rate = (min_lr / lr) ** (1 / (step_num - 1))
|
| 385 |
+
step_size = total_iters / step_num
|
| 386 |
+
func = partial(step_lr, lr, decay_rate, step_size)
|
| 387 |
+
|
| 388 |
+
return func
|
| 389 |
+
|
| 390 |
+
def set_optimizer_lr(optimizer, lr_scheduler_func, epoch):
|
| 391 |
+
lr = lr_scheduler_func(epoch)
|
| 392 |
+
for param_group in optimizer.param_groups:
|
| 393 |
+
param_group['lr'] = lr
|
faster-rcnn-pytorch-master/nets/resnet50.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
from torch.hub import load_state_dict_from_url
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class Bottleneck(nn.Module):
|
| 8 |
+
expansion = 4
|
| 9 |
+
def __init__(self, inplanes, planes, stride=1, downsample=None):
|
| 10 |
+
super(Bottleneck, self).__init__()
|
| 11 |
+
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, stride=stride, bias=False)
|
| 12 |
+
self.bn1 = nn.BatchNorm2d(planes)
|
| 13 |
+
|
| 14 |
+
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)
|
| 15 |
+
self.bn2 = nn.BatchNorm2d(planes)
|
| 16 |
+
|
| 17 |
+
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
|
| 18 |
+
self.bn3 = nn.BatchNorm2d(planes * 4)
|
| 19 |
+
|
| 20 |
+
self.relu = nn.ReLU(inplace=True)
|
| 21 |
+
self.downsample = downsample
|
| 22 |
+
self.stride = stride
|
| 23 |
+
|
| 24 |
+
def forward(self, x):
|
| 25 |
+
residual = x
|
| 26 |
+
|
| 27 |
+
out = self.conv1(x)
|
| 28 |
+
out = self.bn1(out)
|
| 29 |
+
out = self.relu(out)
|
| 30 |
+
|
| 31 |
+
out = self.conv2(out)
|
| 32 |
+
out = self.bn2(out)
|
| 33 |
+
out = self.relu(out)
|
| 34 |
+
|
| 35 |
+
out = self.conv3(out)
|
| 36 |
+
out = self.bn3(out)
|
| 37 |
+
if self.downsample is not None:
|
| 38 |
+
residual = self.downsample(x)
|
| 39 |
+
|
| 40 |
+
out += residual
|
| 41 |
+
out = self.relu(out)
|
| 42 |
+
|
| 43 |
+
return out
|
| 44 |
+
|
| 45 |
+
class ResNet(nn.Module):
|
| 46 |
+
def __init__(self, block, layers, num_classes=1000):
|
| 47 |
+
#-----------------------------------#
|
| 48 |
+
# 假设输入进来的图片是600,600,3
|
| 49 |
+
#-----------------------------------#
|
| 50 |
+
self.inplanes = 64
|
| 51 |
+
super(ResNet, self).__init__()
|
| 52 |
+
|
| 53 |
+
# 600,600,3 -> 300,300,64
|
| 54 |
+
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
|
| 55 |
+
self.bn1 = nn.BatchNorm2d(64)
|
| 56 |
+
self.relu = nn.ReLU(inplace=True)
|
| 57 |
+
|
| 58 |
+
# 300,300,64 -> 150,150,64
|
| 59 |
+
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=0, ceil_mode=True)
|
| 60 |
+
|
| 61 |
+
# 150,150,64 -> 150,150,256
|
| 62 |
+
self.layer1 = self._make_layer(block, 64, layers[0])
|
| 63 |
+
# 150,150,256 -> 75,75,512
|
| 64 |
+
self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
|
| 65 |
+
# 75,75,512 -> 38,38,1024 到这里可以获得一个38,38,1024的共享特征层
|
| 66 |
+
self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
|
| 67 |
+
# self.layer4被用在classifier模型中
|
| 68 |
+
self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
|
| 69 |
+
|
| 70 |
+
self.avgpool = nn.AvgPool2d(7)
|
| 71 |
+
self.fc = nn.Linear(512 * block.expansion, num_classes)
|
| 72 |
+
|
| 73 |
+
for m in self.modules():
|
| 74 |
+
if isinstance(m, nn.Conv2d):
|
| 75 |
+
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
|
| 76 |
+
m.weight.data.normal_(0, math.sqrt(2. / n))
|
| 77 |
+
elif isinstance(m, nn.BatchNorm2d):
|
| 78 |
+
m.weight.data.fill_(1)
|
| 79 |
+
m.bias.data.zero_()
|
| 80 |
+
|
| 81 |
+
def _make_layer(self, block, planes, blocks, stride=1):
|
| 82 |
+
downsample = None
|
| 83 |
+
#-------------------------------------------------------------------#
|
| 84 |
+
# 当模型需要进行高和宽的压缩的时候,就需要用到残差边的downsample
|
| 85 |
+
#-------------------------------------------------------------------#
|
| 86 |
+
if stride != 1 or self.inplanes != planes * block.expansion:
|
| 87 |
+
downsample = nn.Sequential(
|
| 88 |
+
nn.Conv2d(self.inplanes, planes * block.expansion,kernel_size=1, stride=stride, bias=False),
|
| 89 |
+
nn.BatchNorm2d(planes * block.expansion),
|
| 90 |
+
)
|
| 91 |
+
layers = []
|
| 92 |
+
layers.append(block(self.inplanes, planes, stride, downsample))
|
| 93 |
+
self.inplanes = planes * block.expansion
|
| 94 |
+
for i in range(1, blocks):
|
| 95 |
+
layers.append(block(self.inplanes, planes))
|
| 96 |
+
return nn.Sequential(*layers)
|
| 97 |
+
|
| 98 |
+
def forward(self, x):
|
| 99 |
+
x = self.conv1(x)
|
| 100 |
+
x = self.bn1(x)
|
| 101 |
+
x = self.relu(x)
|
| 102 |
+
x = self.maxpool(x)
|
| 103 |
+
|
| 104 |
+
x = self.layer1(x)
|
| 105 |
+
x = self.layer2(x)
|
| 106 |
+
x = self.layer3(x)
|
| 107 |
+
x = self.layer4(x)
|
| 108 |
+
|
| 109 |
+
x = self.avgpool(x)
|
| 110 |
+
x = x.view(x.size(0), -1)
|
| 111 |
+
x = self.fc(x)
|
| 112 |
+
return x
|
| 113 |
+
|
| 114 |
+
def resnet50(pretrained = False):
|
| 115 |
+
model = ResNet(Bottleneck, [3, 4, 6, 3])
|
| 116 |
+
if pretrained:
|
| 117 |
+
state_dict = load_state_dict_from_url("https://download.pytorch.org/models/resnet50-19c8e357.pth", model_dir="./model_data")
|
| 118 |
+
model.load_state_dict(state_dict)
|
| 119 |
+
#----------------------------------------------------------------------------#
|
| 120 |
+
# 获取特征提取部分,从conv1到model.layer3,最终获得一个38,38,1024的特征层
|
| 121 |
+
#----------------------------------------------------------------------------#
|
| 122 |
+
features = list([model.conv1, model.bn1, model.relu, model.maxpool, model.layer1, model.layer2, model.layer3])
|
| 123 |
+
#----------------------------------------------------------------------------#
|
| 124 |
+
# 获取分类部分,从model.layer4到model.avgpool
|
| 125 |
+
#----------------------------------------------------------------------------#
|
| 126 |
+
classifier = list([model.layer4, model.avgpool])
|
| 127 |
+
|
| 128 |
+
features = nn.Sequential(*features)
|
| 129 |
+
classifier = nn.Sequential(*classifier)
|
| 130 |
+
return features, classifier
|
faster-rcnn-pytorch-master/nets/rpn.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import numpy as np
|
| 3 |
+
import torch
|
| 4 |
+
from torch import nn
|
| 5 |
+
from torch.nn import functional as F
|
| 6 |
+
from torchvision.ops import nms
|
| 7 |
+
from utils.anchors import _enumerate_shifted_anchor, generate_anchor_base
|
| 8 |
+
from utils.utils_bbox import loc2bbox
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class ProposalCreator():
|
| 12 |
+
def __init__(
|
| 13 |
+
self,
|
| 14 |
+
mode,
|
| 15 |
+
nms_iou = 0.7,
|
| 16 |
+
n_train_pre_nms = 12000,
|
| 17 |
+
n_train_post_nms = 600,
|
| 18 |
+
n_test_pre_nms = 3000,
|
| 19 |
+
n_test_post_nms = 300,
|
| 20 |
+
min_size = 16
|
| 21 |
+
|
| 22 |
+
):
|
| 23 |
+
#-----------------------------------#
|
| 24 |
+
# 设置预测还是训练
|
| 25 |
+
#-----------------------------------#
|
| 26 |
+
self.mode = mode
|
| 27 |
+
#-----------------------------------#
|
| 28 |
+
# 建议框非极大抑制的iou大小
|
| 29 |
+
#-----------------------------------#
|
| 30 |
+
self.nms_iou = nms_iou
|
| 31 |
+
#-----------------------------------#
|
| 32 |
+
# 训练用到的建议框数量
|
| 33 |
+
#-----------------------------------#
|
| 34 |
+
self.n_train_pre_nms = n_train_pre_nms
|
| 35 |
+
self.n_train_post_nms = n_train_post_nms
|
| 36 |
+
#-----------------------------------#
|
| 37 |
+
# 预测用到的建议框数量
|
| 38 |
+
#-----------------------------------#
|
| 39 |
+
self.n_test_pre_nms = n_test_pre_nms
|
| 40 |
+
self.n_test_post_nms = n_test_post_nms
|
| 41 |
+
self.min_size = min_size
|
| 42 |
+
|
| 43 |
+
def __call__(self, loc, score, anchor, img_size, scale=1.):
|
| 44 |
+
if self.mode == "training":
|
| 45 |
+
n_pre_nms = self.n_train_pre_nms
|
| 46 |
+
n_post_nms = self.n_train_post_nms
|
| 47 |
+
else:
|
| 48 |
+
n_pre_nms = self.n_test_pre_nms
|
| 49 |
+
n_post_nms = self.n_test_post_nms
|
| 50 |
+
|
| 51 |
+
#-----------------------------------#
|
| 52 |
+
# 将先验框转换成tensor
|
| 53 |
+
#-----------------------------------#
|
| 54 |
+
anchor = torch.from_numpy(anchor).type_as(loc)
|
| 55 |
+
#-----------------------------------#
|
| 56 |
+
# 将RPN网络预测结果转化成建议框
|
| 57 |
+
#-----------------------------------#
|
| 58 |
+
roi = loc2bbox(anchor, loc)
|
| 59 |
+
#-----------------------------------#
|
| 60 |
+
# 防止建议框超出图像边缘
|
| 61 |
+
#-----------------------------------#
|
| 62 |
+
roi[:, [0, 2]] = torch.clamp(roi[:, [0, 2]], min = 0, max = img_size[1])
|
| 63 |
+
roi[:, [1, 3]] = torch.clamp(roi[:, [1, 3]], min = 0, max = img_size[0])
|
| 64 |
+
|
| 65 |
+
#-----------------------------------#
|
| 66 |
+
# 建议框的宽高的最小值不可以小于16
|
| 67 |
+
#-----------------------------------#
|
| 68 |
+
min_size = self.min_size * scale
|
| 69 |
+
keep = torch.where(((roi[:, 2] - roi[:, 0]) >= min_size) & ((roi[:, 3] - roi[:, 1]) >= min_size))[0]
|
| 70 |
+
#-----------------------------------#
|
| 71 |
+
# 将对应的建议框保留下来
|
| 72 |
+
#-----------------------------------#
|
| 73 |
+
roi = roi[keep, :]
|
| 74 |
+
score = score[keep]
|
| 75 |
+
|
| 76 |
+
#-----------------------------------#
|
| 77 |
+
# 根据得分进行排序,取出建议框
|
| 78 |
+
#-----------------------------------#
|
| 79 |
+
order = torch.argsort(score, descending=True)
|
| 80 |
+
if n_pre_nms > 0:
|
| 81 |
+
order = order[:n_pre_nms]
|
| 82 |
+
roi = roi[order, :]
|
| 83 |
+
score = score[order]
|
| 84 |
+
|
| 85 |
+
#-----------------------------------#
|
| 86 |
+
# 对建议框进行非极大抑制
|
| 87 |
+
# 使用官方的非极大抑制会快非常多
|
| 88 |
+
#-----------------------------------#
|
| 89 |
+
keep = nms(roi, score, self.nms_iou)
|
| 90 |
+
if len(keep) < n_post_nms:
|
| 91 |
+
index_extra = np.random.choice(range(len(keep)), size=(n_post_nms - len(keep)), replace=True)
|
| 92 |
+
keep = torch.cat([keep, keep[index_extra]])
|
| 93 |
+
keep = keep[:n_post_nms]
|
| 94 |
+
roi = roi[keep]
|
| 95 |
+
return roi
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class RegionProposalNetwork(nn.Module):
|
| 99 |
+
def __init__(
|
| 100 |
+
self,
|
| 101 |
+
in_channels = 512,
|
| 102 |
+
mid_channels = 512,
|
| 103 |
+
ratios = [0.5, 1, 2],
|
| 104 |
+
anchor_scales = [8, 16, 32],
|
| 105 |
+
feat_stride = 16,
|
| 106 |
+
mode = "training",
|
| 107 |
+
):
|
| 108 |
+
super(RegionProposalNetwork, self).__init__()
|
| 109 |
+
#-----------------------------------------#
|
| 110 |
+
# 生成基础先验框,shape为[9, 4]
|
| 111 |
+
#-----------------------------------------#
|
| 112 |
+
self.anchor_base = generate_anchor_base(anchor_scales = anchor_scales, ratios = ratios)
|
| 113 |
+
n_anchor = self.anchor_base.shape[0]
|
| 114 |
+
|
| 115 |
+
#-----------------------------------------#
|
| 116 |
+
# 先进行一个3x3的卷积,可理解为特征整合
|
| 117 |
+
#-----------------------------------------#
|
| 118 |
+
self.conv1 = nn.Conv2d(in_channels, mid_channels, 3, 1, 1)
|
| 119 |
+
#-----------------------------------------#
|
| 120 |
+
# 分类预测先验框内部是否包含物体
|
| 121 |
+
#-----------------------------------------#
|
| 122 |
+
self.score = nn.Conv2d(mid_channels, n_anchor * 2, 1, 1, 0)
|
| 123 |
+
#-----------------------------------------#
|
| 124 |
+
# 回归预测对先验框进行调整
|
| 125 |
+
#-----------------------------------------#
|
| 126 |
+
self.loc = nn.Conv2d(mid_channels, n_anchor * 4, 1, 1, 0)
|
| 127 |
+
|
| 128 |
+
#-----------------------------------------#
|
| 129 |
+
# 特征点间距步长
|
| 130 |
+
#-----------------------------------------#
|
| 131 |
+
self.feat_stride = feat_stride
|
| 132 |
+
#-----------------------------------------#
|
| 133 |
+
# 用于对建议框解码并进行非极大抑制
|
| 134 |
+
#-----------------------------------------#
|
| 135 |
+
self.proposal_layer = ProposalCreator(mode)
|
| 136 |
+
#--------------------------------------#
|
| 137 |
+
# 对FPN的网络部分进行权值初始化
|
| 138 |
+
#--------------------------------------#
|
| 139 |
+
normal_init(self.conv1, 0, 0.01)
|
| 140 |
+
normal_init(self.score, 0, 0.01)
|
| 141 |
+
normal_init(self.loc, 0, 0.01)
|
| 142 |
+
|
| 143 |
+
def forward(self, x, img_size, scale=1.):
|
| 144 |
+
n, _, h, w = x.shape
|
| 145 |
+
#-----------------------------------------#
|
| 146 |
+
# 先进行一个3x3的卷积,可理解为特征整合
|
| 147 |
+
#-----------------------------------------#
|
| 148 |
+
x = F.relu(self.conv1(x))
|
| 149 |
+
#-----------------------------------------#
|
| 150 |
+
# 回归预测对先验框进行调整
|
| 151 |
+
#-----------------------------------------#
|
| 152 |
+
rpn_locs = self.loc(x)
|
| 153 |
+
rpn_locs = rpn_locs.permute(0, 2, 3, 1).contiguous().view(n, -1, 4)
|
| 154 |
+
#-----------------------------------------#
|
| 155 |
+
# 分类预测先验框内部是否包含物体
|
| 156 |
+
#-----------------------------------------#
|
| 157 |
+
rpn_scores = self.score(x)
|
| 158 |
+
rpn_scores = rpn_scores.permute(0, 2, 3, 1).contiguous().view(n, -1, 2)
|
| 159 |
+
|
| 160 |
+
#--------------------------------------------------------------------------------------#
|
| 161 |
+
# 进行softmax概率计算,每个先验框只有两个判别结果
|
| 162 |
+
# 内部包含物体或者内部不包含物体,rpn_softmax_scores[:, :, 1]的内容为包含物体的概率
|
| 163 |
+
#--------------------------------------------------------------------------------------#
|
| 164 |
+
rpn_softmax_scores = F.softmax(rpn_scores, dim=-1)
|
| 165 |
+
rpn_fg_scores = rpn_softmax_scores[:, :, 1].contiguous()
|
| 166 |
+
rpn_fg_scores = rpn_fg_scores.view(n, -1)
|
| 167 |
+
|
| 168 |
+
#------------------------------------------------------------------------------------------------#
|
| 169 |
+
# 生成先验框,此时获得的anchor是布满网格点的,当输入图片为600,600,3的时候,shape为(12996, 4)
|
| 170 |
+
#------------------------------------------------------------------------------------------------#
|
| 171 |
+
anchor = _enumerate_shifted_anchor(np.array(self.anchor_base), self.feat_stride, h, w)
|
| 172 |
+
rois = list()
|
| 173 |
+
roi_indices = list()
|
| 174 |
+
for i in range(n):
|
| 175 |
+
roi = self.proposal_layer(rpn_locs[i], rpn_fg_scores[i], anchor, img_size, scale = scale)
|
| 176 |
+
batch_index = i * torch.ones((len(roi),))
|
| 177 |
+
rois.append(roi.unsqueeze(0))
|
| 178 |
+
roi_indices.append(batch_index.unsqueeze(0))
|
| 179 |
+
|
| 180 |
+
rois = torch.cat(rois, dim=0).type_as(x)
|
| 181 |
+
roi_indices = torch.cat(roi_indices, dim=0).type_as(x)
|
| 182 |
+
anchor = torch.from_numpy(anchor).unsqueeze(0).float().to(x.device)
|
| 183 |
+
|
| 184 |
+
return rpn_locs, rpn_scores, rois, roi_indices, anchor
|
| 185 |
+
|
| 186 |
+
def normal_init(m, mean, stddev, truncated=False):
|
| 187 |
+
if truncated:
|
| 188 |
+
m.weight.data.normal_().fmod_(2).mul_(stddev).add_(mean) # not a perfect approximation
|
| 189 |
+
else:
|
| 190 |
+
m.weight.data.normal_(mean, stddev)
|
| 191 |
+
m.bias.data.zero_()
|
faster-rcnn-pytorch-master/nets/vgg16.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from torch.hub import load_state_dict_from_url
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
#--------------------------------------#
|
| 7 |
+
# VGG16的结构
|
| 8 |
+
#--------------------------------------#
|
| 9 |
+
class VGG(nn.Module):
|
| 10 |
+
def __init__(self, features, num_classes=1000, init_weights=True):
|
| 11 |
+
super(VGG, self).__init__()
|
| 12 |
+
self.features = features
|
| 13 |
+
#--------------------------------------#
|
| 14 |
+
# 平均池化到7x7大小
|
| 15 |
+
#--------------------------------------#
|
| 16 |
+
self.avgpool = nn.AdaptiveAvgPool2d((7, 7))
|
| 17 |
+
#--------------------------------------#
|
| 18 |
+
# 分类部分
|
| 19 |
+
#--------------------------------------#
|
| 20 |
+
self.classifier = nn.Sequential(
|
| 21 |
+
nn.Linear(512 * 7 * 7, 4096),
|
| 22 |
+
nn.ReLU(True),
|
| 23 |
+
nn.Dropout(),
|
| 24 |
+
nn.Linear(4096, 4096),
|
| 25 |
+
nn.ReLU(True),
|
| 26 |
+
nn.Dropout(),
|
| 27 |
+
nn.Linear(4096, num_classes),
|
| 28 |
+
)
|
| 29 |
+
if init_weights:
|
| 30 |
+
self._initialize_weights()
|
| 31 |
+
|
| 32 |
+
def forward(self, x):
|
| 33 |
+
#--------------------------------------#
|
| 34 |
+
# 特征提取
|
| 35 |
+
#--------------------------------------#
|
| 36 |
+
x = self.features(x)
|
| 37 |
+
#--------------------------------------#
|
| 38 |
+
# 平均池化
|
| 39 |
+
#--------------------------------------#
|
| 40 |
+
x = self.avgpool(x)
|
| 41 |
+
#--------------------------------------#
|
| 42 |
+
# 平铺后
|
| 43 |
+
#--------------------------------------#
|
| 44 |
+
x = torch.flatten(x, 1)
|
| 45 |
+
#--------------------------------------#
|
| 46 |
+
# 分类部分
|
| 47 |
+
#--------------------------------------#
|
| 48 |
+
x = self.classifier(x)
|
| 49 |
+
return x
|
| 50 |
+
|
| 51 |
+
def _initialize_weights(self):
|
| 52 |
+
for m in self.modules():
|
| 53 |
+
if isinstance(m, nn.Conv2d):
|
| 54 |
+
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
|
| 55 |
+
if m.bias is not None:
|
| 56 |
+
nn.init.constant_(m.bias, 0)
|
| 57 |
+
elif isinstance(m, nn.BatchNorm2d):
|
| 58 |
+
nn.init.constant_(m.weight, 1)
|
| 59 |
+
nn.init.constant_(m.bias, 0)
|
| 60 |
+
elif isinstance(m, nn.Linear):
|
| 61 |
+
nn.init.normal_(m.weight, 0, 0.01)
|
| 62 |
+
nn.init.constant_(m.bias, 0)
|
| 63 |
+
|
| 64 |
+
'''
|
| 65 |
+
假设输入图像为(600, 600, 3),随着cfg的循环,特征层变化如下:
|
| 66 |
+
600,600,3 -> 600,600,64 -> 600,600,64 -> 300,300,64 -> 300,300,128 -> 300,300,128 -> 150,150,128 -> 150,150,256 -> 150,150,256 -> 150,150,256
|
| 67 |
+
-> 75,75,256 -> 75,75,512 -> 75,75,512 -> 75,75,512 -> 37,37,512 -> 37,37,512 -> 37,37,512 -> 37,37,512
|
| 68 |
+
到cfg结束,我们获得了一个37,37,512的特征层
|
| 69 |
+
'''
|
| 70 |
+
|
| 71 |
+
cfg = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M']
|
| 72 |
+
|
| 73 |
+
#--------------------------------------#
|
| 74 |
+
# 特征提取部分
|
| 75 |
+
#--------------------------------------#
|
| 76 |
+
def make_layers(cfg, batch_norm=False):
|
| 77 |
+
layers = []
|
| 78 |
+
in_channels = 3
|
| 79 |
+
for v in cfg:
|
| 80 |
+
if v == 'M':
|
| 81 |
+
layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
|
| 82 |
+
else:
|
| 83 |
+
conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
|
| 84 |
+
if batch_norm:
|
| 85 |
+
layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
|
| 86 |
+
else:
|
| 87 |
+
layers += [conv2d, nn.ReLU(inplace=True)]
|
| 88 |
+
in_channels = v
|
| 89 |
+
return nn.Sequential(*layers)
|
| 90 |
+
|
| 91 |
+
def decom_vgg16(pretrained = False):
|
| 92 |
+
model = VGG(make_layers(cfg))
|
| 93 |
+
if pretrained:
|
| 94 |
+
state_dict = load_state_dict_from_url("https://download.pytorch.org/models/vgg16-397923af.pth", model_dir="./model_data")
|
| 95 |
+
model.load_state_dict(state_dict)
|
| 96 |
+
#----------------------------------------------------------------------------#
|
| 97 |
+
# 获取特征提取部分,最终获得一个37,37,1024的特征层
|
| 98 |
+
#----------------------------------------------------------------------------#
|
| 99 |
+
features = list(model.features)[:30]
|
| 100 |
+
#----------------------------------------------------------------------------#
|
| 101 |
+
# 获取分类部分,需要除去Dropout部分
|
| 102 |
+
#----------------------------------------------------------------------------#
|
| 103 |
+
classifier = list(model.classifier)
|
| 104 |
+
del classifier[6]
|
| 105 |
+
del classifier[5]
|
| 106 |
+
del classifier[2]
|
| 107 |
+
|
| 108 |
+
features = nn.Sequential(*features)
|
| 109 |
+
classifier = nn.Sequential(*classifier)
|
| 110 |
+
return features, classifier
|
faster-rcnn-pytorch-master/predict.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#----------------------------------------------------#
|
| 2 |
+
# 将单张图片预测、摄像头检测和FPS测试功能
|
| 3 |
+
# 整合到了一个py文件中,通过指定mode进行模式的修改。
|
| 4 |
+
#----------------------------------------------------#
|
| 5 |
+
import time
|
| 6 |
+
|
| 7 |
+
import cv2
|
| 8 |
+
import numpy as np
|
| 9 |
+
from PIL import Image
|
| 10 |
+
|
| 11 |
+
from frcnn import FRCNN
|
| 12 |
+
|
| 13 |
+
if __name__ == "__main__":
|
| 14 |
+
frcnn = FRCNN()
|
| 15 |
+
#----------------------------------------------------------------------------------------------------------#
|
| 16 |
+
# mode用于指定测试的模式:
|
| 17 |
+
# 'predict' 表示单张图片预测,如果想对预测过程进行修改,如保存图片,截取对象等,可以先看下方详细的注释
|
| 18 |
+
# 'video' 表示视频检测,可调用摄像头或者视频进行检测,详情查看下方注释。
|
| 19 |
+
# 'fps' 表示测试fps,使用的图片是img里面的street.jpg,详情查看下方注释。
|
| 20 |
+
# 'dir_predict' 表示遍历文件夹进行检测并保存。默认遍历img文件夹,保存img_out文件夹,详情查看下方注释。
|
| 21 |
+
#----------------------------------------------------------------------------------------------------------#
|
| 22 |
+
mode = "predict"
|
| 23 |
+
#-------------------------------------------------------------------------#
|
| 24 |
+
# crop 指定了是否在单张图片预测后对目标进行截取
|
| 25 |
+
# count 指定了是否进行目标的计数
|
| 26 |
+
# crop、count仅在mode='predict'时有效
|
| 27 |
+
#-------------------------------------------------------------------------#
|
| 28 |
+
crop = False
|
| 29 |
+
count = False
|
| 30 |
+
#----------------------------------------------------------------------------------------------------------#
|
| 31 |
+
# video_path 用于指定视频的路径,当video_path=0时表示检测摄像头
|
| 32 |
+
# 想要检测视频,则设置如video_path = "xxx.mp4"即可,代表读取出根目录下的xxx.mp4文件。
|
| 33 |
+
# video_save_path 表示视频保存的路径,当video_save_path=""时表示不保存
|
| 34 |
+
# 想要保存视频,则设置如video_save_path = "yyy.mp4"即可,代表保存为根目录下的yyy.mp4文件。
|
| 35 |
+
# video_fps 用于保存的视频的fps
|
| 36 |
+
#
|
| 37 |
+
# video_path、video_save_path和video_fps仅在mode='video'时有效
|
| 38 |
+
# 保存视频时需要ctrl+c退出或者运行到最后一帧才会完成完整的保存步骤。
|
| 39 |
+
#----------------------------------------------------------------------------------------------------------#
|
| 40 |
+
video_path = 0
|
| 41 |
+
video_save_path = ""
|
| 42 |
+
video_fps = 25.0
|
| 43 |
+
#----------------------------------------------------------------------------------------------------------#
|
| 44 |
+
# test_interval 用于指定测量fps的时候,图片检测的次数。理论上test_interval越大,fps越准确。
|
| 45 |
+
# fps_image_path 用于指定测试的fps图片
|
| 46 |
+
#
|
| 47 |
+
# test_interval和fps_image_path仅在mode='fps'有效
|
| 48 |
+
#----------------------------------------------------------------------------------------------------------#
|
| 49 |
+
test_interval = 100
|
| 50 |
+
fps_image_path = "img/street.jpg"
|
| 51 |
+
#-------------------------------------------------------------------------#
|
| 52 |
+
# dir_origin_path 指定了用于检测的图片的文件夹路径
|
| 53 |
+
# dir_save_path 指定了检测完图片的保存路径
|
| 54 |
+
#
|
| 55 |
+
# dir_origin_path和dir_save_path仅在mode='dir_predict'时有效
|
| 56 |
+
#-------------------------------------------------------------------------#
|
| 57 |
+
dir_origin_path = "img/"
|
| 58 |
+
dir_save_path = "img_out/"
|
| 59 |
+
|
| 60 |
+
if mode == "predict":
|
| 61 |
+
'''
|
| 62 |
+
1、该代码无法直接进行批量预测,如果想要批量预测,可以利用os.listdir()遍历文件夹,利用Image.open打开图片文件进行预测。
|
| 63 |
+
具体流程可以参考get_dr_txt.py,在get_dr_txt.py即实现了遍历还实现了目标信息的保存。
|
| 64 |
+
2、如果想要进行检测完的图片的保存,利用r_image.save("img.jpg")即可保存,直接在predict.py里进行修改即可。
|
| 65 |
+
3、如果想要获得预测框的坐标,可以进入frcnn.detect_image函数,在绘图部分读取top,left,bottom,right这四个值。
|
| 66 |
+
4、如果想要利用预测框截取下目标,可以进入frcnn.detect_image函数,在绘图部分利用获取到的top,left,bottom,right这四个值
|
| 67 |
+
在原图上利用矩阵的方式进行截取。
|
| 68 |
+
5、如果想要在预测图上写额外的字,比如检测到的特定目标的数量,可以进入frcnn.detect_image函数,在绘图部分对predicted_class进行判断,
|
| 69 |
+
比如判断if predicted_class == 'car': 即可判断当前目标是否为车,然后记录数量即可。利用draw.text即可写字。
|
| 70 |
+
'''
|
| 71 |
+
while True:
|
| 72 |
+
img = input('Input image filename:')
|
| 73 |
+
try:
|
| 74 |
+
image = Image.open(img)
|
| 75 |
+
except:
|
| 76 |
+
print('Open Error! Try again!')
|
| 77 |
+
continue
|
| 78 |
+
else:
|
| 79 |
+
r_image = frcnn.detect_image(image, crop = crop, count = count)
|
| 80 |
+
r_image.show()
|
| 81 |
+
|
| 82 |
+
elif mode == "video":
|
| 83 |
+
capture=cv2.VideoCapture(video_path)
|
| 84 |
+
if video_save_path!="":
|
| 85 |
+
fourcc = cv2.VideoWriter_fourcc(*'XVID')
|
| 86 |
+
size = (int(capture.get(cv2.CAP_PROP_FRAME_WIDTH)), int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT)))
|
| 87 |
+
out = cv2.VideoWriter(video_save_path, fourcc, video_fps, size)
|
| 88 |
+
|
| 89 |
+
fps = 0.0
|
| 90 |
+
while(True):
|
| 91 |
+
t1 = time.time()
|
| 92 |
+
# 读取某一帧
|
| 93 |
+
ref,frame=capture.read()
|
| 94 |
+
# 格式转变,BGRtoRGB
|
| 95 |
+
frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
|
| 96 |
+
# 转变成Image
|
| 97 |
+
frame = Image.fromarray(np.uint8(frame))
|
| 98 |
+
# 进行检测
|
| 99 |
+
frame = np.array(frcnn.detect_image(frame))
|
| 100 |
+
# RGBtoBGR满足opencv显示格式
|
| 101 |
+
frame = cv2.cvtColor(frame,cv2.COLOR_RGB2BGR)
|
| 102 |
+
|
| 103 |
+
fps = ( fps + (1./(time.time()-t1)) ) / 2
|
| 104 |
+
print("fps= %.2f"%(fps))
|
| 105 |
+
frame = cv2.putText(frame, "fps= %.2f"%(fps), (0, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
| 106 |
+
|
| 107 |
+
cv2.imshow("video",frame)
|
| 108 |
+
c= cv2.waitKey(1) & 0xff
|
| 109 |
+
if video_save_path!="":
|
| 110 |
+
out.write(frame)
|
| 111 |
+
|
| 112 |
+
if c==27:
|
| 113 |
+
capture.release()
|
| 114 |
+
break
|
| 115 |
+
capture.release()
|
| 116 |
+
out.release()
|
| 117 |
+
cv2.destroyAllWindows()
|
| 118 |
+
|
| 119 |
+
elif mode == "fps":
|
| 120 |
+
img = Image.open(fps_image_path)
|
| 121 |
+
tact_time = frcnn.get_FPS(img, test_interval)
|
| 122 |
+
print(str(tact_time) + ' seconds, ' + str(1/tact_time) + 'FPS, @batch_size 1')
|
| 123 |
+
|
| 124 |
+
elif mode == "dir_predict":
|
| 125 |
+
import os
|
| 126 |
+
from tqdm import tqdm
|
| 127 |
+
|
| 128 |
+
img_names = os.listdir(dir_origin_path)
|
| 129 |
+
for img_name in tqdm(img_names):
|
| 130 |
+
if img_name.lower().endswith(('.bmp', '.dib', '.png', '.jpg', '.jpeg', '.pbm', '.pgm', '.ppm', '.tif', '.tiff')):
|
| 131 |
+
image_path = os.path.join(dir_origin_path, img_name)
|
| 132 |
+
image = Image.open(image_path)
|
| 133 |
+
r_image = frcnn.detect_image(image)
|
| 134 |
+
if not os.path.exists(dir_save_path):
|
| 135 |
+
os.makedirs(dir_save_path)
|
| 136 |
+
r_image.save(os.path.join(dir_save_path, img_name.replace(".jpg", ".png")), quality=95, subsampling=0)
|
| 137 |
+
|
| 138 |
+
else:
|
| 139 |
+
raise AssertionError("Please specify the correct mode: 'predict', 'video', 'fps' or 'dir_predict'.")
|
faster-rcnn-pytorch-master/requirements.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
torchvision
|
| 3 |
+
tensorboard
|
| 4 |
+
scipy==1.2.1
|
| 5 |
+
numpy==1.17.0
|
| 6 |
+
matplotlib==3.1.2
|
| 7 |
+
opencv_python==4.1.2.30
|
| 8 |
+
tqdm==4.60.0
|
| 9 |
+
Pillow==8.2.0
|
| 10 |
+
h5py==2.10.0
|
faster-rcnn-pytorch-master/summary.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#--------------------------------------------#
|
| 2 |
+
# 该部分代码用于看网络结构
|
| 3 |
+
#--------------------------------------------#
|
| 4 |
+
import torch
|
| 5 |
+
from thop import clever_format, profile
|
| 6 |
+
from torchsummary import summary
|
| 7 |
+
|
| 8 |
+
from nets.frcnn import FasterRCNN
|
| 9 |
+
|
| 10 |
+
if __name__ == "__main__":
|
| 11 |
+
input_shape = [600, 600]
|
| 12 |
+
num_classes = 21
|
| 13 |
+
|
| 14 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 15 |
+
model = FasterRCNN(num_classes, backbone = 'vgg').to(device)
|
| 16 |
+
summary(model, (3, input_shape[0], input_shape[1]))
|
| 17 |
+
|
| 18 |
+
dummy_input = torch.randn(1, 3, input_shape[0], input_shape[1]).to(device)
|
| 19 |
+
flops, params = profile(model.to(device), (dummy_input, ), verbose=False)
|
| 20 |
+
#--------------------------------------------------------#
|
| 21 |
+
# flops * 2是因为profile没有将卷积作为两个operations
|
| 22 |
+
# 有些论文将卷积算乘法、加法两个operations。此时乘2
|
| 23 |
+
# 有些论文只考虑乘法的运算次数,忽略加法。此时不乘2
|
| 24 |
+
# 本代码选择乘2,参考YOLOX。
|
| 25 |
+
#--------------------------------------------------------#
|
| 26 |
+
flops = flops * 2
|
| 27 |
+
flops, params = clever_format([flops, params], "%.3f")
|
| 28 |
+
print('Total GFLOPS: %s' % (flops))
|
| 29 |
+
print('Total params: %s' % (params))
|
faster-rcnn-pytorch-master/test.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
print(os.path.exists(
|
| 3 |
+
'/home/lab/FH_Banana/faster-rcnn-pytorch-master/VOCdevkit/VOC2007/JPEGImages/DSC01505.JPG'))
|
faster-rcnn-pytorch-master/train.py
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#-------------------------------------#
|
| 2 |
+
# 对数据集进行训练
|
| 3 |
+
#-------------------------------------#
|
| 4 |
+
import datetime
|
| 5 |
+
import os
|
| 6 |
+
from functools import partial
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
import torch.backends.cudnn as cudnn
|
| 11 |
+
import torch.optim as optim
|
| 12 |
+
from torch.utils.data import DataLoader
|
| 13 |
+
|
| 14 |
+
from nets.frcnn import FasterRCNN
|
| 15 |
+
from nets.frcnn_training import (FasterRCNNTrainer, get_lr_scheduler,
|
| 16 |
+
set_optimizer_lr, weights_init)
|
| 17 |
+
from utils.callbacks import EvalCallback, LossHistory
|
| 18 |
+
from utils.dataloader import FRCNNDataset, frcnn_dataset_collate
|
| 19 |
+
from utils.utils import (get_classes, seed_everything, show_config,
|
| 20 |
+
worker_init_fn)
|
| 21 |
+
from utils.utils_fit import fit_one_epoch
|
| 22 |
+
|
| 23 |
+
'''
|
| 24 |
+
训练自己的目标检测模型一定需要注意以下几点:
|
| 25 |
+
1、训练前仔细检查自己的格式是否满足要求,该库要求数据集格式为VOC格式,需要准备好的内容有输入图片和标签
|
| 26 |
+
输入图片为.jpg图片,无需固定大小,传入训练前会自动进行resize。
|
| 27 |
+
灰度图会自动转成RGB图片进行训练,无需自己修改。
|
| 28 |
+
输入图片如果后缀非jpg,需要自己批量转成jpg后再开始训练。
|
| 29 |
+
|
| 30 |
+
标签为.xml格式,文件中会有需要检测的目标信息,标签文件和输入图片文件相对应。
|
| 31 |
+
|
| 32 |
+
2、损失值的大小用于判断是否收敛,比较重要的是有收敛的趋势,即验证集损失不断下降,如果验证集损失基本上不改变的话,模型基本上就收敛了。
|
| 33 |
+
损失值的具体大小并没有什么意义,大和小只在于损失的计算方式,并不是接近于0才好。如果想要让损失好看点,可以直接到对应的损失函数里面除上10000。
|
| 34 |
+
训练过程中的损失值会保存在logs文件夹下的loss_%Y_%m_%d_%H_%M_%S文件夹中
|
| 35 |
+
|
| 36 |
+
3、训练好的权值文件保存在logs文件夹中,每个训练世代(Epoch)包含若干训练步长(Step),每个训练步长(Step)进行一次梯度下降。
|
| 37 |
+
如果只是训练了几个Step是不会保存的,Epoch和Step的概念要捋清楚一下。
|
| 38 |
+
'''
|
| 39 |
+
if __name__ == "__main__":
|
| 40 |
+
#-------------------------------#
|
| 41 |
+
# 是否使用Cuda
|
| 42 |
+
# 没有GPU可以设置成False
|
| 43 |
+
#-------------------------------#
|
| 44 |
+
Cuda = True
|
| 45 |
+
#----------------------------------------------#
|
| 46 |
+
# Seed 用于固定随机种子
|
| 47 |
+
# 使得每次独立训练都可以获得一样的结果
|
| 48 |
+
#----------------------------------------------#
|
| 49 |
+
seed = 11
|
| 50 |
+
#---------------------------------------------------------------------#
|
| 51 |
+
# train_gpu 训练用到的GPU
|
| 52 |
+
# 默认为第一张卡、双卡为[0, 1]、三卡为[0, 1, 2]
|
| 53 |
+
# 在使用多GPU时,每个卡上的batch为总batch除以卡的数量。
|
| 54 |
+
#---------------------------------------------------------------------#
|
| 55 |
+
train_gpu = [2,3]
|
| 56 |
+
#---------------------------------------------------------------------#
|
| 57 |
+
# fp16 是否使用混合精度训练
|
| 58 |
+
# 可减少约一半的显存、需要pytorch1.7.1以上
|
| 59 |
+
#---------------------------------------------------------------------#
|
| 60 |
+
fp16 = False
|
| 61 |
+
#---------------------------------------------------------------------#
|
| 62 |
+
# classes_path 指向model_data下的txt,与自己训练的数据集相关
|
| 63 |
+
# 训练前一定要修改classes_path,使其对应自己的数据集
|
| 64 |
+
#---------------------------------------------------------------------#
|
| 65 |
+
classes_path = '/home/lab/LJ/wampee/faster-rcnn-pytorch-master/model_data/class.txt'
|
| 66 |
+
#----------------------------------------------------------------------------------------------------------------------------#
|
| 67 |
+
# 权值文件的下载请看README,可以通过网盘下载。模型的 预训练权重 对不同数据集是通用的,因为特征是通用的。
|
| 68 |
+
# 模型的 预训练权重 比较重要的部分是 主干特征提取网络的权值部分,用于进行特征提取。
|
| 69 |
+
# 预训练权重对于99%的情况都必须要用,不用的话主干部分的权值太过随机,特征提取效果不明显,网络训练的结果也不会好
|
| 70 |
+
#
|
| 71 |
+
# 如果训练过程中存在中断训练的操作,可以将model_path设置成logs文件夹下的权值文件,将已经训练了一部分的权值再次载入。
|
| 72 |
+
# 同时修改下方的 冻结阶段 或者 解冻阶段 的参数,来保证模型epoch的连续性。
|
| 73 |
+
#
|
| 74 |
+
# 当model_path = ''的时候不加载整个模型的权值。
|
| 75 |
+
#
|
| 76 |
+
# 此处使用的是整个模型的权重,因此是在train.py进行加载的,下面的pretrain不影响此处的权值加载。
|
| 77 |
+
# 如果想要让模型从主干的预训练权值开始训练,则设置model_path = '',下面的pretrain = True,此时仅加载主干。
|
| 78 |
+
# 如果想要让模型从0开始训练,则设置model_path = '',下面的pretrain = Fasle,Freeze_Train = Fasle,此时从0开始训练,且没有冻结主干的过程。
|
| 79 |
+
#
|
| 80 |
+
# 一般来讲,网络从0开始的训练效果会很差,因为权值太过随机,特征提取效果不明显,因此非常、非常、非常不建议大家从0开始训练!
|
| 81 |
+
# 如果一定要从0开始,可以了解imagenet数据集,首先训练分类模型,获得网络的主干部分权值,分类模型的 主干部分 和该模型通用,基于此进行训练。
|
| 82 |
+
#----------------------------------------------------------------------------------------------------------------------------#
|
| 83 |
+
model_path = ''
|
| 84 |
+
#------------------------------------------------------#
|
| 85 |
+
# input_shape 输入的shape大小
|
| 86 |
+
#------------------------------------------------------#
|
| 87 |
+
input_shape = [640, 640]
|
| 88 |
+
#---------------------------------------------#
|
| 89 |
+
# vgg
|
| 90 |
+
# resnet50
|
| 91 |
+
#---------------------------------------------#
|
| 92 |
+
backbone = "resnet50"
|
| 93 |
+
#----------------------------------------------------------------------------------------------------------------------------#
|
| 94 |
+
# pretrained 是否使用主干网络的预训练权重,此处使用的是主干的权重,因此是在模型构建的时候进行加载的。
|
| 95 |
+
# 如果设置了model_path,则主干的权值无需加载,pretrained的值无意义。
|
| 96 |
+
# 如果不设置model_path,pretrained = True,此时仅加载主干开始训练。
|
| 97 |
+
# 如果不设置model_path,pretrained = False,Freeze_Train = Fasle,此时从0开始训练,且没有冻结主干的过程。
|
| 98 |
+
#----------------------------------------------------------------------------------------------------------------------------#
|
| 99 |
+
pretrained = False
|
| 100 |
+
#------------------------------------------------------------------------#
|
| 101 |
+
# anchors_size用于设定先验框的大小,每个特征点均存在9个先验框。
|
| 102 |
+
# anchors_size每个数对应3个先验框。
|
| 103 |
+
# 当anchors_size = [8, 16, 32]的时候,生成的先验框宽高约为:
|
| 104 |
+
# [90, 180] ; [180, 360]; [360, 720]; [128, 128];
|
| 105 |
+
# [256, 256]; [512, 512]; [180, 90] ; [360, 180];
|
| 106 |
+
# [720, 360]; 详情查看anchors.py
|
| 107 |
+
# 如果想要检测小物体,可以减小anchors_size靠前的数。
|
| 108 |
+
# 比如设置anchors_size = [4, 16, 32]
|
| 109 |
+
#------------------------------------------------------------------------#
|
| 110 |
+
anchors_size = [8, 16, 32]
|
| 111 |
+
|
| 112 |
+
#----------------------------------------------------------------------------------------------------------------------------#
|
| 113 |
+
# 训练分为两个阶段,分别是冻结阶段和解冻阶段。设置冻结阶段是为了满足机器性能不足的同学的训练需求。
|
| 114 |
+
# 冻结训练需要的显存较小,显卡非常差的情况下,可设置Freeze_Epoch等于UnFreeze_Epoch,此时仅仅进行冻结训练。
|
| 115 |
+
#
|
| 116 |
+
# 在此提供若干参数设置建议,各位训练者根据自己的需求进行灵活调整:
|
| 117 |
+
# (一)从整个模型的预训练权重开始训练:
|
| 118 |
+
# Adam:
|
| 119 |
+
# Init_Epoch = 0,Freeze_Epoch = 50,UnFreeze_Epoch = 100,Freeze_Train = True,optimizer_type = 'adam',Init_lr = 1e-4。(冻结)
|
| 120 |
+
# Init_Epoch = 0,UnFreeze_Epoch = 100,Freeze_Train = False,optimizer_type = 'adam',Init_lr = 1e-4。(不冻结)
|
| 121 |
+
# SGD:
|
| 122 |
+
# Init_Epoch = 0,Freeze_Epoch = 50,UnFreeze_Epoch = 150,Freeze_Train = True,optimizer_type = 'sgd',Init_lr = 1e-2。(冻结)
|
| 123 |
+
# Init_Epoch = 0,UnFreeze_Epoch = 150,Freeze_Train = False,optimizer_type = 'sgd',Init_lr = 1e-2。(不冻结)
|
| 124 |
+
# 其中:UnFreeze_Epoch可以在100-300之间调整。
|
| 125 |
+
# (二)从主干网络的预训练权重开始训练:
|
| 126 |
+
# Adam:
|
| 127 |
+
# Init_Epoch = 0,Freeze_Epoch = 50,UnFreeze_Epoch = 100,Freeze_Train = True,optimizer_type = 'adam',Init_lr = 1e-4。(冻结)
|
| 128 |
+
# Init_Epoch = 0,UnFreeze_Epoch = 100,Freeze_Train = False,optimizer_type = 'adam',Init_lr = 1e-4。(不冻结)
|
| 129 |
+
# SGD:
|
| 130 |
+
# Init_Epoch = 0,Freeze_Epoch = 50,UnFreeze_Epoch = 150,Freeze_Train = True,optimizer_type = 'sgd',Init_lr = 1e-2。(冻结)
|
| 131 |
+
# Init_Epoch = 0,UnFreeze_Epoch = 150,Freeze_Train = False,optimizer_type = 'sgd',Init_lr = 1e-2。(不冻结)
|
| 132 |
+
# 其中:由于从主干网络的预训练权重开始训练,主干的权值不一定适合目标检测,需要更多的训练跳出局部最优解。
|
| 133 |
+
# UnFreeze_Epoch可以在150-300之间调整,YOLOV5和YOLOX均推荐使用300。
|
| 134 |
+
# Adam相较于SGD收敛的快一些。因此UnFreeze_Epoch理论上可以小一点,但依然推荐更多的Epoch。
|
| 135 |
+
# (三)batch_size的设置:
|
| 136 |
+
# 在显卡能够接受的范围内,以大为好。显存不足与数据集大小无关,提示显存不足(OOM或者CUDA out of memory)请调小batch_size。
|
| 137 |
+
# faster rcnn的Batch BatchNormalization层已经冻结,batch_size可以为1
|
| 138 |
+
#----------------------------------------------------------------------------------------------------------------------------#
|
| 139 |
+
#------------------------------------------------------------------#
|
| 140 |
+
# 冻结阶段训练参数
|
| 141 |
+
# 此时模型的主干被冻结了,特征提取网络不发生改变
|
| 142 |
+
# 占用的显存较小,仅对网络进行微调
|
| 143 |
+
# Init_Epoch 模型当前开始的训练世代,其值可以大于Freeze_Epoch,如设置:
|
| 144 |
+
# Init_Epoch = 60、Freeze_Epoch = 50、UnFreeze_Epoch = 100
|
| 145 |
+
# 会跳过冻结阶段,直接从60代开始,并调整对应的学习率。
|
| 146 |
+
# (断点续练时使用)
|
| 147 |
+
# Freeze_Epoch 模型冻结训练的Freeze_Epoch
|
| 148 |
+
# (当Freeze_Train=False时失效)
|
| 149 |
+
# Freeze_batch_size 模型冻结训练的batch_size
|
| 150 |
+
# (当Freeze_Train=False时失效)
|
| 151 |
+
#------------------------------------------------------------------#
|
| 152 |
+
Init_Epoch = 0
|
| 153 |
+
Freeze_Epoch = 50
|
| 154 |
+
Freeze_batch_size = 16
|
| 155 |
+
#------------------------------------------------------------------#
|
| 156 |
+
# 解冻阶段训练参数
|
| 157 |
+
# 此时模型的主干不被冻结了,特征提取网络会发生改变
|
| 158 |
+
# 占用的显存较大,网络所有的参数都会发生改变
|
| 159 |
+
# UnFreeze_Epoch 模型总共训练的epoch
|
| 160 |
+
# SGD需要更长的时间收敛,因此设置较大的UnFreeze_Epoch
|
| 161 |
+
# Adam可以使用相对较小的UnFreeze_Epoch
|
| 162 |
+
# Unfreeze_batch_size 模型在解冻后的batch_size
|
| 163 |
+
#------------------------------------------------------------------#
|
| 164 |
+
UnFreeze_Epoch = 400
|
| 165 |
+
Unfreeze_batch_size = 16
|
| 166 |
+
#------------------------------------------------------------------#
|
| 167 |
+
# Freeze_Train 是否进行冻结训练
|
| 168 |
+
# 默认先冻结主干训练后解冻训练。
|
| 169 |
+
# 如果设置Freeze_Train=False,建议使用优化器为sgd
|
| 170 |
+
#------------------------------------------------------------------#
|
| 171 |
+
Freeze_Train = False
|
| 172 |
+
|
| 173 |
+
#------------------------------------------------------------------#
|
| 174 |
+
# 其它训练参数:学习率、优化器、学习率下降有关
|
| 175 |
+
#------------------------------------------------------------------#
|
| 176 |
+
#------------------------------------------------------------------#
|
| 177 |
+
# Init_lr 模型的最大学习率
|
| 178 |
+
# 当使用Adam优化器时建议设置 Init_lr=1e-4
|
| 179 |
+
# 当使用SGD优化器时建议设置 Init_lr=1e-2
|
| 180 |
+
# Min_lr 模型的最小学习率,默认为最大学习率的0.01
|
| 181 |
+
#------------------------------------------------------------------#
|
| 182 |
+
Init_lr = 0.01
|
| 183 |
+
Min_lr = Init_lr * 0.1
|
| 184 |
+
#------------------------------------------------------------------#
|
| 185 |
+
# optimizer_type 使用到的优化器种类,可选的有adam、sgd
|
| 186 |
+
# 当使用Adam优化器时建议设置 Init_lr=1e-4
|
| 187 |
+
# 当使用SGD优化器时建议设置 Init_lr=1e-2
|
| 188 |
+
# momentum 优化器内部使用到的momentum参数
|
| 189 |
+
# weight_decay 权值衰减,可防止过拟合
|
| 190 |
+
# adam会导致weight_decay错误,使用adam时建议设置为0。
|
| 191 |
+
#------------------------------------------------------------------#
|
| 192 |
+
optimizer_type = "adam"
|
| 193 |
+
momentum = 0.937
|
| 194 |
+
weight_decay = 0.005
|
| 195 |
+
#------------------------------------------------------------------#
|
| 196 |
+
# lr_decay_type 使用到的学习率下降方式,可选的有'step'、'cos'
|
| 197 |
+
#------------------------------------------------------------------#
|
| 198 |
+
lr_decay_type = 'cos'
|
| 199 |
+
#------------------------------------------------------------------#
|
| 200 |
+
# save_period 多少个epoch保存一次权值
|
| 201 |
+
#------------------------------------------------------------------#
|
| 202 |
+
save_period = 5
|
| 203 |
+
#------------------------------------------------------------------#
|
| 204 |
+
# save_dir 权值与日志文件保存的文件夹
|
| 205 |
+
#------------------------------------------------------------------#
|
| 206 |
+
save_dir = 'logs'
|
| 207 |
+
#------------------------------------------------------------------#
|
| 208 |
+
# eval_flag 是否在训练时进行评估,评估对象为验证集
|
| 209 |
+
# 安装pycocotools库后,评估体验更佳。
|
| 210 |
+
# eval_period 代表多少个epoch评估一次,不建议频繁的评估
|
| 211 |
+
# 评估需要消耗较多的时间,频繁评估会导致训练非常慢
|
| 212 |
+
# 此处获得的mAP会与get_map.py获得的会有所不同,原因有二:
|
| 213 |
+
# (一)此处获得的mAP为验证集的mAP。
|
| 214 |
+
# (二)此处设置评估参数较为保守,目的是加快评估速度。
|
| 215 |
+
#------------------------------------------------------------------#
|
| 216 |
+
eval_flag = True
|
| 217 |
+
eval_period = 5
|
| 218 |
+
#------------------------------------------------------------------#
|
| 219 |
+
# num_workers 用于设置是否使用多线程读取数据,1代表关闭多线程
|
| 220 |
+
# 开启后会加快数据读取速度,但是会占用更多内存
|
| 221 |
+
# 在IO为瓶颈的时候再开启多线程,即GPU运算速度远大于读取图片的速度。
|
| 222 |
+
#------------------------------------------------------------------#
|
| 223 |
+
num_workers = 4
|
| 224 |
+
#----------------------------------------------------#
|
| 225 |
+
# 获得图片路径和标签
|
| 226 |
+
#----------------------------------------------------#
|
| 227 |
+
train_annotation_path = '/home/lab/LJ/wampee/faster-rcnn-pytorch-master/VOCdevkit/VOC2007/ImageSets/Main/train.txt'
|
| 228 |
+
val_annotation_path = '/home/lab/LJ/wampee/faster-rcnn-pytorch-master/VOCdevkit/VOC2007/ImageSets/Main/val.txt'
|
| 229 |
+
|
| 230 |
+
#----------------------------------------------------#
|
| 231 |
+
# 获取classes和anchor
|
| 232 |
+
#----------------------------------------------------#
|
| 233 |
+
class_names, num_classes = get_classes(classes_path)
|
| 234 |
+
|
| 235 |
+
#------------------------------------------------------#
|
| 236 |
+
# 设置用到的显卡
|
| 237 |
+
#------------------------------------------------------#
|
| 238 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = ','.join(str(x) for x in train_gpu)
|
| 239 |
+
ngpus_per_node = len(train_gpu)
|
| 240 |
+
print('Number of devices: {}'.format(ngpus_per_node))
|
| 241 |
+
seed_everything(seed)
|
| 242 |
+
|
| 243 |
+
model = FasterRCNN(num_classes, anchor_scales = anchors_size, backbone = backbone, pretrained = pretrained)
|
| 244 |
+
if not pretrained:
|
| 245 |
+
weights_init(model)
|
| 246 |
+
if model_path != '':
|
| 247 |
+
#------------------------------------------------------#
|
| 248 |
+
# 权值文件请看README,百度网盘下载
|
| 249 |
+
#------------------------------------------------------#
|
| 250 |
+
print('Load weights {}.'.format(model_path))
|
| 251 |
+
|
| 252 |
+
#------------------------------------------------------#
|
| 253 |
+
# 根据预训练权重的Key和模型的Key进行加载
|
| 254 |
+
#------------------------------------------------------#
|
| 255 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 256 |
+
model_dict = model.state_dict()
|
| 257 |
+
pretrained_dict = torch.load(model_path, map_location = device)
|
| 258 |
+
load_key, no_load_key, temp_dict = [], [], {}
|
| 259 |
+
for k, v in pretrained_dict.items():
|
| 260 |
+
if k in model_dict.keys() and np.shape(model_dict[k]) == np.shape(v):
|
| 261 |
+
temp_dict[k] = v
|
| 262 |
+
load_key.append(k)
|
| 263 |
+
else:
|
| 264 |
+
no_load_key.append(k)
|
| 265 |
+
model_dict.update(temp_dict)
|
| 266 |
+
model.load_state_dict(model_dict)
|
| 267 |
+
#------------------------------------------------------#
|
| 268 |
+
# 显示没有匹配上的Key
|
| 269 |
+
#------------------------------------------------------#
|
| 270 |
+
print("\nSuccessful Load Key:", str(load_key)[:500], "……\nSuccessful Load Key Num:", len(load_key))
|
| 271 |
+
print("\nFail To Load Key:", str(no_load_key)[:500], "……\nFail To Load Key num:", len(no_load_key))
|
| 272 |
+
print("\n\033[1;33;44m温馨提示,head部分没有载入是正常现象,Backbone部分没有载入是错误的。\033[0m")
|
| 273 |
+
|
| 274 |
+
#----------------------#
|
| 275 |
+
# 记录Loss
|
| 276 |
+
#----------------------#
|
| 277 |
+
time_str = datetime.datetime.strftime(datetime.datetime.now(),'%Y_%m_%d_%H_%M_%S')
|
| 278 |
+
log_dir = os.path.join(save_dir, "loss_" + str(time_str))
|
| 279 |
+
loss_history = LossHistory(log_dir, model, input_shape=input_shape)
|
| 280 |
+
|
| 281 |
+
#------------------------------------------------------------------#
|
| 282 |
+
# torch 1.2不支持amp,建议使用torch 1.7.1及以上正确使用fp16
|
| 283 |
+
# 因此torch1.2这里显示"could not be resolve"
|
| 284 |
+
#------------------------------------------------------------------#
|
| 285 |
+
if fp16:
|
| 286 |
+
from torch.cuda.amp import GradScaler as GradScaler
|
| 287 |
+
scaler = GradScaler()
|
| 288 |
+
else:
|
| 289 |
+
scaler = None
|
| 290 |
+
|
| 291 |
+
model_train = model.train()
|
| 292 |
+
if Cuda:
|
| 293 |
+
model_train = torch.nn.DataParallel(model_train)
|
| 294 |
+
cudnn.benchmark = True
|
| 295 |
+
model_train = model_train.cuda()
|
| 296 |
+
|
| 297 |
+
#---------------------------#
|
| 298 |
+
# 读取数据集对应的txt
|
| 299 |
+
#---------------------------#
|
| 300 |
+
with open(train_annotation_path, encoding='utf-8') as f:
|
| 301 |
+
train_lines = f.readlines()
|
| 302 |
+
with open(val_annotation_path, encoding='utf-8') as f:
|
| 303 |
+
val_lines = f.readlines()
|
| 304 |
+
num_train = len(train_lines)
|
| 305 |
+
num_val = len(val_lines)
|
| 306 |
+
|
| 307 |
+
show_config(
|
| 308 |
+
classes_path = classes_path, model_path = model_path, input_shape = input_shape, \
|
| 309 |
+
Init_Epoch = Init_Epoch, Freeze_Epoch = Freeze_Epoch, UnFreeze_Epoch = UnFreeze_Epoch, Freeze_batch_size = Freeze_batch_size, Unfreeze_batch_size = Unfreeze_batch_size, Freeze_Train = Freeze_Train, \
|
| 310 |
+
Init_lr = Init_lr, Min_lr = Min_lr, optimizer_type = optimizer_type, momentum = momentum, lr_decay_type = lr_decay_type, \
|
| 311 |
+
save_period = save_period, save_dir = save_dir, num_workers = num_workers, num_train = num_train, num_val = num_val
|
| 312 |
+
)
|
| 313 |
+
#---------------------------------------------------------#
|
| 314 |
+
# 总训练世代指的是遍历全部数据的总次数
|
| 315 |
+
# 总训练步长指的是梯度下降的总次数
|
| 316 |
+
# 每个训练世代包含若干训练步长,每个训练步长进行一次梯度下降。
|
| 317 |
+
# 此处仅建议最低训练世代,上不封顶,计算时只考虑了解冻部分
|
| 318 |
+
#----------------------------------------------------------#
|
| 319 |
+
wanted_step = 5e4 if optimizer_type == "sgd" else 1.5e4
|
| 320 |
+
total_step = num_train // Unfreeze_batch_size * UnFreeze_Epoch
|
| 321 |
+
if total_step <= wanted_step:
|
| 322 |
+
if num_train // Unfreeze_batch_size == 0:
|
| 323 |
+
raise ValueError('数据集过小,无法进行训练,请扩充数据集。')
|
| 324 |
+
wanted_epoch = wanted_step // (num_train // Unfreeze_batch_size) + 1
|
| 325 |
+
print("\n\033[1;33;44m[Warning] 使用%s优化器时,建议将训练总步长设置到%d以上。\033[0m"%(optimizer_type, wanted_step))
|
| 326 |
+
print("\033[1;33;44m[Warning] 本次运行的总训练数据量为%d,Unfreeze_batch_size为%d,共训练%d个Epoch,计算出总训练步长为%d。\033[0m"%(num_train, Unfreeze_batch_size, UnFreeze_Epoch, total_step))
|
| 327 |
+
print("\033[1;33;44m[Warning] 由于总训练步长为%d,小于建议总步长%d,建议设置总世代为%d。\033[0m"%(total_step, wanted_step, wanted_epoch))
|
| 328 |
+
|
| 329 |
+
#------------------------------------------------------#
|
| 330 |
+
# 主干特征提取网络特征通用,冻结训练可以加快训练速度
|
| 331 |
+
# 也可以在训练初期防止权值被破坏。
|
| 332 |
+
# Init_Epoch为起始世代
|
| 333 |
+
# Freeze_Epoch为冻结训练的世代
|
| 334 |
+
# UnFreeze_Epoch总训练世代
|
| 335 |
+
# 提示OOM或者显存不足请调小Batch_size
|
| 336 |
+
#------------------------------------------------------#
|
| 337 |
+
if True:
|
| 338 |
+
UnFreeze_flag = False
|
| 339 |
+
#------------------------------------#
|
| 340 |
+
# 冻结一定部分训练
|
| 341 |
+
#------------------------------------#
|
| 342 |
+
if Freeze_Train:
|
| 343 |
+
for param in model.extractor.parameters():
|
| 344 |
+
param.requires_grad = False
|
| 345 |
+
# ------------------------------------#
|
| 346 |
+
# 冻结bn层
|
| 347 |
+
# ------------------------------------#
|
| 348 |
+
model.freeze_bn()
|
| 349 |
+
|
| 350 |
+
#-------------------------------------------------------------------#
|
| 351 |
+
# 如果不冻结训练的话,直接设置batch_size为Unfreeze_batch_size
|
| 352 |
+
#-------------------------------------------------------------------#
|
| 353 |
+
batch_size = Freeze_batch_size if Freeze_Train else Unfreeze_batch_size
|
| 354 |
+
|
| 355 |
+
#-------------------------------------------------------------------#
|
| 356 |
+
# 判断当前batch_size,自适应调整学习率
|
| 357 |
+
#-------------------------------------------------------------------#
|
| 358 |
+
nbs = 16
|
| 359 |
+
lr_limit_max = 1e-4 if optimizer_type == 'adam' else 5e-2
|
| 360 |
+
lr_limit_min = 1e-4 if optimizer_type == 'adam' else 5e-4
|
| 361 |
+
Init_lr_fit = min(max(batch_size / nbs * Init_lr, lr_limit_min), lr_limit_max)
|
| 362 |
+
Min_lr_fit = min(max(batch_size / nbs * Min_lr, lr_limit_min * 1e-2), lr_limit_max * 1e-2)
|
| 363 |
+
|
| 364 |
+
#---------------------------------------#
|
| 365 |
+
# 根据optimizer_type选择优化器
|
| 366 |
+
#---------------------------------------#
|
| 367 |
+
optimizer = {
|
| 368 |
+
'adam' : optim.Adam(model.parameters(), Init_lr_fit, betas = (momentum, 0.999), weight_decay = weight_decay),
|
| 369 |
+
'sgd' : optim.SGD(model.parameters(), Init_lr_fit, momentum = momentum, nesterov=True, weight_decay = weight_decay)
|
| 370 |
+
}[optimizer_type]
|
| 371 |
+
|
| 372 |
+
#---------------------------------------#
|
| 373 |
+
# 获得学习率下降的公式
|
| 374 |
+
#---------------------------------------#
|
| 375 |
+
lr_scheduler_func = get_lr_scheduler(lr_decay_type, Init_lr_fit, Min_lr_fit, UnFreeze_Epoch)
|
| 376 |
+
|
| 377 |
+
#---------------------------------------#
|
| 378 |
+
# 判断每一个世代的长度
|
| 379 |
+
#---------------------------------------#
|
| 380 |
+
epoch_step = num_train // batch_size
|
| 381 |
+
epoch_step_val = num_val // batch_size
|
| 382 |
+
|
| 383 |
+
if epoch_step == 0 or epoch_step_val == 0:
|
| 384 |
+
raise ValueError("数据集过小,无法继续进行训练,请扩充数据集。")
|
| 385 |
+
|
| 386 |
+
train_dataset = FRCNNDataset(train_lines, input_shape, train = True)
|
| 387 |
+
val_dataset = FRCNNDataset(val_lines, input_shape, train = False)
|
| 388 |
+
|
| 389 |
+
gen = DataLoader(train_dataset, shuffle = True, batch_size = batch_size, num_workers = num_workers, pin_memory=True,
|
| 390 |
+
drop_last=True, collate_fn=frcnn_dataset_collate,
|
| 391 |
+
worker_init_fn=partial(worker_init_fn, rank=0, seed=seed))
|
| 392 |
+
gen_val = DataLoader(val_dataset , shuffle = True, batch_size = batch_size, num_workers = num_workers, pin_memory=True,
|
| 393 |
+
drop_last=True, collate_fn=frcnn_dataset_collate,
|
| 394 |
+
worker_init_fn=partial(worker_init_fn, rank=0, seed=seed))
|
| 395 |
+
|
| 396 |
+
train_util = FasterRCNNTrainer(model_train, optimizer)
|
| 397 |
+
#----------------------#
|
| 398 |
+
# 记录eval的map曲线
|
| 399 |
+
#----------------------#
|
| 400 |
+
eval_callback = EvalCallback(model_train, input_shape, class_names, num_classes, val_lines, log_dir, Cuda, \
|
| 401 |
+
eval_flag=eval_flag, period=eval_period)
|
| 402 |
+
|
| 403 |
+
#---------------------------------------#
|
| 404 |
+
# 开始模型训练
|
| 405 |
+
#---------------------------------------#
|
| 406 |
+
for epoch in range(Init_Epoch, UnFreeze_Epoch):
|
| 407 |
+
#---------------------------------------#
|
| 408 |
+
# 如果模型有冻结学习部分
|
| 409 |
+
# 则解冻,并设置参数
|
| 410 |
+
#---------------------------------------#
|
| 411 |
+
if epoch >= Freeze_Epoch and not UnFreeze_flag and Freeze_Train:
|
| 412 |
+
batch_size = Unfreeze_batch_size
|
| 413 |
+
|
| 414 |
+
#-------------------------------------------------------------------#
|
| 415 |
+
# 判断当前batch_size,自适应调整学习率
|
| 416 |
+
#-------------------------------------------------------------------#
|
| 417 |
+
nbs = 16
|
| 418 |
+
lr_limit_max = 1e-4 if optimizer_type == 'adam' else 5e-2
|
| 419 |
+
lr_limit_min = 1e-4 if optimizer_type == 'adam' else 5e-4
|
| 420 |
+
Init_lr_fit = min(max(batch_size / nbs * Init_lr, lr_limit_min), lr_limit_max)
|
| 421 |
+
Min_lr_fit = min(max(batch_size / nbs * Min_lr, lr_limit_min * 1e-2), lr_limit_max * 1e-2)
|
| 422 |
+
#---------------------------------------#
|
| 423 |
+
# 获得学习率下降的公式
|
| 424 |
+
#---------------------------------------#
|
| 425 |
+
lr_scheduler_func = get_lr_scheduler(lr_decay_type, Init_lr_fit, Min_lr_fit, UnFreeze_Epoch)
|
| 426 |
+
|
| 427 |
+
for param in model.extractor.parameters():
|
| 428 |
+
param.requires_grad = True
|
| 429 |
+
# ------------------------------------#
|
| 430 |
+
# 冻结bn层
|
| 431 |
+
# ------------------------------------#
|
| 432 |
+
model.freeze_bn()
|
| 433 |
+
|
| 434 |
+
epoch_step = num_train // batch_size
|
| 435 |
+
epoch_step_val = num_val // batch_size
|
| 436 |
+
|
| 437 |
+
if epoch_step == 0 or epoch_step_val == 0:
|
| 438 |
+
raise ValueError("数据集过小,无法继续进行训练,请扩充数据集。")
|
| 439 |
+
|
| 440 |
+
gen = DataLoader(train_dataset, shuffle = True, batch_size = batch_size, num_workers = num_workers, pin_memory=True,
|
| 441 |
+
drop_last=True, collate_fn=frcnn_dataset_collate,
|
| 442 |
+
worker_init_fn=partial(worker_init_fn, rank=0, seed=seed))
|
| 443 |
+
gen_val = DataLoader(val_dataset , shuffle = True, batch_size = batch_size, num_workers = num_workers, pin_memory=True,
|
| 444 |
+
drop_last=True, collate_fn=frcnn_dataset_collate,
|
| 445 |
+
worker_init_fn=partial(worker_init_fn, rank=0, seed=seed))
|
| 446 |
+
|
| 447 |
+
UnFreeze_flag = True
|
| 448 |
+
|
| 449 |
+
set_optimizer_lr(optimizer, lr_scheduler_func, epoch)
|
| 450 |
+
|
| 451 |
+
fit_one_epoch(model, train_util, loss_history, eval_callback, optimizer, epoch, epoch_step, epoch_step_val, gen, gen_val, UnFreeze_Epoch, Cuda, fp16, scaler, save_period, save_dir)
|
| 452 |
+
|
| 453 |
+
loss_history.writer.close()
|
faster-rcnn-pytorch-master/utils/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
#
|
faster-rcnn-pytorch-master/utils/anchors.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
#--------------------------------------------#
|
| 4 |
+
# 生成基础的先验框
|
| 5 |
+
#--------------------------------------------#
|
| 6 |
+
def generate_anchor_base(base_size=16, ratios=[0.5, 1, 2], anchor_scales=[8, 16, 32]):
|
| 7 |
+
anchor_base = np.zeros((len(ratios) * len(anchor_scales), 4), dtype=np.float32)
|
| 8 |
+
for i in range(len(ratios)):
|
| 9 |
+
for j in range(len(anchor_scales)):
|
| 10 |
+
h = base_size * anchor_scales[j] * np.sqrt(ratios[i])
|
| 11 |
+
w = base_size * anchor_scales[j] * np.sqrt(1. / ratios[i])
|
| 12 |
+
|
| 13 |
+
index = i * len(anchor_scales) + j
|
| 14 |
+
anchor_base[index, 0] = - h / 2.
|
| 15 |
+
anchor_base[index, 1] = - w / 2.
|
| 16 |
+
anchor_base[index, 2] = h / 2.
|
| 17 |
+
anchor_base[index, 3] = w / 2.
|
| 18 |
+
return anchor_base
|
| 19 |
+
|
| 20 |
+
#--------------------------------------------#
|
| 21 |
+
# 对基础先验框进行拓展对应到所有特征点上
|
| 22 |
+
#--------------------------------------------#
|
| 23 |
+
def _enumerate_shifted_anchor(anchor_base, feat_stride, height, width):
|
| 24 |
+
#---------------------------------#
|
| 25 |
+
# 计算网格中心点
|
| 26 |
+
#---------------------------------#
|
| 27 |
+
shift_x = np.arange(0, width * feat_stride, feat_stride)
|
| 28 |
+
shift_y = np.arange(0, height * feat_stride, feat_stride)
|
| 29 |
+
shift_x, shift_y = np.meshgrid(shift_x, shift_y)
|
| 30 |
+
shift = np.stack((shift_x.ravel(), shift_y.ravel(), shift_x.ravel(), shift_y.ravel(),), axis=1)
|
| 31 |
+
|
| 32 |
+
#---------------------------------#
|
| 33 |
+
# 每个网格点上的9个先验框
|
| 34 |
+
#---------------------------------#
|
| 35 |
+
A = anchor_base.shape[0]
|
| 36 |
+
K = shift.shape[0]
|
| 37 |
+
anchor = anchor_base.reshape((1, A, 4)) + shift.reshape((K, 1, 4))
|
| 38 |
+
#---------------------------------#
|
| 39 |
+
# 所有的先验框
|
| 40 |
+
#---------------------------------#
|
| 41 |
+
anchor = anchor.reshape((K * A, 4)).astype(np.float32)
|
| 42 |
+
return anchor
|
| 43 |
+
|
| 44 |
+
if __name__ == "__main__":
|
| 45 |
+
import matplotlib.pyplot as plt
|
| 46 |
+
nine_anchors = generate_anchor_base()
|
| 47 |
+
print(nine_anchors)
|
| 48 |
+
|
| 49 |
+
height, width, feat_stride = 38,38,16
|
| 50 |
+
anchors_all = _enumerate_shifted_anchor(nine_anchors, feat_stride, height, width)
|
| 51 |
+
print(np.shape(anchors_all))
|
| 52 |
+
|
| 53 |
+
fig = plt.figure()
|
| 54 |
+
ax = fig.add_subplot(111)
|
| 55 |
+
plt.ylim(-300,900)
|
| 56 |
+
plt.xlim(-300,900)
|
| 57 |
+
shift_x = np.arange(0, width * feat_stride, feat_stride)
|
| 58 |
+
shift_y = np.arange(0, height * feat_stride, feat_stride)
|
| 59 |
+
shift_x, shift_y = np.meshgrid(shift_x, shift_y)
|
| 60 |
+
plt.scatter(shift_x,shift_y)
|
| 61 |
+
box_widths = anchors_all[:,2]-anchors_all[:,0]
|
| 62 |
+
box_heights = anchors_all[:,3]-anchors_all[:,1]
|
| 63 |
+
|
| 64 |
+
for i in [108, 109, 110, 111, 112, 113, 114, 115, 116]:
|
| 65 |
+
rect = plt.Rectangle([anchors_all[i, 0],anchors_all[i, 1]],box_widths[i],box_heights[i],color="r",fill=False)
|
| 66 |
+
ax.add_patch(rect)
|
| 67 |
+
plt.show()
|
faster-rcnn-pytorch-master/utils/callbacks.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import matplotlib
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
matplotlib.use('Agg')
|
| 7 |
+
from matplotlib import pyplot as plt
|
| 8 |
+
import scipy.signal
|
| 9 |
+
|
| 10 |
+
import shutil
|
| 11 |
+
import numpy as np
|
| 12 |
+
from PIL import Image
|
| 13 |
+
from torch.utils.tensorboard import SummaryWriter
|
| 14 |
+
from tqdm import tqdm
|
| 15 |
+
|
| 16 |
+
from .utils import cvtColor, resize_image, preprocess_input, get_new_img_size
|
| 17 |
+
from .utils_bbox import DecodeBox
|
| 18 |
+
from .utils_map import get_coco_map, get_map
|
| 19 |
+
|
| 20 |
+
class LossHistory():
|
| 21 |
+
def __init__(self, log_dir, model, input_shape):
|
| 22 |
+
self.log_dir = log_dir
|
| 23 |
+
self.losses = []
|
| 24 |
+
self.val_loss = []
|
| 25 |
+
|
| 26 |
+
os.makedirs(self.log_dir)
|
| 27 |
+
self.writer = SummaryWriter(self.log_dir)
|
| 28 |
+
# try:
|
| 29 |
+
# dummy_input = torch.randn(2, 3, input_shape[0], input_shape[1])
|
| 30 |
+
# self.writer.add_graph(model, dummy_input)
|
| 31 |
+
# except:
|
| 32 |
+
# pass
|
| 33 |
+
|
| 34 |
+
def append_loss(self, epoch, loss, val_loss):
|
| 35 |
+
if not os.path.exists(self.log_dir):
|
| 36 |
+
os.makedirs(self.log_dir)
|
| 37 |
+
|
| 38 |
+
self.losses.append(loss)
|
| 39 |
+
self.val_loss.append(val_loss)
|
| 40 |
+
|
| 41 |
+
with open(os.path.join(self.log_dir, "epoch_loss.txt"), 'a') as f:
|
| 42 |
+
f.write(str(loss))
|
| 43 |
+
f.write("\n")
|
| 44 |
+
with open(os.path.join(self.log_dir, "epoch_val_loss.txt"), 'a') as f:
|
| 45 |
+
f.write(str(val_loss))
|
| 46 |
+
f.write("\n")
|
| 47 |
+
|
| 48 |
+
self.writer.add_scalar('loss', loss, epoch)
|
| 49 |
+
self.writer.add_scalar('val_loss', val_loss, epoch)
|
| 50 |
+
self.loss_plot()
|
| 51 |
+
|
| 52 |
+
def loss_plot(self):
|
| 53 |
+
iters = range(len(self.losses))
|
| 54 |
+
|
| 55 |
+
plt.figure()
|
| 56 |
+
plt.plot(iters, self.losses, 'red', linewidth = 2, label='train loss')
|
| 57 |
+
plt.plot(iters, self.val_loss, 'coral', linewidth = 2, label='val loss')
|
| 58 |
+
try:
|
| 59 |
+
if len(self.losses) < 25:
|
| 60 |
+
num = 5
|
| 61 |
+
else:
|
| 62 |
+
num = 15
|
| 63 |
+
|
| 64 |
+
plt.plot(iters, scipy.signal.savgol_filter(self.losses, num, 3), 'green', linestyle = '--', linewidth = 2, label='smooth train loss')
|
| 65 |
+
plt.plot(iters, scipy.signal.savgol_filter(self.val_loss, num, 3), '#8B4513', linestyle = '--', linewidth = 2, label='smooth val loss')
|
| 66 |
+
except:
|
| 67 |
+
pass
|
| 68 |
+
|
| 69 |
+
plt.grid(True)
|
| 70 |
+
plt.xlabel('Epoch')
|
| 71 |
+
plt.ylabel('Loss')
|
| 72 |
+
plt.legend(loc="upper right")
|
| 73 |
+
|
| 74 |
+
plt.savefig(os.path.join(self.log_dir, "epoch_loss.png"))
|
| 75 |
+
|
| 76 |
+
plt.cla()
|
| 77 |
+
plt.close("all")
|
| 78 |
+
|
| 79 |
+
class EvalCallback():
|
| 80 |
+
def __init__(self, net, input_shape, class_names, num_classes, val_lines, log_dir, cuda, \
|
| 81 |
+
map_out_path=".temp_map_out", max_boxes=100, confidence=0.05, nms_iou=0.5, letterbox_image=True, MINOVERLAP=0.5, eval_flag=True, period=1):
|
| 82 |
+
super(EvalCallback, self).__init__()
|
| 83 |
+
|
| 84 |
+
self.net = net
|
| 85 |
+
self.input_shape = input_shape
|
| 86 |
+
self.class_names = class_names
|
| 87 |
+
self.num_classes = num_classes
|
| 88 |
+
self.val_lines = val_lines
|
| 89 |
+
self.log_dir = log_dir
|
| 90 |
+
self.cuda = cuda
|
| 91 |
+
self.map_out_path = map_out_path
|
| 92 |
+
self.max_boxes = max_boxes
|
| 93 |
+
self.confidence = confidence
|
| 94 |
+
self.nms_iou = nms_iou
|
| 95 |
+
self.letterbox_image = letterbox_image
|
| 96 |
+
self.MINOVERLAP = MINOVERLAP
|
| 97 |
+
self.eval_flag = eval_flag
|
| 98 |
+
self.period = period
|
| 99 |
+
|
| 100 |
+
self.std = torch.Tensor([0.1, 0.1, 0.2, 0.2]).repeat(self.num_classes + 1)[None]
|
| 101 |
+
if self.cuda:
|
| 102 |
+
self.std = self.std.cuda()
|
| 103 |
+
self.bbox_util = DecodeBox(self.std, self.num_classes)
|
| 104 |
+
|
| 105 |
+
self.maps = [0]
|
| 106 |
+
self.epoches = [0]
|
| 107 |
+
if self.eval_flag:
|
| 108 |
+
with open(os.path.join(self.log_dir, "epoch_map.txt"), 'a') as f:
|
| 109 |
+
f.write(str(0))
|
| 110 |
+
f.write("\n")
|
| 111 |
+
|
| 112 |
+
#---------------------------------------------------#
|
| 113 |
+
# 检测图片
|
| 114 |
+
#---------------------------------------------------#
|
| 115 |
+
def get_map_txt(self, image_id, image, class_names, map_out_path):
|
| 116 |
+
f = open(os.path.join(map_out_path, "detection-results/"+image_id+".txt"),"w")
|
| 117 |
+
#---------------------------------------------------#
|
| 118 |
+
# 计算输入图片的高和宽
|
| 119 |
+
#---------------------------------------------------#
|
| 120 |
+
image_shape = np.array(np.shape(image)[0:2])
|
| 121 |
+
input_shape = get_new_img_size(image_shape[0], image_shape[1])
|
| 122 |
+
#---------------------------------------------------------#
|
| 123 |
+
# 在这里将图像转换成RGB图像,防止灰度图在预测时报错。
|
| 124 |
+
# 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
|
| 125 |
+
#---------------------------------------------------------#
|
| 126 |
+
image = cvtColor(image)
|
| 127 |
+
|
| 128 |
+
#---------------------------------------------------------#
|
| 129 |
+
# 给原图像进行resize,resize到短边为600的大小上
|
| 130 |
+
#---------------------------------------------------------#
|
| 131 |
+
image_data = resize_image(image, [input_shape[1], input_shape[0]])
|
| 132 |
+
#---------------------------------------------------------#
|
| 133 |
+
# 添加上batch_size维度
|
| 134 |
+
#---------------------------------------------------------#
|
| 135 |
+
image_data = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)
|
| 136 |
+
|
| 137 |
+
with torch.no_grad():
|
| 138 |
+
images = torch.from_numpy(image_data)
|
| 139 |
+
if self.cuda:
|
| 140 |
+
images = images.cuda()
|
| 141 |
+
|
| 142 |
+
roi_cls_locs, roi_scores, rois, _ = self.net(images)
|
| 143 |
+
#-------------------------------------------------------------#
|
| 144 |
+
# 利用classifier的预测结果对建议框进行解码,获得预测框
|
| 145 |
+
#-------------------------------------------------------------#
|
| 146 |
+
results = self.bbox_util.forward(roi_cls_locs, roi_scores, rois, image_shape, input_shape,
|
| 147 |
+
nms_iou = self.nms_iou, confidence = self.confidence)
|
| 148 |
+
#--------------------------------------#
|
| 149 |
+
# 如果没有检测到物体,则返回原图
|
| 150 |
+
#--------------------------------------#
|
| 151 |
+
if len(results[0]) <= 0:
|
| 152 |
+
return
|
| 153 |
+
|
| 154 |
+
top_label = np.array(results[0][:, 5], dtype = 'int32')
|
| 155 |
+
top_conf = results[0][:, 4]
|
| 156 |
+
top_boxes = results[0][:, :4]
|
| 157 |
+
|
| 158 |
+
top_100 = np.argsort(top_conf)[::-1][:self.max_boxes]
|
| 159 |
+
top_boxes = top_boxes[top_100]
|
| 160 |
+
top_conf = top_conf[top_100]
|
| 161 |
+
top_label = top_label[top_100]
|
| 162 |
+
|
| 163 |
+
for i, c in list(enumerate(top_label)):
|
| 164 |
+
predicted_class = self.class_names[int(c)]
|
| 165 |
+
box = top_boxes[i]
|
| 166 |
+
score = str(top_conf[i])
|
| 167 |
+
|
| 168 |
+
top, left, bottom, right = box
|
| 169 |
+
if predicted_class not in class_names:
|
| 170 |
+
continue
|
| 171 |
+
|
| 172 |
+
f.write("%s %s %s %s %s %s\n" % (predicted_class, score[:6], str(int(left)), str(int(top)), str(int(right)),str(int(bottom))))
|
| 173 |
+
|
| 174 |
+
f.close()
|
| 175 |
+
return
|
| 176 |
+
|
| 177 |
+
def on_epoch_end(self, epoch):
|
| 178 |
+
if epoch % self.period == 0 and self.eval_flag:
|
| 179 |
+
if not os.path.exists(self.map_out_path):
|
| 180 |
+
os.makedirs(self.map_out_path)
|
| 181 |
+
if not os.path.exists(os.path.join(self.map_out_path, "ground-truth")):
|
| 182 |
+
os.makedirs(os.path.join(self.map_out_path, "ground-truth"))
|
| 183 |
+
if not os.path.exists(os.path.join(self.map_out_path, "detection-results")):
|
| 184 |
+
os.makedirs(os.path.join(self.map_out_path, "detection-results"))
|
| 185 |
+
print("Get map.")
|
| 186 |
+
for annotation_line in tqdm(self.val_lines):
|
| 187 |
+
line = annotation_line.split()
|
| 188 |
+
image_id = os.path.basename(line[0]).split('.')[0]
|
| 189 |
+
#------------------------------#
|
| 190 |
+
# 读取图像并转换成RGB图像
|
| 191 |
+
#------------------------------#
|
| 192 |
+
image = Image.open(line[0])
|
| 193 |
+
#------------------------------#
|
| 194 |
+
# 获得预测框
|
| 195 |
+
#------------------------------#
|
| 196 |
+
gt_boxes = np.array([np.array(list(map(int,box.split(',')))) for box in line[1:]])
|
| 197 |
+
#------------------------------#
|
| 198 |
+
# 获得预测txt
|
| 199 |
+
#------------------------------#
|
| 200 |
+
self.get_map_txt(image_id, image, self.class_names, self.map_out_path)
|
| 201 |
+
|
| 202 |
+
#------------------------------#
|
| 203 |
+
# 获得真实框txt
|
| 204 |
+
#------------------------------#
|
| 205 |
+
with open(os.path.join(self.map_out_path, "ground-truth/"+image_id+".txt"), "w") as new_f:
|
| 206 |
+
for box in gt_boxes:
|
| 207 |
+
left, top, right, bottom, obj = box
|
| 208 |
+
obj_name = self.class_names[obj]
|
| 209 |
+
new_f.write("%s %s %s %s %s\n" % (obj_name, left, top, right, bottom))
|
| 210 |
+
|
| 211 |
+
print("Calculate Map.")
|
| 212 |
+
try:
|
| 213 |
+
temp_map = get_coco_map(class_names = self.class_names, path = self.map_out_path)[1]
|
| 214 |
+
except:
|
| 215 |
+
temp_map = get_map(self.MINOVERLAP, False, path = self.map_out_path)
|
| 216 |
+
self.maps.append(temp_map)
|
| 217 |
+
self.epoches.append(epoch)
|
| 218 |
+
|
| 219 |
+
with open(os.path.join(self.log_dir, "epoch_map.txt"), 'a') as f:
|
| 220 |
+
f.write(str(temp_map))
|
| 221 |
+
f.write("\n")
|
| 222 |
+
|
| 223 |
+
plt.figure()
|
| 224 |
+
plt.plot(self.epoches, self.maps, 'red', linewidth = 2, label='train map')
|
| 225 |
+
|
| 226 |
+
plt.grid(True)
|
| 227 |
+
plt.xlabel('Epoch')
|
| 228 |
+
plt.ylabel('Map %s'%str(self.MINOVERLAP))
|
| 229 |
+
plt.title('A Map Curve')
|
| 230 |
+
plt.legend(loc="upper right")
|
| 231 |
+
|
| 232 |
+
plt.savefig(os.path.join(self.log_dir, "epoch_map.png"))
|
| 233 |
+
plt.cla()
|
| 234 |
+
plt.close("all")
|
| 235 |
+
|
| 236 |
+
print("Get map done.")
|
| 237 |
+
shutil.rmtree(self.map_out_path)
|
faster-rcnn-pytorch-master/utils/dataloader.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import numpy as np
|
| 3 |
+
import torch
|
| 4 |
+
from PIL import Image
|
| 5 |
+
from torch.utils.data.dataset import Dataset
|
| 6 |
+
|
| 7 |
+
from utils.utils import cvtColor, preprocess_input
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class FRCNNDataset(Dataset):
|
| 11 |
+
def __init__(self, annotation_lines, input_shape = [600, 600], train = True):
|
| 12 |
+
self.annotation_lines = annotation_lines
|
| 13 |
+
self.length = len(annotation_lines)
|
| 14 |
+
self.input_shape = input_shape
|
| 15 |
+
self.train = train
|
| 16 |
+
|
| 17 |
+
def __len__(self):
|
| 18 |
+
return self.length
|
| 19 |
+
|
| 20 |
+
def __getitem__(self, index):
|
| 21 |
+
index = index % self.length
|
| 22 |
+
#---------------------------------------------------#
|
| 23 |
+
# 训练时进行数据的随机增强
|
| 24 |
+
# 验证时不进行数据的随机增强
|
| 25 |
+
#---------------------------------------------------#
|
| 26 |
+
image, y = self.get_random_data(self.annotation_lines[index], self.input_shape[0:2], random = self.train)
|
| 27 |
+
image = np.transpose(preprocess_input(np.array(image, dtype=np.float32)), (2, 0, 1))
|
| 28 |
+
box_data = np.zeros((len(y), 5))
|
| 29 |
+
if len(y) > 0:
|
| 30 |
+
box_data[:len(y)] = y
|
| 31 |
+
|
| 32 |
+
box = box_data[:, :4]
|
| 33 |
+
label = box_data[:, -1]
|
| 34 |
+
return image, box, label
|
| 35 |
+
|
| 36 |
+
def rand(self, a=0, b=1):
|
| 37 |
+
return np.random.rand()*(b-a) + a
|
| 38 |
+
|
| 39 |
+
def get_random_data(self, annotation_line, input_shape, jitter=.3, hue=.1, sat=0.7, val=0.4, random=True):
|
| 40 |
+
line = annotation_line.split()
|
| 41 |
+
#------------------------------#
|
| 42 |
+
# 读取图像并转换成RGB图像
|
| 43 |
+
#------------------------------#
|
| 44 |
+
image = Image.open(line[0])
|
| 45 |
+
image = cvtColor(image)
|
| 46 |
+
#------------------------------#
|
| 47 |
+
# 获得图像的高宽与目标高宽
|
| 48 |
+
#------------------------------#
|
| 49 |
+
iw, ih = image.size
|
| 50 |
+
h, w = input_shape
|
| 51 |
+
#------------------------------#
|
| 52 |
+
# 获得预测框
|
| 53 |
+
#------------------------------#
|
| 54 |
+
box = np.array([np.array(list(map(int,box.split(',')))) for box in line[1:]])
|
| 55 |
+
|
| 56 |
+
if not random:
|
| 57 |
+
scale = min(w/iw, h/ih)
|
| 58 |
+
nw = int(iw*scale)
|
| 59 |
+
nh = int(ih*scale)
|
| 60 |
+
dx = (w-nw)//2
|
| 61 |
+
dy = (h-nh)//2
|
| 62 |
+
|
| 63 |
+
#---------------------------------#
|
| 64 |
+
# 将图像多余的部分加上灰条
|
| 65 |
+
#---------------------------------#
|
| 66 |
+
image = image.resize((nw,nh), Image.BICUBIC)
|
| 67 |
+
new_image = Image.new('RGB', (w,h), (128,128,128))
|
| 68 |
+
new_image.paste(image, (dx, dy))
|
| 69 |
+
image_data = np.array(new_image, np.float32)
|
| 70 |
+
|
| 71 |
+
#---------------------------------#
|
| 72 |
+
# 对真实框进行调整
|
| 73 |
+
#---------------------------------#
|
| 74 |
+
if len(box)>0:
|
| 75 |
+
np.random.shuffle(box)
|
| 76 |
+
box[:, [0,2]] = box[:, [0,2]]*nw/iw + dx
|
| 77 |
+
box[:, [1,3]] = box[:, [1,3]]*nh/ih + dy
|
| 78 |
+
box[:, 0:2][box[:, 0:2]<0] = 0
|
| 79 |
+
box[:, 2][box[:, 2]>w] = w
|
| 80 |
+
box[:, 3][box[:, 3]>h] = h
|
| 81 |
+
box_w = box[:, 2] - box[:, 0]
|
| 82 |
+
box_h = box[:, 3] - box[:, 1]
|
| 83 |
+
box = box[np.logical_and(box_w>1, box_h>1)] # discard invalid box
|
| 84 |
+
|
| 85 |
+
return image_data, box
|
| 86 |
+
|
| 87 |
+
#------------------------------------------#
|
| 88 |
+
# 对图像进行缩放并且进行长和宽的扭曲
|
| 89 |
+
#------------------------------------------#
|
| 90 |
+
new_ar = iw/ih * self.rand(1-jitter,1+jitter) / self.rand(1-jitter,1+jitter)
|
| 91 |
+
scale = self.rand(.25, 2)
|
| 92 |
+
if new_ar < 1:
|
| 93 |
+
nh = int(scale*h)
|
| 94 |
+
nw = int(nh*new_ar)
|
| 95 |
+
else:
|
| 96 |
+
nw = int(scale*w)
|
| 97 |
+
nh = int(nw/new_ar)
|
| 98 |
+
image = image.resize((nw,nh), Image.BICUBIC)
|
| 99 |
+
|
| 100 |
+
#------------------------------------------#
|
| 101 |
+
# 将图像多余的部分加上灰条
|
| 102 |
+
#------------------------------------------#
|
| 103 |
+
dx = int(self.rand(0, w-nw))
|
| 104 |
+
dy = int(self.rand(0, h-nh))
|
| 105 |
+
new_image = Image.new('RGB', (w,h), (128,128,128))
|
| 106 |
+
new_image.paste(image, (dx, dy))
|
| 107 |
+
image = new_image
|
| 108 |
+
|
| 109 |
+
#------------------------------------------#
|
| 110 |
+
# 翻转图像
|
| 111 |
+
#------------------------------------------#
|
| 112 |
+
flip = self.rand()<.5
|
| 113 |
+
if flip: image = image.transpose(Image.FLIP_LEFT_RIGHT)
|
| 114 |
+
|
| 115 |
+
image_data = np.array(image, np.uint8)
|
| 116 |
+
#---------------------------------#
|
| 117 |
+
# 对图像进行色域变换
|
| 118 |
+
# 计算色域变换的参数
|
| 119 |
+
#---------------------------------#
|
| 120 |
+
r = np.random.uniform(-1, 1, 3) * [hue, sat, val] + 1
|
| 121 |
+
#---------------------------------#
|
| 122 |
+
# 将图像转到HSV上
|
| 123 |
+
#---------------------------------#
|
| 124 |
+
hue, sat, val = cv2.split(cv2.cvtColor(image_data, cv2.COLOR_RGB2HSV))
|
| 125 |
+
dtype = image_data.dtype
|
| 126 |
+
#---------------------------------#
|
| 127 |
+
# 应用变换
|
| 128 |
+
#---------------------------------#
|
| 129 |
+
x = np.arange(0, 256, dtype=r.dtype)
|
| 130 |
+
lut_hue = ((x * r[0]) % 180).astype(dtype)
|
| 131 |
+
lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
|
| 132 |
+
lut_val = np.clip(x * r[2], 0, 255).astype(dtype)
|
| 133 |
+
|
| 134 |
+
image_data = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val)))
|
| 135 |
+
image_data = cv2.cvtColor(image_data, cv2.COLOR_HSV2RGB)
|
| 136 |
+
|
| 137 |
+
#---------------------------------#
|
| 138 |
+
# 对真实框进行调整
|
| 139 |
+
#---------------------------------#
|
| 140 |
+
if len(box)>0:
|
| 141 |
+
np.random.shuffle(box)
|
| 142 |
+
box[:, [0,2]] = box[:, [0,2]]*nw/iw + dx
|
| 143 |
+
box[:, [1,3]] = box[:, [1,3]]*nh/ih + dy
|
| 144 |
+
if flip: box[:, [0,2]] = w - box[:, [2,0]]
|
| 145 |
+
box[:, 0:2][box[:, 0:2]<0] = 0
|
| 146 |
+
box[:, 2][box[:, 2]>w] = w
|
| 147 |
+
box[:, 3][box[:, 3]>h] = h
|
| 148 |
+
box_w = box[:, 2] - box[:, 0]
|
| 149 |
+
box_h = box[:, 3] - box[:, 1]
|
| 150 |
+
box = box[np.logical_and(box_w>1, box_h>1)]
|
| 151 |
+
|
| 152 |
+
return image_data, box
|
| 153 |
+
|
| 154 |
+
# DataLoader中collate_fn使用
|
| 155 |
+
def frcnn_dataset_collate(batch):
|
| 156 |
+
images = []
|
| 157 |
+
bboxes = []
|
| 158 |
+
labels = []
|
| 159 |
+
for img, box, label in batch:
|
| 160 |
+
images.append(img)
|
| 161 |
+
bboxes.append(box)
|
| 162 |
+
labels.append(label)
|
| 163 |
+
images = torch.from_numpy(np.array(images))
|
| 164 |
+
return images, bboxes, labels
|
| 165 |
+
|
faster-rcnn-pytorch-master/utils/utils.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
from PIL import Image
|
| 6 |
+
|
| 7 |
+
#---------------------------------------------------------#
|
| 8 |
+
# 将图像转换成RGB图像,防止灰度图在预测时报错。
|
| 9 |
+
# 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
|
| 10 |
+
#---------------------------------------------------------#
|
| 11 |
+
def cvtColor(image):
|
| 12 |
+
if len(np.shape(image)) == 3 and np.shape(image)[2] == 3:
|
| 13 |
+
return image
|
| 14 |
+
else:
|
| 15 |
+
image = image.convert('RGB')
|
| 16 |
+
return image
|
| 17 |
+
|
| 18 |
+
#---------------------------------------------------#
|
| 19 |
+
# 对输入图像进行resize
|
| 20 |
+
#---------------------------------------------------#
|
| 21 |
+
def resize_image(image, size):
|
| 22 |
+
w, h = size
|
| 23 |
+
new_image = image.resize((w, h), Image.BICUBIC)
|
| 24 |
+
return new_image
|
| 25 |
+
|
| 26 |
+
#---------------------------------------------------#
|
| 27 |
+
# 获得类
|
| 28 |
+
#---------------------------------------------------#
|
| 29 |
+
def get_classes(classes_path):
|
| 30 |
+
with open(classes_path, encoding='utf-8') as f:
|
| 31 |
+
class_names = f.readlines()
|
| 32 |
+
class_names = [c.strip() for c in class_names]
|
| 33 |
+
return class_names, len(class_names)
|
| 34 |
+
|
| 35 |
+
#---------------------------------------------------#
|
| 36 |
+
# 获得学习率
|
| 37 |
+
#---------------------------------------------------#
|
| 38 |
+
def get_lr(optimizer):
|
| 39 |
+
for param_group in optimizer.param_groups:
|
| 40 |
+
return param_group['lr']
|
| 41 |
+
|
| 42 |
+
#---------------------------------------------------#
|
| 43 |
+
# 设置种子
|
| 44 |
+
#---------------------------------------------------#
|
| 45 |
+
def seed_everything(seed=11):
|
| 46 |
+
random.seed(seed)
|
| 47 |
+
np.random.seed(seed)
|
| 48 |
+
torch.manual_seed(seed)
|
| 49 |
+
torch.cuda.manual_seed(seed)
|
| 50 |
+
torch.cuda.manual_seed_all(seed)
|
| 51 |
+
torch.backends.cudnn.deterministic = True
|
| 52 |
+
torch.backends.cudnn.benchmark = False
|
| 53 |
+
|
| 54 |
+
#---------------------------------------------------#
|
| 55 |
+
# 设置Dataloader的种子
|
| 56 |
+
#---------------------------------------------------#
|
| 57 |
+
def worker_init_fn(worker_id, rank, seed):
|
| 58 |
+
worker_seed = rank + seed
|
| 59 |
+
random.seed(worker_seed)
|
| 60 |
+
np.random.seed(worker_seed)
|
| 61 |
+
torch.manual_seed(worker_seed)
|
| 62 |
+
|
| 63 |
+
def preprocess_input(image):
|
| 64 |
+
image /= 255.0
|
| 65 |
+
return image
|
| 66 |
+
|
| 67 |
+
def show_config(**kwargs):
|
| 68 |
+
print('Configurations:')
|
| 69 |
+
print('-' * 70)
|
| 70 |
+
print('|%25s | %40s|' % ('keys', 'values'))
|
| 71 |
+
print('-' * 70)
|
| 72 |
+
for key, value in kwargs.items():
|
| 73 |
+
print('|%25s | %40s|' % (str(key), str(value)))
|
| 74 |
+
print('-' * 70)
|
| 75 |
+
|
| 76 |
+
def get_new_img_size(height, width, img_min_side=600):
|
| 77 |
+
if width <= height:
|
| 78 |
+
f = float(img_min_side) / width
|
| 79 |
+
resized_height = int(f * height)
|
| 80 |
+
resized_width = int(img_min_side)
|
| 81 |
+
else:
|
| 82 |
+
f = float(img_min_side) / height
|
| 83 |
+
resized_width = int(f * width)
|
| 84 |
+
resized_height = int(img_min_side)
|
| 85 |
+
|
| 86 |
+
return resized_height, resized_width
|
faster-rcnn-pytorch-master/utils/utils_bbox.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import torch
|
| 3 |
+
from torch.nn import functional as F
|
| 4 |
+
from torchvision.ops import nms
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def loc2bbox(src_bbox, loc):
|
| 8 |
+
if src_bbox.size()[0] == 0:
|
| 9 |
+
return torch.zeros((0, 4), dtype=loc.dtype)
|
| 10 |
+
|
| 11 |
+
src_width = torch.unsqueeze(src_bbox[:, 2] - src_bbox[:, 0], -1)
|
| 12 |
+
src_height = torch.unsqueeze(src_bbox[:, 3] - src_bbox[:, 1], -1)
|
| 13 |
+
src_ctr_x = torch.unsqueeze(src_bbox[:, 0], -1) + 0.5 * src_width
|
| 14 |
+
src_ctr_y = torch.unsqueeze(src_bbox[:, 1], -1) + 0.5 * src_height
|
| 15 |
+
|
| 16 |
+
dx = loc[:, 0::4]
|
| 17 |
+
dy = loc[:, 1::4]
|
| 18 |
+
dw = loc[:, 2::4]
|
| 19 |
+
dh = loc[:, 3::4]
|
| 20 |
+
|
| 21 |
+
ctr_x = dx * src_width + src_ctr_x
|
| 22 |
+
ctr_y = dy * src_height + src_ctr_y
|
| 23 |
+
w = torch.exp(dw) * src_width
|
| 24 |
+
h = torch.exp(dh) * src_height
|
| 25 |
+
|
| 26 |
+
dst_bbox = torch.zeros_like(loc)
|
| 27 |
+
dst_bbox[:, 0::4] = ctr_x - 0.5 * w
|
| 28 |
+
dst_bbox[:, 1::4] = ctr_y - 0.5 * h
|
| 29 |
+
dst_bbox[:, 2::4] = ctr_x + 0.5 * w
|
| 30 |
+
dst_bbox[:, 3::4] = ctr_y + 0.5 * h
|
| 31 |
+
|
| 32 |
+
return dst_bbox
|
| 33 |
+
|
| 34 |
+
class DecodeBox():
|
| 35 |
+
def __init__(self, std, num_classes):
|
| 36 |
+
self.std = std
|
| 37 |
+
self.num_classes = num_classes + 1
|
| 38 |
+
|
| 39 |
+
def frcnn_correct_boxes(self, box_xy, box_wh, input_shape, image_shape):
|
| 40 |
+
#-----------------------------------------------------------------#
|
| 41 |
+
# 把y轴放前面是因为方便预测框和图像的宽高进行相乘
|
| 42 |
+
#-----------------------------------------------------------------#
|
| 43 |
+
box_yx = box_xy[..., ::-1]
|
| 44 |
+
box_hw = box_wh[..., ::-1]
|
| 45 |
+
input_shape = np.array(input_shape)
|
| 46 |
+
image_shape = np.array(image_shape)
|
| 47 |
+
|
| 48 |
+
box_mins = box_yx - (box_hw / 2.)
|
| 49 |
+
box_maxes = box_yx + (box_hw / 2.)
|
| 50 |
+
boxes = np.concatenate([box_mins[..., 0:1], box_mins[..., 1:2], box_maxes[..., 0:1], box_maxes[..., 1:2]], axis=-1)
|
| 51 |
+
boxes *= np.concatenate([image_shape, image_shape], axis=-1)
|
| 52 |
+
return boxes
|
| 53 |
+
|
| 54 |
+
def forward(self, roi_cls_locs, roi_scores, rois, image_shape, input_shape, nms_iou = 0.3, confidence = 0.5):
|
| 55 |
+
results = []
|
| 56 |
+
bs = len(roi_cls_locs)
|
| 57 |
+
#--------------------------------#
|
| 58 |
+
# batch_size, num_rois, 4
|
| 59 |
+
#--------------------------------#
|
| 60 |
+
rois = rois.view((bs, -1, 4))
|
| 61 |
+
#----------------------------------------------------------------------------------------------------------------#
|
| 62 |
+
# 对每一张图片进行处理,由于在predict.py的时候,我们只输入一张图片,所以for i in range(len(mbox_loc))只进行一次
|
| 63 |
+
#----------------------------------------------------------------------------------------------------------------#
|
| 64 |
+
for i in range(bs):
|
| 65 |
+
#----------------------------------------------------------#
|
| 66 |
+
# 对回归参数进行reshape
|
| 67 |
+
#----------------------------------------------------------#
|
| 68 |
+
roi_cls_loc = roi_cls_locs[i] * self.std
|
| 69 |
+
#----------------------------------------------------------#
|
| 70 |
+
# 第一维度是建议框的数量,第二维度是每个种类
|
| 71 |
+
# 第三维度是对应种类的调整参数
|
| 72 |
+
#----------------------------------------------------------#
|
| 73 |
+
roi_cls_loc = roi_cls_loc.view([-1, self.num_classes, 4])
|
| 74 |
+
|
| 75 |
+
#-------------------------------------------------------------#
|
| 76 |
+
# 利用classifier网络的预测结果对建议框进行调整获得预测框
|
| 77 |
+
# num_rois, 4 -> num_rois, 1, 4 -> num_rois, num_classes, 4
|
| 78 |
+
#-------------------------------------------------------------#
|
| 79 |
+
roi = rois[i].view((-1, 1, 4)).expand_as(roi_cls_loc)
|
| 80 |
+
cls_bbox = loc2bbox(roi.contiguous().view((-1, 4)), roi_cls_loc.contiguous().view((-1, 4)))
|
| 81 |
+
cls_bbox = cls_bbox.view([-1, (self.num_classes), 4])
|
| 82 |
+
#-------------------------------------------------------------#
|
| 83 |
+
# 对预测框进行归一化,调整到0-1之间
|
| 84 |
+
#-------------------------------------------------------------#
|
| 85 |
+
cls_bbox[..., [0, 2]] = (cls_bbox[..., [0, 2]]) / input_shape[1]
|
| 86 |
+
cls_bbox[..., [1, 3]] = (cls_bbox[..., [1, 3]]) / input_shape[0]
|
| 87 |
+
|
| 88 |
+
roi_score = roi_scores[i]
|
| 89 |
+
prob = F.softmax(roi_score, dim=-1)
|
| 90 |
+
|
| 91 |
+
results.append([])
|
| 92 |
+
for c in range(1, self.num_classes):
|
| 93 |
+
#--------------------------------#
|
| 94 |
+
# 取出属于该类的所有框的置信度
|
| 95 |
+
# 判断是否大于门限
|
| 96 |
+
#--------------------------------#
|
| 97 |
+
c_confs = prob[:, c]
|
| 98 |
+
c_confs_m = c_confs > confidence
|
| 99 |
+
|
| 100 |
+
if len(c_confs[c_confs_m]) > 0:
|
| 101 |
+
#-----------------------------------------#
|
| 102 |
+
# 取出得分高于confidence的框
|
| 103 |
+
#-----------------------------------------#
|
| 104 |
+
boxes_to_process = cls_bbox[c_confs_m, c]
|
| 105 |
+
confs_to_process = c_confs[c_confs_m]
|
| 106 |
+
|
| 107 |
+
keep = nms(
|
| 108 |
+
boxes_to_process,
|
| 109 |
+
confs_to_process,
|
| 110 |
+
nms_iou
|
| 111 |
+
)
|
| 112 |
+
#-----------------------------------------#
|
| 113 |
+
# 取出在非极大抑制中效果较好的内容
|
| 114 |
+
#-----------------------------------------#
|
| 115 |
+
good_boxes = boxes_to_process[keep]
|
| 116 |
+
confs = confs_to_process[keep][:, None]
|
| 117 |
+
labels = (c - 1) * torch.ones((len(keep), 1)).cuda() if confs.is_cuda else (c - 1) * torch.ones((len(keep), 1))
|
| 118 |
+
#-----------------------------------------#
|
| 119 |
+
# 将label、置信度、框的位置进行堆叠。
|
| 120 |
+
#-----------------------------------------#
|
| 121 |
+
c_pred = torch.cat((good_boxes, confs, labels), dim=1).cpu().numpy()
|
| 122 |
+
# 添加进result里
|
| 123 |
+
results[-1].extend(c_pred)
|
| 124 |
+
|
| 125 |
+
if len(results[-1]) > 0:
|
| 126 |
+
results[-1] = np.array(results[-1])
|
| 127 |
+
box_xy, box_wh = (results[-1][:, 0:2] + results[-1][:, 2:4])/2, results[-1][:, 2:4] - results[-1][:, 0:2]
|
| 128 |
+
results[-1][:, :4] = self.frcnn_correct_boxes(box_xy, box_wh, input_shape, image_shape)
|
| 129 |
+
|
| 130 |
+
return results
|
| 131 |
+
|
faster-rcnn-pytorch-master/utils/utils_fit.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from tqdm import tqdm
|
| 5 |
+
|
| 6 |
+
from utils.utils import get_lr
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def fit_one_epoch(model, train_util, loss_history, eval_callback, optimizer, epoch, epoch_step, epoch_step_val, gen, gen_val, Epoch, cuda, fp16, scaler, save_period, save_dir):
|
| 10 |
+
total_loss = 0
|
| 11 |
+
rpn_loc_loss = 0
|
| 12 |
+
rpn_cls_loss = 0
|
| 13 |
+
roi_loc_loss = 0
|
| 14 |
+
roi_cls_loss = 0
|
| 15 |
+
|
| 16 |
+
val_loss = 0
|
| 17 |
+
print('Start Train')
|
| 18 |
+
with tqdm(total=epoch_step,desc=f'Epoch {epoch + 1}/{Epoch}',postfix=dict,mininterval=0.3) as pbar:
|
| 19 |
+
for iteration, batch in enumerate(gen):
|
| 20 |
+
if iteration >= epoch_step:
|
| 21 |
+
break
|
| 22 |
+
images, boxes, labels = batch[0], batch[1], batch[2]
|
| 23 |
+
with torch.no_grad():
|
| 24 |
+
if cuda:
|
| 25 |
+
images = images.cuda()
|
| 26 |
+
|
| 27 |
+
rpn_loc, rpn_cls, roi_loc, roi_cls, total = train_util.train_step(images, boxes, labels, 1, fp16, scaler)
|
| 28 |
+
total_loss += total.item()
|
| 29 |
+
rpn_loc_loss += rpn_loc.item()
|
| 30 |
+
rpn_cls_loss += rpn_cls.item()
|
| 31 |
+
roi_loc_loss += roi_loc.item()
|
| 32 |
+
roi_cls_loss += roi_cls.item()
|
| 33 |
+
|
| 34 |
+
pbar.set_postfix(**{'total_loss' : total_loss / (iteration + 1),
|
| 35 |
+
'rpn_loc' : rpn_loc_loss / (iteration + 1),
|
| 36 |
+
'rpn_cls' : rpn_cls_loss / (iteration + 1),
|
| 37 |
+
'roi_loc' : roi_loc_loss / (iteration + 1),
|
| 38 |
+
'roi_cls' : roi_cls_loss / (iteration + 1),
|
| 39 |
+
'lr' : get_lr(optimizer)})
|
| 40 |
+
pbar.update(1)
|
| 41 |
+
|
| 42 |
+
print('Finish Train')
|
| 43 |
+
print('Start Validation')
|
| 44 |
+
with tqdm(total=epoch_step_val, desc=f'Epoch {epoch + 1}/{Epoch}',postfix=dict,mininterval=0.3) as pbar:
|
| 45 |
+
for iteration, batch in enumerate(gen_val):
|
| 46 |
+
if iteration >= epoch_step_val:
|
| 47 |
+
break
|
| 48 |
+
images, boxes, labels = batch[0], batch[1], batch[2]
|
| 49 |
+
with torch.no_grad():
|
| 50 |
+
if cuda:
|
| 51 |
+
images = images.cuda()
|
| 52 |
+
|
| 53 |
+
train_util.optimizer.zero_grad()
|
| 54 |
+
_, _, _, _, val_total = train_util.forward(images, boxes, labels, 1)
|
| 55 |
+
val_loss += val_total.item()
|
| 56 |
+
|
| 57 |
+
pbar.set_postfix(**{'val_loss' : val_loss / (iteration + 1)})
|
| 58 |
+
pbar.update(1)
|
| 59 |
+
|
| 60 |
+
print('Finish Validation')
|
| 61 |
+
loss_history.append_loss(epoch + 1, total_loss / epoch_step, val_loss / epoch_step_val)
|
| 62 |
+
eval_callback.on_epoch_end(epoch + 1)
|
| 63 |
+
print('Epoch:'+ str(epoch + 1) + '/' + str(Epoch))
|
| 64 |
+
print('Total Loss: %.3f || Val Loss: %.3f ' % (total_loss / epoch_step, val_loss / epoch_step_val))
|
| 65 |
+
|
| 66 |
+
#-----------------------------------------------#
|
| 67 |
+
# 保存权值
|
| 68 |
+
#-----------------------------------------------#
|
| 69 |
+
if (epoch + 1) % save_period == 0 or epoch + 1 == Epoch:
|
| 70 |
+
torch.save(model.state_dict(), os.path.join(save_dir, 'ep%03d-loss%.3f-val_loss%.3f.pth' % (epoch + 1, total_loss / epoch_step, val_loss / epoch_step_val)))
|
| 71 |
+
|
| 72 |
+
if len(loss_history.val_loss) <= 1 or (val_loss / epoch_step_val) <= min(loss_history.val_loss):
|
| 73 |
+
print('Save best model to best_epoch_weights.pth')
|
| 74 |
+
torch.save(model.state_dict(), os.path.join(save_dir, "best_epoch_weights.pth"))
|
| 75 |
+
|
| 76 |
+
torch.save(model.state_dict(), os.path.join(save_dir, "last_epoch_weights.pth"))
|
faster-rcnn-pytorch-master/utils/utils_map.py
ADDED
|
@@ -0,0 +1,923 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import glob
|
| 2 |
+
import json
|
| 3 |
+
import math
|
| 4 |
+
import operator
|
| 5 |
+
import os
|
| 6 |
+
import shutil
|
| 7 |
+
import sys
|
| 8 |
+
try:
|
| 9 |
+
from pycocotools.coco import COCO
|
| 10 |
+
from pycocotools.cocoeval import COCOeval
|
| 11 |
+
except:
|
| 12 |
+
pass
|
| 13 |
+
import cv2
|
| 14 |
+
import matplotlib
|
| 15 |
+
matplotlib.use('Agg')
|
| 16 |
+
from matplotlib import pyplot as plt
|
| 17 |
+
import numpy as np
|
| 18 |
+
|
| 19 |
+
'''
|
| 20 |
+
0,0 ------> x (width)
|
| 21 |
+
|
|
| 22 |
+
| (Left,Top)
|
| 23 |
+
| *_________
|
| 24 |
+
| | |
|
| 25 |
+
| |
|
| 26 |
+
y |_________|
|
| 27 |
+
(height) *
|
| 28 |
+
(Right,Bottom)
|
| 29 |
+
'''
|
| 30 |
+
|
| 31 |
+
def log_average_miss_rate(precision, fp_cumsum, num_images):
|
| 32 |
+
"""
|
| 33 |
+
log-average miss rate:
|
| 34 |
+
Calculated by averaging miss rates at 9 evenly spaced FPPI points
|
| 35 |
+
between 10e-2 and 10e0, in log-space.
|
| 36 |
+
|
| 37 |
+
output:
|
| 38 |
+
lamr | log-average miss rate
|
| 39 |
+
mr | miss rate
|
| 40 |
+
fppi | false positives per image
|
| 41 |
+
|
| 42 |
+
references:
|
| 43 |
+
[1] Dollar, Piotr, et al. "Pedestrian Detection: An Evaluation of the
|
| 44 |
+
State of the Art." Pattern Analysis and Machine Intelligence, IEEE
|
| 45 |
+
Transactions on 34.4 (2012): 743 - 761.
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
if precision.size == 0:
|
| 49 |
+
lamr = 0
|
| 50 |
+
mr = 1
|
| 51 |
+
fppi = 0
|
| 52 |
+
return lamr, mr, fppi
|
| 53 |
+
|
| 54 |
+
fppi = fp_cumsum / float(num_images)
|
| 55 |
+
mr = (1 - precision)
|
| 56 |
+
|
| 57 |
+
fppi_tmp = np.insert(fppi, 0, -1.0)
|
| 58 |
+
mr_tmp = np.insert(mr, 0, 1.0)
|
| 59 |
+
|
| 60 |
+
ref = np.logspace(-2.0, 0.0, num = 9)
|
| 61 |
+
for i, ref_i in enumerate(ref):
|
| 62 |
+
j = np.where(fppi_tmp <= ref_i)[-1][-1]
|
| 63 |
+
ref[i] = mr_tmp[j]
|
| 64 |
+
|
| 65 |
+
lamr = math.exp(np.mean(np.log(np.maximum(1e-10, ref))))
|
| 66 |
+
|
| 67 |
+
return lamr, mr, fppi
|
| 68 |
+
|
| 69 |
+
"""
|
| 70 |
+
throw error and exit
|
| 71 |
+
"""
|
| 72 |
+
def error(msg):
|
| 73 |
+
print(msg)
|
| 74 |
+
sys.exit(0)
|
| 75 |
+
|
| 76 |
+
"""
|
| 77 |
+
check if the number is a float between 0.0 and 1.0
|
| 78 |
+
"""
|
| 79 |
+
def is_float_between_0_and_1(value):
|
| 80 |
+
try:
|
| 81 |
+
val = float(value)
|
| 82 |
+
if val > 0.0 and val < 1.0:
|
| 83 |
+
return True
|
| 84 |
+
else:
|
| 85 |
+
return False
|
| 86 |
+
except ValueError:
|
| 87 |
+
return False
|
| 88 |
+
|
| 89 |
+
"""
|
| 90 |
+
Calculate the AP given the recall and precision array
|
| 91 |
+
1st) We compute a version of the measured precision/recall curve with
|
| 92 |
+
precision monotonically decreasing
|
| 93 |
+
2nd) We compute the AP as the area under this curve by numerical integration.
|
| 94 |
+
"""
|
| 95 |
+
def voc_ap(rec, prec):
|
| 96 |
+
"""
|
| 97 |
+
--- Official matlab code VOC2012---
|
| 98 |
+
mrec=[0 ; rec ; 1];
|
| 99 |
+
mpre=[0 ; prec ; 0];
|
| 100 |
+
for i=numel(mpre)-1:-1:1
|
| 101 |
+
mpre(i)=max(mpre(i),mpre(i+1));
|
| 102 |
+
end
|
| 103 |
+
i=find(mrec(2:end)~=mrec(1:end-1))+1;
|
| 104 |
+
ap=sum((mrec(i)-mrec(i-1)).*mpre(i));
|
| 105 |
+
"""
|
| 106 |
+
rec.insert(0, 0.0) # insert 0.0 at begining of list
|
| 107 |
+
rec.append(1.0) # insert 1.0 at end of list
|
| 108 |
+
mrec = rec[:]
|
| 109 |
+
prec.insert(0, 0.0) # insert 0.0 at begining of list
|
| 110 |
+
prec.append(0.0) # insert 0.0 at end of list
|
| 111 |
+
mpre = prec[:]
|
| 112 |
+
"""
|
| 113 |
+
This part makes the precision monotonically decreasing
|
| 114 |
+
(goes from the end to the beginning)
|
| 115 |
+
matlab: for i=numel(mpre)-1:-1:1
|
| 116 |
+
mpre(i)=max(mpre(i),mpre(i+1));
|
| 117 |
+
"""
|
| 118 |
+
for i in range(len(mpre)-2, -1, -1):
|
| 119 |
+
mpre[i] = max(mpre[i], mpre[i+1])
|
| 120 |
+
"""
|
| 121 |
+
This part creates a list of indexes where the recall changes
|
| 122 |
+
matlab: i=find(mrec(2:end)~=mrec(1:end-1))+1;
|
| 123 |
+
"""
|
| 124 |
+
i_list = []
|
| 125 |
+
for i in range(1, len(mrec)):
|
| 126 |
+
if mrec[i] != mrec[i-1]:
|
| 127 |
+
i_list.append(i) # if it was matlab would be i + 1
|
| 128 |
+
"""
|
| 129 |
+
The Average Precision (AP) is the area under the curve
|
| 130 |
+
(numerical integration)
|
| 131 |
+
matlab: ap=sum((mrec(i)-mrec(i-1)).*mpre(i));
|
| 132 |
+
"""
|
| 133 |
+
ap = 0.0
|
| 134 |
+
for i in i_list:
|
| 135 |
+
ap += ((mrec[i]-mrec[i-1])*mpre[i])
|
| 136 |
+
return ap, mrec, mpre
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
"""
|
| 140 |
+
Convert the lines of a file to a list
|
| 141 |
+
"""
|
| 142 |
+
def file_lines_to_list(path):
|
| 143 |
+
# open txt file lines to a list
|
| 144 |
+
with open(path) as f:
|
| 145 |
+
content = f.readlines()
|
| 146 |
+
# remove whitespace characters like `\n` at the end of each line
|
| 147 |
+
content = [x.strip() for x in content]
|
| 148 |
+
return content
|
| 149 |
+
|
| 150 |
+
"""
|
| 151 |
+
Draws text in image
|
| 152 |
+
"""
|
| 153 |
+
def draw_text_in_image(img, text, pos, color, line_width):
|
| 154 |
+
font = cv2.FONT_HERSHEY_PLAIN
|
| 155 |
+
fontScale = 1
|
| 156 |
+
lineType = 1
|
| 157 |
+
bottomLeftCornerOfText = pos
|
| 158 |
+
cv2.putText(img, text,
|
| 159 |
+
bottomLeftCornerOfText,
|
| 160 |
+
font,
|
| 161 |
+
fontScale,
|
| 162 |
+
color,
|
| 163 |
+
lineType)
|
| 164 |
+
text_width, _ = cv2.getTextSize(text, font, fontScale, lineType)[0]
|
| 165 |
+
return img, (line_width + text_width)
|
| 166 |
+
|
| 167 |
+
"""
|
| 168 |
+
Plot - adjust axes
|
| 169 |
+
"""
|
| 170 |
+
def adjust_axes(r, t, fig, axes):
|
| 171 |
+
# get text width for re-scaling
|
| 172 |
+
bb = t.get_window_extent(renderer=r)
|
| 173 |
+
text_width_inches = bb.width / fig.dpi
|
| 174 |
+
# get axis width in inches
|
| 175 |
+
current_fig_width = fig.get_figwidth()
|
| 176 |
+
new_fig_width = current_fig_width + text_width_inches
|
| 177 |
+
propotion = new_fig_width / current_fig_width
|
| 178 |
+
# get axis limit
|
| 179 |
+
x_lim = axes.get_xlim()
|
| 180 |
+
axes.set_xlim([x_lim[0], x_lim[1]*propotion])
|
| 181 |
+
|
| 182 |
+
"""
|
| 183 |
+
Draw plot using Matplotlib
|
| 184 |
+
"""
|
| 185 |
+
def draw_plot_func(dictionary, n_classes, window_title, plot_title, x_label, output_path, to_show, plot_color, true_p_bar):
|
| 186 |
+
# sort the dictionary by decreasing value, into a list of tuples
|
| 187 |
+
sorted_dic_by_value = sorted(dictionary.items(), key=operator.itemgetter(1))
|
| 188 |
+
# unpacking the list of tuples into two lists
|
| 189 |
+
sorted_keys, sorted_values = zip(*sorted_dic_by_value)
|
| 190 |
+
#
|
| 191 |
+
if true_p_bar != "":
|
| 192 |
+
"""
|
| 193 |
+
Special case to draw in:
|
| 194 |
+
- green -> TP: True Positives (object detected and matches ground-truth)
|
| 195 |
+
- red -> FP: False Positives (object detected but does not match ground-truth)
|
| 196 |
+
- orange -> FN: False Negatives (object not detected but present in the ground-truth)
|
| 197 |
+
"""
|
| 198 |
+
fp_sorted = []
|
| 199 |
+
tp_sorted = []
|
| 200 |
+
for key in sorted_keys:
|
| 201 |
+
fp_sorted.append(dictionary[key] - true_p_bar[key])
|
| 202 |
+
tp_sorted.append(true_p_bar[key])
|
| 203 |
+
plt.barh(range(n_classes), fp_sorted, align='center', color='crimson', label='False Positive')
|
| 204 |
+
plt.barh(range(n_classes), tp_sorted, align='center', color='forestgreen', label='True Positive', left=fp_sorted)
|
| 205 |
+
# add legend
|
| 206 |
+
plt.legend(loc='lower right')
|
| 207 |
+
"""
|
| 208 |
+
Write number on side of bar
|
| 209 |
+
"""
|
| 210 |
+
fig = plt.gcf() # gcf - get current figure
|
| 211 |
+
axes = plt.gca()
|
| 212 |
+
r = fig.canvas.get_renderer()
|
| 213 |
+
for i, val in enumerate(sorted_values):
|
| 214 |
+
fp_val = fp_sorted[i]
|
| 215 |
+
tp_val = tp_sorted[i]
|
| 216 |
+
fp_str_val = " " + str(fp_val)
|
| 217 |
+
tp_str_val = fp_str_val + " " + str(tp_val)
|
| 218 |
+
# trick to paint multicolor with offset:
|
| 219 |
+
# first paint everything and then repaint the first number
|
| 220 |
+
t = plt.text(val, i, tp_str_val, color='forestgreen', va='center', fontweight='bold')
|
| 221 |
+
plt.text(val, i, fp_str_val, color='crimson', va='center', fontweight='bold')
|
| 222 |
+
if i == (len(sorted_values)-1): # largest bar
|
| 223 |
+
adjust_axes(r, t, fig, axes)
|
| 224 |
+
else:
|
| 225 |
+
plt.barh(range(n_classes), sorted_values, color=plot_color)
|
| 226 |
+
"""
|
| 227 |
+
Write number on side of bar
|
| 228 |
+
"""
|
| 229 |
+
fig = plt.gcf() # gcf - get current figure
|
| 230 |
+
axes = plt.gca()
|
| 231 |
+
r = fig.canvas.get_renderer()
|
| 232 |
+
for i, val in enumerate(sorted_values):
|
| 233 |
+
str_val = " " + str(val) # add a space before
|
| 234 |
+
if val < 1.0:
|
| 235 |
+
str_val = " {0:.2f}".format(val)
|
| 236 |
+
t = plt.text(val, i, str_val, color=plot_color, va='center', fontweight='bold')
|
| 237 |
+
# re-set axes to show number inside the figure
|
| 238 |
+
if i == (len(sorted_values)-1): # largest bar
|
| 239 |
+
adjust_axes(r, t, fig, axes)
|
| 240 |
+
# set window title
|
| 241 |
+
fig.canvas.set_window_title(window_title)
|
| 242 |
+
# write classes in y axis
|
| 243 |
+
tick_font_size = 12
|
| 244 |
+
plt.yticks(range(n_classes), sorted_keys, fontsize=tick_font_size)
|
| 245 |
+
"""
|
| 246 |
+
Re-scale height accordingly
|
| 247 |
+
"""
|
| 248 |
+
init_height = fig.get_figheight()
|
| 249 |
+
# comput the matrix height in points and inches
|
| 250 |
+
dpi = fig.dpi
|
| 251 |
+
height_pt = n_classes * (tick_font_size * 1.4) # 1.4 (some spacing)
|
| 252 |
+
height_in = height_pt / dpi
|
| 253 |
+
# compute the required figure height
|
| 254 |
+
top_margin = 0.15 # in percentage of the figure height
|
| 255 |
+
bottom_margin = 0.05 # in percentage of the figure height
|
| 256 |
+
figure_height = height_in / (1 - top_margin - bottom_margin)
|
| 257 |
+
# set new height
|
| 258 |
+
if figure_height > init_height:
|
| 259 |
+
fig.set_figheight(figure_height)
|
| 260 |
+
|
| 261 |
+
# set plot title
|
| 262 |
+
plt.title(plot_title, fontsize=14)
|
| 263 |
+
# set axis titles
|
| 264 |
+
# plt.xlabel('classes')
|
| 265 |
+
plt.xlabel(x_label, fontsize='large')
|
| 266 |
+
# adjust size of window
|
| 267 |
+
fig.tight_layout()
|
| 268 |
+
# save the plot
|
| 269 |
+
fig.savefig(output_path)
|
| 270 |
+
# show image
|
| 271 |
+
if to_show:
|
| 272 |
+
plt.show()
|
| 273 |
+
# close the plot
|
| 274 |
+
plt.close()
|
| 275 |
+
|
| 276 |
+
def get_map(MINOVERLAP, draw_plot, score_threhold=0.5, path = './map_out'):
|
| 277 |
+
GT_PATH = os.path.join(path, 'ground-truth')
|
| 278 |
+
DR_PATH = os.path.join(path, 'detection-results')
|
| 279 |
+
IMG_PATH = os.path.join(path, 'images-optional')
|
| 280 |
+
TEMP_FILES_PATH = os.path.join(path, '.temp_files')
|
| 281 |
+
RESULTS_FILES_PATH = os.path.join(path, 'results')
|
| 282 |
+
|
| 283 |
+
show_animation = True
|
| 284 |
+
if os.path.exists(IMG_PATH):
|
| 285 |
+
for dirpath, dirnames, files in os.walk(IMG_PATH):
|
| 286 |
+
if not files:
|
| 287 |
+
show_animation = False
|
| 288 |
+
else:
|
| 289 |
+
show_animation = False
|
| 290 |
+
|
| 291 |
+
if not os.path.exists(TEMP_FILES_PATH):
|
| 292 |
+
os.makedirs(TEMP_FILES_PATH)
|
| 293 |
+
|
| 294 |
+
if os.path.exists(RESULTS_FILES_PATH):
|
| 295 |
+
shutil.rmtree(RESULTS_FILES_PATH)
|
| 296 |
+
else:
|
| 297 |
+
os.makedirs(RESULTS_FILES_PATH)
|
| 298 |
+
if draw_plot:
|
| 299 |
+
try:
|
| 300 |
+
matplotlib.use('TkAgg')
|
| 301 |
+
except:
|
| 302 |
+
pass
|
| 303 |
+
os.makedirs(os.path.join(RESULTS_FILES_PATH, "AP"))
|
| 304 |
+
os.makedirs(os.path.join(RESULTS_FILES_PATH, "F1"))
|
| 305 |
+
os.makedirs(os.path.join(RESULTS_FILES_PATH, "Recall"))
|
| 306 |
+
os.makedirs(os.path.join(RESULTS_FILES_PATH, "Precision"))
|
| 307 |
+
if show_animation:
|
| 308 |
+
os.makedirs(os.path.join(RESULTS_FILES_PATH, "images", "detections_one_by_one"))
|
| 309 |
+
|
| 310 |
+
ground_truth_files_list = glob.glob(GT_PATH + '/*.txt')
|
| 311 |
+
if len(ground_truth_files_list) == 0:
|
| 312 |
+
error("Error: No ground-truth files found!")
|
| 313 |
+
ground_truth_files_list.sort()
|
| 314 |
+
gt_counter_per_class = {}
|
| 315 |
+
counter_images_per_class = {}
|
| 316 |
+
|
| 317 |
+
for txt_file in ground_truth_files_list:
|
| 318 |
+
file_id = txt_file.split(".txt", 1)[0]
|
| 319 |
+
file_id = os.path.basename(os.path.normpath(file_id))
|
| 320 |
+
temp_path = os.path.join(DR_PATH, (file_id + ".txt"))
|
| 321 |
+
if not os.path.exists(temp_path):
|
| 322 |
+
error_msg = "Error. File not found: {}\n".format(temp_path)
|
| 323 |
+
error(error_msg)
|
| 324 |
+
lines_list = file_lines_to_list(txt_file)
|
| 325 |
+
bounding_boxes = []
|
| 326 |
+
is_difficult = False
|
| 327 |
+
already_seen_classes = []
|
| 328 |
+
for line in lines_list:
|
| 329 |
+
try:
|
| 330 |
+
if "difficult" in line:
|
| 331 |
+
class_name, left, top, right, bottom, _difficult = line.split()
|
| 332 |
+
is_difficult = True
|
| 333 |
+
else:
|
| 334 |
+
class_name, left, top, right, bottom = line.split()
|
| 335 |
+
except:
|
| 336 |
+
if "difficult" in line:
|
| 337 |
+
line_split = line.split()
|
| 338 |
+
_difficult = line_split[-1]
|
| 339 |
+
bottom = line_split[-2]
|
| 340 |
+
right = line_split[-3]
|
| 341 |
+
top = line_split[-4]
|
| 342 |
+
left = line_split[-5]
|
| 343 |
+
class_name = ""
|
| 344 |
+
for name in line_split[:-5]:
|
| 345 |
+
class_name += name + " "
|
| 346 |
+
class_name = class_name[:-1]
|
| 347 |
+
is_difficult = True
|
| 348 |
+
else:
|
| 349 |
+
line_split = line.split()
|
| 350 |
+
bottom = line_split[-1]
|
| 351 |
+
right = line_split[-2]
|
| 352 |
+
top = line_split[-3]
|
| 353 |
+
left = line_split[-4]
|
| 354 |
+
class_name = ""
|
| 355 |
+
for name in line_split[:-4]:
|
| 356 |
+
class_name += name + " "
|
| 357 |
+
class_name = class_name[:-1]
|
| 358 |
+
|
| 359 |
+
bbox = left + " " + top + " " + right + " " + bottom
|
| 360 |
+
if is_difficult:
|
| 361 |
+
bounding_boxes.append({"class_name":class_name, "bbox":bbox, "used":False, "difficult":True})
|
| 362 |
+
is_difficult = False
|
| 363 |
+
else:
|
| 364 |
+
bounding_boxes.append({"class_name":class_name, "bbox":bbox, "used":False})
|
| 365 |
+
if class_name in gt_counter_per_class:
|
| 366 |
+
gt_counter_per_class[class_name] += 1
|
| 367 |
+
else:
|
| 368 |
+
gt_counter_per_class[class_name] = 1
|
| 369 |
+
|
| 370 |
+
if class_name not in already_seen_classes:
|
| 371 |
+
if class_name in counter_images_per_class:
|
| 372 |
+
counter_images_per_class[class_name] += 1
|
| 373 |
+
else:
|
| 374 |
+
counter_images_per_class[class_name] = 1
|
| 375 |
+
already_seen_classes.append(class_name)
|
| 376 |
+
|
| 377 |
+
with open(TEMP_FILES_PATH + "/" + file_id + "_ground_truth.json", 'w') as outfile:
|
| 378 |
+
json.dump(bounding_boxes, outfile)
|
| 379 |
+
|
| 380 |
+
gt_classes = list(gt_counter_per_class.keys())
|
| 381 |
+
gt_classes = sorted(gt_classes)
|
| 382 |
+
n_classes = len(gt_classes)
|
| 383 |
+
|
| 384 |
+
dr_files_list = glob.glob(DR_PATH + '/*.txt')
|
| 385 |
+
dr_files_list.sort()
|
| 386 |
+
for class_index, class_name in enumerate(gt_classes):
|
| 387 |
+
bounding_boxes = []
|
| 388 |
+
for txt_file in dr_files_list:
|
| 389 |
+
file_id = txt_file.split(".txt",1)[0]
|
| 390 |
+
file_id = os.path.basename(os.path.normpath(file_id))
|
| 391 |
+
temp_path = os.path.join(GT_PATH, (file_id + ".txt"))
|
| 392 |
+
if class_index == 0:
|
| 393 |
+
if not os.path.exists(temp_path):
|
| 394 |
+
error_msg = "Error. File not found: {}\n".format(temp_path)
|
| 395 |
+
error(error_msg)
|
| 396 |
+
lines = file_lines_to_list(txt_file)
|
| 397 |
+
for line in lines:
|
| 398 |
+
try:
|
| 399 |
+
tmp_class_name, confidence, left, top, right, bottom = line.split()
|
| 400 |
+
except:
|
| 401 |
+
line_split = line.split()
|
| 402 |
+
bottom = line_split[-1]
|
| 403 |
+
right = line_split[-2]
|
| 404 |
+
top = line_split[-3]
|
| 405 |
+
left = line_split[-4]
|
| 406 |
+
confidence = line_split[-5]
|
| 407 |
+
tmp_class_name = ""
|
| 408 |
+
for name in line_split[:-5]:
|
| 409 |
+
tmp_class_name += name + " "
|
| 410 |
+
tmp_class_name = tmp_class_name[:-1]
|
| 411 |
+
|
| 412 |
+
if tmp_class_name == class_name:
|
| 413 |
+
bbox = left + " " + top + " " + right + " " +bottom
|
| 414 |
+
bounding_boxes.append({"confidence":confidence, "file_id":file_id, "bbox":bbox})
|
| 415 |
+
|
| 416 |
+
bounding_boxes.sort(key=lambda x:float(x['confidence']), reverse=True)
|
| 417 |
+
with open(TEMP_FILES_PATH + "/" + class_name + "_dr.json", 'w') as outfile:
|
| 418 |
+
json.dump(bounding_boxes, outfile)
|
| 419 |
+
|
| 420 |
+
sum_AP = 0.0
|
| 421 |
+
ap_dictionary = {}
|
| 422 |
+
lamr_dictionary = {}
|
| 423 |
+
with open(RESULTS_FILES_PATH + "/results.txt", 'w') as results_file:
|
| 424 |
+
results_file.write("# AP and precision/recall per class\n")
|
| 425 |
+
count_true_positives = {}
|
| 426 |
+
|
| 427 |
+
for class_index, class_name in enumerate(gt_classes):
|
| 428 |
+
count_true_positives[class_name] = 0
|
| 429 |
+
dr_file = TEMP_FILES_PATH + "/" + class_name + "_dr.json"
|
| 430 |
+
dr_data = json.load(open(dr_file))
|
| 431 |
+
|
| 432 |
+
nd = len(dr_data)
|
| 433 |
+
tp = [0] * nd
|
| 434 |
+
fp = [0] * nd
|
| 435 |
+
score = [0] * nd
|
| 436 |
+
score_threhold_idx = 0
|
| 437 |
+
for idx, detection in enumerate(dr_data):
|
| 438 |
+
file_id = detection["file_id"]
|
| 439 |
+
score[idx] = float(detection["confidence"])
|
| 440 |
+
if score[idx] >= score_threhold:
|
| 441 |
+
score_threhold_idx = idx
|
| 442 |
+
|
| 443 |
+
if show_animation:
|
| 444 |
+
ground_truth_img = glob.glob1(IMG_PATH, file_id + ".*")
|
| 445 |
+
if len(ground_truth_img) == 0:
|
| 446 |
+
error("Error. Image not found with id: " + file_id)
|
| 447 |
+
elif len(ground_truth_img) > 1:
|
| 448 |
+
error("Error. Multiple image with id: " + file_id)
|
| 449 |
+
else:
|
| 450 |
+
img = cv2.imread(IMG_PATH + "/" + ground_truth_img[0])
|
| 451 |
+
img_cumulative_path = RESULTS_FILES_PATH + "/images/" + ground_truth_img[0]
|
| 452 |
+
if os.path.isfile(img_cumulative_path):
|
| 453 |
+
img_cumulative = cv2.imread(img_cumulative_path)
|
| 454 |
+
else:
|
| 455 |
+
img_cumulative = img.copy()
|
| 456 |
+
bottom_border = 60
|
| 457 |
+
BLACK = [0, 0, 0]
|
| 458 |
+
img = cv2.copyMakeBorder(img, 0, bottom_border, 0, 0, cv2.BORDER_CONSTANT, value=BLACK)
|
| 459 |
+
|
| 460 |
+
gt_file = TEMP_FILES_PATH + "/" + file_id + "_ground_truth.json"
|
| 461 |
+
ground_truth_data = json.load(open(gt_file))
|
| 462 |
+
ovmax = -1
|
| 463 |
+
gt_match = -1
|
| 464 |
+
bb = [float(x) for x in detection["bbox"].split()]
|
| 465 |
+
for obj in ground_truth_data:
|
| 466 |
+
if obj["class_name"] == class_name:
|
| 467 |
+
bbgt = [ float(x) for x in obj["bbox"].split() ]
|
| 468 |
+
bi = [max(bb[0],bbgt[0]), max(bb[1],bbgt[1]), min(bb[2],bbgt[2]), min(bb[3],bbgt[3])]
|
| 469 |
+
iw = bi[2] - bi[0] + 1
|
| 470 |
+
ih = bi[3] - bi[1] + 1
|
| 471 |
+
if iw > 0 and ih > 0:
|
| 472 |
+
ua = (bb[2] - bb[0] + 1) * (bb[3] - bb[1] + 1) + (bbgt[2] - bbgt[0]
|
| 473 |
+
+ 1) * (bbgt[3] - bbgt[1] + 1) - iw * ih
|
| 474 |
+
ov = iw * ih / ua
|
| 475 |
+
if ov > ovmax:
|
| 476 |
+
ovmax = ov
|
| 477 |
+
gt_match = obj
|
| 478 |
+
|
| 479 |
+
if show_animation:
|
| 480 |
+
status = "NO MATCH FOUND!"
|
| 481 |
+
|
| 482 |
+
min_overlap = MINOVERLAP
|
| 483 |
+
if ovmax >= min_overlap:
|
| 484 |
+
if "difficult" not in gt_match:
|
| 485 |
+
if not bool(gt_match["used"]):
|
| 486 |
+
tp[idx] = 1
|
| 487 |
+
gt_match["used"] = True
|
| 488 |
+
count_true_positives[class_name] += 1
|
| 489 |
+
with open(gt_file, 'w') as f:
|
| 490 |
+
f.write(json.dumps(ground_truth_data))
|
| 491 |
+
if show_animation:
|
| 492 |
+
status = "MATCH!"
|
| 493 |
+
else:
|
| 494 |
+
fp[idx] = 1
|
| 495 |
+
if show_animation:
|
| 496 |
+
status = "REPEATED MATCH!"
|
| 497 |
+
else:
|
| 498 |
+
fp[idx] = 1
|
| 499 |
+
if ovmax > 0:
|
| 500 |
+
status = "INSUFFICIENT OVERLAP"
|
| 501 |
+
|
| 502 |
+
"""
|
| 503 |
+
Draw image to show animation
|
| 504 |
+
"""
|
| 505 |
+
if show_animation:
|
| 506 |
+
height, widht = img.shape[:2]
|
| 507 |
+
white = (255,255,255)
|
| 508 |
+
light_blue = (255,200,100)
|
| 509 |
+
green = (0,255,0)
|
| 510 |
+
light_red = (30,30,255)
|
| 511 |
+
margin = 10
|
| 512 |
+
# 1nd line
|
| 513 |
+
v_pos = int(height - margin - (bottom_border / 2.0))
|
| 514 |
+
text = "Image: " + ground_truth_img[0] + " "
|
| 515 |
+
img, line_width = draw_text_in_image(img, text, (margin, v_pos), white, 0)
|
| 516 |
+
text = "Class [" + str(class_index) + "/" + str(n_classes) + "]: " + class_name + " "
|
| 517 |
+
img, line_width = draw_text_in_image(img, text, (margin + line_width, v_pos), light_blue, line_width)
|
| 518 |
+
if ovmax != -1:
|
| 519 |
+
color = light_red
|
| 520 |
+
if status == "INSUFFICIENT OVERLAP":
|
| 521 |
+
text = "IoU: {0:.2f}% ".format(ovmax*100) + "< {0:.2f}% ".format(min_overlap*100)
|
| 522 |
+
else:
|
| 523 |
+
text = "IoU: {0:.2f}% ".format(ovmax*100) + ">= {0:.2f}% ".format(min_overlap*100)
|
| 524 |
+
color = green
|
| 525 |
+
img, _ = draw_text_in_image(img, text, (margin + line_width, v_pos), color, line_width)
|
| 526 |
+
# 2nd line
|
| 527 |
+
v_pos += int(bottom_border / 2.0)
|
| 528 |
+
rank_pos = str(idx+1)
|
| 529 |
+
text = "Detection #rank: " + rank_pos + " confidence: {0:.2f}% ".format(float(detection["confidence"])*100)
|
| 530 |
+
img, line_width = draw_text_in_image(img, text, (margin, v_pos), white, 0)
|
| 531 |
+
color = light_red
|
| 532 |
+
if status == "MATCH!":
|
| 533 |
+
color = green
|
| 534 |
+
text = "Result: " + status + " "
|
| 535 |
+
img, line_width = draw_text_in_image(img, text, (margin + line_width, v_pos), color, line_width)
|
| 536 |
+
|
| 537 |
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 538 |
+
if ovmax > 0:
|
| 539 |
+
bbgt = [ int(round(float(x))) for x in gt_match["bbox"].split() ]
|
| 540 |
+
cv2.rectangle(img,(bbgt[0],bbgt[1]),(bbgt[2],bbgt[3]),light_blue,2)
|
| 541 |
+
cv2.rectangle(img_cumulative,(bbgt[0],bbgt[1]),(bbgt[2],bbgt[3]),light_blue,2)
|
| 542 |
+
cv2.putText(img_cumulative, class_name, (bbgt[0],bbgt[1] - 5), font, 0.6, light_blue, 1, cv2.LINE_AA)
|
| 543 |
+
bb = [int(i) for i in bb]
|
| 544 |
+
cv2.rectangle(img,(bb[0],bb[1]),(bb[2],bb[3]),color,2)
|
| 545 |
+
cv2.rectangle(img_cumulative,(bb[0],bb[1]),(bb[2],bb[3]),color,2)
|
| 546 |
+
cv2.putText(img_cumulative, class_name, (bb[0],bb[1] - 5), font, 0.6, color, 1, cv2.LINE_AA)
|
| 547 |
+
|
| 548 |
+
cv2.imshow("Animation", img)
|
| 549 |
+
cv2.waitKey(20)
|
| 550 |
+
output_img_path = RESULTS_FILES_PATH + "/images/detections_one_by_one/" + class_name + "_detection" + str(idx) + ".jpg"
|
| 551 |
+
cv2.imwrite(output_img_path, img)
|
| 552 |
+
cv2.imwrite(img_cumulative_path, img_cumulative)
|
| 553 |
+
|
| 554 |
+
cumsum = 0
|
| 555 |
+
for idx, val in enumerate(fp):
|
| 556 |
+
fp[idx] += cumsum
|
| 557 |
+
cumsum += val
|
| 558 |
+
|
| 559 |
+
cumsum = 0
|
| 560 |
+
for idx, val in enumerate(tp):
|
| 561 |
+
tp[idx] += cumsum
|
| 562 |
+
cumsum += val
|
| 563 |
+
|
| 564 |
+
rec = tp[:]
|
| 565 |
+
for idx, val in enumerate(tp):
|
| 566 |
+
rec[idx] = float(tp[idx]) / np.maximum(gt_counter_per_class[class_name], 1)
|
| 567 |
+
|
| 568 |
+
prec = tp[:]
|
| 569 |
+
for idx, val in enumerate(tp):
|
| 570 |
+
prec[idx] = float(tp[idx]) / np.maximum((fp[idx] + tp[idx]), 1)
|
| 571 |
+
|
| 572 |
+
ap, mrec, mprec = voc_ap(rec[:], prec[:])
|
| 573 |
+
F1 = np.array(rec)*np.array(prec)*2 / np.where((np.array(prec)+np.array(rec))==0, 1, (np.array(prec)+np.array(rec)))
|
| 574 |
+
|
| 575 |
+
sum_AP += ap
|
| 576 |
+
text = "{0:.2f}%".format(ap*100) + " = " + class_name + " AP " #class_name + " AP = {0:.2f}%".format(ap*100)
|
| 577 |
+
|
| 578 |
+
if len(prec)>0:
|
| 579 |
+
F1_text = "{0:.2f}".format(F1[score_threhold_idx]) + " = " + class_name + " F1 "
|
| 580 |
+
Recall_text = "{0:.2f}%".format(rec[score_threhold_idx]*100) + " = " + class_name + " Recall "
|
| 581 |
+
Precision_text = "{0:.2f}%".format(prec[score_threhold_idx]*100) + " = " + class_name + " Precision "
|
| 582 |
+
else:
|
| 583 |
+
F1_text = "0.00" + " = " + class_name + " F1 "
|
| 584 |
+
Recall_text = "0.00%" + " = " + class_name + " Recall "
|
| 585 |
+
Precision_text = "0.00%" + " = " + class_name + " Precision "
|
| 586 |
+
|
| 587 |
+
rounded_prec = [ '%.2f' % elem for elem in prec ]
|
| 588 |
+
rounded_rec = [ '%.2f' % elem for elem in rec ]
|
| 589 |
+
results_file.write(text + "\n Precision: " + str(rounded_prec) + "\n Recall :" + str(rounded_rec) + "\n\n")
|
| 590 |
+
|
| 591 |
+
if len(prec)>0:
|
| 592 |
+
print(text + "\t||\tscore_threhold=" + str(score_threhold) + " : " + "F1=" + "{0:.2f}".format(F1[score_threhold_idx])\
|
| 593 |
+
+ " ; Recall=" + "{0:.2f}%".format(rec[score_threhold_idx]*100) + " ; Precision=" + "{0:.2f}%".format(prec[score_threhold_idx]*100))
|
| 594 |
+
else:
|
| 595 |
+
print(text + "\t||\tscore_threhold=" + str(score_threhold) + " : " + "F1=0.00% ; Recall=0.00% ; Precision=0.00%")
|
| 596 |
+
ap_dictionary[class_name] = ap
|
| 597 |
+
|
| 598 |
+
n_images = counter_images_per_class[class_name]
|
| 599 |
+
lamr, mr, fppi = log_average_miss_rate(np.array(rec), np.array(fp), n_images)
|
| 600 |
+
lamr_dictionary[class_name] = lamr
|
| 601 |
+
|
| 602 |
+
if draw_plot:
|
| 603 |
+
plt.plot(rec, prec, '-o')
|
| 604 |
+
area_under_curve_x = mrec[:-1] + [mrec[-2]] + [mrec[-1]]
|
| 605 |
+
area_under_curve_y = mprec[:-1] + [0.0] + [mprec[-1]]
|
| 606 |
+
plt.fill_between(area_under_curve_x, 0, area_under_curve_y, alpha=0.2, edgecolor='r')
|
| 607 |
+
|
| 608 |
+
fig = plt.gcf()
|
| 609 |
+
fig.canvas.set_window_title('AP ' + class_name)
|
| 610 |
+
|
| 611 |
+
plt.title('class: ' + text)
|
| 612 |
+
plt.xlabel('Recall')
|
| 613 |
+
plt.ylabel('Precision')
|
| 614 |
+
axes = plt.gca()
|
| 615 |
+
axes.set_xlim([0.0,1.0])
|
| 616 |
+
axes.set_ylim([0.0,1.05])
|
| 617 |
+
fig.savefig(RESULTS_FILES_PATH + "/AP/" + class_name + ".png")
|
| 618 |
+
plt.cla()
|
| 619 |
+
|
| 620 |
+
plt.plot(score, F1, "-", color='orangered')
|
| 621 |
+
plt.title('class: ' + F1_text + "\nscore_threhold=" + str(score_threhold))
|
| 622 |
+
plt.xlabel('Score_Threhold')
|
| 623 |
+
plt.ylabel('F1')
|
| 624 |
+
axes = plt.gca()
|
| 625 |
+
axes.set_xlim([0.0,1.0])
|
| 626 |
+
axes.set_ylim([0.0,1.05])
|
| 627 |
+
fig.savefig(RESULTS_FILES_PATH + "/F1/" + class_name + ".png")
|
| 628 |
+
plt.cla()
|
| 629 |
+
|
| 630 |
+
plt.plot(score, rec, "-H", color='gold')
|
| 631 |
+
plt.title('class: ' + Recall_text + "\nscore_threhold=" + str(score_threhold))
|
| 632 |
+
plt.xlabel('Score_Threhold')
|
| 633 |
+
plt.ylabel('Recall')
|
| 634 |
+
axes = plt.gca()
|
| 635 |
+
axes.set_xlim([0.0,1.0])
|
| 636 |
+
axes.set_ylim([0.0,1.05])
|
| 637 |
+
fig.savefig(RESULTS_FILES_PATH + "/Recall/" + class_name + ".png")
|
| 638 |
+
plt.cla()
|
| 639 |
+
|
| 640 |
+
plt.plot(score, prec, "-s", color='palevioletred')
|
| 641 |
+
plt.title('class: ' + Precision_text + "\nscore_threhold=" + str(score_threhold))
|
| 642 |
+
plt.xlabel('Score_Threhold')
|
| 643 |
+
plt.ylabel('Precision')
|
| 644 |
+
axes = plt.gca()
|
| 645 |
+
axes.set_xlim([0.0,1.0])
|
| 646 |
+
axes.set_ylim([0.0,1.05])
|
| 647 |
+
fig.savefig(RESULTS_FILES_PATH + "/Precision/" + class_name + ".png")
|
| 648 |
+
plt.cla()
|
| 649 |
+
|
| 650 |
+
if show_animation:
|
| 651 |
+
cv2.destroyAllWindows()
|
| 652 |
+
if n_classes == 0:
|
| 653 |
+
print("未检测到任何种类,请检查标签信息与get_map.py中的classes_path是否修改。")
|
| 654 |
+
return 0
|
| 655 |
+
results_file.write("\n# mAP of all classes\n")
|
| 656 |
+
mAP = sum_AP / n_classes
|
| 657 |
+
text = "mAP = {0:.2f}%".format(mAP*100)
|
| 658 |
+
results_file.write(text + "\n")
|
| 659 |
+
print(text)
|
| 660 |
+
|
| 661 |
+
shutil.rmtree(TEMP_FILES_PATH)
|
| 662 |
+
|
| 663 |
+
"""
|
| 664 |
+
Count total of detection-results
|
| 665 |
+
"""
|
| 666 |
+
det_counter_per_class = {}
|
| 667 |
+
for txt_file in dr_files_list:
|
| 668 |
+
lines_list = file_lines_to_list(txt_file)
|
| 669 |
+
for line in lines_list:
|
| 670 |
+
class_name = line.split()[0]
|
| 671 |
+
if class_name in det_counter_per_class:
|
| 672 |
+
det_counter_per_class[class_name] += 1
|
| 673 |
+
else:
|
| 674 |
+
det_counter_per_class[class_name] = 1
|
| 675 |
+
dr_classes = list(det_counter_per_class.keys())
|
| 676 |
+
|
| 677 |
+
"""
|
| 678 |
+
Write number of ground-truth objects per class to results.txt
|
| 679 |
+
"""
|
| 680 |
+
with open(RESULTS_FILES_PATH + "/results.txt", 'a') as results_file:
|
| 681 |
+
results_file.write("\n# Number of ground-truth objects per class\n")
|
| 682 |
+
for class_name in sorted(gt_counter_per_class):
|
| 683 |
+
results_file.write(class_name + ": " + str(gt_counter_per_class[class_name]) + "\n")
|
| 684 |
+
|
| 685 |
+
"""
|
| 686 |
+
Finish counting true positives
|
| 687 |
+
"""
|
| 688 |
+
for class_name in dr_classes:
|
| 689 |
+
if class_name not in gt_classes:
|
| 690 |
+
count_true_positives[class_name] = 0
|
| 691 |
+
|
| 692 |
+
"""
|
| 693 |
+
Write number of detected objects per class to results.txt
|
| 694 |
+
"""
|
| 695 |
+
with open(RESULTS_FILES_PATH + "/results.txt", 'a') as results_file:
|
| 696 |
+
results_file.write("\n# Number of detected objects per class\n")
|
| 697 |
+
for class_name in sorted(dr_classes):
|
| 698 |
+
n_det = det_counter_per_class[class_name]
|
| 699 |
+
text = class_name + ": " + str(n_det)
|
| 700 |
+
text += " (tp:" + str(count_true_positives[class_name]) + ""
|
| 701 |
+
text += ", fp:" + str(n_det - count_true_positives[class_name]) + ")\n"
|
| 702 |
+
results_file.write(text)
|
| 703 |
+
|
| 704 |
+
"""
|
| 705 |
+
Plot the total number of occurences of each class in the ground-truth
|
| 706 |
+
"""
|
| 707 |
+
if draw_plot:
|
| 708 |
+
window_title = "ground-truth-info"
|
| 709 |
+
plot_title = "ground-truth\n"
|
| 710 |
+
plot_title += "(" + str(len(ground_truth_files_list)) + " files and " + str(n_classes) + " classes)"
|
| 711 |
+
x_label = "Number of objects per class"
|
| 712 |
+
output_path = RESULTS_FILES_PATH + "/ground-truth-info.png"
|
| 713 |
+
to_show = False
|
| 714 |
+
plot_color = 'forestgreen'
|
| 715 |
+
draw_plot_func(
|
| 716 |
+
gt_counter_per_class,
|
| 717 |
+
n_classes,
|
| 718 |
+
window_title,
|
| 719 |
+
plot_title,
|
| 720 |
+
x_label,
|
| 721 |
+
output_path,
|
| 722 |
+
to_show,
|
| 723 |
+
plot_color,
|
| 724 |
+
'',
|
| 725 |
+
)
|
| 726 |
+
|
| 727 |
+
# """
|
| 728 |
+
# Plot the total number of occurences of each class in the "detection-results" folder
|
| 729 |
+
# """
|
| 730 |
+
# if draw_plot:
|
| 731 |
+
# window_title = "detection-results-info"
|
| 732 |
+
# # Plot title
|
| 733 |
+
# plot_title = "detection-results\n"
|
| 734 |
+
# plot_title += "(" + str(len(dr_files_list)) + " files and "
|
| 735 |
+
# count_non_zero_values_in_dictionary = sum(int(x) > 0 for x in list(det_counter_per_class.values()))
|
| 736 |
+
# plot_title += str(count_non_zero_values_in_dictionary) + " detected classes)"
|
| 737 |
+
# # end Plot title
|
| 738 |
+
# x_label = "Number of objects per class"
|
| 739 |
+
# output_path = RESULTS_FILES_PATH + "/detection-results-info.png"
|
| 740 |
+
# to_show = False
|
| 741 |
+
# plot_color = 'forestgreen'
|
| 742 |
+
# true_p_bar = count_true_positives
|
| 743 |
+
# draw_plot_func(
|
| 744 |
+
# det_counter_per_class,
|
| 745 |
+
# len(det_counter_per_class),
|
| 746 |
+
# window_title,
|
| 747 |
+
# plot_title,
|
| 748 |
+
# x_label,
|
| 749 |
+
# output_path,
|
| 750 |
+
# to_show,
|
| 751 |
+
# plot_color,
|
| 752 |
+
# true_p_bar
|
| 753 |
+
# )
|
| 754 |
+
|
| 755 |
+
"""
|
| 756 |
+
Draw log-average miss rate plot (Show lamr of all classes in decreasing order)
|
| 757 |
+
"""
|
| 758 |
+
if draw_plot:
|
| 759 |
+
window_title = "lamr"
|
| 760 |
+
plot_title = "log-average miss rate"
|
| 761 |
+
x_label = "log-average miss rate"
|
| 762 |
+
output_path = RESULTS_FILES_PATH + "/lamr.png"
|
| 763 |
+
to_show = False
|
| 764 |
+
plot_color = 'royalblue'
|
| 765 |
+
draw_plot_func(
|
| 766 |
+
lamr_dictionary,
|
| 767 |
+
n_classes,
|
| 768 |
+
window_title,
|
| 769 |
+
plot_title,
|
| 770 |
+
x_label,
|
| 771 |
+
output_path,
|
| 772 |
+
to_show,
|
| 773 |
+
plot_color,
|
| 774 |
+
""
|
| 775 |
+
)
|
| 776 |
+
|
| 777 |
+
"""
|
| 778 |
+
Draw mAP plot (Show AP's of all classes in decreasing order)
|
| 779 |
+
"""
|
| 780 |
+
if draw_plot:
|
| 781 |
+
window_title = "mAP"
|
| 782 |
+
plot_title = "mAP = {0:.2f}%".format(mAP*100)
|
| 783 |
+
x_label = "Average Precision"
|
| 784 |
+
output_path = RESULTS_FILES_PATH + "/mAP.png"
|
| 785 |
+
to_show = True
|
| 786 |
+
plot_color = 'royalblue'
|
| 787 |
+
draw_plot_func(
|
| 788 |
+
ap_dictionary,
|
| 789 |
+
n_classes,
|
| 790 |
+
window_title,
|
| 791 |
+
plot_title,
|
| 792 |
+
x_label,
|
| 793 |
+
output_path,
|
| 794 |
+
to_show,
|
| 795 |
+
plot_color,
|
| 796 |
+
""
|
| 797 |
+
)
|
| 798 |
+
return mAP
|
| 799 |
+
|
| 800 |
+
def preprocess_gt(gt_path, class_names):
|
| 801 |
+
image_ids = os.listdir(gt_path)
|
| 802 |
+
results = {}
|
| 803 |
+
|
| 804 |
+
images = []
|
| 805 |
+
bboxes = []
|
| 806 |
+
for i, image_id in enumerate(image_ids):
|
| 807 |
+
lines_list = file_lines_to_list(os.path.join(gt_path, image_id))
|
| 808 |
+
boxes_per_image = []
|
| 809 |
+
image = {}
|
| 810 |
+
image_id = os.path.splitext(image_id)[0]
|
| 811 |
+
image['file_name'] = image_id + '.jpg'
|
| 812 |
+
image['width'] = 1
|
| 813 |
+
image['height'] = 1
|
| 814 |
+
#-----------------------------------------------------------------#
|
| 815 |
+
# 感谢 多学学英语吧 的提醒
|
| 816 |
+
# 解决了'Results do not correspond to current coco set'问题
|
| 817 |
+
#-----------------------------------------------------------------#
|
| 818 |
+
image['id'] = str(image_id)
|
| 819 |
+
|
| 820 |
+
for line in lines_list:
|
| 821 |
+
difficult = 0
|
| 822 |
+
if "difficult" in line:
|
| 823 |
+
line_split = line.split()
|
| 824 |
+
left, top, right, bottom, _difficult = line_split[-5:]
|
| 825 |
+
class_name = ""
|
| 826 |
+
for name in line_split[:-5]:
|
| 827 |
+
class_name += name + " "
|
| 828 |
+
class_name = class_name[:-1]
|
| 829 |
+
difficult = 1
|
| 830 |
+
else:
|
| 831 |
+
line_split = line.split()
|
| 832 |
+
left, top, right, bottom = line_split[-4:]
|
| 833 |
+
class_name = ""
|
| 834 |
+
for name in line_split[:-4]:
|
| 835 |
+
class_name += name + " "
|
| 836 |
+
class_name = class_name[:-1]
|
| 837 |
+
|
| 838 |
+
left, top, right, bottom = float(left), float(top), float(right), float(bottom)
|
| 839 |
+
if class_name not in class_names:
|
| 840 |
+
continue
|
| 841 |
+
cls_id = class_names.index(class_name) + 1
|
| 842 |
+
bbox = [left, top, right - left, bottom - top, difficult, str(image_id), cls_id, (right - left) * (bottom - top) - 10.0]
|
| 843 |
+
boxes_per_image.append(bbox)
|
| 844 |
+
images.append(image)
|
| 845 |
+
bboxes.extend(boxes_per_image)
|
| 846 |
+
results['images'] = images
|
| 847 |
+
|
| 848 |
+
categories = []
|
| 849 |
+
for i, cls in enumerate(class_names):
|
| 850 |
+
category = {}
|
| 851 |
+
category['supercategory'] = cls
|
| 852 |
+
category['name'] = cls
|
| 853 |
+
category['id'] = i + 1
|
| 854 |
+
categories.append(category)
|
| 855 |
+
results['categories'] = categories
|
| 856 |
+
|
| 857 |
+
annotations = []
|
| 858 |
+
for i, box in enumerate(bboxes):
|
| 859 |
+
annotation = {}
|
| 860 |
+
annotation['area'] = box[-1]
|
| 861 |
+
annotation['category_id'] = box[-2]
|
| 862 |
+
annotation['image_id'] = box[-3]
|
| 863 |
+
annotation['iscrowd'] = box[-4]
|
| 864 |
+
annotation['bbox'] = box[:4]
|
| 865 |
+
annotation['id'] = i
|
| 866 |
+
annotations.append(annotation)
|
| 867 |
+
results['annotations'] = annotations
|
| 868 |
+
return results
|
| 869 |
+
|
| 870 |
+
def preprocess_dr(dr_path, class_names):
|
| 871 |
+
image_ids = os.listdir(dr_path)
|
| 872 |
+
results = []
|
| 873 |
+
for image_id in image_ids:
|
| 874 |
+
lines_list = file_lines_to_list(os.path.join(dr_path, image_id))
|
| 875 |
+
image_id = os.path.splitext(image_id)[0]
|
| 876 |
+
for line in lines_list:
|
| 877 |
+
line_split = line.split()
|
| 878 |
+
confidence, left, top, right, bottom = line_split[-5:]
|
| 879 |
+
class_name = ""
|
| 880 |
+
for name in line_split[:-5]:
|
| 881 |
+
class_name += name + " "
|
| 882 |
+
class_name = class_name[:-1]
|
| 883 |
+
left, top, right, bottom = float(left), float(top), float(right), float(bottom)
|
| 884 |
+
result = {}
|
| 885 |
+
result["image_id"] = str(image_id)
|
| 886 |
+
if class_name not in class_names:
|
| 887 |
+
continue
|
| 888 |
+
result["category_id"] = class_names.index(class_name) + 1
|
| 889 |
+
result["bbox"] = [left, top, right - left, bottom - top]
|
| 890 |
+
result["score"] = float(confidence)
|
| 891 |
+
results.append(result)
|
| 892 |
+
return results
|
| 893 |
+
|
| 894 |
+
def get_coco_map(class_names, path):
|
| 895 |
+
GT_PATH = os.path.join(path, 'ground-truth')
|
| 896 |
+
DR_PATH = os.path.join(path, 'detection-results')
|
| 897 |
+
COCO_PATH = os.path.join(path, 'coco_eval')
|
| 898 |
+
|
| 899 |
+
if not os.path.exists(COCO_PATH):
|
| 900 |
+
os.makedirs(COCO_PATH)
|
| 901 |
+
|
| 902 |
+
GT_JSON_PATH = os.path.join(COCO_PATH, 'instances_gt.json')
|
| 903 |
+
DR_JSON_PATH = os.path.join(COCO_PATH, 'instances_dr.json')
|
| 904 |
+
|
| 905 |
+
with open(GT_JSON_PATH, "w") as f:
|
| 906 |
+
results_gt = preprocess_gt(GT_PATH, class_names)
|
| 907 |
+
json.dump(results_gt, f, indent=4)
|
| 908 |
+
|
| 909 |
+
with open(DR_JSON_PATH, "w") as f:
|
| 910 |
+
results_dr = preprocess_dr(DR_PATH, class_names)
|
| 911 |
+
json.dump(results_dr, f, indent=4)
|
| 912 |
+
if len(results_dr) == 0:
|
| 913 |
+
print("未检测到任何目标。")
|
| 914 |
+
return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
| 915 |
+
|
| 916 |
+
cocoGt = COCO(GT_JSON_PATH)
|
| 917 |
+
cocoDt = cocoGt.loadRes(DR_JSON_PATH)
|
| 918 |
+
cocoEval = COCOeval(cocoGt, cocoDt, 'bbox')
|
| 919 |
+
cocoEval.evaluate()
|
| 920 |
+
cocoEval.accumulate()
|
| 921 |
+
cocoEval.summarize()
|
| 922 |
+
|
| 923 |
+
return cocoEval.stats
|
faster-rcnn-pytorch-master/voc_annotation.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import random
|
| 3 |
+
import xml.etree.ElementTree as ET
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
|
| 7 |
+
from utils.utils import get_classes
|
| 8 |
+
|
| 9 |
+
#--------------------------------------------------------------------------------------------------------------------------------#
|
| 10 |
+
# annotation_mode用于指定该文件运行时计算的内容
|
| 11 |
+
# annotation_mode为0代表整个标签处理过程,包括获得VOCdevkit/VOC2007/ImageSets里面的txt以及训练用的2007_train.txt、2007_val.txt
|
| 12 |
+
# annotation_mode为1代表获得VOCdevkit/VOC2007/ImageSets里面的txt
|
| 13 |
+
# annotation_mode为2代表获得训练用的2007_train.txt、2007_val.txt
|
| 14 |
+
#--------------------------------------------------------------------------------------------------------------------------------#
|
| 15 |
+
annotation_mode = 2
|
| 16 |
+
#-------------------------------------------------------------------#
|
| 17 |
+
# 必须要修改,用于生成2007_train.txt、2007_val.txt的目标信息
|
| 18 |
+
# 与训练和预测所用的classes_path一致即可
|
| 19 |
+
# 如果生成的2007_train.txt里面没有目标信息
|
| 20 |
+
# 那么就是因为classes没有设定正确
|
| 21 |
+
# 仅在annotation_mode为0和2的时候有效
|
| 22 |
+
#-------------------------------------------------------------------#
|
| 23 |
+
classes_path = '/home/lab/LJ/wampee/faster-rcnn-pytorch-master/model_data/class.txt'
|
| 24 |
+
#--------------------------------------------------------------------------------------------------------------------------------#
|
| 25 |
+
# trainval_percent用于指定(训练集+验证集)与测试集的比例,默认情况下 (训练集+验证集):测试集 = 9:1
|
| 26 |
+
# train_percent用于指定(训练集+验证集)中训练集与验证集的比例,默认情况下 训练集:验证集 = 9:1
|
| 27 |
+
# 仅在annotation_mode为0和1的时候有效
|
| 28 |
+
#--------------------------------------------------------------------------------------------------------------------------------#
|
| 29 |
+
trainval_percent = 1.0
|
| 30 |
+
train_percent = 0.7
|
| 31 |
+
#-------------------------------------------------------#
|
| 32 |
+
# 指向VOC数据集所在的文件夹
|
| 33 |
+
# 默认指向根目录下的VOC数据集
|
| 34 |
+
#-------------------------------------------------------#
|
| 35 |
+
VOCdevkit_path = '/home/lab/LJ/wampee/faster-rcnn-pytorch-master/VOCdevkit/VOC2007'
|
| 36 |
+
|
| 37 |
+
VOCdevkit_sets = [('2007', 'train'), ('2007', 'val')]
|
| 38 |
+
classes, _ = get_classes(classes_path)
|
| 39 |
+
|
| 40 |
+
#-------------------------------------------------------#
|
| 41 |
+
# 统计目标数量
|
| 42 |
+
#-------------------------------------------------------#
|
| 43 |
+
photo_nums = np.zeros(len(VOCdevkit_sets))
|
| 44 |
+
nums = np.zeros(len(classes))
|
| 45 |
+
def convert_annotation(year, image_id, list_file):
|
| 46 |
+
in_file = open(os.path.join(VOCdevkit_path, 'VOC%s/Annotations/%s.xml'%(year, image_id)), encoding='utf-8')
|
| 47 |
+
tree=ET.parse(in_file)
|
| 48 |
+
root = tree.getroot()
|
| 49 |
+
|
| 50 |
+
for obj in root.iter('object'):
|
| 51 |
+
difficult = 0
|
| 52 |
+
if obj.find('difficult')!=None:
|
| 53 |
+
difficult = obj.find('difficult').text
|
| 54 |
+
cls = obj.find('name').text
|
| 55 |
+
if cls not in classes or int(difficult)==1:
|
| 56 |
+
continue
|
| 57 |
+
cls_id = classes.index(cls)
|
| 58 |
+
xmlbox = obj.find('bndbox')
|
| 59 |
+
b = (int(float(xmlbox.find('xmin').text)), int(float(xmlbox.find('ymin').text)), int(float(xmlbox.find('xmax').text)), int(float(xmlbox.find('ymax').text)))
|
| 60 |
+
list_file.write(" " + ",".join([str(a) for a in b]) + ',' + str(cls_id))
|
| 61 |
+
|
| 62 |
+
nums[classes.index(cls)] = nums[classes.index(cls)] + 1
|
| 63 |
+
|
| 64 |
+
if __name__ == "__main__":
|
| 65 |
+
random.seed(0)
|
| 66 |
+
if " " in os.path.abspath(VOCdevkit_path):
|
| 67 |
+
raise ValueError("数据集存放的文件夹路径与图片名称中不可以存在空格,否则会影响正常的模型训练,请注意修改。")
|
| 68 |
+
|
| 69 |
+
if annotation_mode == 0 or annotation_mode == 1:
|
| 70 |
+
print("Generate txt in ImageSets.")
|
| 71 |
+
xmlfilepath = os.path.join(VOCdevkit_path, '/home/lab/LJ/wampee/faster-rcnn-pytorch-master/VOCdevkit/VOC2007/Annotations')
|
| 72 |
+
saveBasePath = os.path.join(VOCdevkit_path, '/home/lab/LJ/wampee/faster-rcnn-pytorch-master/VOCdevkit/VOC2007/ImageSets/Main')
|
| 73 |
+
temp_xml = os.listdir(xmlfilepath)
|
| 74 |
+
total_xml = []
|
| 75 |
+
for xml in temp_xml:
|
| 76 |
+
if xml.endswith(".xml"):
|
| 77 |
+
total_xml.append(xml)
|
| 78 |
+
|
| 79 |
+
num = len(total_xml)
|
| 80 |
+
list = range(num)
|
| 81 |
+
tv = int(num*trainval_percent)
|
| 82 |
+
tr = int(tv*train_percent)
|
| 83 |
+
trainval= random.sample(list,tv)
|
| 84 |
+
train = random.sample(trainval,tr)
|
| 85 |
+
|
| 86 |
+
print("train and val size",tv)
|
| 87 |
+
print("train size",tr)
|
| 88 |
+
ftrainval = open(os.path.join(saveBasePath,'trainval.txt'), 'w')
|
| 89 |
+
ftest = open(os.path.join(saveBasePath,'test.txt'), 'w')
|
| 90 |
+
ftrain = open(os.path.join(saveBasePath,'train.txt'), 'w')
|
| 91 |
+
fval = open(os.path.join(saveBasePath,'val.txt'), 'w')
|
| 92 |
+
|
| 93 |
+
for i in list:
|
| 94 |
+
name=total_xml[i][:-4]+'\n'
|
| 95 |
+
if i in trainval:
|
| 96 |
+
ftrainval.write(name)
|
| 97 |
+
if i in train:
|
| 98 |
+
ftrain.write(name)
|
| 99 |
+
else:
|
| 100 |
+
fval.write(name)
|
| 101 |
+
else:
|
| 102 |
+
ftest.write(name)
|
| 103 |
+
|
| 104 |
+
ftrainval.close()
|
| 105 |
+
ftrain.close()
|
| 106 |
+
fval.close()
|
| 107 |
+
ftest.close()
|
| 108 |
+
print("Generate txt in ImageSets done.")
|
| 109 |
+
|
| 110 |
+
if annotation_mode == 0 or annotation_mode == 2:
|
| 111 |
+
print("Generate 2007_train.txt and 2007_val.txt for train.")
|
| 112 |
+
type_index = 0
|
| 113 |
+
for year, image_set in VOCdevkit_sets:
|
| 114 |
+
image_ids = open(os.path.join(VOCdevkit_path, 'VOC%s/ImageSets/Main/%s.txt'%(year, image_set)), encoding='utf-8').read().strip().split()
|
| 115 |
+
list_file = open('%s_%s.txt'%(year, image_set), 'w', encoding='utf-8')
|
| 116 |
+
for image_id in image_ids:
|
| 117 |
+
list_file.write('%s/VOC%s/JPEGImages/%s.jpg'%(os.path.abspath(VOCdevkit_path), year, image_id))
|
| 118 |
+
|
| 119 |
+
convert_annotation(year, image_id, list_file)
|
| 120 |
+
list_file.write('\n')
|
| 121 |
+
photo_nums[type_index] = len(image_ids)
|
| 122 |
+
type_index += 1
|
| 123 |
+
list_file.close()
|
| 124 |
+
print("Generate 2007_train.txt and 2007_val.txt for train done.")
|
| 125 |
+
|
| 126 |
+
def printTable(List1, List2):
|
| 127 |
+
for i in range(len(List1[0])):
|
| 128 |
+
print("|", end=' ')
|
| 129 |
+
for j in range(len(List1)):
|
| 130 |
+
print(List1[j][i].rjust(int(List2[j])), end=' ')
|
| 131 |
+
print("|", end=' ')
|
| 132 |
+
print()
|
| 133 |
+
|
| 134 |
+
str_nums = [str(int(x)) for x in nums]
|
| 135 |
+
tableData = [
|
| 136 |
+
classes, str_nums
|
| 137 |
+
]
|
| 138 |
+
colWidths = [0]*len(tableData)
|
| 139 |
+
len1 = 0
|
| 140 |
+
for i in range(len(tableData)):
|
| 141 |
+
for j in range(len(tableData[i])):
|
| 142 |
+
if len(tableData[i][j]) > colWidths[i]:
|
| 143 |
+
colWidths[i] = len(tableData[i][j])
|
| 144 |
+
printTable(tableData, colWidths)
|
| 145 |
+
|
| 146 |
+
if photo_nums[0] <= 500:
|
| 147 |
+
print("训练集数量小于500,属于较小的数据量,请注意设置较大的训练世代(Epoch)以满足足够的梯度下降次数(Step)。")
|
| 148 |
+
|
| 149 |
+
if np.sum(nums) == 0:
|
| 150 |
+
print("在数据集中并未获得任何目标,请注意修改classes_path对应自己的数据集,并且保证标签名字正确,否则训练将会没有任何效果!")
|
| 151 |
+
print("在数据集中并未获得任何目标,请注意修改classes_path对应自己的数据集,并且保证标签名字正确,否则训练将会没有任何效果!")
|
| 152 |
+
print("在数据集中并未获得任何目标,请注意修改classes_path对应自己的数据集,并且保证标签名字正确,否则训练将会没有任何效果!")
|
| 153 |
+
print("(重要的事情说三遍)。")
|
faster-rcnn-pytorch-master/常见问题汇总.md
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
问题汇总的博客地址为[https://blog.csdn.net/weixin_44791964/article/details/107517428](https://blog.csdn.net/weixin_44791964/article/details/107517428)。
|
| 2 |
+
|
| 3 |
+
# 问题汇总
|
| 4 |
+
## 1、下载问题
|
| 5 |
+
### a、代码下载
|
| 6 |
+
**问:up主,可以给我发一份代码吗,代码在哪里下载啊?
|
| 7 |
+
答:Github上的地址就在视频简介里。复制一下就能进去下载了。**
|
| 8 |
+
|
| 9 |
+
**问:up主,为什么我下载的代码提示压缩包损坏?
|
| 10 |
+
答:重新去Github下载。**
|
| 11 |
+
|
| 12 |
+
**问:up主,为什么我下载的代码和你在视频以及博客上的代码不一样?
|
| 13 |
+
答:我常常会对代码进行更新,最终以实际的代码为准。**
|
| 14 |
+
|
| 15 |
+
### b、 权值下载
|
| 16 |
+
**问:up主,为什么我下载的代码里面,model_data下面没有.pth或者.h5文件?
|
| 17 |
+
答:我一般会把权值上传到Github和百度网盘,在GITHUB的README里面就能找到。**
|
| 18 |
+
|
| 19 |
+
### c、 数据集下载
|
| 20 |
+
**问:up主,XXXX数据集在哪里下载啊?
|
| 21 |
+
答:一般数据集的下载地址我会放在README里面,基本上都有,没有的话请及时联系我添加,直接发github的issue即可**。
|
| 22 |
+
|
| 23 |
+
## 2、环境配置问题
|
| 24 |
+
### a、现在库中所用的环境
|
| 25 |
+
**pytorch代码对应的pytorch版本为1.2,博客地址对应**[https://blog.csdn.net/weixin_44791964/article/details/106037141](https://blog.csdn.net/weixin_44791964/article/details/106037141)。
|
| 26 |
+
|
| 27 |
+
**keras代码对应的tensorflow版本为1.13.2,keras版本是2.1.5,博客地址对应**[https://blog.csdn.net/weixin_44791964/article/details/104702142](https://blog.csdn.net/weixin_44791964/article/details/104702142)。
|
| 28 |
+
|
| 29 |
+
**tf2代码对应的tensorflow版本为2.2.0,无需安装keras,博客地址对应**[https://blog.csdn.net/weixin_44791964/article/details/109161493](https://blog.csdn.net/weixin_44791964/article/details/109161493)。
|
| 30 |
+
|
| 31 |
+
**问:你的代码某某某版本的tensorflow和pytorch能用嘛?
|
| 32 |
+
答:最好按照我推荐的配置,配置教程也有!其它版本的我没有试过!可能出现问题但是一般问题不大。仅需要改少量代码即可。**
|
| 33 |
+
|
| 34 |
+
### b、30系列显卡环境配置
|
| 35 |
+
30系显卡由于框架更新不可使用上述环境配置教程。
|
| 36 |
+
当前我已经测试的可以用的30显卡配置如下:
|
| 37 |
+
**pytorch代码对应的pytorch版本为1.7.0,cuda为11.0,cudnn为8.0.5**。
|
| 38 |
+
|
| 39 |
+
**keras代码无法在win10下配置cuda11,在ubuntu下可以百度查询一下,配置tensorflow版本为1.15.4,keras版本是2.1.5或者2.3.1(少量函数接口不同,代码可能还需要少量调整。)**
|
| 40 |
+
|
| 41 |
+
**tf2代码对应的tensorflow版本为2.4.0,cuda为11.0,cudnn为8.0.5**。
|
| 42 |
+
|
| 43 |
+
### c、GPU利用问题与环境使用问题
|
| 44 |
+
**问:为什么我安装了tensorflow-gpu但是却没用利用GPU进行训练呢?
|
| 45 |
+
答:确认tensorflow-gpu已经装好,利用pip list查看tensorflow版本,然后查看任务管理器或者利用nvidia命令看看是否使用了gpu进行训练,任务管理器的话要看显存使用情况。**
|
| 46 |
+
|
| 47 |
+
**问:up主,我好像没有在用gpu进行训练啊,怎么看是不是用了GPU进行训练?
|
| 48 |
+
答:查看是否使用GPU进行训练一般使用NVIDIA在命令行的查看命令,如果要看任务管理器的话,请看性能部分GPU的显存是否利用,或者查看任务管理器的Cuda,而非Copy。**
|
| 49 |
+

|
| 50 |
+
|
| 51 |
+
**问:up主,为什么我按照你的环境配置后还是不能使用?
|
| 52 |
+
答:请把你的GPU、CUDA、CUDNN、TF版本以及PYTORCH版本B站私聊告诉我。**
|
| 53 |
+
|
| 54 |
+
**问:出现如下错误**
|
| 55 |
+
```python
|
| 56 |
+
Traceback (most recent call last):
|
| 57 |
+
File "C:\Users\focus\Anaconda3\ana\envs\tensorflow-gpu\lib\site-packages\tensorflow\python\pywrap_tensorflow.py", line 58, in <module>
|
| 58 |
+
from tensorflow.python.pywrap_tensorflow_internal import *
|
| 59 |
+
File "C:\Users\focus\Anaconda3\ana\envs\tensorflow-gpu\lib\site-packages\tensorflow\python\pywrap_tensorflow_internal.py", line 28, in <module>
|
| 60 |
+
pywrap_tensorflow_internal = swig_import_helper()
|
| 61 |
+
File "C:\Users\focus\Anaconda3\ana\envs\tensorflow-gpu\lib\site-packages\tensorflow\python\pywrap_tensorflow_internal.py", line 24, in swig_import_helper
|
| 62 |
+
_mod = imp.load_module('_pywrap_tensorflow_internal', fp, pathname, description)
|
| 63 |
+
File "C:\Users\focus\Anaconda3\ana\envs\tensorflow-gpu\lib\imp.py", line 243, in load_modulereturn load_dynamic(name, filename, file)
|
| 64 |
+
File "C:\Users\focus\Anaconda3\ana\envs\tensorflow-gpu\lib\imp.py", line 343, in load_dynamic
|
| 65 |
+
return _load(spec)
|
| 66 |
+
ImportError: DLL load failed: 找不到指定的模块。
|
| 67 |
+
```
|
| 68 |
+
**答:如果没重启过就重启一下,否则重新按照步骤安装,还无法解决则把你的GPU、CUDA、CUDNN、TF版本以及PYTORCH版本私聊告诉我。**
|
| 69 |
+
|
| 70 |
+
### d、no module问题
|
| 71 |
+
**问:为什么提示说no module name utils.utils(no module name nets.yolo、no module name nets.ssd等一系列问题)啊?
|
| 72 |
+
答:utils并不需要用pip装,它就在我上传的仓库的根目录,出现这个问题的原因是根目录不对,查查相对目录和根目录的概念。查了基本上就明白了。**
|
| 73 |
+
|
| 74 |
+
**问:为什么提示说no module name matplotlib(no module name PIL,no module name cv2等等)?
|
| 75 |
+
答:这个库没安装打开命令行安装就好。pip install matplotlib**
|
| 76 |
+
|
| 77 |
+
**问:为什么我已经用pip装了opencv(pillow、matplotlib等),还是提示no module name cv2?
|
| 78 |
+
答:没有激活环境装,要激活对应的conda环境进行安装才可以正常使用**
|
| 79 |
+
|
| 80 |
+
**问:为什么提示说No module named 'torch' ?
|
| 81 |
+
答:其实我也真的很想知道为什么会有这个问题……这个pytorch没装是什么情况?一般就俩情况,一个是真的没装,还有一个是装到其它环境了,当前激活的环境不是自己装的环境。**
|
| 82 |
+
|
| 83 |
+
**问:为什么提示说No module named 'tensorflow' ?
|
| 84 |
+
答:同上。**
|
| 85 |
+
|
| 86 |
+
### e、cuda安装失败问题
|
| 87 |
+
一般cuda安装前需要安装Visual Studio,装个2017版本即可。
|
| 88 |
+
|
| 89 |
+
### f、Ubuntu系统问题
|
| 90 |
+
**所有代码在Ubuntu下可以使用,我两个系统都试过。**
|
| 91 |
+
|
| 92 |
+
### g、VSCODE提示错误的问题
|
| 93 |
+
**问:为什么在VSCODE里面提示一大堆的错误啊?
|
| 94 |
+
答:我也提示一大堆的错误,但是不影响,是VSCODE的问题,如果不想看错误的话就装Pycharm。**
|
| 95 |
+
|
| 96 |
+
### h、使用cpu进行训练与预测的问题
|
| 97 |
+
**对于keras和tf2的代码而言,如果想用cpu进行训练和预测,直接装cpu版本的tensorflow就可以了。**
|
| 98 |
+
|
| 99 |
+
**对于pytorch的代码而言,如果想用cpu进行训练和预测,需要将cuda=True修改成cuda=False。**
|
| 100 |
+
|
| 101 |
+
### i、tqdm没有pos参数问题
|
| 102 |
+
**问:运行代码提示'tqdm' object has no attribute 'pos'。
|
| 103 |
+
答:重装tqdm,换个版本就可以了。**
|
| 104 |
+
|
| 105 |
+
### j、提示decode(“utf-8”)的问题
|
| 106 |
+
**由于h5py库的更新,安装过程中会自动安装h5py=3.0.0以上的版本,会导致decode("utf-8")的错误!
|
| 107 |
+
各位一定要在安装完tensorflow后利用命令装h5py=2.10.0!**
|
| 108 |
+
```
|
| 109 |
+
pip install h5py==2.10.0
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
### k、提示TypeError: __array__() takes 1 positional argument but 2 were given错误
|
| 113 |
+
可以修改pillow版本解决。
|
| 114 |
+
```
|
| 115 |
+
pip install pillow==8.2.0
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
### l、其它问题
|
| 119 |
+
**问:为什么提示TypeError: cat() got an unexpected keyword argument 'axis',Traceback (most recent call last),AttributeError: 'Tensor' object has no attribute 'bool'?
|
| 120 |
+
答:这是版本问题,建议使用torch1.2以上版本**
|
| 121 |
+
**其它有很多稀奇古怪的问题,很多是版本问题,建议按照我的视频教程安装Keras和tensorflow。比如装的是tensorflow2,就不用问我说为什么我没法运行Keras-yolo啥的。那是必然不行的。**
|
| 122 |
+
|
| 123 |
+
## 3、目标检测库问题汇总(人脸检测和分类库也可参考)
|
| 124 |
+
### a、shape不匹配问题
|
| 125 |
+
#### 1)、训练时shape不匹配问题
|
| 126 |
+
**问:up主,为什么运行train.py会提示shape不匹配啊?
|
| 127 |
+
答:在keras环境中,因为你训练的种类和原始的种类不同,网络结构会变化,所以最尾部的shape会有少量不匹配。**
|
| 128 |
+
|
| 129 |
+
#### 2)、预测时shape不匹配问题
|
| 130 |
+
**问:为什么我运行predict.py会提示我说shape不匹配呀。
|
| 131 |
+
在Pytorch里面是这样的:**
|
| 132 |
+

|
| 133 |
+
在Keras里面是这样的:
|
| 134 |
+

|
| 135 |
+
**答:原因主要有仨:
|
| 136 |
+
1、在ssd、FasterRCNN里面,可能是train.py里面的num_classes没改。
|
| 137 |
+
2、model_path没改。
|
| 138 |
+
3、classes_path没改。
|
| 139 |
+
请检查清楚了!确定自己所用的model_path和classes_path是对应的!训练的时候用到的num_classes或者classes_path也需要检查!**
|
| 140 |
+
|
| 141 |
+
### b、显存不足问题
|
| 142 |
+
**问:为什么我运行train.py下面的命令行闪的贼快,还提示OOM啥的?
|
| 143 |
+
答:这是在keras中出现的,爆显存了,可以改小batch_size,SSD的显存占用率是最小的,建议用SSD;
|
| 144 |
+
2G显存:SSD、YOLOV4-TINY
|
| 145 |
+
4G显存:YOLOV3
|
| 146 |
+
6G显存:YOLOV4、Retinanet、M2det、Efficientdet、Faster RCNN等
|
| 147 |
+
8G+显存:随便选吧。**
|
| 148 |
+
**需要注意的是,受到BatchNorm2d影响,batch_size不可为1,至少为2。**
|
| 149 |
+
|
| 150 |
+
**问:为什么提示 RuntimeError: CUDA out of memory. Tried to allocate 52.00 MiB (GPU 0; 15.90 GiB total capacity; 14.85 GiB already allocated; 51.88 MiB free; 15.07 GiB reserved in total by PyTorch)?
|
| 151 |
+
答:这是pytorch中出现的,爆显存了,同上。**
|
| 152 |
+
|
| 153 |
+
**问:为什么我显存都没利用,就直接爆显存了?
|
| 154 |
+
答:都爆显存了,自然就不利用了,模型没有开始训练。**
|
| 155 |
+
### c、训练问题(冻结训练,LOSS问题、训练效果问题等)
|
| 156 |
+
**问:为什么要冻结训练和解冻训练呀?
|
| 157 |
+
答:这是迁移学习的思想,��为神经网络主干特征提取部分所提取到的特征是通用的,我们冻结起来训练可以加快训练效率,也可以防止权值被破坏。**
|
| 158 |
+
在冻结阶段,模型的主干被冻结了,特征提取网络不发生改变。占用的显存较小,仅对网络进行微调。
|
| 159 |
+
在解冻阶段,模型的主干不被冻结了,特征提取网络会发生改变。占用的显存较大,网络所有的参数都会发生改变。
|
| 160 |
+
|
| 161 |
+
**问:为什么我的网络不收敛啊,LOSS是XXXX。
|
| 162 |
+
答:不同网络的LOSS不同,LOSS只是一个参考指标,用于查看网络是否收敛,而非评价网络好坏,我的yolo代码都没有归一化,所以LOSS值看起来比较高,LOSS的值不重要,重要的是是否在变小,预测是否有效果。**
|
| 163 |
+
|
| 164 |
+
**问:为什么我的训练效果不好?预测了没有框(框不准)。
|
| 165 |
+
答:**
|
| 166 |
+
|
| 167 |
+
考虑几个问题:
|
| 168 |
+
1、目标信息问题,查看2007_train.txt文件是否有目标信息,没有的话请修改voc_annotation.py。
|
| 169 |
+
2、数据集问题,小于500的自行考虑增加数据集,同时测试不同的模型,确认数据集是好的。
|
| 170 |
+
3、是否解冻训练,如果数据集分布与常规画面差距过大需要进一步解冻训练,调整主干,加强特征提取能力。
|
| 171 |
+
4、网络问题,比如SSD不适合小目标,因为先验框固定了。
|
| 172 |
+
5、训练时长问题,有些同学只训练了几代表示没有效果,按默认参数训练完。
|
| 173 |
+
6、确认自己是否按照步骤去做了,如果比如voc_annotation.py里面的classes是否修改了等。
|
| 174 |
+
7、不同网络的LOSS不同,LOSS只是一个参考指标,用于查看网络是否收敛,而非评价网络好坏,LOSS的值不重要,重要的是是否收敛。
|
| 175 |
+
|
| 176 |
+
**问:我怎么出现了gbk什么的编码错误啊:**
|
| 177 |
+
```python
|
| 178 |
+
UnicodeDecodeError: 'gbk' codec can't decode byte 0xa6 in position 446: illegal multibyte sequence
|
| 179 |
+
```
|
| 180 |
+
**答:标签和路径不要使用中文,如果一定要使用中文,请注意处理的时候编码的问题,改成打开文件的encoding方式改为utf-8。**
|
| 181 |
+
|
| 182 |
+
**问:我的图片是xxx*xxx的分辨率的,可以用吗!**
|
| 183 |
+
**答:可以用,代码里面会自动进行resize或者数据增强。**
|
| 184 |
+
|
| 185 |
+
**问:怎么进行多GPU训练?
|
| 186 |
+
答:pytorch的大多数代码可以直接使用gpu训练,keras的话直接百度就好了,实现并不复杂,我没有多卡没法详细测试,还需要各位同学自己努力了。**
|
| 187 |
+
### d、灰度图问题
|
| 188 |
+
**问:能不能训练灰度图(预测灰度图)啊?
|
| 189 |
+
答:我的大多数库会将灰度图转化成RGB进行训练和预测,如果遇到代码不能训练或者预测灰度图的情况,可以尝试一下在get_random_data里面将Image.open后的结果转换成RGB,预测的时候也这样试试。(仅供参考)**
|
| 190 |
+
|
| 191 |
+
### e、断点续练问题
|
| 192 |
+
**问:我已经训练过几个世代了,能不能从这个基础上继续开始训练
|
| 193 |
+
答:可以,你在训练前,和载入预训练权重一样载入训练过的权重就行了。一般训练好的权重会保存在logs文件夹里面,将model_path修改成你要开始的权值的路径即可。**
|
| 194 |
+
|
| 195 |
+
### f、预训练权重的问题
|
| 196 |
+
**问:如果我要训练其它的数据集,预训练权重要怎么办啊?**
|
| 197 |
+
**答:数据的预训练权重对不同数据集是通用的,因为特征是通用的,预训练权重对于99%的情况都必须要用,不用的话权值太过随机,特征提取效果不明显,网络训练的结果也不会好。**
|
| 198 |
+
|
| 199 |
+
**问:up,我修改了网络,预训练权重还能用吗?
|
| 200 |
+
答:修改了主干的话,如果不是用的现有的网络,基本上预训练权重是不能用的,要么就自己判断权值里卷积核的shape然后自己匹配,要么只能自己预训练去了;修改了后半部分的话,前半部分的主干部分的预训练权重还是可以用的,如果是pytorch代码的话,需要自己修改一下载入权值的方式,判断shape后载入,如果是keras代码,直接by_name=True,skip_mismatch=True即可。**
|
| 201 |
+
权值匹配的方式可以参考如下:
|
| 202 |
+
```python
|
| 203 |
+
# 加快模型训练的效率
|
| 204 |
+
print('Loading weights into state dict...')
|
| 205 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 206 |
+
model_dict = model.state_dict()
|
| 207 |
+
pretrained_dict = torch.load(model_path, map_location=device)
|
| 208 |
+
a = {}
|
| 209 |
+
for k, v in pretrained_dict.items():
|
| 210 |
+
try:
|
| 211 |
+
if np.shape(model_dict[k]) == np.shape(v):
|
| 212 |
+
a[k]=v
|
| 213 |
+
except:
|
| 214 |
+
pass
|
| 215 |
+
model_dict.update(a)
|
| 216 |
+
model.load_state_dict(model_dict)
|
| 217 |
+
print('Finished!')
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
**问:我要怎么不使用预训练权重啊?
|
| 221 |
+
答:把载入预训练权重的代码注释了就行。**
|
| 222 |
+
|
| 223 |
+
**问:为什么我不使用预训练权重效果这么差啊?
|
| 224 |
+
答:因为随机初始化的权值不好,提取的特征不好,也就导致了模型训练的效果不好,voc07+12、coco+voc07+12效果都不一样,预训练权重还是非常重要的。**
|
| 225 |
+
|
| 226 |
+
### g、视频检测问题与摄像头检测问题
|
| 227 |
+
**问:怎么用���像头检测呀?
|
| 228 |
+
答:predict.py修改参数可以进行摄像头检测,也有视频详细解释了摄像头检测的思路。**
|
| 229 |
+
|
| 230 |
+
**问:怎么用视频检测呀?
|
| 231 |
+
答:同上**
|
| 232 |
+
### h、从0开始训练问题
|
| 233 |
+
**问:怎么在模型上从0开始训练?
|
| 234 |
+
答:在算力不足与调参能力不足的情况下从0开始训练毫无意义。模型特征提取能力在随机初始化参数的情况下非常差。没有好的参数调节能力和算力,无法使得网络正常收敛。**
|
| 235 |
+
如果一定要从0开始,那么训练的时候请注意几点:
|
| 236 |
+
- 不载入预训练权重。
|
| 237 |
+
- 不要进行冻结训练,注释冻结模型的代码。
|
| 238 |
+
|
| 239 |
+
**问:为什么我不使用预训练权重效果这么差啊?
|
| 240 |
+
答:因为随机初始化的权值不好,提取的特征不好,也就导致了模型训练的效果不好,voc07+12、coco+voc07+12效果都不一样,预训练权重还是非常重要的。**
|
| 241 |
+
|
| 242 |
+
### i、保存问题
|
| 243 |
+
**问:检测完的图片怎么保存?
|
| 244 |
+
答:一般目标检测用的是Image,所以查询一下PIL库的Image如何进行保存。详细看看predict.py文件的注释。**
|
| 245 |
+
|
| 246 |
+
**问:怎么用视频保存呀?
|
| 247 |
+
答:详细看看predict.py文件的注释。**
|
| 248 |
+
|
| 249 |
+
### j、遍历问题
|
| 250 |
+
**问:如何对一个文件夹的图片进行遍历?
|
| 251 |
+
答:一般使用os.listdir先找出文件夹里面的所有图片,然后根据predict.py文件里面的执行思路检测图片就行了,详细看看predict.py文件的注释。**
|
| 252 |
+
|
| 253 |
+
**问:如何对一个文件夹的图片进行遍历?并且保存。
|
| 254 |
+
答:遍历的话一般使用os.listdir先找出文件夹里面的所有图片,然后根据predict.py文件里面的执行思路检测图片就行了。保存的话一般目标检测用的是Image,所以查询一下PIL库的Image如何进行保存。如果有些库用的是cv2,那就是查一下cv2怎么保存图片。详细看看predict.py文件的注释。**
|
| 255 |
+
|
| 256 |
+
### k、路径问题(No such file or directory)
|
| 257 |
+
**问:我怎么出现了这样的错误呀:**
|
| 258 |
+
```python
|
| 259 |
+
FileNotFoundError: 【Errno 2】 No such file or directory
|
| 260 |
+
……………………………………
|
| 261 |
+
……………………………………
|
| 262 |
+
```
|
| 263 |
+
**答:去检查一下文件夹路径,查看是否有对应文件;并且检查一下2007_train.txt,其中文件路径是否有错。**
|
| 264 |
+
关于路径有几个重要的点:
|
| 265 |
+
**文件夹名称中一定不要有空格。
|
| 266 |
+
注意相对路径和绝对路径。
|
| 267 |
+
多百度路径相关的知识。**
|
| 268 |
+
|
| 269 |
+
**所有的路径问题基本上都是根目录问题,好好查一下相对目录的概念!**
|
| 270 |
+
### l、和原版比较问题
|
| 271 |
+
**问:你这个代码和原版比怎么样,可以达到原版的效果么?
|
| 272 |
+
答:基本上可以达到,我都用voc数据测过,我没有好显卡,没有能力在coco上测试与训练。**
|
| 273 |
+
|
| 274 |
+
**问:你有没有实现yolov4所有的tricks,和原版差距多少?
|
| 275 |
+
答:并没有实现全部的改进部分,由于YOLOV4使用的改进实在太多了,很难完全实现与列出来,这里只列出来了一些我比较感兴趣,而且非常有效的改进。论文中提到的SAM(注意力机制模块),作者自己的源码也没有使用。还有其它很多的tricks,不是所有的tricks都有提升,我也没法实现全部的tricks。至于和原版的比较,我没有能力训练coco数据集,根据使用过的同学反应差距不大。**
|
| 276 |
+
|
| 277 |
+
### m、FPS问题(检测速度问题)
|
| 278 |
+
**问:你这个FPS可以到达多少,可以到 XX FPS么?
|
| 279 |
+
答:FPS和机子的配置有关,配置高就快,配置低就慢。**
|
| 280 |
+
|
| 281 |
+
**问:为什么我用服务器去测试yolov4(or others)的FPS只有十几?
|
| 282 |
+
答:检查是否正确安装了tensorflow-gpu或者pytorch的gpu版本,如果已经正确安装,可以去利用time.time()的方法查看detect_image里面,哪一段代码耗时更长(不仅只有网络耗时长,其它处理部分也会耗时,如绘图等)。**
|
| 283 |
+
|
| 284 |
+
**问:为什么论文中说速度可以达到XX,但是这里却没有?
|
| 285 |
+
答:检查是否正确安装了tensorflow-gpu或者pytorch的gpu版本,如果已经正确安装,可以去利用time.time()的方法查看detect_image里面,哪一段代码耗时更长(不仅只有网络耗时长,其它处理部分也会耗时,如绘图等)。有些论文还会使用多batch进行预测,我并没有去实现这个部分。**
|
| 286 |
+
|
| 287 |
+
### n、预测图片不显示问题
|
| 288 |
+
**问:为什么你的代码在预测完成后不显示图片?只是在命令行告诉我有什么目标。
|
| 289 |
+
答:给系统安装一个图片查看器就行了。**
|
| 290 |
+
|
| 291 |
+
### o、算法评价问题(目标检测的map、PR曲线、Recall、Precision等)
|
| 292 |
+
**问:怎么计算map?
|
| 293 |
+
答:看map视频,都一个流程。**
|
| 294 |
+
|
| 295 |
+
**问:计算map的时候,get_map.py里面有一个MINOVERLAP是什么用的,是iou吗?
|
| 296 |
+
答:是iou,它的作用是判断预测框和真实框的重合成度,如果重合程度大于MINOVERLAP,则预测正确。**
|
| 297 |
+
|
| 298 |
+
**问:为什么get_map.py里面的self.confidence(self.score)要设置的那么小?
|
| 299 |
+
答:看一下map的视频的原理部分,要知道所有的结果然后再进行pr曲线的绘制。**
|
| 300 |
+
|
| 301 |
+
**问:能不能说说怎么绘制PR曲线啥的呀。
|
| 302 |
+
答:可以看mAP视频,结果里面有PR曲线。**
|
| 303 |
+
|
| 304 |
+
**问:怎么计算Recall、Precision指标。
|
| 305 |
+
答:这俩指标应该是相对于特定的置信度的,计算map的时候也会获得。**
|
| 306 |
+
|
| 307 |
+
### p、coco数据集训练问题
|
| 308 |
+
**问:目标检测怎么训练COCO数据集啊?。
|
| 309 |
+
答:coco数据训练所需要的txt文件可以参考qqwweee的yolo3的库,格式都是一样的。**
|
| 310 |
+
|
| 311 |
+
### q、模型优化(模型修改)问题
|
| 312 |
+
**问:up,YOLO系列使用Focal LOSS的代码你有吗,有提升吗?
|
| 313 |
+
答:很多人试过,提升效果也不大(甚至变的更Low),它自己有自己的正负样本的平衡方式。**
|
| 314 |
+
|
| 315 |
+
**问:up,我修改了网络,预训练权重还能用吗?
|
| 316 |
+
答:修改了主干的话,如果不是用的现有的网络,基本上预训练权重是不能用的,要么就自己判断权值里卷积核的shape然后自己匹配,要么只能自己预训练去了;修改了后半部分的话,前半部分的主干部分的预训练权重还是可以用的,如果是pytorch代码的话,需要自己修改一下载入权值的方式,判断shape后载入,如果是keras代码,直接by_name=True,skip_mismatch=True即可。**
|
| 317 |
+
权值匹配的方式可以参考如下:
|
| 318 |
+
```python
|
| 319 |
+
# 加快模型训练的效率
|
| 320 |
+
print('Loading weights into state dict...')
|
| 321 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 322 |
+
model_dict = model.state_dict()
|
| 323 |
+
pretrained_dict = torch.load(model_path, map_location=device)
|
| 324 |
+
a = {}
|
| 325 |
+
for k, v in pretrained_dict.items():
|
| 326 |
+
try:
|
| 327 |
+
if np.shape(model_dict[k]) == np.shape(v):
|
| 328 |
+
a[k]=v
|
| 329 |
+
except:
|
| 330 |
+
pass
|
| 331 |
+
model_dict.update(a)
|
| 332 |
+
model.load_state_dict(model_dict)
|
| 333 |
+
print('Finished!')
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
**问:up,怎么修改模型啊,我想发个小论文!
|
| 337 |
+
答:建议看看yolov3和yolov4的区别,然后看看yolov4的论文,作为一个大型调参现场非常有参考意义,使用了很多tricks。我能给的建议就是多看一些经典模型,然后拆解里面的亮点结构并使用。**
|
| 338 |
+
|
| 339 |
+
### r、部署问题
|
| 340 |
+
我没有具体部署到手机等设备上过,所以很多部署问题我并不了解……
|
| 341 |
+
|
| 342 |
+
## 4、语义分割库问题汇总
|
| 343 |
+
### a、shape不匹配问题
|
| 344 |
+
#### 1)、训练时shape不匹配问题
|
| 345 |
+
**问:up主,为什么运行train.py会提示shape不匹配啊?
|
| 346 |
+
答:在keras环境中,因为你训练的种类和原始的种类不同,网络结构会变化,所以最尾部的shape会有少量不匹配。**
|
| 347 |
+
|
| 348 |
+
#### 2)、预测时shape不匹配问题
|
| 349 |
+
**问:为什么我运行predict.py会提示我说shape不匹配呀。
|
| 350 |
+
在Pytorch里面是这样的:**
|
| 351 |
+

|
| 352 |
+
在Keras里面是这样的:
|
| 353 |
+

|
| 354 |
+
**答:原因主要有二:
|
| 355 |
+
1、train.py里面的num_classes没改。
|
| 356 |
+
2、预测时num_classes没改。
|
| 357 |
+
请检查清楚!训练和预测的时候用到的num_classes都需要检查!**
|
| 358 |
+
|
| 359 |
+
### b、显存不足问题
|
| 360 |
+
**问:为什么我运行train.py下面的命令行闪的贼快,还提示OOM啥的?
|
| 361 |
+
答:这是在keras中出现的,爆显存了,可以改小batch_size。**
|
| 362 |
+
|
| 363 |
+
**需要注意的是,受到BatchNorm2d影响,batch_size不可为1,至少为2。**
|
| 364 |
+
|
| 365 |
+
**问:为什么提示 RuntimeError: CUDA out of memory. Tried to allocate 52.00 MiB (GPU 0; 15.90 GiB total capacity; 14.85 GiB already allocated; 51.88 MiB free; 15.07 GiB reserved in total by PyTorch)?
|
| 366 |
+
答:这是pytorch中出现的,爆显存了,同上。**
|
| 367 |
+
|
| 368 |
+
**问:为什么我显存都没利用,就直接爆显存了?
|
| 369 |
+
答:都爆显存了,自然就不利用了,模型没有开始训练。**
|
| 370 |
+
|
| 371 |
+
### c、训练问题(冻结训练,LOSS问题、训练效果问题等)
|
| 372 |
+
**问:为什么要冻结训练和解冻训练呀?
|
| 373 |
+
答:这是迁移学习的思想,因为神经网络主干特征提取部分所提取到的特征是通用的,我们冻结起来训练可以加快训练效率,也可以防止权值被破坏。**
|
| 374 |
+
**在冻结阶段,模型的主干被冻结了,特征提取网络不发生改变。占用的显存较小,仅对网络进行微调。**
|
| 375 |
+
**在解冻阶段,模型的主干不被冻结了,特征提取网络会发生改变。占用的显存较大,网络所有的参数都会发生改变。**
|
| 376 |
+
|
| 377 |
+
**问:为什么我的网络不收敛啊,LOSS是XXXX。
|
| 378 |
+
答:不同网络的LOSS不同,LOSS只是一个参考指标,用于查看网络是否收敛,而非评价网络好坏,我的yolo代码都没有归一化,所以LOSS值看起来比较高,LOSS的值不重要,重要的是是否在变小,预测是否有效果。**
|
| 379 |
+
|
| 380 |
+
**问:为什么我的训练效果不���?预测了没有目标,结果是一片黑。
|
| 381 |
+
答:**
|
| 382 |
+
**考虑几个问题:
|
| 383 |
+
1、数据集问题,这是最重要的问题。小于500的自行考虑增加数据集;一定要检查数据集的标签,视频中详细解析了VOC数据集的格式,但并不是有输入图片有输出标签即可,还需要确认标签的每一个像素值是否为它对应的种类。很多同学的标签格式不对,最常见的错误格式就是标签的背景为黑,目标为白,此时目标的像素点值为255,无法正常训练,目标需要为1才行。
|
| 384 |
+
2、是否解冻训练,如果数据集分布与常规画面差距过大需要进一步解冻训练,调整主干,加强特征提取能力。
|
| 385 |
+
3、网络问题,可以尝试不同的网络。
|
| 386 |
+
4、训练时长问题,有些同学只训练了几代表示没有效果,按默认参数训练完。
|
| 387 |
+
5、确认自己是否按照步骤去做了。
|
| 388 |
+
6、不同网络的LOSS不同,LOSS只是一个参考指标,用于查看网络是否收敛,而非评价网络好坏,LOSS的值不重要,重要的是是否收敛。**
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
|
| 392 |
+
**问:为什么我的训练效果不好?对小目标预测不准确。
|
| 393 |
+
答:对于deeplab和pspnet而言,可以修改一下downsample_factor,当downsample_factor为16的时候下采样倍数过多,效果不太好,可以修改为8。**
|
| 394 |
+
|
| 395 |
+
**问:我怎么出现了gbk什么的编码错误啊:**
|
| 396 |
+
```python
|
| 397 |
+
UnicodeDecodeError: 'gbk' codec can't decode byte 0xa6 in position 446: illegal multibyte sequence
|
| 398 |
+
```
|
| 399 |
+
**答:标签和路径不要使用中文,如果一定要使用中文,请注意处理的时候编码的问题,改成打开文件的encoding方式改为utf-8。**
|
| 400 |
+
|
| 401 |
+
**问:我的图片是xxx*xxx的分辨率的,可以用吗!**
|
| 402 |
+
**答:可以用,代码里面会自动进行resize或者数据增强。**
|
| 403 |
+
|
| 404 |
+
**问:怎么进行多GPU训练?
|
| 405 |
+
答:pytorch的大多数代码可以直接使用gpu训练,keras的话直接百度就好了,实现并不复杂,我没有多卡没法详细测试,还需要各位同学自己努力了。**
|
| 406 |
+
|
| 407 |
+
### d、灰度图问题
|
| 408 |
+
**问:能不能训练灰度图(预测灰度图)啊?
|
| 409 |
+
答:我的大多数库会将灰度图转化成RGB进行训练和预测,如果遇到代码不能训练或者预测灰度图的情况,可以尝试一下在get_random_data里面将Image.open后的结果转换成RGB,预测的时候也这样试试。(仅供参考)**
|
| 410 |
+
|
| 411 |
+
### e、断点续练问题
|
| 412 |
+
**问:我已经训练过几个世代了,能不能从这个基础上继续开始训练
|
| 413 |
+
答:可以,你在训练前,和载入预训练权重一样载入训练过的权重就行了。一般训练好的权重会保存在logs文件夹里面,将model_path修改成你要开始的权值的路径即可。**
|
| 414 |
+
|
| 415 |
+
### f、预训练权重的问题
|
| 416 |
+
|
| 417 |
+
**问:如果我要训练其它的数据集,预训练权重要怎么办啊?**
|
| 418 |
+
**答:数据的预训练权重对不同数据集是通用的,因为特征是通用的,预训练权重对于99%的情况都必须要用,不用的话权值太过随机,特征提取效果不明显,网络训练的结果也不会好。**
|
| 419 |
+
|
| 420 |
+
**问:up,我修改了网络,预训练权重还能用吗?
|
| 421 |
+
答:修改了主干的话,如果不是用的现有的网络,基本上预训练权重是不能用的,要么就自己判断权值里卷积核的shape然后自己匹配,要么只能自己预训练去了;修改了后半部分的话,前半部分的主干部分的预训练权重还是可以用的,如果是pytorch代码的话,需要自己修改一下载入权值的方式,判断shape后载入,如果是keras代码,直接by_name=True,skip_mismatch=True即可。**
|
| 422 |
+
权值匹配的方式可以参考如下:
|
| 423 |
+
|
| 424 |
+
```python
|
| 425 |
+
# 加快模型训练的效率
|
| 426 |
+
print('Loading weights into state dict...')
|
| 427 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 428 |
+
model_dict = model.state_dict()
|
| 429 |
+
pretrained_dict = torch.load(model_path, map_location=device)
|
| 430 |
+
a = {}
|
| 431 |
+
for k, v in pretrained_dict.items():
|
| 432 |
+
try:
|
| 433 |
+
if np.shape(model_dict[k]) == np.shape(v):
|
| 434 |
+
a[k]=v
|
| 435 |
+
except:
|
| 436 |
+
pass
|
| 437 |
+
model_dict.update(a)
|
| 438 |
+
model.load_state_dict(model_dict)
|
| 439 |
+
print('Finished!')
|
| 440 |
+
```
|
| 441 |
+
|
| 442 |
+
**问:我要怎么不使用预训练权重啊?
|
| 443 |
+
答:把载入预训练权重的代码注释了就行。**
|
| 444 |
+
|
| 445 |
+
**问:为什么我不使用预训练权重效果这么差啊?
|
| 446 |
+
答:因为随机初始化的权值不好,提取的特征不好,也就导致了模型训练的效果不好,预训练权重还是非常重要的。**
|
| 447 |
+
|
| 448 |
+
### g、视频检测问题与摄像头检测问题
|
| 449 |
+
**问:怎么用摄像头检测呀?
|
| 450 |
+
答:predict.py修改参数可以进行摄像头检测,也有视频详细解释了摄像头检测的思路。**
|
| 451 |
+
|
| 452 |
+
**问:怎么用视频检测呀?
|
| 453 |
+
答:同上**
|
| 454 |
+
|
| 455 |
+
### h、从0开始训练问题
|
| 456 |
+
**问:怎么在模型上从0开始训练?
|
| 457 |
+
答:在算力不足与调参能力不足的情况下从0开始训练毫无意义。模型特征提取能力在随机初始化参数的情况下非常差。没有好的参数调��能力和算力,无法使得网络正常收敛。**
|
| 458 |
+
如果一定要从0开始,那么训练的时候请注意几点:
|
| 459 |
+
- 不载入预训练权重。
|
| 460 |
+
- 不要进行冻结训练,注释冻结模型的代码。
|
| 461 |
+
|
| 462 |
+
**问:为什么我不使用预训练权重效果这么差啊?
|
| 463 |
+
答:因为随机初始化的权值不好,提取的特征不好,也就导致了模型训练的效果不好,预训练权重还是非常重要的。**
|
| 464 |
+
|
| 465 |
+
### i、保存问题
|
| 466 |
+
**问:检测完的图片怎么保存?
|
| 467 |
+
答:一般目标检测用的是Image,所以查询一下PIL库的Image如何进行保存。详细看看predict.py文件的注释。**
|
| 468 |
+
|
| 469 |
+
**问:怎么用视频保存呀?
|
| 470 |
+
答:详细看看predict.py文件的注释。**
|
| 471 |
+
|
| 472 |
+
### j、遍历问题
|
| 473 |
+
**问:如何对一个文件夹的图片进行遍历?
|
| 474 |
+
答:一般使用os.listdir先找出文件夹里面的所有图片,然后根据predict.py文件里面的执行思路检测图片就行了,详细看看predict.py文件的注释。**
|
| 475 |
+
|
| 476 |
+
**问:如何对一个文件夹的图片进行遍历?并且保存。
|
| 477 |
+
答:遍历的话一般使用os.listdir先找出文件夹里面的所有图片,然后根据predict.py文件里面的执行思路检测图片就行了。保存的话一般目标检测用的是Image,所以查询一下PIL库的Image如何进行保存。如果有些库用的是cv2,那就是查一下cv2怎么保存图片。详细看看predict.py文件的注释。**
|
| 478 |
+
|
| 479 |
+
### k、路径问题(No such file or directory)
|
| 480 |
+
**问:我怎么出现了这样的错误呀:**
|
| 481 |
+
```python
|
| 482 |
+
FileNotFoundError: 【Errno 2】 No such file or directory
|
| 483 |
+
……………………………………
|
| 484 |
+
……………………………………
|
| 485 |
+
```
|
| 486 |
+
|
| 487 |
+
**答:去检查一下文件夹路径,查看是否有对应文件;并且检查一下2007_train.txt,其中文件路径是否有错。**
|
| 488 |
+
关于路径有几个重要的点:
|
| 489 |
+
**文件夹名称中一定不要有空格。
|
| 490 |
+
注意相对路径和绝对路径。
|
| 491 |
+
多百度路径相关的知识。**
|
| 492 |
+
|
| 493 |
+
**所有的路径问题基本上都是根目录问题,好好查一下相对目录的概念!**
|
| 494 |
+
|
| 495 |
+
### l、FPS问题(检测速度问题)
|
| 496 |
+
**问:你这个FPS可以到达多少,可以到 XX FPS么?
|
| 497 |
+
答:FPS和机子的配置有关,配置高就快,配置低就慢。**
|
| 498 |
+
|
| 499 |
+
**问:为什么论文中说速度可以达到XX,但是这里却没有?
|
| 500 |
+
答:检查是否正确安装了tensorflow-gpu或者pytorch的gpu版本,如果已经正确安装,可以去利用time.time()的方法查看detect_image里面,哪一段代码耗时更长(不仅只有网络耗时长,其它处理部分也会耗时,如绘图等)。有些论文还会使用多batch进行预测,我并没有去实现这个部分。**
|
| 501 |
+
|
| 502 |
+
### m、预测图片不显示问题
|
| 503 |
+
**问:为什么你的代码在预测完成后不显示图片?只是在命令行告诉我有什么目标。
|
| 504 |
+
答:给系统安装一个图片查看器就行了。**
|
| 505 |
+
|
| 506 |
+
### n、算法评价问题(miou)
|
| 507 |
+
**问:怎么计算miou?
|
| 508 |
+
答:参考视频里的miou测量部分。**
|
| 509 |
+
|
| 510 |
+
**问:怎么计算Recall、Precision指标。
|
| 511 |
+
答:现有的代码还无法获得,需要各位同学理解一下混淆矩阵的概念,然后自行计算一下。**
|
| 512 |
+
|
| 513 |
+
### o、模型优化(模型修改)问题
|
| 514 |
+
**问:up,我修改了网络,预训练权重还能用吗?
|
| 515 |
+
答:修改了主干的话,如果不是用的现有的网络,基本上预训练权重是不能用的,要么就自己判断权值里卷积核的shape然后自己匹配,要么只能自己预训练去了;修改了后半部分的话,前半部分的主干部分的预训练权重还是可以用的,如果是pytorch代码的话,需要自己修改一下载入权值的方式,判断shape后载入,如果是keras代码,直接by_name=True,skip_mismatch=True即可。**
|
| 516 |
+
权值匹配的方式可以参考如下:
|
| 517 |
+
|
| 518 |
+
```python
|
| 519 |
+
# 加快模型训练的效率
|
| 520 |
+
print('Loading weights into state dict...')
|
| 521 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 522 |
+
model_dict = model.state_dict()
|
| 523 |
+
pretrained_dict = torch.load(model_path, map_location=device)
|
| 524 |
+
a = {}
|
| 525 |
+
for k, v in pretrained_dict.items():
|
| 526 |
+
try:
|
| 527 |
+
if np.shape(model_dict[k]) == np.shape(v):
|
| 528 |
+
a[k]=v
|
| 529 |
+
except:
|
| 530 |
+
pass
|
| 531 |
+
model_dict.update(a)
|
| 532 |
+
model.load_state_dict(model_dict)
|
| 533 |
+
print('Finished!')
|
| 534 |
+
```
|
| 535 |
+
|
| 536 |
+
|
| 537 |
+
|
| 538 |
+
**问:up,怎么修改模型啊,我想发个小论文!
|
| 539 |
+
答:建议看看目标检测中yolov4的论文,作为一个大型调参现场非常有参考意义,使用了很多tricks。我能给的建议就是多看一些经典模型,然后拆解里面的亮点结构并使用。常用的tricks如注意力机制什么的,可以试试。**
|
| 540 |
+
|
| 541 |
+
### p、部署问题
|
| 542 |
+
我没有具体部署到手机等设备上过,所以很多部署问题我并不了解……
|
| 543 |
+
|
| 544 |
+
## 5、交流群问题
|
| 545 |
+
**问:up,有没有QQ群啥的呢?
|
| 546 |
+
答:没有没有,我没有时间管理QQ群……**
|
| 547 |
+
|
| 548 |
+
## 6、怎么学习的问题
|
| 549 |
+
**问:up,你的学习路线怎么样的?我是个小白我要怎么学?
|
| 550 |
+
答:这���有几点需要注意哈
|
| 551 |
+
1、我不是高手,很多东西我也不会,我的学习路线也不一定适用所有人。
|
| 552 |
+
2、我实验室不做深度学习,所以我很多东西都是自学,自己摸索,正确与否我也不知道。
|
| 553 |
+
3、我个人觉得学习更靠自学**
|
| 554 |
+
学习路线的话,我是先学习了莫烦的python教程,从tensorflow、keras、pytorch入门,入门完之后学的SSD,YOLO,然后了解了很多经典的卷积网,后面就开始学很多不同的代码了,我的学习方法就是一行一行的看,了解整个代码的执行流程,特征层的shape变化等,花了很多时间也没有什么捷径,就是要花时间吧。
|
run.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ultralytics import YOLO
|
| 2 |
+
import matplotlib.pyplot as plt
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
# 1. 加载你的模型
|
| 6 |
+
model = YOLO("D:/wampee_yolov8/wampee/wampee/ultralytics/runs/detect/train4/weights/best.pt")
|
| 7 |
+
|
| 8 |
+
# 2. 使用测试集评估
|
| 9 |
+
results = model.val(data="D:/wampee_yolov8/wampee/wampee/ultralytics/train.yaml", split="test")
|
| 10 |
+
|
| 11 |
+
# 3. 提取指标
|
| 12 |
+
precision = results.box.pr
|
| 13 |
+
recall = results.box.re
|
| 14 |
+
f1 = results.box.f1
|
| 15 |
+
class_names = [results.names[i] for i in range(len(precision))]
|
| 16 |
+
|
| 17 |
+
# 4. 绘图
|
| 18 |
+
x = np.arange(len(class_names))
|
| 19 |
+
plt.figure(figsize=(8, 5))
|
| 20 |
+
plt.plot(x, precision, marker='o', label='Precision')
|
| 21 |
+
plt.plot(x, recall, marker='s', label='Recall')
|
| 22 |
+
plt.plot(x, f1, marker='^', label='F1-score')
|
| 23 |
+
plt.xticks(x, class_names, rotation=45)
|
| 24 |
+
plt.xlabel("Class")
|
| 25 |
+
plt.ylabel("Score")
|
| 26 |
+
plt.title("Precision / Recall / F1-score per Class")
|
| 27 |
+
plt.legend()
|
| 28 |
+
plt.grid(True)
|
| 29 |
+
plt.tight_layout()
|
| 30 |
+
plt.savefig("prf1_per_class.png")
|
| 31 |
+
plt.show()
|
ssd-pytorch-master/.gitignore
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ignore map, miou, datasets
|
| 2 |
+
map_out/
|
| 3 |
+
miou_out/
|
| 4 |
+
VOCdevkit/
|
| 5 |
+
datasets/
|
| 6 |
+
Medical_Datasets/
|
| 7 |
+
lfw/
|
| 8 |
+
logs/
|
| 9 |
+
model_data/
|
| 10 |
+
.temp_map_out/
|
| 11 |
+
|
| 12 |
+
# Byte-compiled / optimized / DLL files
|
| 13 |
+
__pycache__/
|
| 14 |
+
*.py[cod]
|
| 15 |
+
*$py.class
|
| 16 |
+
|
| 17 |
+
# C extensions
|
| 18 |
+
*.so
|
| 19 |
+
|
| 20 |
+
# Distribution / packaging
|
| 21 |
+
.Python
|
| 22 |
+
build/
|
| 23 |
+
develop-eggs/
|
| 24 |
+
dist/
|
| 25 |
+
downloads/
|
| 26 |
+
eggs/
|
| 27 |
+
.eggs/
|
| 28 |
+
lib/
|
| 29 |
+
lib64/
|
| 30 |
+
parts/
|
| 31 |
+
sdist/
|
| 32 |
+
var/
|
| 33 |
+
wheels/
|
| 34 |
+
pip-wheel-metadata/
|
| 35 |
+
share/python-wheels/
|
| 36 |
+
*.egg-info/
|
| 37 |
+
.installed.cfg
|
| 38 |
+
*.egg
|
| 39 |
+
MANIFEST
|
| 40 |
+
|
| 41 |
+
# PyInstaller
|
| 42 |
+
# Usually these files are written by a python script from a template
|
| 43 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 44 |
+
*.manifest
|
| 45 |
+
*.spec
|
| 46 |
+
|
| 47 |
+
# Installer logs
|
| 48 |
+
pip-log.txt
|
| 49 |
+
pip-delete-this-directory.txt
|
| 50 |
+
|
| 51 |
+
# Unit test / coverage reports
|
| 52 |
+
htmlcov/
|
| 53 |
+
.tox/
|
| 54 |
+
.nox/
|
| 55 |
+
.coverage
|
| 56 |
+
.coverage.*
|
| 57 |
+
.cache
|
| 58 |
+
nosetests.xml
|
| 59 |
+
coverage.xml
|
| 60 |
+
*.cover
|
| 61 |
+
*.py,cover
|
| 62 |
+
.hypothesis/
|
| 63 |
+
.pytest_cache/
|
| 64 |
+
|
| 65 |
+
# Translations
|
| 66 |
+
*.mo
|
| 67 |
+
*.pot
|
| 68 |
+
|
| 69 |
+
# Django stuff:
|
| 70 |
+
*.log
|
| 71 |
+
local_settings.py
|
| 72 |
+
db.sqlite3
|
| 73 |
+
db.sqlite3-journal
|
| 74 |
+
|
| 75 |
+
# Flask stuff:
|
| 76 |
+
instance/
|
| 77 |
+
.webassets-cache
|
| 78 |
+
|
| 79 |
+
# Scrapy stuff:
|
| 80 |
+
.scrapy
|
| 81 |
+
|
| 82 |
+
# Sphinx documentation
|
| 83 |
+
docs/_build/
|
| 84 |
+
|
| 85 |
+
# PyBuilder
|
| 86 |
+
target/
|
| 87 |
+
|
| 88 |
+
# Jupyter Notebook
|
| 89 |
+
.ipynb_checkpoints
|
| 90 |
+
|
| 91 |
+
# IPython
|
| 92 |
+
profile_default/
|
| 93 |
+
ipython_config.py
|
| 94 |
+
|
| 95 |
+
# pyenv
|
| 96 |
+
.python-version
|
| 97 |
+
|
| 98 |
+
# pipenv
|
| 99 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 100 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 101 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 102 |
+
# install all needed dependencies.
|
| 103 |
+
#Pipfile.lock
|
| 104 |
+
|
| 105 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
| 106 |
+
__pypackages__/
|
| 107 |
+
|
| 108 |
+
# Celery stuff
|
| 109 |
+
celerybeat-schedule
|
| 110 |
+
celerybeat.pid
|
| 111 |
+
|
| 112 |
+
# SageMath parsed files
|
| 113 |
+
*.sage.py
|
| 114 |
+
|
| 115 |
+
# Environments
|
| 116 |
+
.env
|
| 117 |
+
.venv
|
| 118 |
+
env/
|
| 119 |
+
venv/
|
| 120 |
+
ENV/
|
| 121 |
+
env.bak/
|
| 122 |
+
venv.bak/
|
| 123 |
+
|
| 124 |
+
# Spyder project settings
|
| 125 |
+
.spyderproject
|
| 126 |
+
.spyproject
|
| 127 |
+
|
| 128 |
+
# Rope project settings
|
| 129 |
+
.ropeproject
|
| 130 |
+
|
| 131 |
+
# mkdocs documentation
|
| 132 |
+
/site
|
| 133 |
+
|
| 134 |
+
# mypy
|
| 135 |
+
.mypy_cache/
|
| 136 |
+
.dmypy.json
|
| 137 |
+
dmypy.json
|
| 138 |
+
|
| 139 |
+
# Pyre type checker
|
| 140 |
+
.pyre/
|
ssd-pytorch-master/2007_train.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
ssd-pytorch-master/2007_val.txt
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG20240710105501_part1.jpg 1252,1523,1498,1861,0 1441,1811,1536,2048,0 878,1949,1004,2048,0 1255,776,1373,956,0 1201,812,1273,965,0
|
| 2 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_10_58_IMG_9338_part4.jpg 12,19,238,261,0 163,102,375,350,0 377,0,677,186,0 538,99,795,307,0 283,242,560,525,0 552,297,778,503,0 481,198,714,439,0 1,179,185,377,0 0,353,138,664,0 1,471,119,657,0 81,548,284,833,0 198,519,358,779,0 391,585,663,942,0 721,305,935,570,0 794,260,972,469,0 304,682,491,942,0 122,828,380,1138,0 0,1499,64,1634,0 455,792,699,1044,0 661,708,938,1018,0 921,659,1195,974,0 3,1846,94,1980,0 1246,653,1381,813,0 1366,708,1469,862,0 1452,833,1512,920,0 1202,856,1279,943,0 1323,929,1426,1038,0 1228,992,1334,1098,0 1169,983,1231,1076,0 1014,984,1084,1056,0 1044,1028,1098,1109,0 1107,1044,1158,1116,0 1135,1099,1182,1162,0 1208,1077,1280,1162,0
|
| 3 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114321_part1.jpg 178,275,273,364,0 247,325,316,392,0 159,1583,259,1694,0 250,1504,308,1608,0 375,1443,444,1559,0
|
| 4 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114328_part2.jpg 1044,832,1107,917,0 885,896,955,988,0 976,929,1051,1026,0 940,1082,1012,1164,0 609,907,675,1013,0 675,902,733,988,0 697,967,778,1066,0 629,1006,703,1078,0 453,1002,527,1095,0 525,1028,597,1121,0 594,1055,674,1148,0 660,1102,744,1201,0 756,1120,835,1219,0 770,1164,865,1274,0 594,1120,685,1206,0 429,1043,490,1125,0 386,1073,457,1166,0 305,1105,396,1196,0 500,1123,575,1231,0 571,1183,639,1274,0 656,1187,739,1260,0 743,1241,813,1320,0 669,1242,753,1338,0 614,1265,683,1365,0 540,1276,619,1397,0 462,1196,553,1287,0 364,1169,439,1276,0 314,1201,389,1288,0 258,1181,328,1281,0 136,1109,186,1167,0 766,1355,813,1416,0 793,1388,841,1454,0 650,1363,739,1468,0 732,1416,768,1469,0 653,1469,705,1523,0 717,1466,768,1534,0 770,1461,823,1539,0 805,1448,872,1526,0 722,1514,774,1571,0 675,1518,722,1575,0 683,1570,721,1609,0 787,1565,831,1629,0 837,1555,894,1613,0 860,1610,907,1667,0 169,1311,225,1374,0 0,1285,68,1345,0 114,1337,161,1379,0 1,1413,54,1535,0 20,1477,106,1600,0 0,1622,41,1740,0 92,1541,197,1673,0 134,1447,236,1579,0 228,1424,350,1585,0 276,1602,384,1730,0 454,1694,553,1824,0 478,1646,571,1732,0 533,1704,633,1829,0 319,1835,407,1963,0 392,1828,489,1963,0 444,1910,532,2005,0 323,1998,395,2048,0 515,2002,604,2048,0 643,1868,737,1983,0 605,1762,707,1873,0 721,1690,771,1753,0 757,1685,816,1751,0
|
| 5 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG20240710105501_part2.jpg 4,318,214,507,0 878,1,1007,152,0 775,450,870,578,0 860,471,965,603,0 201,1798,453,2048,0 0,1806,164,2048,0 457,1967,660,2048,0 96,1708,217,1882,0
|
| 6 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114444_part4.jpg 737,0,776,39,0 757,0,815,39,0 819,1,892,52,0 897,2,936,43,0 814,34,861,94,0 864,29,932,103,0 970,28,1027,97,0 762,124,816,192,0 755,166,792,225,0 940,97,992,176,0 890,131,942,196,0 853,164,903,215,0 952,167,1001,233,0 906,187,963,258,0 869,204,915,265,0 827,239,863,301,0 843,252,904,316,0 895,254,938,315,0 942,266,982,323,0 1000,263,1040,316,0 821,308,876,373,0 886,314,935,385,0 916,332,966,395,0 858,349,912,423,0 837,391,897,460,0 762,472,831,533,0 859,507,916,580,0 817,524,867,602,0 738,512,788,585,0 702,507,748,569,0 644,600,694,669,0 696,613,739,669,0 602,688,649,747,0 627,673,680,723,0 679,676,729,744,0 648,730,693,778,0 658,767,712,846,0 709,717,763,781,0 708,772,758,834,0 825,692,875,757,0 801,729,855,795,0 740,802,794,860,0 786,786,842,852,0 797,831,858,902,0 739,854,804,921,0 1005,844,1044,897,0 1018,918,1043,961,0 1055,922,1091,964,0 1124,917,1156,967,0 1110,949,1137,993,0 1125,987,1158,1034,0 1082,976,1114,1018,0 1081,1025,1123,1080,0 1091,1079,1121,1128,0 1122,1080,1151,1121,0 1326,536,1354,576,0 1371,548,1408,590,0 1420,581,1466,639,0 1479,636,1514,682,0 1508,651,1540,694,0 1456,654,1485,696,0 1532,701,1568,744,0 1526,736,1559,772,0 1458,761,1498,803,0 1468,790,1505,832,0 1505,799,1546,849,0 1542,804,1573,848,0 1397,845,1438,890,0 1405,881,1447,936,0 1464,864,1500,911,0 1493,870,1528,920,0 1537,891,1576,949,0 1584,947,1637,1005,0 1367,979,1417,1040,0 1402,935,1444,978,0 1456,944,1493,987,0 1497,931,1522,981,0 1550,947,1592,992,0 1408,988,1429,1031,0 1366,1032,1400,1081,0 1413,1066,1460,1126,0 1432,983,1467,1030,0 1455,1038,1496,1100,0 1493,1065,1548,1127,0 1546,1059,1597,1127,0 1566,996,1612,1065,0 1463,985,1509,1028,0
|
| 7 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/1720668351053_part1.jpg 1280,1078,1383,1291,0 1392,682,1500,941,0 1380,1351,1500,1585,0 1434,1512,1500,1678,0 1203,1563,1475,1872,0 1104,1701,1250,1907,0 1216,1899,1387,2000,0 1367,1897,1500,2000,0 1374,1739,1500,1922,0
|
| 8 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG20240710105623_part4.jpg 300,801,354,870,0 346,713,423,811,0 384,792,448,868,0 366,857,437,945,0 409,932,464,997,0 558,803,634,892,0 493,826,558,913,0 496,726,528,780,0 541,734,591,811,0 499,907,547,966,0 493,924,564,997,0 587,1062,666,1164,0 605,77,688,168,0 698,61,792,168,0 753,110,859,237,0 671,178,763,272,0 717,218,817,300,0 806,47,863,131,0 853,65,917,139,0 945,172,1043,289,0 1035,215,1117,306,0 1100,113,1191,218,0 1029,133,1104,215,0 866,413,983,531,0 780,502,841,599,0 1105,486,1217,623,0 1080,571,1173,672,0 981,580,1109,720,0 764,850,819,937,0 868,831,974,952,0 1006,840,1067,934,0 1001,927,1095,1048,0 1087,831,1201,951,0 1147,939,1240,1047,0 1187,999,1293,1139,0 1242,897,1359,1041,0 808,905,892,1003,0 755,971,839,1073,0 634,1104,702,1189,0 697,1099,810,1217,0 826,1027,908,1134,0 896,1017,960,1117,0 794,1125,900,1218,0 773,1185,876,1304,0 887,1177,941,1276,0 1027,1102,1132,1223,0 1069,1206,1176,1331,0 1171,1324,1280,1486,0 1077,1344,1195,1494,0 947,1299,1081,1422,0 1022,1415,1132,1536,0 813,1274,915,1344,0 806,1349,905,1451,0 730,1397,826,1532,0 848,1439,921,1526,0 926,1440,1029,1536,0 1621,32,1679,102,0 1671,0,1742,75,0 1751,43,1853,129,0 1873,129,1958,208,0 1651,366,1712,440,0 1613,414,1666,485,0 1363,776,1416,830,0 1451,742,1512,803,0 1994,504,2037,564,0 1998,274,2048,340,0 1906,222,1967,272,0
|
| 9 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_11_05_IMG_9364_part4.jpg 45,455,73,488,0 15,512,46,553,0 5,567,32,603,0 41,523,58,555,0 58,504,83,534,0 55,567,91,607,0 43,597,68,627,0 74,544,99,573,0 99,534,124,574,0 70,626,93,647,0 69,649,94,682,0 114,595,141,626,0 144,555,168,583,0 100,638,122,668,0 100,671,131,701,0 114,697,135,729,0 129,663,148,695,0 141,641,161,672,0 141,691,158,713,0 148,613,173,650,0 163,590,179,612,0 180,597,201,626,0 196,608,215,632,0 193,650,219,681,0 215,641,234,668,0 216,603,239,632,0 216,574,234,598,0 246,589,267,613,0 241,607,264,640,0 180,557,202,584,0 896,13,952,69,0 985,10,1025,48,0 968,39,999,79,0 1000,43,1030,82,0 955,77,977,112,0 978,81,1015,129,0 951,118,990,164,0 986,136,1019,181,0 932,178,969,214,0 1078,1,1142,29,0 1041,98,1108,191,0 1088,90,1169,187,0 1140,127,1203,220,0 1156,27,1216,99,0 1025,170,1051,202,0 1051,179,1078,214,0 1039,196,1061,228,0 1016,217,1045,254,0 984,232,1016,278,0 1018,259,1045,288,0 1045,247,1071,289,0 1102,198,1161,271,0 1086,254,1151,332,0 1115,279,1164,346,0 1149,210,1201,256,0 1147,230,1217,306,0 1050,336,1091,386,0 1086,330,1122,373,0 1188,90,1260,164,0 1198,162,1283,249,0 1278,49,1326,102,0 1296,95,1354,151,0 1299,153,1338,192,0 1295,187,1345,237,0 1362,151,1416,218,0 1344,171,1369,210,0 1410,155,1473,232,0 1437,136,1497,195,0 1462,204,1522,267,0 1333,224,1370,269,0 1297,248,1338,299,0 1225,242,1309,343,0 1327,267,1370,335,0 1357,252,1408,316,0 1410,266,1462,335,0 1443,300,1501,359,0 1393,332,1448,392,0 1431,363,1491,435,0 1260,329,1340,422,0 1215,381,1292,472,0 1288,387,1370,482,0 1206,484,1277,572,0
|
| 10 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114334_part1.jpg 1107,380,1151,431,0 1145,363,1185,416,0 1147,407,1186,462,0 1232,334,1266,390,0 1266,321,1305,404,0 1300,312,1339,377,0 1307,354,1350,410,0 1206,409,1238,448,0 1177,453,1207,503,0 1219,448,1255,498,0 872,541,921,619,0 929,601,975,656,0 969,583,1007,634,0 987,560,1019,625,0 1018,560,1051,622,0 755,668,789,707,0 733,693,781,745,0 775,709,814,762,0 773,734,813,787,0 872,662,912,740,0 993,740,1037,798,0 987,722,1019,760,0 922,859,976,921,0 819,887,869,963,0 192,869,248,940,0 1322,1103,1389,1213,0 1254,1150,1307,1236,0 1231,1303,1279,1381,0 1278,1315,1340,1401,0 1356,1298,1430,1390,0 1428,1300,1490,1389,0 1454,1372,1500,1450,0 1278,1390,1328,1457,0 1343,1393,1404,1481,0 1301,1498,1353,1572,0 1200,1502,1271,1594,0 1160,1556,1215,1625,0 1323,1580,1379,1633,0 1338,1614,1407,1731,0 1425,1690,1448,1727,0 1443,1657,1461,1687,0 1466,1642,1494,1675,0 1407,1736,1430,1765,0 1388,1786,1413,1819,0 1387,1816,1411,1850,0 1269,1756,1332,1840,0 1272,1837,1329,1913,0 856,1092,901,1165,0 778,1119,826,1195,0 785,1250,828,1316,0 739,1277,784,1348,0 696,1262,744,1336,0 831,1240,857,1272,0 732,1424,767,1475,0 773,1543,807,1584,0 785,1559,834,1609,0 807,1621,852,1681,0 946,1512,973,1548,0 972,1519,1004,1568,0 900,1601,932,1646,0 576,1742,602,1781,0 698,1792,723,1827,0 564,1796,599,1839,0 537,1816,569,1859,0 571,1837,601,1871,0 565,1881,595,1912,0 1,1010,55,1059,0 51,1030,107,1089,0 198,969,251,1040,0 240,1028,278,1077,0 297,1056,365,1127,0 192,1081,251,1151,0 216,1209,252,1254,0 319,1180,367,1248,0 281,1228,336,1284,0 372,1181,407,1237,0 401,1187,453,1259,0 468,1201,512,1265,0 504,1237,548,1303,0 561,1298,588,1334,0 443,1260,498,1316,0 399,1257,449,1313,0 343,1281,388,1327,0 336,1327,386,1381,0 384,1316,422,1354,0 378,1353,416,1401,0 333,1445,395,1500,0 225,1422,269,1472,0 266,1433,306,1486,0 231,1577,311,1657,0 287,1695,336,1739,0 357,1674,416,1731,0 410,1678,465,1731,0 428,1719,477,1780,0 381,1739,437,1798,0 348,1768,407,1831,0 300,1790,356,1846,0 326,1833,366,1875,0 351,1854,404,1912,0 281,1863,336,1916,0
|
| 11 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG_20240710_105206.jpg 1240,2471,1566,2779,0 1102,2648,1344,2883,0 962,2802,1207,3066,0 1003,2271,1276,2534,0 913,2366,1163,2610,0 1198,2325,1403,2575,0 834,2174,925,2310,0 830,2202,1066,2402,0 1240,2010,1464,2238,0 1209,2155,1423,2333,0 651,1692,935,1951,0 849,1621,1068,1871,0 889,1836,1084,2073,0 737,1864,916,2043,0 658,1592,872,1754,0 1199,1664,1430,1919,0 1283,1886,1504,2036,0 1097,989,1325,1257,0 1047,54,1138,192,0
|
| 12 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114321_part2.jpg 34,407,131,524,0 151,392,261,512,0 182,328,266,438,0 483,152,576,222,0 467,210,558,317,0 1150,432,1230,535,0 1041,535,1126,632,0 299,727,364,814,0 364,763,443,864,0 519,702,598,833,0 441,891,528,997,0 497,953,588,1053,0 554,995,647,1118,0 638,1041,733,1161,0 726,1057,823,1140,0 212,937,288,1018,0 312,992,411,1105,0 433,1004,528,1141,0 371,1082,457,1183,0 115,1040,186,1164,0 230,1079,266,1166,0 108,1155,200,1248,0 189,1204,273,1306,0 140,1247,184,1339,0 264,1154,353,1279,0 325,1194,404,1300,0 408,1268,485,1368,0 478,1231,564,1344,0 508,1152,589,1235,0 325,1317,413,1407,0 280,1313,335,1388,0 487,1420,589,1530,0 440,1415,508,1501,0 638,1407,689,1493,0 0,1466,77,1648,0 789,1949,856,2030,0 851,1975,894,2043,0 1428,1969,1469,2026,0 1499,1970,1536,2019,0 1472,1973,1517,2024,0 1497,2021,1536,2047,0
|
| 13 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG20240710104644_part1.jpg 1639,1251,1796,1434,0 1585,1380,1713,1535,0 1749,1392,1884,1536,0 1856,1390,1942,1520,0 1570,1129,1658,1247,0 1635,235,1706,323,0 1427,293,1481,362,0 1680,58,1733,124,0 1011,31,1100,132,0 924,47,993,127,0 869,156,934,236,0 1000,181,1083,252,0 1016,222,1088,311,0 565,2,608,47,0 647,20,683,60,0 648,79,700,133,0 126,110,187,175,0 110,160,176,227,0 0,9,44,71,0 108,47,155,106,0 595,332,656,414,0 634,443,701,512,0 440,447,482,504,0 502,466,551,518,0 657,512,709,575,0 698,643,766,708,0 726,684,793,759,0 478,658,554,739,0 550,685,613,757,0 389,648,458,713,0 315,652,389,723,0 191,596,258,670,0 168,697,237,750,0 18,790,117,884,0 8,888,104,981,0 199,1400,286,1481,0 128,1484,213,1536,0 220,1427,334,1533,0 792,1139,842,1197,0 779,1194,815,1241,0 859,1250,888,1307,0 1026,1284,1073,1330,0 1102,1324,1139,1371,0 512,798,561,852,0 561,861,609,920,0 924,1169,980,1215,0
|
| 14 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_11_01_IMG_9347_part2.jpg 175,656,415,893,0 212,935,403,1165,0 73,952,201,1204,0 1,1436,173,1739,0 133,1450,218,1596,0 178,1581,336,1767,0 55,1827,263,2016,0 241,1888,438,2015,0 255,1722,384,1875,0
|
| 15 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_11_02_IMG_9352_part1.jpg 1244,1020,1517,1369,0 1556,1372,1764,1512,0 1484,1455,1555,1512,0 721,0,976,151,0 638,128,756,292,0 738,174,843,323,0 735,320,830,418,0 732,585,833,705,0 438,265,558,401,0 449,362,557,466,0 547,359,662,489,0 515,9,592,159,0 62,34,122,103,0 64,157,114,226,0 113,186,153,244,0 147,202,183,259,0 182,189,232,257,0 199,271,240,324,0 956,589,1038,669,0
|
| 16 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_09_47_IMG_9328_part3.jpg 136,393,195,473,0 153,439,216,525,0 218,399,292,493,0 207,473,244,534,0 1406,763,1476,848,0 218,158,263,217,0 272,231,319,319,0 243,261,280,315,0 370,227,422,285,0 270,26,318,88,0 322,33,378,107,0 368,9,432,86,0 284,101,331,151,0 334,106,395,179,0 397,154,446,217,0 424,47,492,141,0 496,33,572,125,0 565,0,645,85,0 540,160,620,257,0 446,173,539,281,0 815,514,847,550,0 782,603,807,640,0 940,602,966,633,0 846,625,866,655,0 863,624,892,665,0 873,605,900,633,0 848,688,882,719,0 847,719,879,755,0 839,765,861,796,0 939,731,964,760,0 939,795,958,827,0 839,823,860,847,0 860,830,877,854,0 879,833,898,857,0 920,827,942,857,0 959,813,977,833,0 959,829,990,861,0 930,855,954,884,0 907,861,928,885,0 906,895,930,915,0 893,902,912,935,0 915,915,942,945,0 833,860,851,887,0
|
| 17 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114413_part2.jpg 51,702,127,801,0 119,713,181,788,0 84,773,168,862,0 480,361,602,499,0 1109,345,1189,445,0 1212,180,1268,246,0 1284,248,1358,331,0 813,437,911,567,0 906,450,986,570,0 957,473,1048,596,0 822,567,891,646,0 853,617,937,732,0 796,645,888,769,0 895,580,960,692,0 940,652,1017,737,0 1060,520,1133,596,0 1081,573,1172,679,0 1130,487,1237,619,0 1137,658,1210,742,0 1091,713,1202,846,0 1332,757,1406,866,0 1440,812,1538,935,0 1151,900,1235,1000,0 1146,995,1237,1090,0 1169,1103,1260,1216,0 1313,1235,1389,1316,0 1645,1372,1725,1454,0 1648,1443,1728,1546,0 1375,1648,1446,1725,0 830,1011,923,1135,0 846,1084,943,1180,0 757,1098,859,1228,0 709,1115,782,1252,0 190,1224,241,1324,0 489,1222,593,1334,0 787,1234,888,1351,0 376,1474,473,1597,0 456,1467,563,1606,0 1269,1968,1345,2080,0 1325,2028,1397,2102,0 1426,2061,1489,2154,0
|
| 18 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_11_02_IMG_9357_part1.jpg 710,1275,848,1439,0 1015,1048,1122,1206,0 1034,1274,1161,1445,0 915,1394,1049,1546,0 1259,1423,1398,1588,0 868,1632,1001,1784,0 706,1783,857,1922,0 825,1829,971,1985,0 758,1885,894,2016,0 903,1788,1035,1945,0 1058,1781,1173,1887,0 1167,1727,1292,1878,0 1258,1867,1376,1992,0 1044,1939,1161,2016,0 1075,1889,1200,2016,0 1168,1889,1264,2016,0 1322,1903,1426,1991,0 1427,1866,1512,2013,0
|
| 19 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_09_49_IMG_9330_part2.jpg 405,252,519,384,0 538,418,644,547,0 113,493,279,700,0 103,649,236,837,0 1,803,96,968,0 363,622,541,821,0 183,795,317,973,0 282,842,439,1075,0 400,831,532,1033,0 511,962,655,1127,0 641,773,756,939,0 393,1102,518,1285,0 344,1119,460,1308,0 137,1187,318,1415,0 376,1420,515,1576,0 413,1509,572,1705,0 542,1432,750,1615,0 85,1516,262,1685,0 198,1649,395,1879,0 25,1748,235,1976,0 97,1883,275,2013,0 266,1907,426,2015,0 570,1946,693,2016,0 815,1465,945,1636,0 753,1672,857,1796,0 1207,1157,1299,1276,0 1059,1401,1184,1537,0 1098,1477,1217,1613,0 1172,1609,1260,1709,0 1290,1668,1402,1786,0 1095,1747,1238,1873,0 963,1958,1046,2016,0
|
| 20 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG20240710105718_part3.jpg 200,0,324,54,0 297,0,436,118,0 632,151,745,263,0 730,196,848,329,0 826,259,942,360,0 887,312,997,447,0 968,365,1036,466,0 513,314,646,443,0 563,396,681,522,0 658,375,807,534,0 768,341,876,464,0 770,439,880,540,0 871,438,963,536,0 624,594,742,718,0 743,571,857,705,0 855,568,979,689,0 791,639,929,780,0 704,720,817,843,0 836,736,950,839,0
|
| 21 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114454_part4.jpg 139,500,232,622,0 176,605,278,732,0 233,657,318,785,0 0,666,39,768,0 26,766,121,863,0 1,829,89,941,0 143,714,231,831,0 100,793,212,915,0 201,846,287,960,0 0,988,78,1111,0 17,1070,87,1175,0 79,1062,187,1201,0 26,1175,112,1297,0 708,232,786,339,0 820,140,913,248,0 878,180,942,262,0 908,253,995,343,0 867,262,911,336,0 999,226,1067,306,0 1062,237,1143,319,0 1043,289,1111,370,0 1089,326,1161,421,0 940,337,1001,422,0 855,339,935,437,0 835,444,917,537,0 876,500,973,629,0 1242,239,1295,317,0 1267,160,1318,218,0 1391,79,1450,157,0 1457,34,1512,114,0 1509,14,1562,71,0 1389,15,1418,65,0 1543,68,1605,144,0 1484,111,1548,179,0 1474,180,1535,249,0 1413,360,1482,429,0 1527,410,1593,505,0 1496,483,1561,563,0 1335,504,1396,599,0 1408,566,1456,646,0 1452,523,1509,629,0 1490,554,1556,651,0 1418,634,1466,710,0 1472,639,1511,710,0 1517,637,1560,715,0 1211,865,1283,960,0 1269,831,1345,918,0 1401,772,1457,840,0 1394,880,1445,963,0 1450,807,1511,888,0 1511,797,1572,877,0 1543,846,1599,922,0 1494,893,1552,963,0 1457,943,1521,1023,0
|
| 22 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG20240710105501_part3.jpg 742,64,993,357,0 1002,90,1243,351,0 1426,80,1536,304,0 985,261,1200,448,0 625,90,799,357,0 725,339,962,611,0 960,502,1128,741,0 922,545,1039,761,0 1144,449,1307,653,0 1136,645,1342,819,0 1155,752,1386,1039,0 1324,670,1507,915,0 1461,632,1535,853,0 984,805,1181,1088,0 1034,962,1275,1151,0 1263,1002,1483,1271,0
|
| 23 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG20240710105737_part1.jpg 1234,63,1480,412,0 1376,1212,1536,1660,0 1111,1283,1415,1694,0 1228,1575,1528,1917,0 830,1868,1083,2048,0 997,1907,1150,2047,0 1097,1874,1388,2048,0 1372,1883,1536,2048,0 1024,1783,1219,1900,0
|
| 24 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_09_47_IMG_9328_part1.jpg 953,278,1025,378,0 860,305,940,413,0 766,413,843,490,0 296,456,375,559,0 401,546,479,639,0 329,579,401,669,0 307,635,387,698,0 355,685,412,782,0 415,696,473,773,0 466,662,527,739,0 1,674,49,754,0 1,826,86,920,0 190,789,262,883,0 553,799,630,896,0 752,856,834,940,0 635,917,744,1022,0 509,968,584,1042,0 1,1095,58,1202,0 516,1091,593,1167,0 538,1048,613,1108,0 962,942,1032,1033,0 326,1331,404,1428,0 405,1347,493,1448,0 35,1407,112,1501,0 110,1417,179,1490,0 46,1557,118,1651,0 54,1482,132,1543,0 1,1572,41,1668,0 14,1619,78,1675,0 303,1812,395,1927,0 224,1955,289,2016,0 395,1830,484,1946,0 441,1651,532,1746,0 461,1743,558,1829,0 472,1827,535,1905,0 500,1590,567,1673,0 559,1576,647,1676,0 618,1559,680,1661,0 657,1509,737,1614,0 715,1465,805,1568,0 722,1582,813,1680,0 767,1559,842,1648,0 690,1628,775,1725,0 693,1751,786,1875,0 525,1884,610,1975,0 611,1919,706,2015,0 636,1811,734,1903,0 681,1893,765,1996,0 754,1893,821,1991,0 765,1943,853,2015,0 758,1753,837,1852,0 923,1602,971,1672,0 1007,1622,1073,1708,0 955,1689,1022,1765,0 904,1669,967,1735,0 968,1945,1038,2016,0 1301,842,1397,950,0 1339,903,1430,1026,0 1401,871,1508,1006,0 1217,933,1314,1053,0 1275,1029,1374,1146,0 1458,1102,1512,1190,0 1389,1200,1501,1296,0 1140,1105,1231,1238,0 1212,1134,1316,1259,0 1208,1253,1312,1366,0 1252,1319,1347,1407,0 1317,1388,1421,1485,0 1237,1394,1335,1496,0 1376,1325,1477,1393,0 1443,1390,1512,1503,0 1372,1440,1447,1554,0 1433,1465,1512,1570,0 1421,1573,1512,1679,0 1461,1702,1512,1788,0 1261,1590,1346,1690,0 1201,1616,1264,1714,0 1212,1689,1303,1763,0 1257,1719,1351,1824,0 1179,1806,1288,1915,0
|
| 25 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114442_part3.jpg 299,274,380,363,0 448,241,526,326,0 466,300,534,367,0 315,360,377,438,0 381,420,444,505,0 433,423,485,501,0 501,434,568,505,0 516,490,591,576,0 592,517,657,606,0 473,512,523,577,0 435,543,485,608,0 344,511,412,582,0 314,554,372,632,0 274,547,324,625,0 352,637,426,726,0 415,611,467,674,0 490,606,564,693,0 605,645,675,719,0 433,651,506,744,0 236,693,297,771,0 303,684,357,773,0 327,715,394,791,0 497,695,564,791,0 311,787,383,876,0 479,857,553,936,0 318,869,388,941,0 403,889,464,978,0 444,899,523,994,0 312,928,360,977,0 347,942,405,1007,0 384,968,441,1043,0 272,988,337,1066,0 328,994,383,1068,0 339,1027,401,1112,0 289,1114,355,1190,0 336,1092,397,1170,0 387,1107,453,1189,0 220,1162,288,1254,0 275,1166,360,1265,0 298,1258,361,1336,0 347,1175,392,1264,0 1099,890,1166,979,0 1304,752,1368,834,0 1288,790,1338,847,0 1174,906,1248,992,0 1276,848,1344,936,0 1235,943,1306,1039,0 1285,963,1359,1054,0 1442,894,1516,1004,0 1149,988,1229,1081,0 1093,1070,1160,1142,0 1129,1108,1203,1194,0 1239,1097,1315,1186,0 1320,1070,1379,1157,0 1379,1053,1454,1152,0 1402,1239,1469,1328,0 1501,1353,1536,1434,0 919,364,967,418,0
|
| 26 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG_20240710_105201.jpg 8,1768,192,1940,0 0,1966,144,2199,0 61,2015,195,2236,0 0,2464,144,2686,0 127,2271,283,2515,0 239,2351,386,2545,0 267,2204,448,2374,0 48,2307,220,2512,0 224,2067,303,2167,0 1061,2668,1133,2758,0 1047,2756,1110,2838,0 1127,2727,1199,2818,0 1159,2691,1218,2756,0 1003,2690,1080,2770,0 974,2658,1045,2729,0 1023,2599,1088,2681,0 966,2513,1018,2593,0 980,2557,1052,2631,0 1110,2565,1187,2649,0 1224,2581,1297,2653,0 1192,2630,1237,2691,0 1198,2470,1255,2538,0 1170,2513,1216,2572,0 1127,2473,1198,2535,0 1099,2414,1166,2506,0 1164,2384,1212,2453,0 1192,2371,1253,2421,0 1123,2310,1185,2379,0 1013,1230,1064,1301,0 756,1176,830,1256,0 771,1241,856,1321,0 818,1287,866,1363,0 841,1303,911,1379,0 876,1340,944,1421,0 986,1300,1041,1372,0 931,1247,990,1327,0 972,1352,1027,1432,0 1019,1353,1076,1432,0 994,1403,1046,1469,0 999,1443,1062,1528,0 1042,1440,1097,1520,0 840,1400,900,1457,0 802,1425,875,1507,0 864,1424,927,1489,0 858,1461,927,1527,0 913,1424,961,1479,0 922,1462,991,1540,0 886,1522,957,1586,0 902,1553,962,1632,0 939,1572,995,1642,0 985,1552,1042,1622,0 940,1528,1000,1583,0 395,1071,454,1138,0 452,1097,520,1162,0 419,1117,469,1174,0 389,1166,471,1237,0 363,1196,431,1262,0 309,1165,353,1265,0 322,1277,389,1353,0 370,1326,438,1412,0 332,1351,383,1420,0 310,1409,395,1487,0 465,1379,535,1459,0 416,1378,470,1432,0 435,1417,504,1510,0 462,1469,534,1551,0 410,1477,485,1567,0 410,1546,474,1616,0 468,1545,529,1622,0 2034,1096,2115,1173,0 1981,1111,2062,1180,0 2002,1162,2074,1238,0 2008,1218,2082,1281,0 2119,1135,2200,1207,0 2144,1207,2213,1297,0 2094,1269,2161,1336,0 2143,1274,2216,1344,0 2064,1317,2132,1374,0 2115,1331,2175,1410,0 2150,1360,2213,1444,0 2084,1431,2160,1497,0 2029,1505,2107,1576,0 1984,1336,2049,1398,0 2061,1365,2118,1420,0 2044,1319,2089,1383,0 1462,537,1539,629,0 1373,543,1439,636,0 1423,572,1483,661,0 1421,656,1509,750,0 1393,843,1473,968,0
|
| 27 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114436_part4.jpg 31,1,115,48,0 60,7,137,95,0 149,45,228,122,0 304,62,379,157,0 275,1,369,48,0 375,31,431,93,0 0,106,47,184,0 35,119,114,216,0 107,95,195,168,0 84,162,152,262,0 145,153,229,240,0 144,234,221,304,0 11,192,69,259,0 63,240,125,295,0 27,306,89,386,0 240,460,342,568,0 0,606,35,678,0 0,663,56,756,0 48,684,123,772,0 195,771,263,881,0 222,807,284,883,0 109,796,181,896,0 1,775,73,878,0 14,834,93,937,0 88,919,160,1004,0 0,946,50,1053,0 998,24,1041,72,0 1088,81,1140,143,0 1086,130,1127,177,0 1042,193,1090,277,0 1074,265,1121,321,0 1142,248,1194,319,0 1179,268,1217,313,0 1191,227,1238,275,0 1139,351,1203,407,0 1134,387,1196,453,0 1171,412,1219,463,0 1190,378,1254,440,0 1380,10,1412,54,0 1443,1,1475,31,0 1428,16,1464,57,0 1431,50,1479,95,0 1347,77,1388,125,0 1375,109,1417,148,0 1351,133,1390,174,0 1399,148,1426,183,0 1428,151,1454,190,0 1341,168,1388,216,0 1370,198,1404,230,0 1403,181,1434,224,0 1455,189,1481,221,0 1443,224,1476,260,0 1329,222,1361,251,0 1360,230,1390,260,0 1397,268,1425,295,0 1378,293,1408,331,0 1358,319,1386,348,0 859,387,892,422,0 919,371,943,409,0 929,412,957,439,0 899,446,930,478,0 863,446,891,477,0 865,481,892,516,0 868,516,898,550,0 933,483,968,519,0 948,545,968,571,0 963,560,986,592,0 972,460,999,500,0 984,510,1010,542,0 975,371,1007,421,0
|
| 28 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114436_part1.jpg 1251,975,1316,1060,0 1361,1053,1419,1130,0 1446,989,1500,1074,0 1352,1175,1407,1236,0 1331,1234,1374,1289,0 1085,1466,1137,1531,0 1285,1577,1348,1651,0 1367,1613,1419,1672,0 1467,1966,1500,2000,0 1242,1595,1302,1671,0 987,1548,1043,1631,0 1030,1581,1069,1642,0 966,1636,1031,1715,0 1019,1692,1083,1774,0 1072,1643,1129,1719,0 1054,1730,1108,1795,0 1101,1675,1163,1757,0 1142,1643,1200,1727,0 1191,1621,1256,1692,0 1187,1703,1242,1763,0 1163,1763,1230,1836,0 1044,1797,1109,1869,0 1104,1789,1167,1857,0 1175,1834,1239,1910,0 1107,1868,1181,1945,0 1048,1883,1081,1919,0 1066,1928,1096,1954,0 909,1784,980,1881,0 969,1851,1034,1931,0 775,1766,817,1807,0 522,1795,561,1843,0 540,1831,578,1874,0 569,1845,600,1883,0 558,1880,593,1916,0 467,1883,510,1925,0 465,1925,518,1981,0 522,1901,566,1948,0 554,1916,600,1963,0 742,1862,778,1907,0
|
| 29 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114417_part2.jpg 17,1567,187,1794,0 4,1814,159,2015,0 0,2032,118,2278,0 51,2222,216,2304,0 377,1807,549,2031,0 248,2157,385,2301,0 353,2197,524,2304,0 471,2162,608,2278,0 535,2191,721,2303,0 828,2218,996,2304,0 1275,1993,1367,2068,0 1267,2058,1338,2135,0 1248,2163,1306,2249,0
|
| 30 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG20240710104651_part3.jpg 87,137,182,217,0 754,0,826,37,0 747,24,816,102,0 816,23,876,107,0 879,2,950,83,0 862,49,908,111,0 905,66,966,131,0 947,0,986,66,0 987,8,1058,89,0 954,128,1000,178,0 1276,47,1326,106,0 1097,246,1150,294,0 1370,84,1433,157,0 1329,86,1379,160,0 1347,39,1388,88,0 1399,23,1452,75,0 1427,73,1488,141,0 1466,32,1519,100,0 1498,1,1550,48,0 1517,102,1567,156,0 1539,23,1588,89,0 1608,77,1647,126,0 1646,30,1710,99,0 1716,2,1791,64,0 1608,25,1650,66,0 1681,160,1738,228,0 1563,173,1608,231,0 1430,290,1491,345,0 1634,218,1677,273,0 1690,247,1743,323,0 1743,173,1790,237,0 1742,119,1792,168,0 1953,157,1998,207,0 1974,217,2006,260,0 1983,277,2028,331,0 1658,322,1704,372,0 1825,409,1871,467,0 1911,285,1943,328,0 1942,318,1974,363,0 1984,385,2024,420,0 1585,957,1656,1074,0 1642,947,1742,1072,0 1646,1047,1731,1145,0 1560,1130,1633,1248,0 1621,1175,1720,1289,0 1713,1120,1784,1215,0 1756,1051,1811,1116,0 1808,913,1861,995,0 1988,959,2042,1023,0 1990,1032,2048,1104,0 1999,1102,2048,1165,0 1832,1102,1900,1171,0 1894,1086,1947,1171,0 1953,1139,2035,1234,0 2000,1176,2047,1254,0 1847,1174,1913,1247,0 1789,1204,1860,1283,0 1778,1164,1827,1217,0 1706,1322,1792,1428,0 1658,1309,1721,1393,0 1708,1416,1804,1536,0 1806,1276,1884,1390,0 1877,1348,1940,1426,0 1833,1453,1887,1524,0 1859,1491,1914,1536,0
|
| 31 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_11_04_IMG_9359_part4.jpg 1,163,47,212,0 199,220,249,295,0 271,277,332,357,0 113,292,165,364,0 221,318,284,391,0 189,362,241,431,0 147,388,192,465,0 94,385,162,468,0 42,403,101,497,0 220,388,281,468,0 281,387,349,472,0 281,464,335,520,0 193,463,254,519,0 219,488,273,563,0 151,521,214,601,0 78,533,139,626,0 122,585,187,668,0 153,628,196,689,0 101,665,167,749,0 737,229,771,274,0 763,252,800,298,0 697,267,724,304,0 725,290,749,329,0 748,296,788,341,0 687,318,717,352,0 719,334,753,385,0 748,325,775,362,0 773,327,813,372,0 632,433,670,478,0 669,419,714,463,0 730,398,762,438,0 776,410,803,442,0 797,398,839,443,0 789,448,830,493,0 761,466,796,503,0 726,456,758,496,0 742,490,778,532,0 1051,32,1092,103,0 1088,81,1144,148,0 1127,71,1174,122,0 1269,2,1300,39,0 1304,4,1333,25,0 1330,26,1358,61,0 1305,50,1340,85,0 1215,111,1269,143,0 1273,110,1331,170,0 1199,150,1267,211,0 1116,182,1162,241,0 1158,179,1209,242,0 1171,227,1216,270,0 1170,263,1219,319,0 1259,234,1314,303,0 1308,217,1362,281,0 1126,306,1177,355,0 1225,300,1271,373,0 1264,311,1310,356,0 1315,340,1380,413,0 1265,359,1323,430,0 1136,356,1187,414,0 1103,406,1161,465,0 1189,376,1238,429,0 1156,419,1203,462,0 1165,456,1215,533,0 1224,420,1278,493,0 1201,442,1258,520,0 1153,535,1218,604,0 1218,556,1264,623,0 1253,549,1324,624,0 1355,74,1388,110,0 1455,1,1509,51,0 1505,34,1563,88,0 1521,0,1583,32,0 1556,10,1602,67,0 1592,1,1649,46,0 1668,1,1726,47,0 1589,73,1623,118,0 1622,70,1669,126,0 1627,121,1669,172,0 1591,118,1623,152,0 1555,115,1591,151,0 1577,141,1614,178,0 1626,194,1663,230,0 1507,172,1534,204,0 1530,189,1559,219,0 1487,439,1520,474,0 1475,459,1510,497,0 1557,455,1593,491,0 1591,490,1630,530,0 1537,478,1569,515,0 1496,492,1527,527,0 1511,526,1554,570,0 1632,540,1671,585,0 1503,548,1534,580,0 1476,579,1508,619,0 1501,603,1542,641,0 1494,629,1524,665,0 1547,609,1581,641,0 1604,574,1631,609,0 1556,582,1586,612,0 1579,601,1608,633,0 1630,588,1663,625,0 1608,552,1634,579,0 1595,616,1629,649,0 1575,636,1603,665,0 1536,632,1565,672,0 1559,658,1594,701,0 1526,668,1560,705,0
|
| 32 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG_20240710_105305.jpg 2120,380,2285,529,0 2137,488,2255,604,0 2030,466,2146,616,0 1952,374,2133,547,0 1591,404,1741,609,0 1710,498,1832,693,0 1513,587,1652,792,0 1656,650,1741,757,0 1410,544,1563,707,0 1367,747,1515,918,0 1470,739,1637,867,0 1334,840,1501,1033,0 1486,851,1636,987,0 1414,935,1580,1112,0 1233,875,1335,945,0 1254,918,1410,1063,0 1347,1107,1518,1279,0 1824,1126,1877,1276,0 1051,1239,1187,1397,0 804,1312,968,1435,0 772,1423,908,1539,0 599,1521,740,1680,0 573,1650,697,1788,0 759,1539,866,1680,0 867,1509,997,1637,0 817,1621,934,1774,0 902,1604,1016,1767,0 1063,1625,1186,1775,0 1099,1561,1242,1725,0 985,1714,1092,1852,0 853,1770,979,1899,0 964,1836,1104,1912,0 1171,1755,1323,1879,0 1106,1802,1263,1923,0 1022,1905,1149,2020,0 1008,2005,1172,2117,0 859,1909,1004,2089,0 1286,1852,1393,2010,0 1352,1962,1494,2129,0 1620,2069,1761,2225,0 1164,2093,1321,2230,0 1035,2131,1235,2300,0 642,2071,801,2194,0 558,2147,728,2280,0 493,2074,620,2190,0 591,2052,663,2168,0 436,1785,596,1955,0 556,1789,666,1923,0 1551,2467,1697,2626,0 1723,2564,1855,2686,0 2042,2577,2183,2737,0 2035,2714,2144,2823,0 1910,2677,2060,2812,0 1860,2824,1966,2946,0 1660,2677,1813,2796,0 1801,2698,1904,2807,0 1694,2787,1782,2899,0 1763,2792,1892,2901,0 1713,2930,1847,3052,0 1567,3001,1717,3155,0 1682,3029,1848,3161,0 2177,3136,2261,3262,0 2235,3070,2304,3189,0 2130,3277,2230,3390,0 2223,3262,2304,3378,0 2097,3389,2227,3508,0 2227,3368,2304,3480,0 2223,3502,2304,3549,0 2241,3543,2304,3665,0 2113,3533,2222,3687,0 2132,3708,2264,3817,0 2227,3677,2304,3769,0
|
| 33 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114454_part3.jpg 90,299,159,379,0 149,56,208,141,0 89,71,140,155,0 500,31,605,133,0 596,0,679,63,0 761,7,866,122,0 695,101,776,214,0 538,126,638,257,0 567,178,649,307,0 647,165,742,292,0 593,248,679,362,0 742,292,857,422,0 538,426,618,533,0 445,583,543,700,0 1001,657,1048,717,0 1070,728,1121,801,0 1093,788,1151,858,0 1154,888,1210,958,0 1157,958,1204,1027,0 1545,433,1640,566,0 1531,661,1620,784,0 1540,768,1628,882,0 1667,668,1728,756,0 1615,702,1666,805,0 1650,771,1728,892,0 1608,863,1705,1000,0
|
| 34 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114442_part1.jpg 1413,215,1452,290,0 1450,206,1498,274,0 1449,162,1488,215,0 1388,553,1440,634,0 1341,956,1409,1048,0 127,1370,201,1438,0 172,1417,238,1504,0 493,1241,548,1302,0 561,1319,613,1382,0 629,1385,668,1435,0 431,1434,480,1502,0 554,1498,589,1543,0 126,1515,192,1591,0 170,1491,223,1558,0 224,1519,278,1573,0 281,1512,329,1575,0 323,1534,381,1608,0 385,1512,453,1580,0 385,1564,424,1616,0 305,1578,357,1641,0 222,1557,280,1628,0 179,1577,233,1651,0 106,1603,168,1681,0 146,1660,217,1738,0 131,1720,187,1786,0 174,1804,243,1864,0 194,1856,249,1920,0 243,1638,305,1705,0 312,1658,363,1724,0 276,1698,347,1777,0
|
| 35 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114424_part1.jpg 425,282,505,378,0 464,0,536,43,0 509,7,587,78,0 463,80,538,147,0 536,62,588,138,0 585,60,647,122,0 659,50,719,111,0 487,134,568,229,0 585,124,650,217,0 538,199,632,305,0 631,210,710,310,0 704,0,801,99,0 849,14,924,113,0 938,65,1027,175,0 697,219,761,319,0 617,307,665,410,0 652,309,739,416,0 722,291,798,384,0 725,368,778,429,0 763,204,839,299,0 810,148,876,251,0 795,279,875,380,0 862,318,944,421,0 886,149,972,285,0 941,232,1003,310,0 996,242,1061,321,0 1035,312,1093,404,0 475,334,561,399,0 482,379,569,488,0 566,379,631,485,0 487,502,564,604,0 558,478,651,595,0 1198,244,1269,322,0 1317,190,1401,293,0 1505,377,1535,480,0 1013,706,1098,817,0 1026,888,1085,974,0 1008,938,1059,1004,0 1006,1040,1059,1107,0 1089,984,1153,1070,0 1101,947,1151,999,0 1100,901,1149,949,0 1144,896,1203,982,0 1152,968,1214,1035,0 1261,867,1315,931,0 1297,925,1349,993,0 1242,928,1297,999,0 1219,999,1290,1091,0 566,1392,627,1474,0 645,1468,712,1554,0 628,1568,694,1653,0 648,1660,701,1730,0 552,1754,602,1835,0 600,1778,675,1849,0 708,1659,766,1740,0 703,1557,749,1621,0 847,1594,924,1668,0 839,1648,906,1737,0 967,1638,1037,1734,0 1271,1424,1335,1485,0 946,1753,1031,1845,0 1071,1721,1105,1782,0 1108,1717,1176,1795,0 1091,1791,1158,1884,0 1479,1904,1525,1969,0 1433,1899,1475,1962,0
|
| 36 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG20240710105733_part3.jpg 156,1420,222,1501,0 120,1470,180,1536,0 229,2,380,156,0 336,85,452,212,0 0,72,149,308,0 135,76,274,247,0 126,212,230,298,0 0,338,56,488,0 0,569,99,708,0 119,0,235,68,0 1413,0,1544,58,0 1401,14,1526,119,0 1472,85,1607,262,0 1602,1,1720,77,0 1818,0,1928,43,0 1632,247,1775,405,0 1768,289,1866,385,0 808,300,876,387,0 1815,454,1927,603,0 1915,466,1993,595,0 1900,679,2032,872,0 1854,896,2000,1071,0 2007,912,2047,1080,0
|
| 37 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_11_01_IMG_9345_part4.jpg 269,121,412,280,0 295,12,413,123,0 350,45,484,164,0 472,1,637,118,0 694,0,861,112,0 843,89,976,211,0 837,0,974,61,0 772,159,938,314,0 542,115,640,224,0 421,129,545,295,0 550,226,679,378,0 465,278,585,413,0 626,281,716,389,0 695,393,816,556,0 488,374,622,480,0 564,427,687,582,0 638,396,725,543,0
|
| 38 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/微信图片_20240711114352_part3.jpg 182,1,365,184,0 460,0,616,73,0 897,0,1046,64,0 282,172,445,341,0 289,283,483,492,0 112,204,302,411,0 621,564,770,724,0 790,611,959,755,0 1082,517,1268,680,0 1163,821,1408,1070,0 1394,849,1626,1116,0 12,1560,229,1819,0 152,1591,370,1889,0 152,2093,313,2304,0 461,2161,726,2304,0 693,2085,935,2304,0 822,2112,1087,2303,0 970,1806,1234,2067,0 1131,2028,1423,2304,0
|
| 39 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_11_01_IMG_9347_part1.jpg 795,668,1058,1059,0 903,1025,1127,1330,0 1055,1422,1194,1621,0 1174,1473,1334,1656,0 1433,1462,1512,1666,0 1341,1596,1461,1750,0 1407,1691,1511,1814,0 1145,1606,1345,1745,0 1063,1709,1221,1876,0 1036,1808,1175,1954,0 1196,1737,1289,1840,0 1122,1842,1275,1993,0 1275,1788,1403,1947,0 1237,1946,1367,2016,0 1462,1826,1512,1937,0 1411,1867,1512,2002,0
|
| 40 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_11_02_IMG_9355_part3.jpg 687,8,918,160,0 821,49,1064,236,0 783,181,1058,453,0 1024,182,1215,453,0 710,494,979,756,0 964,490,1211,773,0 975,433,1227,609,0 1126,11,1280,106,0 1214,61,1349,281,0 1324,0,1512,328,0
|
| 41 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/IMG20240710105558_part2.jpg 336,126,418,257,0 10,375,107,520,0 219,321,319,474,0 372,258,468,387,0 476,242,562,348,0 551,300,631,389,0 544,354,642,454,0 420,444,494,544,0 503,440,610,576,0 315,467,400,582,0 376,524,433,599,0 181,562,275,697,0 486,582,564,695,0 542,554,624,675,0 426,599,496,718,0 474,710,548,812,0 66,874,168,967,0 36,940,138,1072,0 54,1093,135,1202,0 0,1191,50,1330,0 304,823,394,942,0 352,913,442,1041,0 251,984,344,1091,0 294,1104,379,1214,0 398,1060,476,1153,0 480,1016,567,1140,0 525,1050,603,1174,0 598,1071,687,1195,0 541,1205,619,1299,0 454,1232,524,1328,0 489,1262,583,1376,0 360,1227,417,1312,0 306,1376,374,1482,0 417,1319,480,1418,0 476,1389,562,1489,0 499,1462,566,1540,0 150,1362,214,1427,0 1,1452,123,1584,0 164,1470,260,1587,0 143,1430,228,1505,0 226,1423,289,1507,0 228,1523,302,1614,0 44,1601,124,1704,0 151,1594,230,1677,0 225,1621,314,1726,0 151,1688,244,1802,0 294,1589,366,1687,0 396,1629,476,1742,0 436,1587,514,1669,0 362,1617,407,1687,0 272,1881,357,1981,0 299,1951,401,2047,0 435,1769,499,1869,0 480,1773,546,1862,0 386,1797,462,1893,0 416,1880,493,1978,0 476,1863,562,1954,0 822,1721,872,1804,0 764,1866,831,1958,0 744,1965,805,2037,0 666,1914,712,1974,0 864,1770,917,1856,0 926,1693,983,1767,0 848,1825,898,1894,0 865,1929,931,2010,0 933,1844,980,1912,0 983,1843,1022,1917,0 951,1956,997,2010,0 987,1949,1033,2012,0 948,2014,990,2047,0
|
| 42 |
+
/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/JPEGImages/2024_07_10_11_04_IMG_9359.jpg 207,1690,318,1781,0 288,1604,361,1681,0 441,1559,509,1627,0 416,1554,457,1606,0 349,1472,428,1549,0 321,1471,395,1552,0 391,1439,472,1520,0 482,1385,545,1461,0 331,1345,391,1413,0 391,1347,444,1429,0 426,1394,479,1449,0 415,1307,473,1376,0 396,1237,470,1321,0 363,1196,437,1280,0 490,1323,541,1380,0 1041,2105,1110,2195,0 1035,2066,1104,2146,0 990,2016,1042,2094,0 1041,2044,1090,2090,0 950,1934,1027,2020,0 1213,1925,1260,2008,0 1123,1901,1195,1980,0 1070,1879,1138,1943,0 1106,1921,1156,1974,0 1166,1859,1216,1940,0 1186,1812,1259,1911,0 1120,1787,1188,1879,0 1061,1782,1136,1869,0 1138,1727,1206,1807,0 924,1863,988,1930,0 985,1906,1041,1972,0 1025,1858,1077,1942,0 998,1826,1053,1910,0 969,1802,1031,1881,0 935,1794,983,1860,0 850,1760,902,1812,0 940,1658,1023,1740,0 1013,1456,1090,1555,0 959,1456,1027,1541,0 1028,1434,1097,1504,0 1086,1440,1135,1513,0 1142,1405,1208,1496,0 1200,1392,1282,1486,0 1011,1373,1080,1449,0 1052,1320,1110,1415,0 1113,1284,1189,1390,0 1021,1247,1097,1331,0 969,1191,1049,1294,0 1027,1152,1102,1240,0 1078,1109,1159,1207,0 1068,1088,1137,1152,0 1042,1062,1087,1118,0 844,1070,921,1139,0 859,940,916,1016,0 890,916,960,979,0 931,1078,979,1152,0 999,1095,1039,1149,0 1000,1012,1055,1089,0 1059,892,1118,982,0 2104,2171,2202,2277,0 2136,2097,2203,2173,0 2179,2142,2254,2213,0 2079,2052,2159,2145,0 2136,2021,2187,2060,0 2169,2023,2232,2114,0 2234,1990,2297,2080,0 2275,1957,2362,2040,0 2297,1897,2367,1986,0 2043,1899,2124,2002,0 2114,1892,2173,1986,0 2162,1889,2207,1983,0 2199,1859,2269,1952,0 2242,1895,2301,1985,0 2233,1824,2302,1905,0 2287,1787,2360,1876,0 2201,1730,2276,1807,0 2135,1800,2195,1877,0 2648,1944,2694,2001,0 2678,1926,2733,1978,0 2752,2002,2808,2062,0 2736,1963,2781,2011,0 2774,1977,2819,2020,0 2804,1958,2852,2010,0 2793,1930,2823,1962,0 2746,1917,2787,1952,0 2814,1910,2857,1953,0 2697,1821,2738,1868,0 2735,1845,2770,1899,0 2785,1834,2836,1890,0 2759,1839,2794,1874,0 2763,1807,2799,1847,0 2779,1771,2820,1819,0 2740,1798,2769,1842,0 2686,1764,2722,1799,0 2718,1781,2740,1818,0 2746,1746,2789,1789,0 3162,2055,3234,2114,0 3232,2056,3282,2136,0 3262,2056,3327,2145,0 3287,2041,3345,2108,0 3111,1905,3171,1982,0 3171,1969,3230,2048,0 3210,1954,3271,2040,0 3240,1933,3299,2012,0 3331,1849,3401,1936,0 3268,1864,3343,1947,0 3202,1880,3263,1951,0 3143,1863,3209,1930,0 3144,1817,3192,1866,0 3322,1723,3379,1809,0 3265,1744,3341,1823,0 3271,1808,3337,1890,0 3227,1801,3292,1885,0 3165,1775,3228,1838,0 3171,1686,3233,1764,0 3131,1693,3184,1757,0 3179,1740,3233,1786,0 3287,1612,3351,1696,0 3201,1653,3261,1724,0 3233,1610,3284,1660,0 3067,1547,3120,1610,0 3103,1585,3159,1662,0 3144,1575,3205,1640,0 2145,328,2216,404,0 2246,419,2292,476,0 2198,452,2259,506,0 2288,470,2328,509,0 2339,726,2404,791,0 2533,660,2591,715,0 2529,680,2590,756,0 2510,743,2567,812,0 2413,815,2468,894,0 2377,777,2428,830,0 2325,794,2371,862,0 2490,812,2541,894,0 2576,794,2634,859,0 2579,841,2628,914,0 2690,850,2746,914,0 2687,925,2753,979,0 2665,968,2727,1025,0 2640,910,2673,973,0 2466,923,2536,975,0 2503,963,2579,1034,0 2468,966,2520,1028,0 2487,1010,2562,1077,0 2539,1036,2597,1109,0 2177,1047,2266,1150,0 1958,1221,2057,1308,0 1990,1286,2078,1370,0 2024,1358,2091,1450,0 2047,1312,2123,1400,0 2112,1331,2187,1416,0 2081,1391,2152,1469,0 2119,1294,2180,1348,0 2863,560,2947,657,0 2891,748,2960,830,0 2834,803,2904,894,0 2746,844,2813,917,0 2912,852,2971,900,0 2893,887,2961,938,0 2875,918,2946,1006,0 2825,966,2900,1051,0 2935,924,2993,1000,0 2944,1014,3026,1096,0 2895,1039,2962,1115,0 2905,1132,3003,1222,0 3244,962,3317,1040,0 3223,805,3285,886,0 3296,759,3345,836,0 3325,741,3384,822,0 3301,805,3367,866,0 3369,860,3424,913,0 3426,928,3491,990,0 3301,947,3392,1023,0 3479,963,3546,1034,0 3540,994,3608,1067,0 3349,1022,3410,1103,0 3389,1040,3461,1119,0 3432,1023,3505,1093,0 3455,1070,3524,1139,0 3549,1098,3606,1157,0 3458,1141,3539,1207,0 3459,1189,3540,1258,0 3491,1262,3557,1301,0 3487,1287,3533,1346,0 3455,1311,3507,1378,0 3511,1294,3588,1368,0 3535,1359,3599,1429,0 3402,1402,3451,1455,0 3462,1371,3515,1431,0 3523,1407,3575,1482,0 3462,1449,3531,1507,0 3455,1484,3522,1557,0 3513,1533,3581,1607,0 3526,1466,3589,1542,0 3558,1440,3628,1522,0 3631,1456,3683,1526,0 3682,1493,3757,1574,0 3613,1490,3665,1562,0 3642,1575,3712,1642,0 3598,1583,3650,1640,0 3633,1615,3684,1685,0 3653,1624,3710,1693,0 3625,1686,3684,1747,0 3587,1652,3637,1708,0 3575,1631,3617,1677,0 3499,1670,3556,1719,0 3538,1684,3583,1729,0 3715,1360,3749,1404,0 3847,1353,3892,1402,0 3503,1951,3539,1991,0 3479,1964,3529,2007,0 3567,1960,3615,2008,0 3599,1987,3652,2036,0 3643,2045,3693,2097,0 3559,1991,3592,2033,0 3497,1996,3561,2039,0 3525,2032,3580,2081,0 3517,2070,3546,2099,0 3473,2084,3529,2131,0 3515,2106,3552,2147,0 3507,2139,3546,2178,0 3541,2174,3581,2219,0 3569,2157,3614,2212,0 3548,2138,3576,2173,0 3563,2113,3602,2166,0 3584,2146,3622,2188,0 3606,2125,3648,2166,0 3591,2108,3622,2145,0 3618,2084,3653,2125,0 3641,2102,3681,2145,0 3618,2064,3645,2090,0 1488,1486,1529,1525,0 1529,1479,1564,1527,0 1594,1489,1627,1525,0 1554,1543,1592,1579,0 1608,1535,1638,1578,0 1629,1537,1654,1579,0 1646,1538,1672,1569,0 1647,1561,1681,1599,0 1600,1599,1629,1633,0 1614,1619,1644,1657,0 1633,1604,1667,1646,0 1603,1658,1637,1697,0 1629,1663,1671,1703,0 1775,1599,1836,1674,0 1815,1544,1876,1618,0 1850,1764,1909,1835,0 1828,1712,1891,1785,0 1855,1672,1905,1735,0 1847,1607,1902,1680,0 1870,1572,1915,1629,0 1902,1573,1945,1630,0 1898,1634,1949,1703,0 1929,1663,1974,1717,0 1945,1608,1988,1668,0 1980,1601,2024,1646,0 1983,1635,2027,1685,0 2011,1666,2059,1721,0 1984,1688,2017,1722,0 1921,1724,1955,1764,0 1931,1756,1946,1779,0 1951,1755,1975,1786,0 1961,1731,1987,1769,0 1992,1740,2016,1780,0 2040,1741,2070,1782,0 2008,1716,2041,1749,0 1771,1197,1832,1265,0 1889,1197,1914,1235,0 1705,827,1769,902,0 1752,870,1804,929,0 1792,897,1835,960,0 1892,912,1939,986,0 1837,966,1893,1039,0 1865,1008,1908,1070,0 1828,1009,1864,1066,0 1786,1008,1835,1059,0 1804,1046,1853,1113,0 1753,1042,1796,1097,0 1715,1033,1761,1094,0 1718,1078,1761,1131,0 1748,1087,1799,1142,0 1593,869,1651,936,0 1571,785,1628,849,0 1628,771,1689,844,0 1624,659,1706,724,0 1643,1083,1676,1118,0 3869,1402,3902,1452,0 3897,1407,3931,1456,0 3861,1334,3895,1375,0 3871,1301,3909,1344,0 3909,1310,3943,1344,0 3890,1285,3934,1319,0 3904,1258,3928,1288,0 3869,1209,3913,1256,0 3942,1241,3968,1293,0 3968,1226,3998,1283,0 3887,1122,3923,1163,0 3979,1106,4013,1149,0 4020,1110,4032,1145,0 3960,1165,3989,1209,0 3992,1172,4027,1203,0 3989,1192,4031,1235,0 3994,1435,4018,1466,0 4009,1387,4032,1418,0 4006,1323,4032,1351,0 3999,1405,4024,1429,0 3810,681,3844,725,0 3750,738,3784,773,0 3682,738,3716,770,0 3682,766,3723,794,0 3741,763,3779,800,0 3768,784,3798,814,0 3671,832,3698,859,0 3696,829,3733,859,0 3755,873,3783,903,0 3828,878,3843,907,0 3897,896,3935,923,0 3855,893,3875,920,0 3843,914,3872,946,0 3867,913,3892,950,0 3887,912,3913,945,0 3820,945,3848,979,0 3835,966,3864,999,0 3818,989,3855,1031,0 3754,964,3781,998,0 3744,980,3766,1017,0 3696,971,3722,999,0 3679,980,3703,1017,0 3650,925,3676,958,0 3655,906,3673,931,0 3693,939,3732,976,0 3655,964,3683,986,0 3982,931,4026,981,0 3967,843,4009,894,0 3992,837,4032,883,0 3992,781,4027,817,0 4005,815,4029,844,0 4012,796,4031,834,0 4020,745,4032,792,0
|
ssd-pytorch-master/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2020 JiaQi Xu
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
ssd-pytorch-master/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## SSD:Single-Shot MultiBox Detector目标检测模型在Pytorch当中的实现
|
| 2 |
+
---
|
| 3 |
+
|
| 4 |
+
## 目录
|
| 5 |
+
1. [仓库更新 Top News](#仓库更新)
|
| 6 |
+
2. [性能情况 Performance](#性能情况)
|
| 7 |
+
3. [所需环境 Environment](#所需环境)
|
| 8 |
+
4. [文件下载 Download](#文件下载)
|
| 9 |
+
5. [训练步骤 How2train](#训练步骤)
|
| 10 |
+
6. [预测步骤 How2predict](#预测步骤)
|
| 11 |
+
7. [评估步骤 How2eval](#评估步骤)
|
| 12 |
+
8. [参考资料 Reference](#Reference)
|
| 13 |
+
|
| 14 |
+
## Top News
|
| 15 |
+
**`2022-03`**:**进行了大幅度的更新,支持step、cos学习率下降法、支持adam、sgd优化器选择、支持学习率根据batch_size自适应调整、新增图片裁剪。**
|
| 16 |
+
BiliBili视频中的原仓库地址为:https://github.com/bubbliiiing/ssd-pytorch/tree/bilibili
|
| 17 |
+
|
| 18 |
+
**`2021-10`**:**进行了大幅度的更新,增加了mobilenetv2主干的选择、增加大量注释、增加了大量可调整参数、对代码的组成模块进行修改、增加fps、视频预测、批量预测等功能。**
|
| 19 |
+
|
| 20 |
+
## 性能情况
|
| 21 |
+
| 训练数据集 | 权值文件名称 | 测试数据集 | 输入图片大小 | mAP 0.5:0.95 | mAP 0.5 |
|
| 22 |
+
| :-----: | :-----: | :------: | :------: | :------: | :-----: |
|
| 23 |
+
| VOC07+12 | [ssd_weights.pth](https://github.com/bubbliiiing/ssd-pytorch/releases/download/v1.0/ssd_weights.pth) | VOC-Test07 | 300x300| - | 78.55
|
| 24 |
+
| VOC07+12 | [mobilenetv2_ssd_weights.pth](https://github.com/bubbliiiing/ssd-pytorch/releases/download/v1.0/mobilenetv2_ssd_weights.pth) | VOC-Test07 | 300x300| - | 71.32
|
| 25 |
+
|
| 26 |
+
## 所需环境
|
| 27 |
+
torch == 1.2.0
|
| 28 |
+
|
| 29 |
+
## 文件下载
|
| 30 |
+
训练所需的ssd_weights.pth和主干的权值可以在百度云下载。
|
| 31 |
+
链接: https://pan.baidu.com/s/1iUVE50oLkzqhtZbUL9el9w
|
| 32 |
+
提取码: jgn8
|
| 33 |
+
|
| 34 |
+
VOC数据集下载地址如下,里面已经包括了训练集、测试集、验证集(与测试集一样),无需再次划分:
|
| 35 |
+
链接: https://pan.baidu.com/s/1-1Ej6dayrx3g0iAA88uY5A
|
| 36 |
+
提取码: ph32
|
| 37 |
+
|
| 38 |
+
## 训练步骤
|
| 39 |
+
### a、训练VOC07+12数据集
|
| 40 |
+
1. 数据集的准备
|
| 41 |
+
**本文使用VOC格式进行训练,训练前需要下载好VOC07+12的数据集,解压后放在根目录**
|
| 42 |
+
|
| 43 |
+
2. 数据集的处理
|
| 44 |
+
修改voc_annotation.py里面的annotation_mode=2,运行voc_annotation.py生成根目录下的2007_train.txt和2007_val.txt。
|
| 45 |
+
|
| 46 |
+
3. 开始网络训练
|
| 47 |
+
train.py的默认参数用于训练VOC数据集,直接运行train.py即可开始训练。
|
| 48 |
+
|
| 49 |
+
4. 训练结果预测
|
| 50 |
+
训练结果预测需要用到两个文件,分别是ssd.py和predict.py。我们首先需要去ssd.py里面修改model_path以及classes_path,这两个参数必须要修改。
|
| 51 |
+
**model_path指向训练好的权值文件,在logs文件夹里。
|
| 52 |
+
classes_path指向检测类别所对应的txt。**
|
| 53 |
+
完成修改后就可以运行predict.py进行检测了。运行后输入图片路径即可检测。
|
| 54 |
+
|
| 55 |
+
### b、训练自己的数据集
|
| 56 |
+
1. 数据集的准备
|
| 57 |
+
**本文使用VOC格式进行训练,训练前需要自己制作好数据集,**
|
| 58 |
+
训练前将标签文件放在VOCdevkit文件夹下的VOC2007文件夹下的Annotation中。
|
| 59 |
+
训练前将图片文件放在VOCdevkit文件夹下的VOC2007文件夹下的JPEGImages中。
|
| 60 |
+
|
| 61 |
+
2. 数据集的处理
|
| 62 |
+
在完成数据集的摆放之后,我们需要利用voc_annotation.py获得训练用的2007_train.txt和2007_val.txt。
|
| 63 |
+
修改voc_annotation.py里面的参数。第一次训练可以仅修改classes_path,classes_path用于指向检测类别所对应的txt。
|
| 64 |
+
训练自己的数据集时,可以自己建立一个cls_classes.txt,里面写自己所需要区分的类别。
|
| 65 |
+
model_data/cls_classes.txt文件内容为:
|
| 66 |
+
```python
|
| 67 |
+
cat
|
| 68 |
+
dog
|
| 69 |
+
...
|
| 70 |
+
```
|
| 71 |
+
修改voc_annotation.py中的classes_path,使其对应cls_classes.txt,并运行voc_annotation.py。
|
| 72 |
+
|
| 73 |
+
3. 开始网络训练
|
| 74 |
+
**训练的参数较多,均在train.py中,大家可以在下载库后仔细看注释,其中最重要的部分依然是train.py里的classes_path。**
|
| 75 |
+
**classes_path用于指向检测类别所对应的txt,这个txt和voc_annotation.py里面的txt一样!训练自己的数据集必须要修改!**
|
| 76 |
+
修改完classes_path后就可以运行train.py开始训练了,在训练多个epoch后,权值会生成在logs文件夹中。
|
| 77 |
+
|
| 78 |
+
4. 训练结果预测
|
| 79 |
+
训练结果预测需要用到两个文件,分别是ssd.py和predict.py。在ssd.py里面修改model_path以及classes_path。
|
| 80 |
+
**model_path指向训练好的权值文件,在logs文件夹里。
|
| 81 |
+
classes_path指向检测类别所对应的txt。**
|
| 82 |
+
完成修改后就可以运行predict.py进行检测了。运行后输入图片路径即可检测。
|
| 83 |
+
|
| 84 |
+
## 预测步骤
|
| 85 |
+
### a、使用预训练权重
|
| 86 |
+
1. 下载完库后解压,在百度网盘下载,放入model_data,运行predict.py,输入
|
| 87 |
+
```python
|
| 88 |
+
img/street.jpg
|
| 89 |
+
```
|
| 90 |
+
2. 在predict.py里面进行设置可以进行fps测试和video视频检测。
|
| 91 |
+
### b、使用自己训练的权重
|
| 92 |
+
1. 按照训练步骤训练。
|
| 93 |
+
2. 在ssd.py文件里面,在如下部分修改model_path和classes_path使其对应���练好的文件;**model_path对应logs文件夹下面的权值文件,classes_path是model_path对应分的类**。
|
| 94 |
+
```python
|
| 95 |
+
_defaults = {
|
| 96 |
+
#--------------------------------------------------------------------------#
|
| 97 |
+
# 使用自己训练好的模型进行预测一定要修改model_path和classes_path!
|
| 98 |
+
# model_path指向logs文件夹下的权值文件,classes_path指向model_data下的txt
|
| 99 |
+
# 如果出现shape不匹配,同时要注意训练时的model_path和classes_path参数的修改
|
| 100 |
+
#--------------------------------------------------------------------------#
|
| 101 |
+
"model_path" : 'model_data/ssd_weights.pth',
|
| 102 |
+
"classes_path" : 'model_data/voc_classes.txt',
|
| 103 |
+
#---------------------------------------------------------------------#
|
| 104 |
+
# 用于预测的图像大小,和train时使用同一个即可
|
| 105 |
+
#---------------------------------------------------------------------#
|
| 106 |
+
"input_shape" : [300, 300],
|
| 107 |
+
#-------------------------------#
|
| 108 |
+
# 主干网络的选择
|
| 109 |
+
# vgg或者mobilenetv2
|
| 110 |
+
#-------------------------------#
|
| 111 |
+
"backbone" : "vgg",
|
| 112 |
+
#---------------------------------------------------------------------#
|
| 113 |
+
# 只有得分大于置信度的预测框会被保留下来
|
| 114 |
+
#---------------------------------------------------------------------#
|
| 115 |
+
"confidence" : 0.5,
|
| 116 |
+
#---------------------------------------------------------------------#
|
| 117 |
+
# 非极大抑制所用到的nms_iou大小
|
| 118 |
+
#---------------------------------------------------------------------#
|
| 119 |
+
"nms_iou" : 0.45,
|
| 120 |
+
#---------------------------------------------------------------------#
|
| 121 |
+
# 用于指定先验框的大小
|
| 122 |
+
#---------------------------------------------------------------------#
|
| 123 |
+
'anchors_size' : [30, 60, 111, 162, 213, 264, 315],
|
| 124 |
+
#---------------------------------------------------------------------#
|
| 125 |
+
# 该变量用于控制是否使用letterbox_image对输入图像进行不失真的resize,
|
| 126 |
+
# 在多次测试后,发现关闭letterbox_image直接resize的效果更好
|
| 127 |
+
#---------------------------------------------------------------------#
|
| 128 |
+
"letterbox_image" : False,
|
| 129 |
+
#-------------------------------#
|
| 130 |
+
# 是否使用Cuda
|
| 131 |
+
# 没有GPU可以设置成False
|
| 132 |
+
#-------------------------------#
|
| 133 |
+
"cuda" : True,
|
| 134 |
+
}
|
| 135 |
+
```
|
| 136 |
+
3. 运行predict.py,输入
|
| 137 |
+
```python
|
| 138 |
+
img/street.jpg
|
| 139 |
+
```
|
| 140 |
+
4. 在predict.py里面进行设置可以进行fps测试和video视频检测。
|
| 141 |
+
|
| 142 |
+
## 评估步骤
|
| 143 |
+
### a、评估VOC07+12的测试集
|
| 144 |
+
1. 本文使用VOC格式进行评估。VOC07+12已经划分好了测试集,无需利用voc_annotation.py生成ImageSets文件夹下的txt。
|
| 145 |
+
2. 在ssd.py里面修改model_path以及classes_path。**model_path指向训练好的权值文件,在logs文件夹里。classes_path指向检测类别所对应的txt。**
|
| 146 |
+
3. 运行get_map.py即可获得评估结果,评估结果会保存在map_out文件夹中。
|
| 147 |
+
|
| 148 |
+
### b、评估自己的数据集
|
| 149 |
+
1. 本文使用VOC格式进行评估。
|
| 150 |
+
2. 如果在训练前已经运行过voc_annotation.py文件,代码会自动将数据集划分成训练集、验证集和测试集。如果想要修改测试集的比例,可以修改voc_annotation.py文件下的trainval_percent。trainval_percent用于指定(训练集+验证集)与测试集的比例,默认情况下 (训练集+验证集):测试集 = 9:1。train_percent用于指定(训练集+验证集)中训练集与验证集的比例,默认情况下 训练集:验证集 = 9:1。
|
| 151 |
+
3. 利用voc_annotation.py划分测试集后,前往get_map.py文件修改classes_path,classes_path用于指向检测类别所对应的txt,这个txt和训练时的txt一样。评估自己的数据集必须要修改。
|
| 152 |
+
4. 在ssd.py里面修改model_path以及classes_path。**model_path指向训练好的权值文件,在logs文件夹里。classes_path指向检测类别所对应的txt。**
|
| 153 |
+
5. 运行get_map.py即可获得评估结果,评估结果会保存在map_out文件夹中。
|
| 154 |
+
|
| 155 |
+
## Reference
|
| 156 |
+
https://github.com/pierluigiferrari/ssd_keras
|
| 157 |
+
https://github.com/kuhung/SSD_keras
|
ssd-pytorch-master/get_map.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import xml.etree.ElementTree as ET
|
| 3 |
+
|
| 4 |
+
from PIL import Image
|
| 5 |
+
from tqdm import tqdm
|
| 6 |
+
|
| 7 |
+
from utils.utils import get_classes
|
| 8 |
+
from utils.utils_map import get_coco_map, get_map
|
| 9 |
+
from ssd import SSD
|
| 10 |
+
|
| 11 |
+
if __name__ == "__main__":
|
| 12 |
+
'''
|
| 13 |
+
Recall和Precision不像AP是一个面积的概念,因此在门限值(Confidence)不同时,网络的Recall和Precision值是不同的。
|
| 14 |
+
默认情况下,本代码计算的Recall和Precision代表的是当门限值(Confidence)为0.5时,所对应的Recall和Precision值。
|
| 15 |
+
|
| 16 |
+
受到mAP计算原理的限制,网络在计算mAP时需要获得近乎所有的预测框,这样才可以计算不同门限条件下的Recall和Precision值
|
| 17 |
+
因此,本代码获得的map_out/detection-results/里面的txt的框的数量一般会比直接predict多一些,目的是列出所有可能的预测框,
|
| 18 |
+
'''
|
| 19 |
+
#------------------------------------------------------------------------------------------------------------------#
|
| 20 |
+
# map_mode用于指定该文件运行时计算的内容
|
| 21 |
+
# map_mode为0代表整个map计算流程,包括获得预测结果、获得真实框、计算VOC_map。
|
| 22 |
+
# map_mode为1代表仅仅获得预测结果。
|
| 23 |
+
# map_mode为2代表仅仅获得真实框。
|
| 24 |
+
# map_mode为3代表仅仅计算VOC_map。
|
| 25 |
+
# map_mode为4代表利用COCO工具箱计算当前数据集的0.50:0.95map。需要获得预测结果、获得真实框后并安装pycocotools才行
|
| 26 |
+
#-------------------------------------------------------------------------------------------------------------------#
|
| 27 |
+
map_mode = 0
|
| 28 |
+
#--------------------------------------------------------------------------------------#
|
| 29 |
+
# 此处的classes_path用于指定需要测量VOC_map的类别
|
| 30 |
+
# 一般情况下与训练和预测所用的classes_path一致即可
|
| 31 |
+
#--------------------------------------------------------------------------------------#
|
| 32 |
+
classes_path = 'model_data/voc_classes.txt'
|
| 33 |
+
#--------------------------------------------------------------------------------------#
|
| 34 |
+
# MINOVERLAP用于指定想要获得的mAP0.x,mAP0.x的意义是什么请同学们百度一下。
|
| 35 |
+
# 比如计算mAP0.75,可以设定MINOVERLAP = 0.75。
|
| 36 |
+
#
|
| 37 |
+
# 当某一预测框与真实框重合度大于MINOVERLAP时,该预测框被认为是正样本,否则为负样本。
|
| 38 |
+
# 因此MINOVERLAP的值越大,预测框要预测的越准确才能被认为是正样本,此时算出来的mAP值越低,
|
| 39 |
+
#--------------------------------------------------------------------------------------#
|
| 40 |
+
MINOVERLAP = 0.5
|
| 41 |
+
#--------------------------------------------------------------------------------------#
|
| 42 |
+
# 受到mAP计算原理的限制,网络在计算mAP时需要获得近乎所有的预测框,这样才可以计算mAP
|
| 43 |
+
# 因此,confidence的值应当设置的尽量小进而获得全部可能的预测框。
|
| 44 |
+
#
|
| 45 |
+
# 该值一般不调整。因为计算mAP需要获得近乎所有的预测框,此处的confidence不能随便更改。
|
| 46 |
+
# 想要获得不同门限值下的Recall和Precision值,请修改下方的score_threhold。
|
| 47 |
+
#--------------------------------------------------------------------------------------#
|
| 48 |
+
confidence = 0.02
|
| 49 |
+
#--------------------------------------------------------------------------------------#
|
| 50 |
+
# 预测时使用到的非极大抑制值的大小,越大表示非极大抑制越不严格。
|
| 51 |
+
#
|
| 52 |
+
# 该值一般不调整。
|
| 53 |
+
#--------------------------------------------------------------------------------------#
|
| 54 |
+
nms_iou = 0.5
|
| 55 |
+
#---------------------------------------------------------------------------------------------------------------#
|
| 56 |
+
# Recall和Precision不像AP是一个面积的概念,因此在门限值不同时,网络的Recall和Precision值是不同的。
|
| 57 |
+
#
|
| 58 |
+
# 默认情况下,本代码计算的Recall和Precision代表的是当门限值为0.5(此处定义为score_threhold)时所对应的Recall和Precision值。
|
| 59 |
+
# 因为计算mAP需要获得近乎所有的预测框,上面定义的confidence不能随便更改。
|
| 60 |
+
# 这里专门定义一个score_threhold用于代表门限值,进而在计算mAP时找到门限值对应的Recall和Precision值。
|
| 61 |
+
#---------------------------------------------------------------------------------------------------------------#
|
| 62 |
+
score_threhold = 0.5
|
| 63 |
+
#-------------------------------------------------------#
|
| 64 |
+
# map_vis用于指定是否开启VOC_map计算的可视化
|
| 65 |
+
#-------------------------------------------------------#
|
| 66 |
+
map_vis = False
|
| 67 |
+
#-------------------------------------------------------#
|
| 68 |
+
# 指向VOC数据集所在的文件夹
|
| 69 |
+
# 默认指向根目录下的VOC数据集
|
| 70 |
+
#-------------------------------------------------------#
|
| 71 |
+
VOCdevkit_path = 'VOCdevkit'
|
| 72 |
+
#-------------------------------------------------------#
|
| 73 |
+
# 结果输出的文件夹,默认为map_out
|
| 74 |
+
#-------------------------------------------------------#
|
| 75 |
+
map_out_path = 'map_out'
|
| 76 |
+
|
| 77 |
+
image_ids = open(os.path.join(VOCdevkit_path, "VOC2007/ImageSets/Main/test.txt")).read().strip().split()
|
| 78 |
+
|
| 79 |
+
if not os.path.exists(map_out_path):
|
| 80 |
+
os.makedirs(map_out_path)
|
| 81 |
+
if not os.path.exists(os.path.join(map_out_path, 'ground-truth')):
|
| 82 |
+
os.makedirs(os.path.join(map_out_path, 'ground-truth'))
|
| 83 |
+
if not os.path.exists(os.path.join(map_out_path, 'detection-results')):
|
| 84 |
+
os.makedirs(os.path.join(map_out_path, 'detection-results'))
|
| 85 |
+
if not os.path.exists(os.path.join(map_out_path, 'images-optional')):
|
| 86 |
+
os.makedirs(os.path.join(map_out_path, 'images-optional'))
|
| 87 |
+
|
| 88 |
+
class_names, _ = get_classes(classes_path)
|
| 89 |
+
|
| 90 |
+
if map_mode == 0 or map_mode == 1:
|
| 91 |
+
print("Load model.")
|
| 92 |
+
ssd = SSD(confidence = confidence, nms_iou = nms_iou)
|
| 93 |
+
print("Load model done.")
|
| 94 |
+
|
| 95 |
+
print("Get predict result.")
|
| 96 |
+
for image_id in tqdm(image_ids):
|
| 97 |
+
image_path = os.path.join(VOCdevkit_path, "VOC2007/JPEGImages/"+image_id+".jpg")
|
| 98 |
+
image = Image.open(image_path)
|
| 99 |
+
if map_vis:
|
| 100 |
+
image.save(os.path.join(map_out_path, "images-optional/" + image_id + ".jpg"))
|
| 101 |
+
ssd.get_map_txt(image_id, image, class_names, map_out_path)
|
| 102 |
+
print("Get predict result done.")
|
| 103 |
+
|
| 104 |
+
if map_mode == 0 or map_mode == 2:
|
| 105 |
+
print("Get ground truth result.")
|
| 106 |
+
for image_id in tqdm(image_ids):
|
| 107 |
+
with open(os.path.join(map_out_path, "ground-truth/"+image_id+".txt"), "w") as new_f:
|
| 108 |
+
root = ET.parse(os.path.join(VOCdevkit_path, "VOC2007/Annotations/"+image_id+".xml")).getroot()
|
| 109 |
+
for obj in root.findall('object'):
|
| 110 |
+
difficult_flag = False
|
| 111 |
+
if obj.find('difficult')!=None:
|
| 112 |
+
difficult = obj.find('difficult').text
|
| 113 |
+
if int(difficult)==1:
|
| 114 |
+
difficult_flag = True
|
| 115 |
+
obj_name = obj.find('name').text
|
| 116 |
+
if obj_name not in class_names:
|
| 117 |
+
continue
|
| 118 |
+
bndbox = obj.find('bndbox')
|
| 119 |
+
left = bndbox.find('xmin').text
|
| 120 |
+
top = bndbox.find('ymin').text
|
| 121 |
+
right = bndbox.find('xmax').text
|
| 122 |
+
bottom = bndbox.find('ymax').text
|
| 123 |
+
|
| 124 |
+
if difficult_flag:
|
| 125 |
+
new_f.write("%s %s %s %s %s difficult\n" % (obj_name, left, top, right, bottom))
|
| 126 |
+
else:
|
| 127 |
+
new_f.write("%s %s %s %s %s\n" % (obj_name, left, top, right, bottom))
|
| 128 |
+
print("Get ground truth result done.")
|
| 129 |
+
|
| 130 |
+
if map_mode == 0 or map_mode == 3:
|
| 131 |
+
print("Get map.")
|
| 132 |
+
get_map(MINOVERLAP, True, score_threhold = score_threhold, path = map_out_path)
|
| 133 |
+
print("Get map done.")
|
| 134 |
+
|
| 135 |
+
if map_mode == 4:
|
| 136 |
+
print("Get map.")
|
| 137 |
+
get_coco_map(class_names = class_names, path = map_out_path)
|
| 138 |
+
print("Get map done.")
|
ssd-pytorch-master/img/street.jpg
ADDED
|
Git LFS Details
|
ssd-pytorch-master/nets/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
#
|
ssd-pytorch-master/nets/mobilenetv2.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from torch import nn
|
| 2 |
+
from torch.hub import load_state_dict_from_url
|
| 3 |
+
|
| 4 |
+
def _make_divisible(v, divisor, min_value=None):
|
| 5 |
+
if min_value is None:
|
| 6 |
+
min_value = divisor
|
| 7 |
+
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
|
| 8 |
+
if new_v < 0.9 * v:
|
| 9 |
+
new_v += divisor
|
| 10 |
+
return new_v
|
| 11 |
+
|
| 12 |
+
class ConvBNReLU(nn.Sequential):
|
| 13 |
+
def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1):
|
| 14 |
+
padding = (kernel_size - 1) // 2
|
| 15 |
+
super(ConvBNReLU, self).__init__(
|
| 16 |
+
nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False),
|
| 17 |
+
nn.BatchNorm2d(out_planes),
|
| 18 |
+
nn.ReLU6(inplace=True)
|
| 19 |
+
)
|
| 20 |
+
self.out_channels = out_planes
|
| 21 |
+
|
| 22 |
+
class InvertedResidual(nn.Module):
|
| 23 |
+
def __init__(self, inp, oup, stride, expand_ratio):
|
| 24 |
+
super(InvertedResidual, self).__init__()
|
| 25 |
+
self.stride = stride
|
| 26 |
+
assert stride in [1, 2]
|
| 27 |
+
|
| 28 |
+
hidden_dim = int(round(inp * expand_ratio))
|
| 29 |
+
self.use_res_connect = self.stride == 1 and inp == oup
|
| 30 |
+
|
| 31 |
+
layers = []
|
| 32 |
+
if expand_ratio != 1:
|
| 33 |
+
layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1))
|
| 34 |
+
layers.extend([
|
| 35 |
+
ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim),
|
| 36 |
+
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
|
| 37 |
+
nn.BatchNorm2d(oup),
|
| 38 |
+
])
|
| 39 |
+
self.conv = nn.Sequential(*layers)
|
| 40 |
+
|
| 41 |
+
self.out_channels = oup
|
| 42 |
+
|
| 43 |
+
def forward(self, x):
|
| 44 |
+
if self.use_res_connect:
|
| 45 |
+
return x + self.conv(x)
|
| 46 |
+
else:
|
| 47 |
+
return self.conv(x)
|
| 48 |
+
|
| 49 |
+
class MobileNetV2(nn.Module):
|
| 50 |
+
def __init__(self, num_classes=1000, width_mult=1.0, inverted_residual_setting=None, round_nearest=8):
|
| 51 |
+
super(MobileNetV2, self).__init__()
|
| 52 |
+
block = InvertedResidual
|
| 53 |
+
input_channel = 32
|
| 54 |
+
last_channel = 1280
|
| 55 |
+
|
| 56 |
+
if inverted_residual_setting is None:
|
| 57 |
+
inverted_residual_setting = [
|
| 58 |
+
[1, 16, 1, 1],
|
| 59 |
+
[6, 24, 2, 2],
|
| 60 |
+
[6, 32, 3, 2],
|
| 61 |
+
[6, 64, 4, 2],
|
| 62 |
+
[6, 96, 3, 1],
|
| 63 |
+
[6, 160, 3, 2],
|
| 64 |
+
[6, 320, 1, 1],
|
| 65 |
+
]
|
| 66 |
+
|
| 67 |
+
if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4:
|
| 68 |
+
raise ValueError("inverted_residual_setting should be non-empty "
|
| 69 |
+
"or a 4-element list, got {}".format(inverted_residual_setting))
|
| 70 |
+
|
| 71 |
+
input_channel = _make_divisible(input_channel * width_mult, round_nearest)
|
| 72 |
+
self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest)
|
| 73 |
+
features = [ConvBNReLU(3, input_channel, stride=2)]
|
| 74 |
+
for t, c, n, s in inverted_residual_setting:
|
| 75 |
+
output_channel = _make_divisible(c * width_mult, round_nearest)
|
| 76 |
+
for i in range(n):
|
| 77 |
+
stride = s if i == 0 else 1
|
| 78 |
+
features.append(block(input_channel, output_channel, stride, expand_ratio=t))
|
| 79 |
+
input_channel = output_channel
|
| 80 |
+
features.append(ConvBNReLU(input_channel, self.last_channel, kernel_size=1))
|
| 81 |
+
self.features = nn.Sequential(*features)
|
| 82 |
+
|
| 83 |
+
self.classifier = nn.Sequential(
|
| 84 |
+
nn.Dropout(0.2),
|
| 85 |
+
nn.Linear(self.last_channel, num_classes),
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
for m in self.modules():
|
| 89 |
+
if isinstance(m, nn.Conv2d):
|
| 90 |
+
nn.init.kaiming_normal_(m.weight, mode='fan_out')
|
| 91 |
+
if m.bias is not None:
|
| 92 |
+
nn.init.zeros_(m.bias)
|
| 93 |
+
elif isinstance(m, nn.BatchNorm2d):
|
| 94 |
+
nn.init.ones_(m.weight)
|
| 95 |
+
nn.init.zeros_(m.bias)
|
| 96 |
+
elif isinstance(m, nn.Linear):
|
| 97 |
+
nn.init.normal_(m.weight, 0, 0.01)
|
| 98 |
+
nn.init.zeros_(m.bias)
|
| 99 |
+
|
| 100 |
+
def forward(self, x):
|
| 101 |
+
x = self.features(x)
|
| 102 |
+
x = x.mean([2, 3])
|
| 103 |
+
x = self.classifier(x)
|
| 104 |
+
return x
|
| 105 |
+
|
| 106 |
+
def mobilenet_v2(pretrained=False, progress=True, **kwargs):
|
| 107 |
+
model = MobileNetV2(**kwargs)
|
| 108 |
+
if pretrained:
|
| 109 |
+
state_dict = load_state_dict_from_url('https://download.pytorch.org/models/mobilenet_v2-b0353104.pth', model_dir="./model_data", progress=progress)
|
| 110 |
+
model.load_state_dict(state_dict)
|
| 111 |
+
del model.classifier
|
| 112 |
+
return model
|
| 113 |
+
|
| 114 |
+
if __name__ == "__main__":
|
| 115 |
+
net = mobilenet_v2()
|
| 116 |
+
for i, layer in enumerate(net.features):
|
| 117 |
+
print(i, layer)
|
ssd-pytorch-master/nets/resnet.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import torch.utils.model_zoo as model_zoo
|
| 5 |
+
|
| 6 |
+
def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
|
| 7 |
+
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
|
| 8 |
+
padding=dilation, groups=groups, bias=False, dilation=dilation)
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def conv1x1(in_planes, out_planes, stride=1):
|
| 12 |
+
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class BasicBlock(nn.Module):
|
| 16 |
+
expansion = 1
|
| 17 |
+
|
| 18 |
+
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
|
| 19 |
+
base_width=64, dilation=1, norm_layer=None):
|
| 20 |
+
super(BasicBlock, self).__init__()
|
| 21 |
+
if norm_layer is None:
|
| 22 |
+
norm_layer = nn.BatchNorm2d
|
| 23 |
+
if groups != 1 or base_width != 64:
|
| 24 |
+
raise ValueError('BasicBlock only supports groups=1 and base_width=64')
|
| 25 |
+
if dilation > 1:
|
| 26 |
+
raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
|
| 27 |
+
self.conv1 = conv3x3(inplanes, planes, stride)
|
| 28 |
+
self.bn1 = norm_layer(planes)
|
| 29 |
+
self.relu = nn.ReLU(inplace=True)
|
| 30 |
+
self.conv2 = conv3x3(planes, planes)
|
| 31 |
+
self.bn2 = norm_layer(planes)
|
| 32 |
+
self.downsample = downsample
|
| 33 |
+
self.stride = stride
|
| 34 |
+
|
| 35 |
+
def forward(self, x):
|
| 36 |
+
identity = x
|
| 37 |
+
|
| 38 |
+
out = self.conv1(x)
|
| 39 |
+
out = self.bn1(out)
|
| 40 |
+
out = self.relu(out)
|
| 41 |
+
|
| 42 |
+
out = self.conv2(out)
|
| 43 |
+
out = self.bn2(out)
|
| 44 |
+
|
| 45 |
+
if self.downsample is not None:
|
| 46 |
+
identity = self.downsample(x)
|
| 47 |
+
|
| 48 |
+
out += identity
|
| 49 |
+
out = self.relu(out)
|
| 50 |
+
|
| 51 |
+
return out
|
| 52 |
+
|
| 53 |
+
class Bottleneck(nn.Module):
|
| 54 |
+
expansion = 4
|
| 55 |
+
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
|
| 56 |
+
base_width=64, dilation=1, norm_layer=None):
|
| 57 |
+
super(Bottleneck, self).__init__()
|
| 58 |
+
if norm_layer is None:
|
| 59 |
+
norm_layer = nn.BatchNorm2d
|
| 60 |
+
width = int(planes * (base_width / 64.)) * groups
|
| 61 |
+
# 利用1x1卷积下降通道数
|
| 62 |
+
self.conv1 = conv1x1(inplanes, width)
|
| 63 |
+
self.bn1 = norm_layer(width)
|
| 64 |
+
# 利用3x3卷积进行特征提取
|
| 65 |
+
self.conv2 = conv3x3(width, width, stride, groups, dilation)
|
| 66 |
+
self.bn2 = norm_layer(width)
|
| 67 |
+
# 利用1x1卷积上升通道数
|
| 68 |
+
self.conv3 = conv1x1(width, planes * self.expansion)
|
| 69 |
+
self.bn3 = norm_layer(planes * self.expansion)
|
| 70 |
+
|
| 71 |
+
self.relu = nn.ReLU(inplace=True)
|
| 72 |
+
self.downsample = downsample
|
| 73 |
+
self.stride = stride
|
| 74 |
+
|
| 75 |
+
def forward(self, x):
|
| 76 |
+
identity = x
|
| 77 |
+
|
| 78 |
+
out = self.conv1(x)
|
| 79 |
+
out = self.bn1(out)
|
| 80 |
+
out = self.relu(out)
|
| 81 |
+
|
| 82 |
+
out = self.conv2(out)
|
| 83 |
+
out = self.bn2(out)
|
| 84 |
+
out = self.relu(out)
|
| 85 |
+
|
| 86 |
+
out = self.conv3(out)
|
| 87 |
+
out = self.bn3(out)
|
| 88 |
+
|
| 89 |
+
if self.downsample is not None:
|
| 90 |
+
identity = self.downsample(x)
|
| 91 |
+
|
| 92 |
+
out += identity
|
| 93 |
+
out = self.relu(out)
|
| 94 |
+
|
| 95 |
+
return out
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class ResNet(nn.Module):
|
| 99 |
+
def __init__(self, block, layers, num_classes=1000):
|
| 100 |
+
#-----------------------------------------------------------#
|
| 101 |
+
# 假设输入图像为600,600,3
|
| 102 |
+
# 当我们使用resnet50的时候
|
| 103 |
+
#-----------------------------------------------------------#
|
| 104 |
+
self.inplanes = 64
|
| 105 |
+
super(ResNet, self).__init__()
|
| 106 |
+
# 600,600,3 -> 300,300,64
|
| 107 |
+
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
|
| 108 |
+
self.bn1 = nn.BatchNorm2d(64)
|
| 109 |
+
self.relu = nn.ReLU(inplace=True)
|
| 110 |
+
# 300,300,64 -> 150,150,64
|
| 111 |
+
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=0, ceil_mode=True) # change
|
| 112 |
+
# 150,150,64 -> 150,150,256
|
| 113 |
+
self.layer1 = self._make_layer(block, 64, layers[0])
|
| 114 |
+
# 150,150,256 -> 75,75,512
|
| 115 |
+
self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
|
| 116 |
+
# 75,75,512 -> 38,38,1024
|
| 117 |
+
self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
|
| 118 |
+
# 38,38,1024 -> 19,19,2048
|
| 119 |
+
self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
|
| 120 |
+
self.features = [self.conv1, self.bn1, self.relu, self.maxpool, self.layer1, self.layer2, self.layer3]
|
| 121 |
+
|
| 122 |
+
self.avgpool = nn.AvgPool2d(7)
|
| 123 |
+
self.fc = nn.Linear(512 * block.expansion, num_classes)
|
| 124 |
+
|
| 125 |
+
for m in self.modules():
|
| 126 |
+
if isinstance(m, nn.Conv2d):
|
| 127 |
+
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
|
| 128 |
+
m.weight.data.normal_(0, math.sqrt(2. / n))
|
| 129 |
+
elif isinstance(m, nn.BatchNorm2d):
|
| 130 |
+
m.weight.data.fill_(1)
|
| 131 |
+
m.bias.data.zero_()
|
| 132 |
+
|
| 133 |
+
def _make_layer(self, block, planes, blocks, stride=1):
|
| 134 |
+
downsample = None
|
| 135 |
+
if stride != 1 or self.inplanes != planes * block.expansion:
|
| 136 |
+
downsample = nn.Sequential(
|
| 137 |
+
nn.Conv2d(self.inplanes, planes * block.expansion,
|
| 138 |
+
kernel_size=1, stride=stride, bias=False),
|
| 139 |
+
nn.BatchNorm2d(planes * block.expansion),
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
layers = []
|
| 143 |
+
layers.append(block(self.inplanes, planes, stride, downsample))
|
| 144 |
+
self.inplanes = planes * block.expansion
|
| 145 |
+
for i in range(1, blocks):
|
| 146 |
+
layers.append(block(self.inplanes, planes))
|
| 147 |
+
|
| 148 |
+
return nn.Sequential(*layers)
|
| 149 |
+
|
| 150 |
+
def forward(self, x):
|
| 151 |
+
x = self.conv1(x)
|
| 152 |
+
x = self.bn1(x)
|
| 153 |
+
x = self.relu(x)
|
| 154 |
+
x = self.maxpool(x)
|
| 155 |
+
|
| 156 |
+
x = self.layer1(x)
|
| 157 |
+
x = self.layer2(x)
|
| 158 |
+
x = self.layer3(x)
|
| 159 |
+
x = self.layer4(x)
|
| 160 |
+
|
| 161 |
+
x = self.avgpool(x)
|
| 162 |
+
x = x.view(x.size(0), -1)
|
| 163 |
+
x = self.fc(x)
|
| 164 |
+
|
| 165 |
+
return x
|
| 166 |
+
|
| 167 |
+
def resnet50(pretrained=False, **kwargs):
|
| 168 |
+
model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)
|
| 169 |
+
if pretrained:
|
| 170 |
+
model.load_state_dict(model_zoo.load_url('https://s3.amazonaws.com/pytorch/models/resnet50-19c8e357.pth', model_dir='model_data'), strict=False)
|
| 171 |
+
|
| 172 |
+
del model.avgpool
|
| 173 |
+
del model.fc
|
| 174 |
+
return model
|
ssd-pytorch-master/nets/ssd.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
import torch.nn.init as init
|
| 5 |
+
|
| 6 |
+
from nets.mobilenetv2 import InvertedResidual, mobilenet_v2
|
| 7 |
+
from nets.vgg import vgg as add_vgg
|
| 8 |
+
from nets.resnet import resnet50
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class L2Norm(nn.Module):
|
| 12 |
+
def __init__(self,n_channels, scale):
|
| 13 |
+
super(L2Norm,self).__init__()
|
| 14 |
+
self.n_channels = n_channels
|
| 15 |
+
self.gamma = scale or None
|
| 16 |
+
self.eps = 1e-10
|
| 17 |
+
self.weight = nn.Parameter(torch.Tensor(self.n_channels))
|
| 18 |
+
self.reset_parameters()
|
| 19 |
+
|
| 20 |
+
def reset_parameters(self):
|
| 21 |
+
init.constant_(self.weight,self.gamma)
|
| 22 |
+
|
| 23 |
+
def forward(self, x):
|
| 24 |
+
norm = x.pow(2).sum(dim=1, keepdim=True).sqrt()+self.eps
|
| 25 |
+
#x /= norm
|
| 26 |
+
x = torch.div(x,norm)
|
| 27 |
+
out = self.weight.unsqueeze(0).unsqueeze(2).unsqueeze(3).expand_as(x) * x
|
| 28 |
+
return out
|
| 29 |
+
|
| 30 |
+
def add_extras(in_channels, backbone_name):
|
| 31 |
+
layers = []
|
| 32 |
+
if backbone_name == 'mobilenetv2':
|
| 33 |
+
layers += [InvertedResidual(in_channels, 512, stride=2, expand_ratio=0.2)]
|
| 34 |
+
layers += [InvertedResidual(512, 256, stride=2, expand_ratio=0.25)]
|
| 35 |
+
layers += [InvertedResidual(256, 256, stride=2, expand_ratio=0.5)]
|
| 36 |
+
layers += [InvertedResidual(256, 64, stride=2, expand_ratio=0.25)]
|
| 37 |
+
else:
|
| 38 |
+
# Block 6
|
| 39 |
+
# 19,19,1024 -> 19,19,256 -> 10,10,512
|
| 40 |
+
layers += [nn.Conv2d(in_channels, 256, kernel_size=1, stride=1)]
|
| 41 |
+
layers += [nn.Conv2d(256, 512, kernel_size=3, stride=2, padding=1)]
|
| 42 |
+
|
| 43 |
+
# Block 7
|
| 44 |
+
# 10,10,512 -> 10,10,128 -> 5,5,256
|
| 45 |
+
layers += [nn.Conv2d(512, 128, kernel_size=1, stride=1)]
|
| 46 |
+
layers += [nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1)]
|
| 47 |
+
|
| 48 |
+
# Block 8
|
| 49 |
+
# 5,5,256 -> 5,5,128 -> 3,3,256
|
| 50 |
+
layers += [nn.Conv2d(256, 128, kernel_size=1, stride=1)]
|
| 51 |
+
layers += [nn.Conv2d(128, 256, kernel_size=3, stride=1)]
|
| 52 |
+
|
| 53 |
+
# Block 9
|
| 54 |
+
# 3,3,256 -> 3,3,128 -> 1,1,256
|
| 55 |
+
layers += [nn.Conv2d(256, 128, kernel_size=1, stride=1)]
|
| 56 |
+
layers += [nn.Conv2d(128, 256, kernel_size=3, stride=1)]
|
| 57 |
+
|
| 58 |
+
return nn.ModuleList(layers)
|
| 59 |
+
|
| 60 |
+
class SSD300(nn.Module):
|
| 61 |
+
def __init__(self, num_classes, backbone_name, pretrained = False):
|
| 62 |
+
super(SSD300, self).__init__()
|
| 63 |
+
self.num_classes = num_classes
|
| 64 |
+
if backbone_name == "vgg":
|
| 65 |
+
self.vgg = add_vgg(pretrained)
|
| 66 |
+
self.extras = add_extras(1024, backbone_name)
|
| 67 |
+
self.L2Norm = L2Norm(512, 20)
|
| 68 |
+
mbox = [4, 6, 6, 6, 4, 4]
|
| 69 |
+
|
| 70 |
+
loc_layers = []
|
| 71 |
+
conf_layers = []
|
| 72 |
+
backbone_source = [21, -2]
|
| 73 |
+
#---------------------------------------------------#
|
| 74 |
+
# 在add_vgg获得的特征层里
|
| 75 |
+
# 第21层和-2层可以用来进行回归预测和分类预测。
|
| 76 |
+
# 分别是conv4-3(38,38,512)和conv7(19,19,1024)的输出
|
| 77 |
+
#---------------------------------------------------#
|
| 78 |
+
for k, v in enumerate(backbone_source):
|
| 79 |
+
loc_layers += [nn.Conv2d(self.vgg[v].out_channels, mbox[k] * 4, kernel_size = 3, padding = 1)]
|
| 80 |
+
conf_layers += [nn.Conv2d(self.vgg[v].out_channels, mbox[k] * num_classes, kernel_size = 3, padding = 1)]
|
| 81 |
+
#-------------------------------------------------------------#
|
| 82 |
+
# 在add_extras获得的特征层里
|
| 83 |
+
# 第1层、第3层、第5层、第7层可以用来进行回归预测和分类预测。
|
| 84 |
+
# shape分别为(10,10,512), (5,5,256), (3,3,256), (1,1,256)
|
| 85 |
+
#-------------------------------------------------------------#
|
| 86 |
+
for k, v in enumerate(self.extras[1::2], 2):
|
| 87 |
+
loc_layers += [nn.Conv2d(v.out_channels, mbox[k] * 4, kernel_size = 3, padding = 1)]
|
| 88 |
+
conf_layers += [nn.Conv2d(v.out_channels, mbox[k] * num_classes, kernel_size = 3, padding = 1)]
|
| 89 |
+
elif backbone_name == "mobilenetv2":
|
| 90 |
+
self.mobilenet = mobilenet_v2(pretrained).features
|
| 91 |
+
self.extras = add_extras(1280, backbone_name)
|
| 92 |
+
self.L2Norm = L2Norm(96, 20)
|
| 93 |
+
mbox = [6, 6, 6, 6, 6, 6]
|
| 94 |
+
|
| 95 |
+
loc_layers = []
|
| 96 |
+
conf_layers = []
|
| 97 |
+
backbone_source = [13, -1]
|
| 98 |
+
for k, v in enumerate(backbone_source):
|
| 99 |
+
loc_layers += [nn.Conv2d(self.mobilenet[v].out_channels, mbox[k] * 4, kernel_size = 3, padding = 1)]
|
| 100 |
+
conf_layers += [nn.Conv2d(self.mobilenet[v].out_channels, mbox[k] * num_classes, kernel_size = 3, padding = 1)]
|
| 101 |
+
for k, v in enumerate(self.extras, 2):
|
| 102 |
+
loc_layers += [nn.Conv2d(v.out_channels, mbox[k] * 4, kernel_size = 3, padding = 1)]
|
| 103 |
+
conf_layers += [nn.Conv2d(v.out_channels, mbox[k] * num_classes, kernel_size = 3, padding = 1)]
|
| 104 |
+
elif backbone_name == "resnet50":
|
| 105 |
+
self.resnet = nn.Sequential(*resnet50(pretrained).features)
|
| 106 |
+
self.extras = add_extras(1024, backbone_name)
|
| 107 |
+
self.L2Norm = L2Norm(512, 20)
|
| 108 |
+
mbox = [4, 6, 6, 6, 4, 4]
|
| 109 |
+
|
| 110 |
+
loc_layers = []
|
| 111 |
+
conf_layers = []
|
| 112 |
+
out_channels = [512, 1024]
|
| 113 |
+
#---------------------------------------------------#
|
| 114 |
+
# 在add_vgg获得的特征层里
|
| 115 |
+
# 第layer3层和layer4层可以用来进行回归预测和分类预测。
|
| 116 |
+
#---------------------------------------------------#
|
| 117 |
+
for k, v in enumerate(out_channels):
|
| 118 |
+
loc_layers += [nn.Conv2d(out_channels[k], mbox[k] * 4, kernel_size = 3, padding = 1)]
|
| 119 |
+
conf_layers += [nn.Conv2d(out_channels[k], mbox[k] * num_classes, kernel_size = 3, padding = 1)]
|
| 120 |
+
#-------------------------------------------------------------#
|
| 121 |
+
# 在add_extras获得的特征层里
|
| 122 |
+
# 第1层、第3层、第5层、第7层可以用来进行回归预测和分类预测。
|
| 123 |
+
# shape分别为(10,10,512), (5,5,256), (3,3,256), (1,1,256)
|
| 124 |
+
#-------------------------------------------------------------#
|
| 125 |
+
for k, v in enumerate(self.extras[1::2], 2):
|
| 126 |
+
loc_layers += [nn.Conv2d(v.out_channels, mbox[k] * 4, kernel_size = 3, padding = 1)]
|
| 127 |
+
conf_layers += [nn.Conv2d(v.out_channels, mbox[k] * num_classes, kernel_size = 3, padding = 1)]
|
| 128 |
+
else:
|
| 129 |
+
raise ValueError("The backbone_name is not support")
|
| 130 |
+
|
| 131 |
+
self.loc = nn.ModuleList(loc_layers)
|
| 132 |
+
self.conf = nn.ModuleList(conf_layers)
|
| 133 |
+
self.backbone_name = backbone_name
|
| 134 |
+
|
| 135 |
+
def forward(self, x):
|
| 136 |
+
#---------------------------#
|
| 137 |
+
# x是300,300,3
|
| 138 |
+
#---------------------------#
|
| 139 |
+
sources = list()
|
| 140 |
+
loc = list()
|
| 141 |
+
conf = list()
|
| 142 |
+
|
| 143 |
+
#---------------------------#
|
| 144 |
+
# 获得conv4_3的内容
|
| 145 |
+
# shape为38,38,512
|
| 146 |
+
#---------------------------#
|
| 147 |
+
if self.backbone_name == "vgg":
|
| 148 |
+
for k in range(23):
|
| 149 |
+
x = self.vgg[k](x)
|
| 150 |
+
elif self.backbone_name == "mobilenetv2":
|
| 151 |
+
for k in range(14):
|
| 152 |
+
x = self.mobilenet[k](x)
|
| 153 |
+
elif self.backbone_name == "resnet50":
|
| 154 |
+
for k in range(6):
|
| 155 |
+
x = self.resnet[k](x)
|
| 156 |
+
#---------------------------#
|
| 157 |
+
# conv4_3的内容
|
| 158 |
+
# 需要进行L2标准化
|
| 159 |
+
#---------------------------#
|
| 160 |
+
s = self.L2Norm(x)
|
| 161 |
+
sources.append(s)
|
| 162 |
+
|
| 163 |
+
#---------------------------#
|
| 164 |
+
# 获得conv7的内容
|
| 165 |
+
# shape为19,19,1024
|
| 166 |
+
#---------------------------#
|
| 167 |
+
if self.backbone_name == "vgg":
|
| 168 |
+
for k in range(23, len(self.vgg)):
|
| 169 |
+
x = self.vgg[k](x)
|
| 170 |
+
elif self.backbone_name == "mobilenetv2":
|
| 171 |
+
for k in range(14, len(self.mobilenet)):
|
| 172 |
+
x = self.mobilenet[k](x)
|
| 173 |
+
elif self.backbone_name == "resnet50":
|
| 174 |
+
for k in range(6, len(self.resnet)):
|
| 175 |
+
x = self.resnet[k](x)
|
| 176 |
+
|
| 177 |
+
sources.append(x)
|
| 178 |
+
#-------------------------------------------------------------#
|
| 179 |
+
# 在add_extras获得的特征层里
|
| 180 |
+
# 第1层、第3层、第5层、第7层可以用来进行回归预测和分类预测。
|
| 181 |
+
# shape分别为(10,10,512), (5,5,256), (3,3,256), (1,1,256)
|
| 182 |
+
#-------------------------------------------------------------#
|
| 183 |
+
for k, v in enumerate(self.extras):
|
| 184 |
+
x = F.relu(v(x), inplace=True)
|
| 185 |
+
if self.backbone_name == "vgg" or self.backbone_name == "resnet50":
|
| 186 |
+
if k % 2 == 1:
|
| 187 |
+
sources.append(x)
|
| 188 |
+
else:
|
| 189 |
+
sources.append(x)
|
| 190 |
+
|
| 191 |
+
#-------------------------------------------------------------#
|
| 192 |
+
# 为获得的6个有效特征层添加回归预测和分类预测
|
| 193 |
+
#-------------------------------------------------------------#
|
| 194 |
+
for (x, l, c) in zip(sources, self.loc, self.conf):
|
| 195 |
+
loc.append(l(x).permute(0, 2, 3, 1).contiguous())
|
| 196 |
+
conf.append(c(x).permute(0, 2, 3, 1).contiguous())
|
| 197 |
+
|
| 198 |
+
#-------------------------------------------------------------#
|
| 199 |
+
# 进行reshape方便堆叠
|
| 200 |
+
#-------------------------------------------------------------#
|
| 201 |
+
loc = torch.cat([o.view(o.size(0), -1) for o in loc], 1)
|
| 202 |
+
conf = torch.cat([o.view(o.size(0), -1) for o in conf], 1)
|
| 203 |
+
#-------------------------------------------------------------#
|
| 204 |
+
# loc会reshape到batch_size, num_anchors, 4
|
| 205 |
+
# conf会reshap到batch_size, num_anchors, self.num_classes
|
| 206 |
+
#-------------------------------------------------------------#
|
| 207 |
+
output = (
|
| 208 |
+
loc.view(loc.size(0), -1, 4),
|
| 209 |
+
conf.view(conf.size(0), -1, self.num_classes),
|
| 210 |
+
)
|
| 211 |
+
return output
|
ssd-pytorch-master/nets/ssd_training.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from functools import partial
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class MultiboxLoss(nn.Module):
|
| 9 |
+
def __init__(self, num_classes, alpha=1.0, neg_pos_ratio=3.0,
|
| 10 |
+
background_label_id=0, negatives_for_hard=100.0):
|
| 11 |
+
self.num_classes = num_classes
|
| 12 |
+
self.alpha = alpha
|
| 13 |
+
self.neg_pos_ratio = neg_pos_ratio
|
| 14 |
+
if background_label_id != 0:
|
| 15 |
+
raise Exception('Only 0 as background label id is supported')
|
| 16 |
+
self.background_label_id = background_label_id
|
| 17 |
+
self.negatives_for_hard = torch.FloatTensor([negatives_for_hard])[0]
|
| 18 |
+
|
| 19 |
+
def _l1_smooth_loss(self, y_true, y_pred):
|
| 20 |
+
abs_loss = torch.abs(y_true - y_pred)
|
| 21 |
+
sq_loss = 0.5 * (y_true - y_pred)**2
|
| 22 |
+
l1_loss = torch.where(abs_loss < 1.0, sq_loss, abs_loss - 0.5)
|
| 23 |
+
return torch.sum(l1_loss, -1)
|
| 24 |
+
|
| 25 |
+
def _softmax_loss(self, y_true, y_pred):
|
| 26 |
+
y_pred = torch.clamp(y_pred, min = 1e-7)
|
| 27 |
+
softmax_loss = -torch.sum(y_true * torch.log(y_pred),
|
| 28 |
+
axis=-1)
|
| 29 |
+
return softmax_loss
|
| 30 |
+
|
| 31 |
+
def forward(self, y_true, y_pred):
|
| 32 |
+
# --------------------------------------------- #
|
| 33 |
+
# y_true batch_size, 8732, 4 + self.num_classes + 1
|
| 34 |
+
# y_pred batch_size, 8732, 4 + self.num_classes
|
| 35 |
+
# --------------------------------------------- #
|
| 36 |
+
num_boxes = y_true.size()[1]
|
| 37 |
+
y_pred = torch.cat([y_pred[0], nn.Softmax(-1)(y_pred[1])], dim = -1)
|
| 38 |
+
|
| 39 |
+
# --------------------------------------------- #
|
| 40 |
+
# 分类的loss
|
| 41 |
+
# batch_size,8732,21 -> batch_size,8732
|
| 42 |
+
# --------------------------------------------- #
|
| 43 |
+
conf_loss = self._softmax_loss(y_true[:, :, 4:-1], y_pred[:, :, 4:])
|
| 44 |
+
|
| 45 |
+
# --------------------------------------------- #
|
| 46 |
+
# 框的位置的loss
|
| 47 |
+
# batch_size,8732,4 -> batch_size,8732
|
| 48 |
+
# --------------------------------------------- #
|
| 49 |
+
loc_loss = self._l1_smooth_loss(y_true[:, :, :4],
|
| 50 |
+
y_pred[:, :, :4])
|
| 51 |
+
|
| 52 |
+
# --------------------------------------------- #
|
| 53 |
+
# 获取所有的正标签的loss
|
| 54 |
+
# --------------------------------------------- #
|
| 55 |
+
pos_loc_loss = torch.sum(loc_loss * y_true[:, :, -1],
|
| 56 |
+
axis=1)
|
| 57 |
+
pos_conf_loss = torch.sum(conf_loss * y_true[:, :, -1],
|
| 58 |
+
axis=1)
|
| 59 |
+
|
| 60 |
+
# --------------------------------------------- #
|
| 61 |
+
# 每一张图的正样本的个数
|
| 62 |
+
# num_pos [batch_size,]
|
| 63 |
+
# --------------------------------------------- #
|
| 64 |
+
num_pos = torch.sum(y_true[:, :, -1], axis=-1)
|
| 65 |
+
|
| 66 |
+
# --------------------------------------------- #
|
| 67 |
+
# 每一张图的负样本的个数
|
| 68 |
+
# num_neg [batch_size,]
|
| 69 |
+
# --------------------------------------------- #
|
| 70 |
+
num_neg = torch.min(self.neg_pos_ratio * num_pos, num_boxes - num_pos)
|
| 71 |
+
# 找到了哪些值是大于0的
|
| 72 |
+
pos_num_neg_mask = num_neg > 0
|
| 73 |
+
# --------------------------------------------- #
|
| 74 |
+
# 如果所有的图,正样本的数量均为0
|
| 75 |
+
# 那么则默认选取100个先验框作为负样本
|
| 76 |
+
# --------------------------------------------- #
|
| 77 |
+
has_min = torch.sum(pos_num_neg_mask)
|
| 78 |
+
|
| 79 |
+
# --------------------------------------------- #
|
| 80 |
+
# 从这里往后,与视频中看到的代码有些许不同。
|
| 81 |
+
# 由于以前的负样本选取方式存在一些问题,
|
| 82 |
+
# 我对该部分代码进行重构。
|
| 83 |
+
# 求整个batch应该的负样本数量总和
|
| 84 |
+
# --------------------------------------------- #
|
| 85 |
+
num_neg_batch = torch.sum(num_neg) if has_min > 0 else self.negatives_for_hard
|
| 86 |
+
|
| 87 |
+
# --------------------------------------------- #
|
| 88 |
+
# 对预测结果进行判断,如果该先验框没有包含物体
|
| 89 |
+
# 那么它的不属于背景的预测概率过大的话
|
| 90 |
+
# 就是难分类样本
|
| 91 |
+
# --------------------------------------------- #
|
| 92 |
+
confs_start = 4 + self.background_label_id + 1
|
| 93 |
+
confs_end = confs_start + self.num_classes - 1
|
| 94 |
+
|
| 95 |
+
# --------------------------------------------- #
|
| 96 |
+
# batch_size,8732
|
| 97 |
+
# 把不是背景的概率求和,求和后的概率越大
|
| 98 |
+
# 代表越难分类。
|
| 99 |
+
# --------------------------------------------- #
|
| 100 |
+
max_confs = torch.sum(y_pred[:, :, confs_start:confs_end], dim=2)
|
| 101 |
+
|
| 102 |
+
# --------------------------------------------------- #
|
| 103 |
+
# 只有没有包含物体的先验框才得到保留
|
| 104 |
+
# 我们在整个batch里面选取最难分类的num_neg_batch个
|
| 105 |
+
# 先验框作为负样本。
|
| 106 |
+
# --------------------------------------------------- #
|
| 107 |
+
max_confs = (max_confs * (1 - y_true[:, :, -1])).view([-1])
|
| 108 |
+
|
| 109 |
+
_, indices = torch.topk(max_confs, k = int(num_neg_batch.cpu().numpy().tolist()))
|
| 110 |
+
|
| 111 |
+
neg_conf_loss = torch.gather(conf_loss.view([-1]), 0, indices)
|
| 112 |
+
|
| 113 |
+
# 进行归一化
|
| 114 |
+
num_pos = torch.where(num_pos != 0, num_pos, torch.ones_like(num_pos))
|
| 115 |
+
total_loss = torch.sum(pos_conf_loss) + torch.sum(neg_conf_loss) + torch.sum(self.alpha * pos_loc_loss)
|
| 116 |
+
total_loss = total_loss / torch.sum(num_pos)
|
| 117 |
+
return total_loss
|
| 118 |
+
|
| 119 |
+
def weights_init(net, init_type='normal', init_gain=0.02):
|
| 120 |
+
def init_func(m):
|
| 121 |
+
classname = m.__class__.__name__
|
| 122 |
+
if hasattr(m, 'weight') and classname.find('Conv') != -1:
|
| 123 |
+
if init_type == 'normal':
|
| 124 |
+
torch.nn.init.normal_(m.weight.data, 0.0, init_gain)
|
| 125 |
+
elif init_type == 'xavier':
|
| 126 |
+
torch.nn.init.xavier_normal_(m.weight.data, gain=init_gain)
|
| 127 |
+
elif init_type == 'kaiming':
|
| 128 |
+
torch.nn.init.kaiming_normal_(m.weight.data, a=0, mode='fan_in')
|
| 129 |
+
elif init_type == 'orthogonal':
|
| 130 |
+
torch.nn.init.orthogonal_(m.weight.data, gain=init_gain)
|
| 131 |
+
else:
|
| 132 |
+
raise NotImplementedError('initialization method [%s] is not implemented' % init_type)
|
| 133 |
+
elif classname.find('BatchNorm2d') != -1:
|
| 134 |
+
torch.nn.init.normal_(m.weight.data, 1.0, 0.02)
|
| 135 |
+
torch.nn.init.constant_(m.bias.data, 0.0)
|
| 136 |
+
print('initialize network with %s type' % init_type)
|
| 137 |
+
net.apply(init_func)
|
| 138 |
+
|
| 139 |
+
def get_lr_scheduler(lr_decay_type, lr, min_lr, total_iters, warmup_iters_ratio = 0.05, warmup_lr_ratio = 0.1, no_aug_iter_ratio = 0.05, step_num = 10):
|
| 140 |
+
def yolox_warm_cos_lr(lr, min_lr, total_iters, warmup_total_iters, warmup_lr_start, no_aug_iter, iters):
|
| 141 |
+
if iters <= warmup_total_iters:
|
| 142 |
+
# lr = (lr - warmup_lr_start) * iters / float(warmup_total_iters) + warmup_lr_start
|
| 143 |
+
lr = (lr - warmup_lr_start) * pow(iters / float(warmup_total_iters), 2) + warmup_lr_start
|
| 144 |
+
elif iters >= total_iters - no_aug_iter:
|
| 145 |
+
lr = min_lr
|
| 146 |
+
else:
|
| 147 |
+
lr = min_lr + 0.5 * (lr - min_lr) * (
|
| 148 |
+
1.0 + math.cos(math.pi* (iters - warmup_total_iters) / (total_iters - warmup_total_iters - no_aug_iter))
|
| 149 |
+
)
|
| 150 |
+
return lr
|
| 151 |
+
|
| 152 |
+
def step_lr(lr, decay_rate, step_size, iters):
|
| 153 |
+
if step_size < 1:
|
| 154 |
+
raise ValueError("step_size must above 1.")
|
| 155 |
+
n = iters // step_size
|
| 156 |
+
out_lr = lr * decay_rate ** n
|
| 157 |
+
return out_lr
|
| 158 |
+
|
| 159 |
+
if lr_decay_type == "cos":
|
| 160 |
+
warmup_total_iters = min(max(warmup_iters_ratio * total_iters, 1), 3)
|
| 161 |
+
warmup_lr_start = max(warmup_lr_ratio * lr, 1e-6)
|
| 162 |
+
no_aug_iter = min(max(no_aug_iter_ratio * total_iters, 1), 15)
|
| 163 |
+
func = partial(yolox_warm_cos_lr ,lr, min_lr, total_iters, warmup_total_iters, warmup_lr_start, no_aug_iter)
|
| 164 |
+
else:
|
| 165 |
+
decay_rate = (min_lr / lr) ** (1 / (step_num - 1))
|
| 166 |
+
step_size = total_iters / step_num
|
| 167 |
+
func = partial(step_lr, lr, decay_rate, step_size)
|
| 168 |
+
|
| 169 |
+
return func
|
| 170 |
+
|
| 171 |
+
def set_optimizer_lr(optimizer, lr_scheduler_func, epoch):
|
| 172 |
+
lr = lr_scheduler_func(epoch)
|
| 173 |
+
for param_group in optimizer.param_groups:
|
| 174 |
+
param_group['lr'] = lr
|
ssd-pytorch-master/nets/vgg.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn as nn
|
| 2 |
+
from torch.hub import load_state_dict_from_url
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
'''
|
| 6 |
+
该代码用于获得VGG主干特征提取网络的输出。
|
| 7 |
+
输入变量i代表的是输入图片的通道数,通常为3。
|
| 8 |
+
|
| 9 |
+
300, 300, 3 -> 300, 300, 64 -> 300, 300, 64 -> 150, 150, 64 -> 150, 150, 128 -> 150, 150, 128 -> 75, 75, 128 ->
|
| 10 |
+
75, 75, 256 -> 75, 75, 256 -> 75, 75, 256 -> 38, 38, 256 -> 38, 38, 512 -> 38, 38, 512 -> 38, 38, 512 -> 19, 19, 512 ->
|
| 11 |
+
19, 19, 512 -> 19, 19, 512 -> 19, 19, 512 -> 19, 19, 512 -> 19, 19, 1024 -> 19, 19, 1024
|
| 12 |
+
|
| 13 |
+
38, 38, 512的序号是22
|
| 14 |
+
19, 19, 1024的序号是34
|
| 15 |
+
'''
|
| 16 |
+
base = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M',
|
| 17 |
+
512, 512, 512]
|
| 18 |
+
|
| 19 |
+
def vgg(pretrained = False):
|
| 20 |
+
layers = []
|
| 21 |
+
in_channels = 3
|
| 22 |
+
for v in base:
|
| 23 |
+
if v == 'M':
|
| 24 |
+
layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
|
| 25 |
+
elif v == 'C':
|
| 26 |
+
layers += [nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)]
|
| 27 |
+
else:
|
| 28 |
+
conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
|
| 29 |
+
layers += [conv2d, nn.ReLU(inplace=True)]
|
| 30 |
+
in_channels = v
|
| 31 |
+
# 19, 19, 512 -> 19, 19, 512
|
| 32 |
+
pool5 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
|
| 33 |
+
# 19, 19, 512 -> 19, 19, 1024
|
| 34 |
+
conv6 = nn.Conv2d(512, 1024, kernel_size=3, padding=6, dilation=6)
|
| 35 |
+
# 19, 19, 1024 -> 19, 19, 1024
|
| 36 |
+
conv7 = nn.Conv2d(1024, 1024, kernel_size=1)
|
| 37 |
+
layers += [pool5, conv6,
|
| 38 |
+
nn.ReLU(inplace=True), conv7, nn.ReLU(inplace=True)]
|
| 39 |
+
|
| 40 |
+
model = nn.ModuleList(layers)
|
| 41 |
+
if pretrained:
|
| 42 |
+
state_dict = load_state_dict_from_url("https://download.pytorch.org/models/vgg16-397923af.pth", model_dir="./model_data")
|
| 43 |
+
state_dict = {k.replace('features.', '') : v for k, v in state_dict.items()}
|
| 44 |
+
model.load_state_dict(state_dict, strict = False)
|
| 45 |
+
return model
|
| 46 |
+
|
| 47 |
+
if __name__ == "__main__":
|
| 48 |
+
net = vgg()
|
| 49 |
+
for i, layer in enumerate(net):
|
| 50 |
+
print(i, layer)
|
ssd-pytorch-master/predict.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#-----------------------------------------------------------------------#
|
| 2 |
+
# predict.py将单张图片预测、摄像头检测、FPS测试和目录遍历检测等功能
|
| 3 |
+
# 整合到了一个py文件中,通过指定mode进行模式的修改。
|
| 4 |
+
#-----------------------------------------------------------------------#
|
| 5 |
+
import time
|
| 6 |
+
|
| 7 |
+
import cv2
|
| 8 |
+
import numpy as np
|
| 9 |
+
from PIL import Image
|
| 10 |
+
|
| 11 |
+
from ssd import SSD
|
| 12 |
+
|
| 13 |
+
if __name__ == "__main__":
|
| 14 |
+
ssd = SSD()
|
| 15 |
+
#----------------------------------------------------------------------------------------------------------#
|
| 16 |
+
# mode用于指定测试的模式:
|
| 17 |
+
# 'predict' 表示单张图片预测,如果想对预测过程进行修改,如保存图片,截取对象等,可以先看下方详细的注释
|
| 18 |
+
# 'video' 表示视频检测,可调用摄像头或者视频进行检测,详情查看下方注释。
|
| 19 |
+
# 'fps' 表示测试fps,使用的图片是img里面的street.jpg,详情查看下方注释。
|
| 20 |
+
# 'dir_predict' 表示遍历文件夹进行检测并保存。默认遍历img文件夹,保存img_out文件夹,详情查看下方注释。
|
| 21 |
+
# 'export_onnx' 表示将模型导出为onnx,需要pytorch1.7.1以上。
|
| 22 |
+
#----------------------------------------------------------------------------------------------------------#
|
| 23 |
+
mode = "predict"
|
| 24 |
+
#-------------------------------------------------------------------------#
|
| 25 |
+
# crop 指定了是否在单张图片预测后对目标进行截取
|
| 26 |
+
# count 指定了是否进行目标的计数
|
| 27 |
+
# crop、count仅在mode='predict'时有效
|
| 28 |
+
#-------------------------------------------------------------------------#
|
| 29 |
+
crop = False
|
| 30 |
+
count = False
|
| 31 |
+
#----------------------------------------------------------------------------------------------------------#
|
| 32 |
+
# video_path 用于指定视频的路径,当video_path=0时表示检测摄像头
|
| 33 |
+
# 想要检测视频,则设置如video_path = "xxx.mp4"即可,代表读取出根目录下的xxx.mp4文件。
|
| 34 |
+
# video_save_path 表示视频保存的路径,当video_save_path=""时表示不保存
|
| 35 |
+
# 想要保存视频,则设置如video_save_path = "yyy.mp4"即可,代表保存为根目录下的yyy.mp4文件。
|
| 36 |
+
# video_fps 用于保存的视频的fps
|
| 37 |
+
#
|
| 38 |
+
# video_path、video_save_path和video_fps仅在mode='video'时有效
|
| 39 |
+
# 保存视频时需要ctrl+c退出或者运行到最后一帧才会完成完整的保存步骤。
|
| 40 |
+
#----------------------------------------------------------------------------------------------------------#
|
| 41 |
+
video_path = 0
|
| 42 |
+
video_save_path = ""
|
| 43 |
+
video_fps = 25.0
|
| 44 |
+
#----------------------------------------------------------------------------------------------------------#
|
| 45 |
+
# test_interval 用于指定测量fps的时候,图片检测的次数。理论上test_interval越大,fps越准确。
|
| 46 |
+
# fps_image_path 用于指定测试的fps图片
|
| 47 |
+
#
|
| 48 |
+
# test_interval和fps_image_path仅在mode='fps'有效
|
| 49 |
+
#----------------------------------------------------------------------------------------------------------#
|
| 50 |
+
test_interval = 100
|
| 51 |
+
fps_image_path = "img/street.jpg"
|
| 52 |
+
#-------------------------------------------------------------------------#
|
| 53 |
+
# dir_origin_path 指定了用于检测的图片的文件夹路径
|
| 54 |
+
# dir_save_path 指定了检测完图片的保存路径
|
| 55 |
+
#
|
| 56 |
+
# dir_origin_path和dir_save_path仅在mode='dir_predict'时有效
|
| 57 |
+
#-------------------------------------------------------------------------#
|
| 58 |
+
dir_origin_path = "img/"
|
| 59 |
+
dir_save_path = "img_out/"
|
| 60 |
+
#-------------------------------------------------------------------------#
|
| 61 |
+
# simplify 使用Simplify onnx
|
| 62 |
+
# onnx_save_path 指定了onnx的保存路径
|
| 63 |
+
#-------------------------------------------------------------------------#
|
| 64 |
+
simplify = True
|
| 65 |
+
onnx_save_path = "model_data/models.onnx"
|
| 66 |
+
|
| 67 |
+
if mode == "predict":
|
| 68 |
+
'''
|
| 69 |
+
1、如果想要进行检测完的图片的保存,利用r_image.save("img.jpg")即可保存,直接在predict.py里进行修改即可。
|
| 70 |
+
2、如果想要获得预测框的坐标,可以进入ssd.detect_image函数,在绘图部分读取top,left,bottom,right这四个值。
|
| 71 |
+
3、如果想要利用预测框截取下目标,可以进入ssd.detect_image函数,在绘图部分利用获取到的top,left,bottom,right这四个值
|
| 72 |
+
在原图上利用矩阵的方式进行截取。
|
| 73 |
+
4、如果想要在预测图上写额外的字,比如检测到的特定目标的数量,可以进入ssd.detect_image函数,在绘图部分对predicted_class进行判断,
|
| 74 |
+
比如判断if predicted_class == 'car': 即可判断当前目标是否为车,然后记录数量���可。利用draw.text即可写字。
|
| 75 |
+
'''
|
| 76 |
+
while True:
|
| 77 |
+
img = input('Input image filename:')
|
| 78 |
+
try:
|
| 79 |
+
image = Image.open(img)
|
| 80 |
+
except:
|
| 81 |
+
print('Open Error! Try again!')
|
| 82 |
+
continue
|
| 83 |
+
else:
|
| 84 |
+
r_image = ssd.detect_image(image, crop = crop, count=count)
|
| 85 |
+
r_image.show()
|
| 86 |
+
|
| 87 |
+
elif mode == "video":
|
| 88 |
+
capture = cv2.VideoCapture(video_path)
|
| 89 |
+
if video_save_path!="":
|
| 90 |
+
fourcc = cv2.VideoWriter_fourcc(*'XVID')
|
| 91 |
+
size = (int(capture.get(cv2.CAP_PROP_FRAME_WIDTH)), int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT)))
|
| 92 |
+
out = cv2.VideoWriter(video_save_path, fourcc, video_fps, size)
|
| 93 |
+
|
| 94 |
+
ref, frame = capture.read()
|
| 95 |
+
if not ref:
|
| 96 |
+
raise ValueError("未能正确读取摄像头(视频),请注意是否正确安装摄像头(是否正确填写视频路径)。")
|
| 97 |
+
|
| 98 |
+
fps = 0.0
|
| 99 |
+
while(True):
|
| 100 |
+
t1 = time.time()
|
| 101 |
+
# 读取某一帧
|
| 102 |
+
ref, frame = capture.read()
|
| 103 |
+
if not ref:
|
| 104 |
+
break
|
| 105 |
+
# 格式转变,BGRtoRGB
|
| 106 |
+
frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
|
| 107 |
+
# 转变成Image
|
| 108 |
+
frame = Image.fromarray(np.uint8(frame))
|
| 109 |
+
# 进行检测
|
| 110 |
+
frame = np.array(ssd.detect_image(frame))
|
| 111 |
+
# RGBtoBGR满足opencv显示格式
|
| 112 |
+
frame = cv2.cvtColor(frame,cv2.COLOR_RGB2BGR)
|
| 113 |
+
|
| 114 |
+
fps = ( fps + (1./(time.time()-t1)) ) / 2
|
| 115 |
+
print("fps= %.2f"%(fps))
|
| 116 |
+
frame = cv2.putText(frame, "fps= %.2f"%(fps), (0, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
| 117 |
+
|
| 118 |
+
cv2.imshow("video",frame)
|
| 119 |
+
c= cv2.waitKey(1) & 0xff
|
| 120 |
+
if video_save_path!="":
|
| 121 |
+
out.write(frame)
|
| 122 |
+
|
| 123 |
+
if c==27:
|
| 124 |
+
capture.release()
|
| 125 |
+
break
|
| 126 |
+
|
| 127 |
+
print("Video Detection Done!")
|
| 128 |
+
capture.release()
|
| 129 |
+
if video_save_path!="":
|
| 130 |
+
print("Save processed video to the path :" + video_save_path)
|
| 131 |
+
out.release()
|
| 132 |
+
cv2.destroyAllWindows()
|
| 133 |
+
|
| 134 |
+
elif mode == "fps":
|
| 135 |
+
img = Image.open(fps_image_path)
|
| 136 |
+
tact_time = ssd.get_FPS(img, test_interval)
|
| 137 |
+
print(str(tact_time) + ' seconds, ' + str(1/tact_time) + 'FPS, @batch_size 1')
|
| 138 |
+
|
| 139 |
+
elif mode == "dir_predict":
|
| 140 |
+
import os
|
| 141 |
+
|
| 142 |
+
from tqdm import tqdm
|
| 143 |
+
|
| 144 |
+
img_names = os.listdir(dir_origin_path)
|
| 145 |
+
for img_name in tqdm(img_names):
|
| 146 |
+
if img_name.lower().endswith(('.bmp', '.dib', '.png', '.jpg', '.jpeg', '.pbm', '.pgm', '.ppm', '.tif', '.tiff')):
|
| 147 |
+
image_path = os.path.join(dir_origin_path, img_name)
|
| 148 |
+
image = Image.open(image_path)
|
| 149 |
+
r_image = ssd.detect_image(image)
|
| 150 |
+
if not os.path.exists(dir_save_path):
|
| 151 |
+
os.makedirs(dir_save_path)
|
| 152 |
+
r_image.save(os.path.join(dir_save_path, img_name.replace(".jpg", ".png")), quality=95, subsampling=0)
|
| 153 |
+
|
| 154 |
+
elif mode == "export_onnx":
|
| 155 |
+
ssd.convert_to_onnx(simplify, onnx_save_path)
|
| 156 |
+
|
| 157 |
+
else:
|
| 158 |
+
raise AssertionError("Please specify the correct mode: 'predict', 'video', 'fps' or 'dir_predict'.")
|
ssd-pytorch-master/requirements.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
torchvision
|
| 3 |
+
tensorboard
|
| 4 |
+
scipy==1.2.1
|
| 5 |
+
numpy==1.17.0
|
| 6 |
+
matplotlib==3.1.2
|
| 7 |
+
opencv_python==4.1.2.30
|
| 8 |
+
tqdm==4.60.0
|
| 9 |
+
Pillow==8.2.0
|
| 10 |
+
h5py==2.10.0
|
ssd-pytorch-master/ssd.py
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import colorsys
|
| 2 |
+
import os
|
| 3 |
+
import time
|
| 4 |
+
import warnings
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
+
import torch.backends.cudnn as cudnn
|
| 9 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 10 |
+
|
| 11 |
+
from nets.ssd import SSD300
|
| 12 |
+
from utils.anchors import get_anchors
|
| 13 |
+
from utils.utils import (cvtColor, get_classes, preprocess_input, resize_image,
|
| 14 |
+
show_config)
|
| 15 |
+
from utils.utils_bbox import BBoxUtility
|
| 16 |
+
|
| 17 |
+
warnings.filterwarnings("ignore")
|
| 18 |
+
|
| 19 |
+
#--------------------------------------------#
|
| 20 |
+
# 使用自己训练好的模型预测需要修改3个参数
|
| 21 |
+
# model_path、backbone和classes_path都需要修改!
|
| 22 |
+
# 如果出现shape不匹配
|
| 23 |
+
# 一定要注意训练时的config里面的num_classes、
|
| 24 |
+
# model_path和classes_path参数的修改
|
| 25 |
+
#--------------------------------------------#
|
| 26 |
+
class SSD(object):
|
| 27 |
+
_defaults = {
|
| 28 |
+
#--------------------------------------------------------------------------#
|
| 29 |
+
# 使用自己训练好的模型进行预测一定要修改model_path和classes_path!
|
| 30 |
+
# model_path指向logs文件夹下的权值文件,classes_path指向model_data下的txt
|
| 31 |
+
#
|
| 32 |
+
# 训练好后logs文件夹下存在多个权值文件,选择验证集损失较低的即可。
|
| 33 |
+
# 验证集损失较低不代表mAP较高,仅代表该权值在验证集上泛化性能较好。
|
| 34 |
+
# 如果出现shape不匹配,同时要注意训练时的model_path和classes_path参数的修改
|
| 35 |
+
#--------------------------------------------------------------------------#
|
| 36 |
+
"model_path" : 'model_data/ssd_weights.pth',
|
| 37 |
+
"classes_path" : 'model_data/voc_classes.txt',
|
| 38 |
+
#---------------------------------------------------------------------#
|
| 39 |
+
# 用于预测的图像大小,和train时使用同一个即可
|
| 40 |
+
#---------------------------------------------------------------------#
|
| 41 |
+
"input_shape" : [300, 300],
|
| 42 |
+
#-------------------------------#
|
| 43 |
+
# 主干网络的选择
|
| 44 |
+
# vgg或者mobilenetv2或者resnet50
|
| 45 |
+
#-------------------------------#
|
| 46 |
+
"backbone" : "vgg",
|
| 47 |
+
#---------------------------------------------------------------------#
|
| 48 |
+
# 只有得分大于置信度的预测框会被保留下来
|
| 49 |
+
#---------------------------------------------------------------------#
|
| 50 |
+
"confidence" : 0.5,
|
| 51 |
+
#---------------------------------------------------------------------#
|
| 52 |
+
# 非极大抑制所用到的nms_iou大小
|
| 53 |
+
#---------------------------------------------------------------------#
|
| 54 |
+
"nms_iou" : 0.45,
|
| 55 |
+
#---------------------------------------------------------------------#
|
| 56 |
+
# 用于指定先验框的大小
|
| 57 |
+
#---------------------------------------------------------------------#
|
| 58 |
+
'anchors_size' : [30, 60, 111, 162, 213, 264, 315],
|
| 59 |
+
#---------------------------------------------------------------------#
|
| 60 |
+
# 该变量用于控制是否使用letterbox_image对输入图像进行不失真的resize,
|
| 61 |
+
# 在多次测试后,发现关闭letterbox_image直接resize的效果更好
|
| 62 |
+
#---------------------------------------------------------------------#
|
| 63 |
+
"letterbox_image" : False,
|
| 64 |
+
#-------------------------------#
|
| 65 |
+
# 是否使用Cuda
|
| 66 |
+
# 没有GPU可以设置成False
|
| 67 |
+
#-------------------------------#
|
| 68 |
+
"cuda" : True,
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
@classmethod
|
| 72 |
+
def get_defaults(cls, n):
|
| 73 |
+
if n in cls._defaults:
|
| 74 |
+
return cls._defaults[n]
|
| 75 |
+
else:
|
| 76 |
+
return "Unrecognized attribute name '" + n + "'"
|
| 77 |
+
|
| 78 |
+
#---------------------------------------------------#
|
| 79 |
+
# 初始化ssd
|
| 80 |
+
#---------------------------------------------------#
|
| 81 |
+
def __init__(self, **kwargs):
|
| 82 |
+
self.__dict__.update(self._defaults)
|
| 83 |
+
for name, value in kwargs.items():
|
| 84 |
+
setattr(self, name, value)
|
| 85 |
+
#---------------------------------------------------#
|
| 86 |
+
# 计算总的类的数量
|
| 87 |
+
#---------------------------------------------------#
|
| 88 |
+
self.class_names, self.num_classes = get_classes(self.classes_path)
|
| 89 |
+
self.anchors = torch.from_numpy(get_anchors(self.input_shape, self.anchors_size, self.backbone)).type(torch.FloatTensor)
|
| 90 |
+
if self.cuda:
|
| 91 |
+
self.anchors = self.anchors.cuda()
|
| 92 |
+
self.num_classes = self.num_classes + 1
|
| 93 |
+
|
| 94 |
+
#---------------------------------------------------#
|
| 95 |
+
# 画框设置不同的颜色
|
| 96 |
+
#---------------------------------------------------#
|
| 97 |
+
hsv_tuples = [(x / self.num_classes, 1., 1.) for x in range(self.num_classes)]
|
| 98 |
+
self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
|
| 99 |
+
self.colors = list(map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), self.colors))
|
| 100 |
+
|
| 101 |
+
self.bbox_util = BBoxUtility(self.num_classes)
|
| 102 |
+
self.generate()
|
| 103 |
+
|
| 104 |
+
show_config(**self._defaults)
|
| 105 |
+
|
| 106 |
+
#---------------------------------------------------#
|
| 107 |
+
# 载入模型
|
| 108 |
+
#---------------------------------------------------#
|
| 109 |
+
def generate(self, onnx=False):
|
| 110 |
+
#-------------------------------#
|
| 111 |
+
# 载入模型与权值
|
| 112 |
+
#-------------------------------#
|
| 113 |
+
self.net = SSD300(self.num_classes, self.backbone)
|
| 114 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 115 |
+
self.net.load_state_dict(torch.load(self.model_path, map_location=device))
|
| 116 |
+
self.net = self.net.eval()
|
| 117 |
+
print('{} model, anchors, and classes loaded.'.format(self.model_path))
|
| 118 |
+
if not onnx:
|
| 119 |
+
if self.cuda:
|
| 120 |
+
self.net = torch.nn.DataParallel(self.net)
|
| 121 |
+
self.net = self.net.cuda()
|
| 122 |
+
|
| 123 |
+
#---------------------------------------------------#
|
| 124 |
+
# 检测图片
|
| 125 |
+
#---------------------------------------------------#
|
| 126 |
+
def detect_image(self, image, crop = False, count = False):
|
| 127 |
+
#---------------------------------------------------#
|
| 128 |
+
# 计算输入图片的高和宽
|
| 129 |
+
#---------------------------------------------------#
|
| 130 |
+
image_shape = np.array(np.shape(image)[0:2])
|
| 131 |
+
#---------------------------------------------------------#
|
| 132 |
+
# 在这里将图像转换成RGB图像,防止灰度图在预测时报错。
|
| 133 |
+
# 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
|
| 134 |
+
#---------------------------------------------------------#
|
| 135 |
+
image = cvtColor(image)
|
| 136 |
+
#---------------------------------------------------------#
|
| 137 |
+
# 给图像增加灰条,实现不失真的resize
|
| 138 |
+
# 也可以直接resize进行识别
|
| 139 |
+
#---------------------------------------------------------#
|
| 140 |
+
image_data = resize_image(image, (self.input_shape[1], self.input_shape[0]), self.letterbox_image)
|
| 141 |
+
#---------------------------------------------------------#
|
| 142 |
+
# 添加上batch_size维度,图片预处理,归一化。
|
| 143 |
+
#---------------------------------------------------------#
|
| 144 |
+
image_data = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)
|
| 145 |
+
|
| 146 |
+
with torch.no_grad():
|
| 147 |
+
#---------------------------------------------------#
|
| 148 |
+
# 转化成torch的形式
|
| 149 |
+
#---------------------------------------------------#
|
| 150 |
+
images = torch.from_numpy(image_data).type(torch.FloatTensor)
|
| 151 |
+
if self.cuda:
|
| 152 |
+
images = images.cuda()
|
| 153 |
+
#---------------------------------------------------------#
|
| 154 |
+
# 将图像输入网络当中进行预测!
|
| 155 |
+
#---------------------------------------------------------#
|
| 156 |
+
outputs = self.net(images)
|
| 157 |
+
#-----------------------------------------------------------#
|
| 158 |
+
# 将预测结果进行解码
|
| 159 |
+
#-----------------------------------------------------------#
|
| 160 |
+
results = self.bbox_util.decode_box(outputs, self.anchors, image_shape, self.input_shape, self.letterbox_image,
|
| 161 |
+
nms_iou = self.nms_iou, confidence = self.confidence)
|
| 162 |
+
#--------------------------------------#
|
| 163 |
+
# 如果没有检测到物体,则返回原图
|
| 164 |
+
#--------------------------------------#
|
| 165 |
+
if len(results[0]) <= 0:
|
| 166 |
+
return image
|
| 167 |
+
|
| 168 |
+
top_label = np.array(results[0][:, 4], dtype = 'int32')
|
| 169 |
+
top_conf = results[0][:, 5]
|
| 170 |
+
top_boxes = results[0][:, :4]
|
| 171 |
+
#---------------------------------------------------------#
|
| 172 |
+
# 设置字体与边框厚度
|
| 173 |
+
#---------------------------------------------------------#
|
| 174 |
+
font = ImageFont.truetype(font='model_data/simhei.ttf', size=np.floor(3e-2 * np.shape(image)[1] + 0.5).astype('int32'))
|
| 175 |
+
thickness = max((np.shape(image)[0] + np.shape(image)[1]) // self.input_shape[0], 1)
|
| 176 |
+
#---------------------------------------------------------#
|
| 177 |
+
# 计数
|
| 178 |
+
#---------------------------------------------------------#
|
| 179 |
+
if count:
|
| 180 |
+
print("top_label:", top_label)
|
| 181 |
+
classes_nums = np.zeros([self.num_classes])
|
| 182 |
+
for i in range(self.num_classes):
|
| 183 |
+
num = np.sum(top_label == i)
|
| 184 |
+
if num > 0:
|
| 185 |
+
print(self.class_names[i], " : ", num)
|
| 186 |
+
classes_nums[i] = num
|
| 187 |
+
print("classes_nums:", classes_nums)
|
| 188 |
+
#---------------------------------------------------------#
|
| 189 |
+
# 是否进行目标的裁剪
|
| 190 |
+
#---------------------------------------------------------#
|
| 191 |
+
if crop:
|
| 192 |
+
for i, c in list(enumerate(top_boxes)):
|
| 193 |
+
top, left, bottom, right = top_boxes[i]
|
| 194 |
+
top = max(0, np.floor(top).astype('int32'))
|
| 195 |
+
left = max(0, np.floor(left).astype('int32'))
|
| 196 |
+
bottom = min(image.size[1], np.floor(bottom).astype('int32'))
|
| 197 |
+
right = min(image.size[0], np.floor(right).astype('int32'))
|
| 198 |
+
|
| 199 |
+
dir_save_path = "img_crop"
|
| 200 |
+
if not os.path.exists(dir_save_path):
|
| 201 |
+
os.makedirs(dir_save_path)
|
| 202 |
+
crop_image = image.crop([left, top, right, bottom])
|
| 203 |
+
crop_image.save(os.path.join(dir_save_path, "crop_" + str(i) + ".png"), quality=95, subsampling=0)
|
| 204 |
+
print("save crop_" + str(i) + ".png to " + dir_save_path)
|
| 205 |
+
#---------------------------------------------------------#
|
| 206 |
+
# 图像绘制
|
| 207 |
+
#---------------------------------------------------------#
|
| 208 |
+
for i, c in list(enumerate(top_label)):
|
| 209 |
+
predicted_class = self.class_names[int(c)]
|
| 210 |
+
box = top_boxes[i]
|
| 211 |
+
score = top_conf[i]
|
| 212 |
+
|
| 213 |
+
top, left, bottom, right = box
|
| 214 |
+
|
| 215 |
+
top = max(0, np.floor(top).astype('int32'))
|
| 216 |
+
left = max(0, np.floor(left).astype('int32'))
|
| 217 |
+
bottom = min(image.size[1], np.floor(bottom).astype('int32'))
|
| 218 |
+
right = min(image.size[0], np.floor(right).astype('int32'))
|
| 219 |
+
|
| 220 |
+
label = '{} {:.2f}'.format(predicted_class, score)
|
| 221 |
+
draw = ImageDraw.Draw(image)
|
| 222 |
+
label_size = draw.textsize(label, font)
|
| 223 |
+
label = label.encode('utf-8')
|
| 224 |
+
print(label, top, left, bottom, right)
|
| 225 |
+
|
| 226 |
+
if top - label_size[1] >= 0:
|
| 227 |
+
text_origin = np.array([left, top - label_size[1]])
|
| 228 |
+
else:
|
| 229 |
+
text_origin = np.array([left, top + 1])
|
| 230 |
+
|
| 231 |
+
for i in range(thickness):
|
| 232 |
+
draw.rectangle([left + i, top + i, right - i, bottom - i], outline=self.colors[c])
|
| 233 |
+
draw.rectangle([tuple(text_origin), tuple(text_origin + label_size)], fill=self.colors[c])
|
| 234 |
+
draw.text(text_origin, str(label,'UTF-8'), fill=(0, 0, 0), font=font)
|
| 235 |
+
del draw
|
| 236 |
+
|
| 237 |
+
return image
|
| 238 |
+
|
| 239 |
+
def get_FPS(self, image, test_interval):
|
| 240 |
+
#---------------------------------------------------#
|
| 241 |
+
# 计算输入图片的高和宽
|
| 242 |
+
#---------------------------------------------------#
|
| 243 |
+
image_shape = np.array(np.shape(image)[0:2])
|
| 244 |
+
#---------------------------------------------------------#
|
| 245 |
+
# 在这里将图像转换成RGB图像,防止灰度图在预测时报错。
|
| 246 |
+
# 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
|
| 247 |
+
#---------------------------------------------------------#
|
| 248 |
+
image = cvtColor(image)
|
| 249 |
+
#---------------------------------------------------------#
|
| 250 |
+
# 给图像增加灰条,实现不失真的resize
|
| 251 |
+
# 也可以直接resize进行识别
|
| 252 |
+
#---------------------------------------------------------#
|
| 253 |
+
image_data = resize_image(image, (self.input_shape[1], self.input_shape[0]), self.letterbox_image)
|
| 254 |
+
#---------------------------------------------------------#
|
| 255 |
+
# 添加上batch_size维度,图片预处理,归一化。
|
| 256 |
+
#---------------------------------------------------------#
|
| 257 |
+
image_data = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)
|
| 258 |
+
|
| 259 |
+
with torch.no_grad():
|
| 260 |
+
#---------------------------------------------------#
|
| 261 |
+
# 转化成torch的形式
|
| 262 |
+
#---------------------------------------------------#
|
| 263 |
+
images = torch.from_numpy(image_data).type(torch.FloatTensor)
|
| 264 |
+
if self.cuda:
|
| 265 |
+
images = images.cuda()
|
| 266 |
+
#---------------------------------------------------------#
|
| 267 |
+
# 将图像输入网络当中进行预测!
|
| 268 |
+
#---------------------------------------------------------#
|
| 269 |
+
outputs = self.net(images)
|
| 270 |
+
#-----------------------------------------------------------#
|
| 271 |
+
# 将预测结果进行解码
|
| 272 |
+
#-----------------------------------------------------------#
|
| 273 |
+
results = self.bbox_util.decode_box(outputs, self.anchors, image_shape, self.input_shape, self.letterbox_image,
|
| 274 |
+
nms_iou = self.nms_iou, confidence = self.confidence)
|
| 275 |
+
|
| 276 |
+
t1 = time.time()
|
| 277 |
+
for _ in range(test_interval):
|
| 278 |
+
with torch.no_grad():
|
| 279 |
+
#---------------------------------------------------------#
|
| 280 |
+
# 将图像输入网络当中进行预测!
|
| 281 |
+
#---------------------------------------------------------#
|
| 282 |
+
outputs = self.net(images)
|
| 283 |
+
#-----------------------------------------------------------#
|
| 284 |
+
# 将预测结果进行解码
|
| 285 |
+
#-----------------------------------------------------------#
|
| 286 |
+
results = self.bbox_util.decode_box(outputs, self.anchors, image_shape, self.input_shape, self.letterbox_image,
|
| 287 |
+
nms_iou = self.nms_iou, confidence = self.confidence)
|
| 288 |
+
|
| 289 |
+
t2 = time.time()
|
| 290 |
+
tact_time = (t2 - t1) / test_interval
|
| 291 |
+
return tact_time
|
| 292 |
+
|
| 293 |
+
def convert_to_onnx(self, simplify, model_path):
|
| 294 |
+
import onnx
|
| 295 |
+
self.generate(onnx=True)
|
| 296 |
+
|
| 297 |
+
im = torch.zeros(1, 3, *self.input_shape).to('cpu') # image size(1, 3, 512, 512) BCHW
|
| 298 |
+
input_layer_names = ["images"]
|
| 299 |
+
output_layer_names = ["output"]
|
| 300 |
+
|
| 301 |
+
# Export the model
|
| 302 |
+
print(f'Starting export with onnx {onnx.__version__}.')
|
| 303 |
+
torch.onnx.export(self.net,
|
| 304 |
+
im,
|
| 305 |
+
f = model_path,
|
| 306 |
+
verbose = False,
|
| 307 |
+
opset_version = 12,
|
| 308 |
+
training = torch.onnx.TrainingMode.EVAL,
|
| 309 |
+
do_constant_folding = True,
|
| 310 |
+
input_names = input_layer_names,
|
| 311 |
+
output_names = output_layer_names,
|
| 312 |
+
dynamic_axes = None)
|
| 313 |
+
|
| 314 |
+
# Checks
|
| 315 |
+
model_onnx = onnx.load(model_path) # load onnx model
|
| 316 |
+
onnx.checker.check_model(model_onnx) # check onnx model
|
| 317 |
+
|
| 318 |
+
# Simplify onnx
|
| 319 |
+
if simplify:
|
| 320 |
+
import onnxsim
|
| 321 |
+
print(f'Simplifying with onnx-simplifier {onnxsim.__version__}.')
|
| 322 |
+
model_onnx, check = onnxsim.simplify(
|
| 323 |
+
model_onnx,
|
| 324 |
+
dynamic_input_shape=False,
|
| 325 |
+
input_shapes=None)
|
| 326 |
+
assert check, 'assert check failed'
|
| 327 |
+
onnx.save(model_onnx, model_path)
|
| 328 |
+
|
| 329 |
+
print('Onnx model save as {}'.format(model_path))
|
| 330 |
+
|
| 331 |
+
def get_map_txt(self, image_id, image, class_names, map_out_path):
|
| 332 |
+
f = open(os.path.join(map_out_path, "detection-results/"+image_id+".txt"),"w")
|
| 333 |
+
#---------------------------------------------------#
|
| 334 |
+
# 计算输入图片的高和宽
|
| 335 |
+
#---------------------------------------------------#
|
| 336 |
+
image_shape = np.array(np.shape(image)[0:2])
|
| 337 |
+
#---------------------------------------------------------#
|
| 338 |
+
# 在这里将图像转换成RGB图像,防止灰度图在预测时报错。
|
| 339 |
+
# 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
|
| 340 |
+
#---------------------------------------------------------#
|
| 341 |
+
image = cvtColor(image)
|
| 342 |
+
#---------------------------------------------------------#
|
| 343 |
+
# 给图像增加灰条,实现不失真的resize
|
| 344 |
+
# 也可以直接resize进行识别
|
| 345 |
+
#---------------------------------------------------------#
|
| 346 |
+
image_data = resize_image(image, (self.input_shape[1], self.input_shape[0]), self.letterbox_image)
|
| 347 |
+
#---------------------------------------------------------#
|
| 348 |
+
# 添加上batch_size维度,图片预处理,归一化。
|
| 349 |
+
#---------------------------------------------------------#
|
| 350 |
+
image_data = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)
|
| 351 |
+
|
| 352 |
+
with torch.no_grad():
|
| 353 |
+
#---------------------------------------------------#
|
| 354 |
+
# 转化成torch的形式
|
| 355 |
+
#---------------------------------------------------#
|
| 356 |
+
images = torch.from_numpy(image_data).type(torch.FloatTensor)
|
| 357 |
+
if self.cuda:
|
| 358 |
+
images = images.cuda()
|
| 359 |
+
#---------------------------------------------------------#
|
| 360 |
+
# 将图像输入网络当中进行预测!
|
| 361 |
+
#---------------------------------------------------------#
|
| 362 |
+
outputs = self.net(images)
|
| 363 |
+
#-----------------------------------------------------------#
|
| 364 |
+
# 将预测结果进行解码
|
| 365 |
+
#-----------------------------------------------------------#
|
| 366 |
+
results = self.bbox_util.decode_box(outputs, self.anchors, image_shape, self.input_shape, self.letterbox_image,
|
| 367 |
+
nms_iou = self.nms_iou, confidence = self.confidence)
|
| 368 |
+
#--------------------------------------#
|
| 369 |
+
# 如果没有检测到物体,则返回原图
|
| 370 |
+
#--------------------------------------#
|
| 371 |
+
if len(results[0]) <= 0:
|
| 372 |
+
return
|
| 373 |
+
|
| 374 |
+
top_label = np.array(results[0][:, 4], dtype = 'int32')
|
| 375 |
+
top_conf = results[0][:, 5]
|
| 376 |
+
top_boxes = results[0][:, :4]
|
| 377 |
+
|
| 378 |
+
for i, c in list(enumerate(top_label)):
|
| 379 |
+
predicted_class = self.class_names[int(c)]
|
| 380 |
+
box = top_boxes[i]
|
| 381 |
+
score = str(top_conf[i])
|
| 382 |
+
|
| 383 |
+
top, left, bottom, right = box
|
| 384 |
+
if predicted_class not in class_names:
|
| 385 |
+
continue
|
| 386 |
+
|
| 387 |
+
f.write("%s %s %s %s %s %s\n" % (predicted_class, score[:6], str(int(left)), str(int(top)), str(int(right)),str(int(bottom))))
|
| 388 |
+
|
| 389 |
+
f.close()
|
| 390 |
+
return
|
ssd-pytorch-master/summary.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#--------------------------------------------#
|
| 2 |
+
# 该部分代码用于看网络结构
|
| 3 |
+
#--------------------------------------------#
|
| 4 |
+
import torch
|
| 5 |
+
from thop import clever_format, profile
|
| 6 |
+
from torchsummary import summary
|
| 7 |
+
|
| 8 |
+
from nets.ssd import SSD300
|
| 9 |
+
|
| 10 |
+
if __name__ == "__main__":
|
| 11 |
+
input_shape = [300, 300]
|
| 12 |
+
num_classes = 21
|
| 13 |
+
backbone = "vgg"
|
| 14 |
+
|
| 15 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 16 |
+
m = SSD300(num_classes, backbone).to(device)
|
| 17 |
+
summary(m, (3, input_shape[0], input_shape[1]))
|
| 18 |
+
|
| 19 |
+
dummy_input = torch.randn(1, 3, input_shape[0], input_shape[1]).to(device)
|
| 20 |
+
flops, params = profile(m.to(device), (dummy_input, ), verbose=False)
|
| 21 |
+
#--------------------------------------------------------#
|
| 22 |
+
# flops * 2是因为profile没有将卷积作为两个operations
|
| 23 |
+
# 有些论文将卷积算乘法、加法两个operations。此时乘2
|
| 24 |
+
# 有些论文只考虑乘法的运算次数,忽略加法。此时不乘2
|
| 25 |
+
# 本代码选择乘2,参考YOLOX。
|
| 26 |
+
#--------------------------------------------------------#
|
| 27 |
+
flops = flops * 2
|
| 28 |
+
flops, params = clever_format([flops, params], "%.3f")
|
| 29 |
+
print('Total GFLOPS: %s' % (flops))
|
| 30 |
+
print('Total params: %s' % (params))
|
| 31 |
+
|
ssd-pytorch-master/train.py
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import datetime
|
| 2 |
+
import os
|
| 3 |
+
import warnings
|
| 4 |
+
from functools import partial
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
+
import torch.backends.cudnn as cudnn
|
| 9 |
+
import torch.distributed as dist
|
| 10 |
+
import torch.nn as nn
|
| 11 |
+
import torch.optim as optim
|
| 12 |
+
from torch.utils.data import DataLoader
|
| 13 |
+
|
| 14 |
+
from nets.ssd import SSD300
|
| 15 |
+
from nets.ssd_training import (MultiboxLoss, get_lr_scheduler,
|
| 16 |
+
set_optimizer_lr, weights_init)
|
| 17 |
+
from utils.anchors import get_anchors
|
| 18 |
+
from utils.callbacks import EvalCallback, LossHistory
|
| 19 |
+
from utils.dataloader import SSDDataset, ssd_dataset_collate
|
| 20 |
+
from utils.utils import (download_weights, get_classes, seed_everything,
|
| 21 |
+
show_config, worker_init_fn)
|
| 22 |
+
from utils.utils_fit import fit_one_epoch
|
| 23 |
+
|
| 24 |
+
warnings.filterwarnings("ignore")
|
| 25 |
+
|
| 26 |
+
'''
|
| 27 |
+
训练自己的目标检测模型一定需要注意以下几点:
|
| 28 |
+
1、训练前仔细检查自己的格式是否满足要求,该库要求数据集格式为VOC格式,需要准备好的内容有输入图片和标签
|
| 29 |
+
输入图片为.jpg图片,无需固定大小,传入训练前会自动进行resize。
|
| 30 |
+
灰度图会自动转成RGB图片进行训练,无需自己修改。
|
| 31 |
+
输入图片如果后缀非jpg,需要自己批量转成jpg后再开始训练。
|
| 32 |
+
|
| 33 |
+
标签为.xml格式,文件中会有需要检测的目标信息,标签文件和输入图片文件相对应。
|
| 34 |
+
|
| 35 |
+
2、损失值的大小用于判断是否收敛,比较重要的是有收敛的趋势,即验证集损失不断下降,如果验证集损失基本上不改变的话,模型基本上就收敛了。
|
| 36 |
+
损失值的具体大小并没有什么意义,大和小只在于损失的计算方式,并不是接近于0才好。如果想要让损失好看点,可以直接到对应的损失函数里面除上10000。
|
| 37 |
+
训练过程中的损失值会保存在logs文件夹下的loss_%Y_%m_%d_%H_%M_%S文件夹中
|
| 38 |
+
|
| 39 |
+
3、训练好的权值文件保存在logs文件夹中,每个训练世代(Epoch)包含若干训练步长(Step),每个训练步长(Step)进行一次梯度下降。
|
| 40 |
+
如果只是训练了几个Step是不会保存的,Epoch和Step的概念要捋清楚一下。
|
| 41 |
+
'''
|
| 42 |
+
if __name__ == "__main__":
|
| 43 |
+
#---------------------------------#
|
| 44 |
+
# Cuda 是否使用Cuda
|
| 45 |
+
# 没有GPU可以设置成False
|
| 46 |
+
#---------------------------------#
|
| 47 |
+
Cuda = True
|
| 48 |
+
#----------------------------------------------#
|
| 49 |
+
# Seed 用于固定随机种子
|
| 50 |
+
# 使得每次独立训练都可以获得一样的结果
|
| 51 |
+
#----------------------------------------------#
|
| 52 |
+
seed = 11
|
| 53 |
+
#---------------------------------------------------------------------#
|
| 54 |
+
# distributed 用于指定是否使用单机多卡分布式运行
|
| 55 |
+
# 终端指令仅支持Ubuntu。CUDA_VISIBLE_DEVICES用于在Ubuntu下指定显卡。
|
| 56 |
+
# Windows系统下默认使用DP模式调用所有显卡,不支持DDP。
|
| 57 |
+
# DP模式:
|
| 58 |
+
# 设置 distributed = False
|
| 59 |
+
# 在终端中输入 CUDA_VISIBLE_DEVICES=0,1 python train.py
|
| 60 |
+
# DDP模式:
|
| 61 |
+
# 设置 distributed = True
|
| 62 |
+
# 在终端中输入 CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 train.py
|
| 63 |
+
#---------------------------------------------------------------------#
|
| 64 |
+
distributed = False
|
| 65 |
+
#---------------------------------------------------------------------#
|
| 66 |
+
# sync_bn 是否使用sync_bn,DDP模式多卡可用
|
| 67 |
+
#---------------------------------------------------------------------#
|
| 68 |
+
sync_bn = False
|
| 69 |
+
#---------------------------------------------------------------------#
|
| 70 |
+
# fp16 是否使用混合精度训练
|
| 71 |
+
# 可减少约一半的显存、需要pytorch1.7.1以上
|
| 72 |
+
#---------------------------------------------------------------------#
|
| 73 |
+
fp16 = False
|
| 74 |
+
#---------------------------------------------------------------------#
|
| 75 |
+
# classes_path 指向model_data下的txt,与自己训练的数据集相关
|
| 76 |
+
# 训练前一定要修改classes_path,使其对应自己的数据集
|
| 77 |
+
#---------------------------------------------------------------------#
|
| 78 |
+
classes_path = '/home/lab/LJ/wampee/ssd-pytorch-master/VOCdevkit/VOC2007/cls_classes.txt'
|
| 79 |
+
#----------------------------------------------------------------------------------------------------------------------------#
|
| 80 |
+
# 权值文件的下载请看README,可以通过网盘下载。模型的 预训练权重 对不同数据集是通用的,因为特征是通用的。
|
| 81 |
+
# 模型的 预训练权重 比较重要的部分是 主干特征提取网络的权值部分,用于进行特征提取。
|
| 82 |
+
# 预训练权重对于99%的情况都必须要用,不用的话主干部分的权值太过随机,特征提取效果不明显,网络训练的结果也不会好
|
| 83 |
+
#
|
| 84 |
+
# 如果训练过程中存在中��训练的操作,可以将model_path设置成logs文件夹下的权值文件,将已经训练了一部分的权值再次载入。
|
| 85 |
+
# 同时修改下方的 冻结阶段 或者 解冻阶段 的参数,来保证模型epoch的连续性。
|
| 86 |
+
#
|
| 87 |
+
# 当model_path = ''的时候不加载整个模型的权值。
|
| 88 |
+
#
|
| 89 |
+
# 此处使用的是整个模型的权重,因此是在train.py进行加载的,下面的pretrain不影响此处的权值加载。
|
| 90 |
+
# 如果想要让模型从主干的预训练权值开始训练,则设置model_path = '',下面的pretrain = True,此时仅加载主干。
|
| 91 |
+
# 如果想要让模型从0开始训练,则设置model_path = '',下面的pretrain = Fasle,Freeze_Train = Fasle,此时从0开始训练,且没有冻结主干的过程。
|
| 92 |
+
# 一般来讲,从0开始训练效果会很差,因为权值太过随机,特征提取效果不明显。
|
| 93 |
+
#
|
| 94 |
+
# 网络一般不从0开始训练,至少会使用主干部分的权值,有些论文提到可以不用预训练,主要原因是他们 数据集较大 且 调参能力优秀。
|
| 95 |
+
# 如果一定要训练网络的主干部分,可以了解imagenet数据集,首先训练分类模型,分类模型的 主干部分 和该模型通用,基于此进行训练。
|
| 96 |
+
#----------------------------------------------------------------------------------------------------------------------------#
|
| 97 |
+
model_path = 'model_data/ssd_weights.pth'
|
| 98 |
+
#------------------------------------------------------#
|
| 99 |
+
# input_shape 输入的shape大小
|
| 100 |
+
#------------------------------------------------------#
|
| 101 |
+
input_shape = [300, 300]
|
| 102 |
+
#------------------------------------------------------#
|
| 103 |
+
# vgg或者mobilenetv2或者resnet50
|
| 104 |
+
#------------------------------------------------------#
|
| 105 |
+
backbone = "vgg"
|
| 106 |
+
#----------------------------------------------------------------------------------------------------------------------------#
|
| 107 |
+
# pretrained 是否使用主干网络的预训练权重,此处使用的是主干的权重,因此是在模型构建的时候进行加载的。
|
| 108 |
+
# 如果设置了model_path,则主干的权值无需加载,pretrained的值无意义。
|
| 109 |
+
# 如果不设置model_path,pretrained = True,此时仅加载主干开始训练。
|
| 110 |
+
# 如果不设置model_path,pretrained = False,Freeze_Train = Fasle,此时从0开始训练,且没有冻结主干的过程。
|
| 111 |
+
#----------------------------------------------------------------------------------------------------------------------------#
|
| 112 |
+
pretrained = False
|
| 113 |
+
#------------------------------------------------------#
|
| 114 |
+
# 可用于设定先验框的大小,默认的anchors_size
|
| 115 |
+
# 是根据voc数据集设定的,大多数情况下都是通用的!
|
| 116 |
+
# 如果想要检测小物体,可以修改anchors_size
|
| 117 |
+
# 一般调小浅层先验框的大小就行了!因为浅层负责小物体检测!
|
| 118 |
+
# 比如anchors_size = [21, 45, 99, 153, 207, 261, 315]
|
| 119 |
+
#------------------------------------------------------#
|
| 120 |
+
anchors_size = [30, 60, 111, 162, 213, 264, 315]
|
| 121 |
+
|
| 122 |
+
#----------------------------------------------------------------------------------------------------------------------------#
|
| 123 |
+
# 训练分为两个阶段,分别是冻结阶段和解冻阶段。设置冻结阶段是为了满足机器性能不足的同学的训练需求。
|
| 124 |
+
# 冻结训练需要的显存较小,显卡非常差的情况下,可设置Freeze_Epoch等于UnFreeze_Epoch,此时仅仅进行冻结训练。
|
| 125 |
+
#
|
| 126 |
+
# 在此提供若干参数设置建议,各位训练者根据自己的需求进行灵活调整:
|
| 127 |
+
# (一)从整个模型的预训练权重开始训练:
|
| 128 |
+
# Adam:
|
| 129 |
+
# Init_Epoch = 0,Freeze_Epoch = 50,UnFreeze_Epoch = 100,Freeze_Train = True,optimizer_type = 'adam',Init_lr = 6e-4,weight_decay = 0。(冻结)
|
| 130 |
+
# Init_Epoch = 0,UnFreeze_Epoch = 100,Freeze_Train = False,optimizer_type = 'adam',Init_lr = 6e-4,weight_decay = 0。(不冻结)
|
| 131 |
+
# SGD:
|
| 132 |
+
# Init_Epoch = 0,Freeze_Epoch = 50,UnFreeze_Epoch = 200,Freeze_Train = True,optimizer_type = 'sgd',Init_lr = 2e-3,weight_decay = 5e-4。(冻结)
|
| 133 |
+
# Init_Epoch = 0,UnFreeze_Epoch = 200,Freeze_Train = False,optimizer_type = 'sgd',Init_lr = 2e-3,weight_decay = 5e-4。(不冻结)
|
| 134 |
+
# 其中:UnFreeze_Epoch可以在100-300之间调整。
|
| 135 |
+
# (二)从主干网络的预训练权重开始训练:
|
| 136 |
+
# Adam:
|
| 137 |
+
# Init_Epoch = 0,Freeze_Epoch = 50,UnFreeze_Epoch = 100,Freeze_Train = True,optimizer_type = 'adam',Init_lr = 6e-4,weight_decay = 0。(冻结)
|
| 138 |
+
# Init_Epoch = 0,UnFreeze_Epoch = 100,Freeze_Train = False,optimizer_type = 'adam',Init_lr = 6e-4,weight_decay = 0。(不冻结)
|
| 139 |
+
# SGD:
|
| 140 |
+
# Init_Epoch = 0,Freeze_Epoch = 50,UnFreeze_Epoch = 200,Freeze_Train = True,optimizer_type = 'sgd',Init_lr = 2e-3,weight_decay = 5e-4。(冻结)
|
| 141 |
+
# Init_Epoch = 0,UnFreeze_Epoch = 200,Freeze_Train = False,optimizer_type = 'sgd',Init_lr = 2e-3,weight_decay = 5e-4。(不冻结)
|
| 142 |
+
# 其中:由于从主干网络的预训练权重开始训练,主干的权值不一定适合目标检测,需要更多的训练跳出局部最优解。
|
| 143 |
+
# UnFreeze_Epoch可以在200-300之间调整,YOLOV5和YOLOX均推荐使用300。
|
| 144 |
+
# Adam相较于SGD收敛的快一些。因此UnFreeze_Epoch理论上可以小一点,但依然推荐更多的Epoch。
|
| 145 |
+
# (三)batch_size的设置:
|
| 146 |
+
# 在显卡能够接受的范围内,以大为好。显存不足与数据集大小无关,提示显存不足(OOM或者CUDA out of memory)请调小batch_size。
|
| 147 |
+
# 受到BatchNorm层影响,batch_size最小为2,不能为1。
|
| 148 |
+
# 正常情况下Freeze_batch_size建议为Unfreeze_batch_size的1-2倍。不建议设置的差距过大,因为关系到学习率的自动调整。
|
| 149 |
+
#----------------------------------------------------------------------------------------------------------------------------#
|
| 150 |
+
#------------------------------------------------------------------#
|
| 151 |
+
# 冻结阶段训练参数
|
| 152 |
+
# 此时模型的主干被冻结了,特征提取网络不发生改变
|
| 153 |
+
# 占用的显存较小,仅对网络进行微调
|
| 154 |
+
# Init_Epoch 模型当前开始的训练世代,其值可以大于Freeze_Epoch,如设置:
|
| 155 |
+
# Init_Epoch = 60、Freeze_Epoch = 50、UnFreeze_Epoch = 100
|
| 156 |
+
# 会跳过冻结阶段,直接从60代开始,并调整对应的学习率。
|
| 157 |
+
# (断点续练时使用)
|
| 158 |
+
# Freeze_Epoch 模型冻结训练的Freeze_Epoch
|
| 159 |
+
# (当Freeze_Train=False时失效)
|
| 160 |
+
# Freeze_batch_size 模型冻结训练的batch_size
|
| 161 |
+
# (当Freeze_Train=False时失效)
|
| 162 |
+
#------------------------------------------------------------------#
|
| 163 |
+
Init_Epoch = 400
|
| 164 |
+
Freeze_Epoch = 50
|
| 165 |
+
Freeze_batch_size = 16
|
| 166 |
+
#------------------------------------------------------------------#
|
| 167 |
+
# 解冻阶段训练参数
|
| 168 |
+
# 此时模型的主干不被冻结了,特征提取网络会发生改变
|
| 169 |
+
# 占用的显存较大,网络所有的参数都会发生改变
|
| 170 |
+
# UnFreeze_Epoch 模型总共训练的epoch
|
| 171 |
+
# SGD需要更长的时间收敛,因此设置较大的UnFreeze_Epoch
|
| 172 |
+
# Adam可以使用相对较小的UnFreeze_Epoch
|
| 173 |
+
# Unfreeze_batch_size 模型在解冻后的batch_size
|
| 174 |
+
#------------------------------------------------------------------#
|
| 175 |
+
UnFreeze_Epoch = 1037
|
| 176 |
+
Unfreeze_batch_size = 16
|
| 177 |
+
#------------------------------------------------------------------#
|
| 178 |
+
# Freeze_Train 是否进行冻结训练
|
| 179 |
+
# 默认先冻结主干训练后解冻训练。
|
| 180 |
+
# 如果设置Freeze_Train=False,建议使用优化器为sgd
|
| 181 |
+
#------------------------------------------------------------------#
|
| 182 |
+
Freeze_Train = True
|
| 183 |
+
|
| 184 |
+
#------------------------------------------------------------------#
|
| 185 |
+
# 其它训练参数:学习率、优化器、学习率下降有关
|
| 186 |
+
#------------------------------------------------------------------#
|
| 187 |
+
#------------------------------------------------------------------#
|
| 188 |
+
# Init_lr 模型的最大学习率
|
| 189 |
+
# 当使用Adam优化器时建议设置 Init_lr=6e-4
|
| 190 |
+
# 当使用SGD优化器时建议设置 Init_lr=2e-3
|
| 191 |
+
# Min_lr 模型的最小学习率,默认为最大学习率的0.01
|
| 192 |
+
#------------------------------------------------------------------#
|
| 193 |
+
Init_lr = 2e-3
|
| 194 |
+
Min_lr = Init_lr * 0.01
|
| 195 |
+
#------------------------------------------------------------------#
|
| 196 |
+
# optimizer_type 使用到的优化器种类,可选的有adam、sgd
|
| 197 |
+
# 当使用Adam优化器时建议设置 Init_lr=6e-4
|
| 198 |
+
# 当使用SGD优化器时建议设置 Init_lr=2e-3
|
| 199 |
+
# momentum 优化器内部使用到的momentum参数
|
| 200 |
+
# weight_decay 权值衰减,可防止过拟合
|
| 201 |
+
# adam会导致weight_decay错误,使用adam时建议设置为0。
|
| 202 |
+
#------------------------------------------------------------------#
|
| 203 |
+
optimizer_type = "sgd"
|
| 204 |
+
momentum = 0.937
|
| 205 |
+
weight_decay = 5e-4
|
| 206 |
+
#------------------------------------------------------------------#
|
| 207 |
+
# lr_decay_type 使用到的学习率下降方式,可选的有'step'、'cos'
|
| 208 |
+
#------------------------------------------------------------------#
|
| 209 |
+
lr_decay_type = 'cos'
|
| 210 |
+
#------------------------------------------------------------------#
|
| 211 |
+
# save_period 多少个epoch保存一次权值
|
| 212 |
+
#------------------------------------------------------------------#
|
| 213 |
+
save_period = 10
|
| 214 |
+
#------------------------------------------------------------------#
|
| 215 |
+
# save_dir 权值与日志文件保存的文件夹
|
| 216 |
+
#------------------------------------------------------------------#
|
| 217 |
+
save_dir = 'logs'
|
| 218 |
+
#------------------------------------------------------------------#
|
| 219 |
+
# eval_flag 是否在训练时进行评估,评估对象为验证集
|
| 220 |
+
# 安装pycocotools库后,评估体验更佳。
|
| 221 |
+
# eval_period 代表多少个epoch评估一次,不建议频繁的评估
|
| 222 |
+
# 评估需要消耗较多的时间,频繁评估会导致训练非常慢
|
| 223 |
+
# 此处获得的mAP会与get_map.py获得的会有所不同,原因有二:
|
| 224 |
+
# (一)此处获得的mAP为验证集的mAP。
|
| 225 |
+
# (二)此处设置评估参数较为保守,目的是加快评估速度。
|
| 226 |
+
#------------------------------------------------------------------#
|
| 227 |
+
eval_flag = True
|
| 228 |
+
eval_period = 10
|
| 229 |
+
#------------------------------------------------------------------#
|
| 230 |
+
# num_workers 用于设置是否使用多线程读取数据,1代表关闭多线程
|
| 231 |
+
# 开启后会加快数据读取速度,但是会占用更多内存
|
| 232 |
+
# keras里开启多线程有些时候速度反而慢了许多
|
| 233 |
+
# 在IO为瓶颈的时候再开启多线程,即GPU运算速度远大于读取图片的速度。
|
| 234 |
+
#------------------------------------------------------------------#
|
| 235 |
+
num_workers = 4
|
| 236 |
+
|
| 237 |
+
#------------------------------------------------------#
|
| 238 |
+
# train_annotation_path 训练图片路径和标签
|
| 239 |
+
# val_annotation_path 验证图片路径和标签
|
| 240 |
+
#------------------------------------------------------#
|
| 241 |
+
train_annotation_path = '2007_train.txt'
|
| 242 |
+
val_annotation_path = '2007_val.txt'
|
| 243 |
+
|
| 244 |
+
seed_everything(seed)
|
| 245 |
+
#------------------------------------------------------#
|
| 246 |
+
# 设置用到的显卡
|
| 247 |
+
#------------------------------------------------------#
|
| 248 |
+
ngpus_per_node = torch.cuda.device_count()
|
| 249 |
+
if distributed:
|
| 250 |
+
dist.init_process_group(backend="nccl")
|
| 251 |
+
local_rank = int(os.environ["LOCAL_RANK"])
|
| 252 |
+
rank = int(os.environ["RANK"])
|
| 253 |
+
device = torch.device("cuda", local_rank)
|
| 254 |
+
if local_rank == 0:
|
| 255 |
+
print(f"[{os.getpid()}] (rank = {rank}, local_rank = {local_rank}) training...")
|
| 256 |
+
print("Gpu Device Count : ", ngpus_per_node)
|
| 257 |
+
else:
|
| 258 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 259 |
+
local_rank = 0
|
| 260 |
+
rank = 0
|
| 261 |
+
|
| 262 |
+
if pretrained:
|
| 263 |
+
if distributed:
|
| 264 |
+
if local_rank == 0:
|
| 265 |
+
download_weights(backbone)
|
| 266 |
+
dist.barrier()
|
| 267 |
+
else:
|
| 268 |
+
download_weights(backbone)
|
| 269 |
+
|
| 270 |
+
#----------------------------------------------------#
|
| 271 |
+
# 获取classes和anchor
|
| 272 |
+
#----------------------------------------------------#
|
| 273 |
+
class_names, num_classes = get_classes(classes_path)
|
| 274 |
+
num_classes += 1
|
| 275 |
+
anchors = get_anchors(input_shape, anchors_size, backbone)
|
| 276 |
+
|
| 277 |
+
model = SSD300(num_classes, backbone, pretrained)
|
| 278 |
+
if not pretrained:
|
| 279 |
+
weights_init(model)
|
| 280 |
+
if model_path != '':
|
| 281 |
+
#------------------------------------------------------#
|
| 282 |
+
# 权值文件请看README,百度网盘下载
|
| 283 |
+
#------------------------------------------------------#
|
| 284 |
+
if local_rank == 0:
|
| 285 |
+
print('Load weights {}.'.format(model_path))
|
| 286 |
+
|
| 287 |
+
#------------------------------------------------------#
|
| 288 |
+
# 根据预训练权重的Key和模型的Key进行加载
|
| 289 |
+
#------------------------------------------------------#
|
| 290 |
+
model_dict = model.state_dict()
|
| 291 |
+
pretrained_dict = torch.load(model_path, map_location = device)
|
| 292 |
+
load_key, no_load_key, temp_dict = [], [], {}
|
| 293 |
+
for k, v in pretrained_dict.items():
|
| 294 |
+
if k in model_dict.keys() and np.shape(model_dict[k]) == np.shape(v):
|
| 295 |
+
temp_dict[k] = v
|
| 296 |
+
load_key.append(k)
|
| 297 |
+
else:
|
| 298 |
+
no_load_key.append(k)
|
| 299 |
+
model_dict.update(temp_dict)
|
| 300 |
+
model.load_state_dict(model_dict)
|
| 301 |
+
#------------------------------------------------------#
|
| 302 |
+
# 显示没有匹配上的Key
|
| 303 |
+
#------------------------------------------------------#
|
| 304 |
+
if local_rank == 0:
|
| 305 |
+
print("\nSuccessful Load Key:", str(load_key)[:500], "……\nSuccessful Load Key Num:", len(load_key))
|
| 306 |
+
print("\nFail To Load Key:", str(no_load_key)[:500], "……\nFail To Load Key num:", len(no_load_key))
|
| 307 |
+
print("\n\033[1;33;44m温馨提示���head部分没有载入是正常现象,Backbone部分没有载入是错误的。\033[0m")
|
| 308 |
+
|
| 309 |
+
#----------------------#
|
| 310 |
+
# 获得损失函数
|
| 311 |
+
#----------------------#
|
| 312 |
+
criterion = MultiboxLoss(num_classes, neg_pos_ratio=3.0)
|
| 313 |
+
#----------------------#
|
| 314 |
+
# 记录Loss
|
| 315 |
+
#----------------------#
|
| 316 |
+
if local_rank == 0:
|
| 317 |
+
time_str = datetime.datetime.strftime(datetime.datetime.now(),'%Y_%m_%d_%H_%M_%S')
|
| 318 |
+
log_dir = os.path.join(save_dir, "loss_" + str(time_str))
|
| 319 |
+
loss_history = LossHistory(log_dir, model, input_shape=input_shape)
|
| 320 |
+
else:
|
| 321 |
+
loss_history = None
|
| 322 |
+
|
| 323 |
+
#------------------------------------------------------------------#
|
| 324 |
+
# torch 1.2不支持amp,建议使用torch 1.7.1及以上正确使用fp16
|
| 325 |
+
# 因此torch1.2这里显示"could not be resolve"
|
| 326 |
+
#------------------------------------------------------------------#
|
| 327 |
+
if fp16:
|
| 328 |
+
from torch.cuda.amp import GradScaler as GradScaler
|
| 329 |
+
scaler = GradScaler()
|
| 330 |
+
else:
|
| 331 |
+
scaler = None
|
| 332 |
+
|
| 333 |
+
model_train = model.train()
|
| 334 |
+
#----------------------------#
|
| 335 |
+
# 多卡同步Bn
|
| 336 |
+
#----------------------------#
|
| 337 |
+
if sync_bn and ngpus_per_node > 1 and distributed:
|
| 338 |
+
model_train = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model_train)
|
| 339 |
+
elif sync_bn:
|
| 340 |
+
print("Sync_bn is not support in one gpu or not distributed.")
|
| 341 |
+
|
| 342 |
+
if Cuda:
|
| 343 |
+
if distributed:
|
| 344 |
+
#----------------------------#
|
| 345 |
+
# 多卡平行运行
|
| 346 |
+
#----------------------------#
|
| 347 |
+
model_train = model_train.cuda(local_rank)
|
| 348 |
+
model_train = torch.nn.parallel.DistributedDataParallel(model_train, device_ids=[local_rank], find_unused_parameters=True)
|
| 349 |
+
else:
|
| 350 |
+
model_train = torch.nn.DataParallel(model)
|
| 351 |
+
cudnn.benchmark = True
|
| 352 |
+
model_train = model_train.cuda()
|
| 353 |
+
|
| 354 |
+
#---------------------------#
|
| 355 |
+
# 读取数据集对应的txt
|
| 356 |
+
#---------------------------#
|
| 357 |
+
with open(train_annotation_path, encoding='utf-8') as f:
|
| 358 |
+
train_lines = f.readlines()
|
| 359 |
+
with open(val_annotation_path, encoding='utf-8') as f:
|
| 360 |
+
val_lines = f.readlines()
|
| 361 |
+
num_train = len(train_lines)
|
| 362 |
+
num_val = len(val_lines)
|
| 363 |
+
|
| 364 |
+
if local_rank == 0:
|
| 365 |
+
show_config(
|
| 366 |
+
classes_path = classes_path, model_path = model_path, input_shape = input_shape, \
|
| 367 |
+
Init_Epoch = Init_Epoch, Freeze_Epoch = Freeze_Epoch, UnFreeze_Epoch = UnFreeze_Epoch, Freeze_batch_size = Freeze_batch_size, Unfreeze_batch_size = Unfreeze_batch_size, Freeze_Train = Freeze_Train, \
|
| 368 |
+
Init_lr = Init_lr, Min_lr = Min_lr, optimizer_type = optimizer_type, momentum = momentum, lr_decay_type = lr_decay_type, \
|
| 369 |
+
save_period = save_period, save_dir = save_dir, num_workers = num_workers, num_train = num_train, num_val = num_val
|
| 370 |
+
)
|
| 371 |
+
#---------------------------------------------------------#
|
| 372 |
+
# 总训练世代指的是遍历全部数据的总次数
|
| 373 |
+
# 总训练步长指的是梯度下降的总次数
|
| 374 |
+
# 每个训练世代包含若干训练步长,每个训练步长进行一次梯度下降。
|
| 375 |
+
# 此处仅建议最低训练世代,上不封顶,计算时只考虑了解冻部分
|
| 376 |
+
#----------------------------------------------------------#
|
| 377 |
+
wanted_step = 5e4 if optimizer_type == "sgd" else 1.5e4
|
| 378 |
+
total_step = num_train // Unfreeze_batch_size * UnFreeze_Epoch
|
| 379 |
+
if total_step <= wanted_step:
|
| 380 |
+
if num_train // Unfreeze_batch_size == 0:
|
| 381 |
+
raise ValueError('数据集过小,无法进行训练,请扩充数据集。')
|
| 382 |
+
wanted_epoch = wanted_step // (num_train // Unfreeze_batch_size) + 1
|
| 383 |
+
print("\n\033[1;33;44m[Warning] 使用%s优化器时,建议将训练总步长设置到%d以上。\033[0m"%(optimizer_type, wanted_step))
|
| 384 |
+
print("\033[1;33;44m[Warning] 本次运行的总训练数据量为%d,Unfreeze_batch_size为%d,共训练%d个Epoch,计算出总训练步长为%d。\033[0m"%(num_train, Unfreeze_batch_size, UnFreeze_Epoch, total_step))
|
| 385 |
+
print("\033[1;33;44m[Warning] 由于总训练步长为%d,小于建议总步长%d,建议设置总世代为%d。\033[0m"%(total_step, wanted_step, wanted_epoch))
|
| 386 |
+
|
| 387 |
+
#------------------------------------------------------#
|
| 388 |
+
# 主干特征提取网络特征通用,冻结训练可以加快训练速度
|
| 389 |
+
# 也可以在训练初期防止权值被破坏。
|
| 390 |
+
# Init_Epoch为起始世代
|
| 391 |
+
# Freeze_Epoch为冻结训练的世代
|
| 392 |
+
# UnFreeze_Epoch总训练世代
|
| 393 |
+
# 提示OOM或者显存不足请调小Batch_size
|
| 394 |
+
#------------------------------------------------------#
|
| 395 |
+
if True:
|
| 396 |
+
UnFreeze_flag = False
|
| 397 |
+
#------------------------------------#
|
| 398 |
+
# 冻结一定部分训练
|
| 399 |
+
#------------------------------------#
|
| 400 |
+
if Freeze_Train:
|
| 401 |
+
if backbone == "vgg":
|
| 402 |
+
for param in model.vgg[:28].parameters():
|
| 403 |
+
param.requires_grad = False
|
| 404 |
+
elif backbone == "mobilenetv2":
|
| 405 |
+
for param in model.mobilenet.parameters():
|
| 406 |
+
param.requires_grad = False
|
| 407 |
+
else:
|
| 408 |
+
for param in model.resnet.parameters():
|
| 409 |
+
param.requires_grad = False
|
| 410 |
+
|
| 411 |
+
#-------------------------------------------------------------------#
|
| 412 |
+
# 如果不冻结训练的话,直接设置batch_size为Unfreeze_batch_size
|
| 413 |
+
#-------------------------------------------------------------------#
|
| 414 |
+
batch_size = Freeze_batch_size if Freeze_Train else Unfreeze_batch_size
|
| 415 |
+
|
| 416 |
+
#-------------------------------------------------------------------#
|
| 417 |
+
# 判断当前batch_size,自适应调整学习率
|
| 418 |
+
#-------------------------------------------------------------------#
|
| 419 |
+
nbs = 64
|
| 420 |
+
lr_limit_max = 1e-3 if optimizer_type == 'adam' else 5e-2
|
| 421 |
+
lr_limit_min = 3e-4 if optimizer_type == 'adam' else 5e-5
|
| 422 |
+
Init_lr_fit = min(max(batch_size / nbs * Init_lr, lr_limit_min), lr_limit_max)
|
| 423 |
+
Min_lr_fit = min(max(batch_size / nbs * Min_lr, lr_limit_min * 1e-2), lr_limit_max * 1e-2)
|
| 424 |
+
|
| 425 |
+
#---------------------------------------#
|
| 426 |
+
# 根据optimizer_type选择优化器
|
| 427 |
+
#---------------------------------------#
|
| 428 |
+
optimizer = {
|
| 429 |
+
'adam' : optim.Adam(model.parameters(), Init_lr_fit, betas = (momentum, 0.999), weight_decay = weight_decay),
|
| 430 |
+
'sgd' : optim.SGD(model.parameters(), Init_lr_fit, momentum = momentum, nesterov=True, weight_decay = weight_decay)
|
| 431 |
+
}[optimizer_type]
|
| 432 |
+
|
| 433 |
+
#---------------------------------------#
|
| 434 |
+
# 获得学习率下降的公式
|
| 435 |
+
#---------------------------------------#
|
| 436 |
+
lr_scheduler_func = get_lr_scheduler(lr_decay_type, Init_lr_fit, Min_lr_fit, UnFreeze_Epoch)
|
| 437 |
+
|
| 438 |
+
#---------------------------------------#
|
| 439 |
+
# 判断每一个世代的长度
|
| 440 |
+
#---------------------------------------#
|
| 441 |
+
epoch_step = num_train // batch_size
|
| 442 |
+
epoch_step_val = num_val // batch_size
|
| 443 |
+
|
| 444 |
+
if epoch_step == 0 or epoch_step_val == 0:
|
| 445 |
+
raise ValueError("数据集过小,无法继续进行训练,请扩充数据集。")
|
| 446 |
+
|
| 447 |
+
train_dataset = SSDDataset(train_lines, input_shape, anchors, batch_size, num_classes, train = True)
|
| 448 |
+
val_dataset = SSDDataset(val_lines, input_shape, anchors, batch_size, num_classes, train = False)
|
| 449 |
+
|
| 450 |
+
if distributed:
|
| 451 |
+
train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset, shuffle=True,)
|
| 452 |
+
val_sampler = torch.utils.data.distributed.DistributedSampler(val_dataset, shuffle=False,)
|
| 453 |
+
batch_size = batch_size // ngpus_per_node
|
| 454 |
+
shuffle = False
|
| 455 |
+
else:
|
| 456 |
+
train_sampler = None
|
| 457 |
+
val_sampler = None
|
| 458 |
+
shuffle = True
|
| 459 |
+
|
| 460 |
+
gen = DataLoader(train_dataset, shuffle = shuffle, batch_size = batch_size, num_workers = num_workers, pin_memory=True,
|
| 461 |
+
drop_last=True, collate_fn=ssd_dataset_collate, sampler=train_sampler,
|
| 462 |
+
worker_init_fn=partial(worker_init_fn, rank=rank, seed=seed))
|
| 463 |
+
gen_val = DataLoader(val_dataset , shuffle = shuffle, batch_size = batch_size, num_workers = num_workers, pin_memory=True,
|
| 464 |
+
drop_last=True, collate_fn=ssd_dataset_collate, sampler=val_sampler,
|
| 465 |
+
worker_init_fn=partial(worker_init_fn, rank=rank, seed=seed))
|
| 466 |
+
|
| 467 |
+
#----------------------#
|
| 468 |
+
# 记录eval的map曲线
|
| 469 |
+
#----------------------#
|
| 470 |
+
if local_rank == 0:
|
| 471 |
+
eval_callback = EvalCallback(model, input_shape, anchors, class_names, num_classes, val_lines, log_dir, Cuda, \
|
| 472 |
+
eval_flag=eval_flag, period=eval_period)
|
| 473 |
+
else:
|
| 474 |
+
eval_callback = None
|
| 475 |
+
|
| 476 |
+
#---------------------------------------#
|
| 477 |
+
# 开始模型训练
|
| 478 |
+
#---------------------------------------#
|
| 479 |
+
for epoch in range(Init_Epoch, UnFreeze_Epoch):
|
| 480 |
+
#---------------------------------------#
|
| 481 |
+
# 如果模型有冻结学习部分
|
| 482 |
+
# 则解冻,并设置参数
|
| 483 |
+
#---------------------------------------#
|
| 484 |
+
if epoch >= Freeze_Epoch and not UnFreeze_flag and Freeze_Train:
|
| 485 |
+
batch_size = Unfreeze_batch_size
|
| 486 |
+
|
| 487 |
+
#-------------------------------------------------------------------#
|
| 488 |
+
# 判断当前batch_size,自适应调整学习率
|
| 489 |
+
#-------------------------------------------------------------------#
|
| 490 |
+
nbs = 64
|
| 491 |
+
lr_limit_max = 1e-3 if optimizer_type == 'adam' else 5e-2
|
| 492 |
+
lr_limit_min = 3e-4 if optimizer_type == 'adam' else 5e-5
|
| 493 |
+
Init_lr_fit = min(max(batch_size / nbs * Init_lr, lr_limit_min), lr_limit_max)
|
| 494 |
+
Min_lr_fit = min(max(batch_size / nbs * Min_lr, lr_limit_min * 1e-2), lr_limit_max * 1e-2)
|
| 495 |
+
#---------------------------------------#
|
| 496 |
+
# 获得学习率下降的公式
|
| 497 |
+
#---------------------------------------#
|
| 498 |
+
lr_scheduler_func = get_lr_scheduler(lr_decay_type, Init_lr_fit, Min_lr_fit, UnFreeze_Epoch)
|
| 499 |
+
|
| 500 |
+
if backbone == "vgg":
|
| 501 |
+
for param in model.vgg[:28].parameters():
|
| 502 |
+
param.requires_grad = True
|
| 503 |
+
elif backbone == "mobilenetv2":
|
| 504 |
+
for param in model.mobilenet.parameters():
|
| 505 |
+
param.requires_grad = True
|
| 506 |
+
else:
|
| 507 |
+
for param in model.resnet.parameters():
|
| 508 |
+
param.requires_grad = True
|
| 509 |
+
|
| 510 |
+
epoch_step = num_train // batch_size
|
| 511 |
+
epoch_step_val = num_val // batch_size
|
| 512 |
+
|
| 513 |
+
if epoch_step == 0 or epoch_step_val == 0:
|
| 514 |
+
raise ValueError("数据集过小,无法继续进行训练,请扩充数据集。")
|
| 515 |
+
|
| 516 |
+
if distributed:
|
| 517 |
+
batch_size = batch_size // ngpus_per_node
|
| 518 |
+
|
| 519 |
+
gen = DataLoader(train_dataset, shuffle = shuffle, batch_size = batch_size, num_workers = num_workers, pin_memory=True,
|
| 520 |
+
drop_last=True, collate_fn=ssd_dataset_collate, sampler=train_sampler,
|
| 521 |
+
worker_init_fn=partial(worker_init_fn, rank=rank, seed=seed))
|
| 522 |
+
gen_val = DataLoader(val_dataset , shuffle = shuffle, batch_size = batch_size, num_workers = num_workers, pin_memory=True,
|
| 523 |
+
drop_last=True, collate_fn=ssd_dataset_collate, sampler=val_sampler,
|
| 524 |
+
worker_init_fn=partial(worker_init_fn, rank=rank, seed=seed))
|
| 525 |
+
|
| 526 |
+
UnFreeze_flag = True
|
| 527 |
+
|
| 528 |
+
if distributed:
|
| 529 |
+
train_sampler.set_epoch(epoch)
|
| 530 |
+
|
| 531 |
+
set_optimizer_lr(optimizer, lr_scheduler_func, epoch)
|
| 532 |
+
|
| 533 |
+
fit_one_epoch(model_train, model, criterion, loss_history, eval_callback, optimizer, epoch,
|
| 534 |
+
epoch_step, epoch_step_val, gen, gen_val, UnFreeze_Epoch, Cuda, fp16, scaler, save_period, save_dir, local_rank)
|
| 535 |
+
|
| 536 |
+
if distributed:
|
| 537 |
+
dist.barrier()
|
| 538 |
+
|
| 539 |
+
if local_rank == 0:
|
| 540 |
+
loss_history.writer.close()
|
ssd-pytorch-master/utils/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
#
|
ssd-pytorch-master/utils/anchors.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class AnchorBox():
|
| 5 |
+
def __init__(self, input_shape, min_size, max_size=None, aspect_ratios=None, flip=True):
|
| 6 |
+
self.input_shape = input_shape
|
| 7 |
+
|
| 8 |
+
self.min_size = min_size
|
| 9 |
+
self.max_size = max_size
|
| 10 |
+
|
| 11 |
+
self.aspect_ratios = []
|
| 12 |
+
for ar in aspect_ratios:
|
| 13 |
+
self.aspect_ratios.append(ar)
|
| 14 |
+
self.aspect_ratios.append(1.0 / ar)
|
| 15 |
+
|
| 16 |
+
def call(self, layer_shape, mask=None):
|
| 17 |
+
# --------------------------------- #
|
| 18 |
+
# 获取输入进来的特征层的宽和高
|
| 19 |
+
# 比如38x38
|
| 20 |
+
# --------------------------------- #
|
| 21 |
+
layer_height = layer_shape[0]
|
| 22 |
+
layer_width = layer_shape[1]
|
| 23 |
+
# --------------------------------- #
|
| 24 |
+
# 获取输入进来的图片的宽和高
|
| 25 |
+
# 比如300x300
|
| 26 |
+
# --------------------------------- #
|
| 27 |
+
img_height = self.input_shape[0]
|
| 28 |
+
img_width = self.input_shape[1]
|
| 29 |
+
|
| 30 |
+
box_widths = []
|
| 31 |
+
box_heights = []
|
| 32 |
+
# --------------------------------- #
|
| 33 |
+
# self.aspect_ratios一般有两个值
|
| 34 |
+
# [1, 1, 2, 1/2]
|
| 35 |
+
# [1, 1, 2, 1/2, 3, 1/3]
|
| 36 |
+
# --------------------------------- #
|
| 37 |
+
for ar in self.aspect_ratios:
|
| 38 |
+
# 首先添加一个较小的正方形
|
| 39 |
+
if ar == 1 and len(box_widths) == 0:
|
| 40 |
+
box_widths.append(self.min_size)
|
| 41 |
+
box_heights.append(self.min_size)
|
| 42 |
+
# 然后添加一个较大的正方形
|
| 43 |
+
elif ar == 1 and len(box_widths) > 0:
|
| 44 |
+
box_widths.append(np.sqrt(self.min_size * self.max_size))
|
| 45 |
+
box_heights.append(np.sqrt(self.min_size * self.max_size))
|
| 46 |
+
# 然后添加长方形
|
| 47 |
+
elif ar != 1:
|
| 48 |
+
box_widths.append(self.min_size * np.sqrt(ar))
|
| 49 |
+
box_heights.append(self.min_size / np.sqrt(ar))
|
| 50 |
+
|
| 51 |
+
# --------------------------------- #
|
| 52 |
+
# 获得所有先验框的宽高1/2
|
| 53 |
+
# --------------------------------- #
|
| 54 |
+
box_widths = 0.5 * np.array(box_widths)
|
| 55 |
+
box_heights = 0.5 * np.array(box_heights)
|
| 56 |
+
|
| 57 |
+
# --------------------------------- #
|
| 58 |
+
# 每一个特征层对应的步长
|
| 59 |
+
# --------------------------------- #
|
| 60 |
+
step_x = img_width / layer_width
|
| 61 |
+
step_y = img_height / layer_height
|
| 62 |
+
|
| 63 |
+
# --------------------------------- #
|
| 64 |
+
# 生成网格中心
|
| 65 |
+
# --------------------------------- #
|
| 66 |
+
linx = np.linspace(0.5 * step_x, img_width - 0.5 * step_x,
|
| 67 |
+
layer_width)
|
| 68 |
+
liny = np.linspace(0.5 * step_y, img_height - 0.5 * step_y,
|
| 69 |
+
layer_height)
|
| 70 |
+
centers_x, centers_y = np.meshgrid(linx, liny)
|
| 71 |
+
centers_x = centers_x.reshape(-1, 1)
|
| 72 |
+
centers_y = centers_y.reshape(-1, 1)
|
| 73 |
+
|
| 74 |
+
# 每一个先验框需要两个(centers_x, centers_y),前一个用来计算左上角,后一个计算右下角
|
| 75 |
+
num_anchors_ = len(self.aspect_ratios)
|
| 76 |
+
anchor_boxes = np.concatenate((centers_x, centers_y), axis=1)
|
| 77 |
+
anchor_boxes = np.tile(anchor_boxes, (1, 2 * num_anchors_))
|
| 78 |
+
# 获得先验框的左上角和右下角
|
| 79 |
+
anchor_boxes[:, ::4] -= box_widths
|
| 80 |
+
anchor_boxes[:, 1::4] -= box_heights
|
| 81 |
+
anchor_boxes[:, 2::4] += box_widths
|
| 82 |
+
anchor_boxes[:, 3::4] += box_heights
|
| 83 |
+
|
| 84 |
+
# --------------------------------- #
|
| 85 |
+
# 将先验框变成小数的形式
|
| 86 |
+
# 归一化
|
| 87 |
+
# --------------------------------- #
|
| 88 |
+
anchor_boxes[:, ::2] /= img_width
|
| 89 |
+
anchor_boxes[:, 1::2] /= img_height
|
| 90 |
+
anchor_boxes = anchor_boxes.reshape(-1, 4)
|
| 91 |
+
|
| 92 |
+
anchor_boxes = np.minimum(np.maximum(anchor_boxes, 0.0), 1.0)
|
| 93 |
+
return anchor_boxes
|
| 94 |
+
|
| 95 |
+
#---------------------------------------------------#
|
| 96 |
+
# 用于计算共享特征层的大小
|
| 97 |
+
#---------------------------------------------------#
|
| 98 |
+
def get_vgg_output_length(height, width):
|
| 99 |
+
filter_sizes = [3, 3, 3, 3, 3, 3, 3, 3]
|
| 100 |
+
padding = [1, 1, 1, 1, 1, 1, 0, 0]
|
| 101 |
+
stride = [2, 2, 2, 2, 2, 2, 1, 1]
|
| 102 |
+
feature_heights = []
|
| 103 |
+
feature_widths = []
|
| 104 |
+
|
| 105 |
+
for i in range(len(filter_sizes)):
|
| 106 |
+
height = (height + 2*padding[i] - filter_sizes[i]) // stride[i] + 1
|
| 107 |
+
width = (width + 2*padding[i] - filter_sizes[i]) // stride[i] + 1
|
| 108 |
+
feature_heights.append(height)
|
| 109 |
+
feature_widths.append(width)
|
| 110 |
+
return np.array(feature_heights)[-6:], np.array(feature_widths)[-6:]
|
| 111 |
+
|
| 112 |
+
def get_mobilenet_output_length(height, width):
|
| 113 |
+
filter_sizes = [3, 3, 3, 3, 3, 3, 3, 3, 3]
|
| 114 |
+
padding = [1, 1, 1, 1, 1, 1, 1, 1, 1]
|
| 115 |
+
stride = [2, 2, 2, 2, 2, 2, 2, 2, 2]
|
| 116 |
+
feature_heights = []
|
| 117 |
+
feature_widths = []
|
| 118 |
+
|
| 119 |
+
for i in range(len(filter_sizes)):
|
| 120 |
+
height = (height + 2*padding[i] - filter_sizes[i]) // stride[i] + 1
|
| 121 |
+
width = (width + 2*padding[i] - filter_sizes[i]) // stride[i] + 1
|
| 122 |
+
feature_heights.append(height)
|
| 123 |
+
feature_widths.append(width)
|
| 124 |
+
return np.array(feature_heights)[-6:], np.array(feature_widths)[-6:]
|
| 125 |
+
|
| 126 |
+
def get_anchors(input_shape = [300,300], anchors_size = [30, 60, 111, 162, 213, 264, 315], backbone = 'vgg'):
|
| 127 |
+
if backbone == 'vgg' or backbone == 'resnet50':
|
| 128 |
+
feature_heights, feature_widths = get_vgg_output_length(input_shape[0], input_shape[1])
|
| 129 |
+
aspect_ratios = [[1, 2], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2], [1, 2]]
|
| 130 |
+
else:
|
| 131 |
+
feature_heights, feature_widths = get_mobilenet_output_length(input_shape[0], input_shape[1])
|
| 132 |
+
aspect_ratios = [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
|
| 133 |
+
|
| 134 |
+
anchors = []
|
| 135 |
+
for i in range(len(feature_heights)):
|
| 136 |
+
anchor_boxes = AnchorBox(input_shape, anchors_size[i], max_size = anchors_size[i+1],
|
| 137 |
+
aspect_ratios = aspect_ratios[i]).call([feature_heights[i], feature_widths[i]])
|
| 138 |
+
anchors.append(anchor_boxes)
|
| 139 |
+
|
| 140 |
+
anchors = np.concatenate(anchors, axis=0)
|
| 141 |
+
return anchors
|
| 142 |
+
|
| 143 |
+
if __name__ == '__main__':
|
| 144 |
+
import matplotlib.pyplot as plt
|
| 145 |
+
class AnchorBox_for_Vision():
|
| 146 |
+
def __init__(self, input_shape, min_size, max_size=None, aspect_ratios=None, flip=True):
|
| 147 |
+
# 获得输入图片的大小,300x300
|
| 148 |
+
self.input_shape = input_shape
|
| 149 |
+
|
| 150 |
+
# 先验框的短边
|
| 151 |
+
self.min_size = min_size
|
| 152 |
+
# 先验框的长边
|
| 153 |
+
self.max_size = max_size
|
| 154 |
+
|
| 155 |
+
# [1, 2] => [1, 1, 2, 1/2]
|
| 156 |
+
# [1, 2, 3] => [1, 1, 2, 1/2, 3, 1/3]
|
| 157 |
+
self.aspect_ratios = []
|
| 158 |
+
for ar in aspect_ratios:
|
| 159 |
+
self.aspect_ratios.append(ar)
|
| 160 |
+
self.aspect_ratios.append(1.0 / ar)
|
| 161 |
+
|
| 162 |
+
def call(self, layer_shape, mask=None):
|
| 163 |
+
# --------------------------------- #
|
| 164 |
+
# 获取输入进来的特征层的宽和高
|
| 165 |
+
# 比如3x3
|
| 166 |
+
# --------------------------------- #
|
| 167 |
+
layer_height = layer_shape[0]
|
| 168 |
+
layer_width = layer_shape[1]
|
| 169 |
+
# --------------------------------- #
|
| 170 |
+
# 获取输入进来的图片的宽和高
|
| 171 |
+
# 比如300x300
|
| 172 |
+
# --------------------------------- #
|
| 173 |
+
img_height = self.input_shape[0]
|
| 174 |
+
img_width = self.input_shape[1]
|
| 175 |
+
|
| 176 |
+
box_widths = []
|
| 177 |
+
box_heights = []
|
| 178 |
+
# --------------------------------- #
|
| 179 |
+
# self.aspect_ratios一般有两个值
|
| 180 |
+
# [1, 1, 2, 1/2]
|
| 181 |
+
# [1, 1, 2, 1/2, 3, 1/3]
|
| 182 |
+
# --------------------------------- #
|
| 183 |
+
for ar in self.aspect_ratios:
|
| 184 |
+
# 首先添加一个较小的正方形
|
| 185 |
+
if ar == 1 and len(box_widths) == 0:
|
| 186 |
+
box_widths.append(self.min_size)
|
| 187 |
+
box_heights.append(self.min_size)
|
| 188 |
+
# 然后添加一个较大的正方形
|
| 189 |
+
elif ar == 1 and len(box_widths) > 0:
|
| 190 |
+
box_widths.append(np.sqrt(self.min_size * self.max_size))
|
| 191 |
+
box_heights.append(np.sqrt(self.min_size * self.max_size))
|
| 192 |
+
# 然后添加长方形
|
| 193 |
+
elif ar != 1:
|
| 194 |
+
box_widths.append(self.min_size * np.sqrt(ar))
|
| 195 |
+
box_heights.append(self.min_size / np.sqrt(ar))
|
| 196 |
+
|
| 197 |
+
print("box_widths:", box_widths)
|
| 198 |
+
print("box_heights:", box_heights)
|
| 199 |
+
|
| 200 |
+
# --------------------------------- #
|
| 201 |
+
# 获得所有先验框的宽高1/2
|
| 202 |
+
# --------------------------------- #
|
| 203 |
+
box_widths = 0.5 * np.array(box_widths)
|
| 204 |
+
box_heights = 0.5 * np.array(box_heights)
|
| 205 |
+
|
| 206 |
+
# --------------------------------- #
|
| 207 |
+
# 每一个特征层对应的步长
|
| 208 |
+
# 3x3的步长为100
|
| 209 |
+
# --------------------------------- #
|
| 210 |
+
step_x = img_width / layer_width
|
| 211 |
+
step_y = img_height / layer_height
|
| 212 |
+
|
| 213 |
+
# --------------------------------- #
|
| 214 |
+
# 生成网格中心
|
| 215 |
+
# --------------------------------- #
|
| 216 |
+
linx = np.linspace(0.5 * step_x, img_width - 0.5 * step_x, layer_width)
|
| 217 |
+
liny = np.linspace(0.5 * step_y, img_height - 0.5 * step_y, layer_height)
|
| 218 |
+
# 构建网格
|
| 219 |
+
centers_x, centers_y = np.meshgrid(linx, liny)
|
| 220 |
+
centers_x = centers_x.reshape(-1, 1)
|
| 221 |
+
centers_y = centers_y.reshape(-1, 1)
|
| 222 |
+
|
| 223 |
+
if layer_height == 3:
|
| 224 |
+
fig = plt.figure()
|
| 225 |
+
ax = fig.add_subplot(111)
|
| 226 |
+
plt.ylim(-50,350)
|
| 227 |
+
plt.xlim(-50,350)
|
| 228 |
+
plt.scatter(centers_x,centers_y)
|
| 229 |
+
|
| 230 |
+
# 每一个先验框需要两个(centers_x, centers_y),前一个用来计算左上角,后一个计算右下角
|
| 231 |
+
num_anchors_ = len(self.aspect_ratios)
|
| 232 |
+
anchor_boxes = np.concatenate((centers_x, centers_y), axis=1)
|
| 233 |
+
anchor_boxes = np.tile(anchor_boxes, (1, 2 * num_anchors_))
|
| 234 |
+
|
| 235 |
+
# 获得先验框的左上角和右下角
|
| 236 |
+
anchor_boxes[:, ::4] -= box_widths
|
| 237 |
+
anchor_boxes[:, 1::4] -= box_heights
|
| 238 |
+
anchor_boxes[:, 2::4] += box_widths
|
| 239 |
+
anchor_boxes[:, 3::4] += box_heights
|
| 240 |
+
|
| 241 |
+
print(np.shape(anchor_boxes))
|
| 242 |
+
if layer_height == 3:
|
| 243 |
+
rect1 = plt.Rectangle([anchor_boxes[4, 0],anchor_boxes[4, 1]],box_widths[0]*2,box_heights[0]*2,color="r",fill=False)
|
| 244 |
+
rect2 = plt.Rectangle([anchor_boxes[4, 4],anchor_boxes[4, 5]],box_widths[1]*2,box_heights[1]*2,color="r",fill=False)
|
| 245 |
+
rect3 = plt.Rectangle([anchor_boxes[4, 8],anchor_boxes[4, 9]],box_widths[2]*2,box_heights[2]*2,color="r",fill=False)
|
| 246 |
+
rect4 = plt.Rectangle([anchor_boxes[4, 12],anchor_boxes[4, 13]],box_widths[3]*2,box_heights[3]*2,color="r",fill=False)
|
| 247 |
+
|
| 248 |
+
ax.add_patch(rect1)
|
| 249 |
+
ax.add_patch(rect2)
|
| 250 |
+
ax.add_patch(rect3)
|
| 251 |
+
ax.add_patch(rect4)
|
| 252 |
+
|
| 253 |
+
plt.show()
|
| 254 |
+
# --------------------------------- #
|
| 255 |
+
# 将先验框变成小数的形式
|
| 256 |
+
# 归一化
|
| 257 |
+
# --------------------------------- #
|
| 258 |
+
anchor_boxes[:, ::2] /= img_width
|
| 259 |
+
anchor_boxes[:, 1::2] /= img_height
|
| 260 |
+
anchor_boxes = anchor_boxes.reshape(-1, 4)
|
| 261 |
+
|
| 262 |
+
anchor_boxes = np.minimum(np.maximum(anchor_boxes, 0.0), 1.0)
|
| 263 |
+
return anchor_boxes
|
| 264 |
+
|
| 265 |
+
# 输入图片大小为300, 300
|
| 266 |
+
input_shape = [300, 300]
|
| 267 |
+
# 指定先验框的大小,即宽高
|
| 268 |
+
anchors_size = [30, 60, 111, 162, 213, 264, 315]
|
| 269 |
+
# feature_heights [38, 19, 10, 5, 3, 1]
|
| 270 |
+
# feature_widths [38, 19, 10, 5, 3, 1]
|
| 271 |
+
feature_heights, feature_widths = get_vgg_output_length(input_shape[0], input_shape[1])
|
| 272 |
+
# 对先验框的数量进行一个指定 4,6
|
| 273 |
+
aspect_ratios = [[1, 2], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2], [1, 2]]
|
| 274 |
+
|
| 275 |
+
anchors = []
|
| 276 |
+
for i in range(len(feature_heights)):
|
| 277 |
+
anchors.append(AnchorBox_for_Vision(input_shape, anchors_size[i], max_size = anchors_size[i+1],
|
| 278 |
+
aspect_ratios = aspect_ratios[i]).call([feature_heights[i], feature_widths[i]]))
|
| 279 |
+
|
| 280 |
+
anchors = np.concatenate(anchors, axis=0)
|
| 281 |
+
print(np.shape(anchors))
|