├── LICENSE ├── README-CornerNet-lite.md ├── README.md ├── __init__.py ├── conda_packagelist.txt ├── configs ├── CornerNet-multi_scale.json ├── CornerNet.json ├── CornerNet_Saccade.json ├── CornerNet_Saccade_anno.json └── CornerNet_Squeeze.json ├── core ├── __init__.py ├── base.py ├── config.py ├── dbs │ ├── __init__.py │ ├── base.py │ ├── coco.py │ ├── dagm.py │ ├── detection.py │ └── myData.py ├── detectors.py ├── external │ ├── Makefile │ ├── __init__.py │ ├── bbox.c │ ├── bbox.cpython-35m-x86_64-linux-gnu.so │ ├── bbox.cpython-37m-x86_64-linux-gnu.so │ ├── bbox.pyx │ ├── nms.c │ ├── nms.cpython-35m-x86_64-linux-gnu.so │ ├── nms.cpython-37m-x86_64-linux-gnu.so │ ├── nms.pyx │ └── setup.py ├── font_lib │ └── Microsoft-Yahei-UI-Light.ttc ├── models │ ├── CornerNet.py │ ├── CornerNet_Saccade.py │ ├── CornerNet_Squeeze.py │ ├── __init__.py │ └── py_utils │ │ ├── __init__.py │ │ ├── _cpools │ │ ├── .gitignore │ │ ├── .idea │ │ │ ├── _cpools.iml │ │ │ ├── misc.xml │ │ │ ├── modules.xml │ │ │ ├── vcs.xml │ │ │ └── workspace.xml │ │ ├── __init__.py │ │ ├── setup.py │ │ └── src │ │ │ ├── bottom_pool.cpp │ │ │ ├── left_pool.cpp │ │ │ ├── right_pool.cpp │ │ │ └── top_pool.cpp │ │ ├── data_parallel.py │ │ ├── losses.py │ │ ├── modules.py │ │ ├── scatter_gather.py │ │ └── utils.py ├── nnet │ ├── __init__.py │ └── py_factory.py ├── paths.py ├── sample │ ├── __init__.py │ ├── cornernet.py │ ├── cornernet_saccade.py │ └── utils.py ├── test │ ├── __init__.py │ ├── cornernet.py │ └── cornernet_saccade.py ├── utils │ ├── __init__.py │ ├── timer.py │ └── tqdm.py └── vis_utils.py ├── data └── myData │ ├── csv2xml.py │ ├── data_split.py │ └── 说明.txt ├── demo.jpg ├── demo.py ├── demo_out.jpg ├── docs ├── iou.png ├── metric.png ├── pic1.png └── score.png ├── evaluate.py ├── evaluatemyData.py ├── requirement.txt ├── train.py └── trainmyData.py /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Princeton University 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README-CornerNet-lite.md: -------------------------------------------------------------------------------- 1 | # CornerNet-Lite: Training, Evaluation and Testing Code 2 | Code for reproducing results in the following paper: 3 | 4 | [**CornerNet-Lite: Efficient Keypoint Based Object Detection**](https://arxiv.org/abs/1904.08900) 5 | Hei Law, Yun Teng, Olga Russakovsky, Jia Deng 6 | *arXiv:1904.08900* 7 | 8 | ## Getting Started 9 | ### Software Requirement 10 | - Python 3.7 11 | - PyTorch 1.0.0 12 | - CUDA 10 13 | - GCC 4.9.2 or above 14 | 15 | ### Installing Dependencies 16 | Please first install [Anaconda](https://anaconda.org) and create an Anaconda environment using the provided package list `conda_packagelist.txt`. 17 | ``` 18 | conda create --name CornerNet_Lite --file conda_packagelist.txt --channel pytorch 19 | ``` 20 | 21 | After you create the environment, please activate it. 22 | ``` 23 | source activate CornerNet_Lite 24 | ``` 25 | 26 | ### Compiling Corner Pooling Layers 27 | Compile the C++ implementation of the corner pooling layers. (GCC4.9.2 or above is required.) 28 | ``` 29 | cd /core/models/py_utils/_cpools/ 30 | python setup.py install --user 31 | ``` 32 | 33 | ### Compiling NMS 34 | Compile the NMS code which are originally from [Faster R-CNN](https://github.com/rbgirshick/py-faster-rcnn/blob/master/lib/nms/cpu_nms.pyx) and [Soft-NMS](https://github.com/bharatsingh430/soft-nms/blob/master/lib/nms/cpu_nms.pyx). 35 | ``` 36 | cd /core/external 37 | make 38 | ``` 39 | 40 | ### Downloading Models 41 | In this repo, we provide models for the following detectors: 42 | - [CornerNet-Saccade](https://drive.google.com/file/d/1MQDyPRI0HgDHxHToudHqQ-2m8TVBciaa/view?usp=sharing) 43 | - [CornerNet-Squeeze](https://drive.google.com/file/d/1qM8BBYCLUBcZx_UmLT0qMXNTh-Yshp4X/view?usp=sharing) 44 | - [CornerNet](https://drive.google.com/file/d/1e8At_iZWyXQgLlMwHkB83kN-AN85Uff1/view?usp=sharing) 45 | 46 | Put the CornerNet-Saccade model under `/cache/nnet/CornerNet_Saccade/`, CornerNet-Squeeze model under `/cache/nnet/CornerNet_Squeeze/` and CornerNet model under `/cache/nnet/CornerNet/`. (\* Note we use underscore instead of dash in both the directory names for CornerNet-Saccade and CornerNet-Squeeze.) 47 | 48 | Note: The CornerNet model is the same as the one in the original [CornerNet repo](https://github.com/princeton-vl/CornerNet). We just ported it to this new repo. 49 | 50 | ### Running the Demo Script 51 | After downloading the models, you should be able to use the detectors on your own images. We provide a demo script `demo.py` to test if the repo is installed correctly. 52 | ``` 53 | python demo.py 54 | ``` 55 | This script applies CornerNet-Saccade to `demo.jpg` and writes the results to `demo_out.jpg`. 56 | 57 | In the demo script, the default detector is CornerNet-Saccade. You can modify the demo script to test different detectors. For example, if you want to test CornerNet-Squeeze: 58 | ```python 59 | #!/usr/bin/env python 60 | 61 | import cv2 62 | from core.detectors import CornerNet_Squeeze 63 | from core.vis_utils import draw_bboxes 64 | 65 | detector = CornerNet_Squeeze() 66 | image = cv2.imread("demo.jpg") 67 | 68 | bboxes = detector(image) 69 | image = draw_bboxes(image, bboxes) 70 | cv2.imwrite("demo_out.jpg", image) 71 | ``` 72 | 73 | ### Using CornerNet-Lite in Your Project 74 | It is also easy to use CornerNet-Lite in your project. You will need to change the directory name from `CornerNet-Lite` to `CornerNet_Lite`. Otherwise, you won't be able to import CornerNet-Lite. 75 | ``` 76 | Your project 77 | │ README.md 78 | │ ... 79 | │ foo.py 80 | │ 81 | └───CornerNet_Lite 82 | │ 83 | └───directory1 84 | │ 85 | └───... 86 | ``` 87 | 88 | In `foo.py`, you can easily import CornerNet-Saccade by adding: 89 | ```python 90 | from CornerNet_Lite import CornerNet_Saccade 91 | 92 | def foo(): 93 | cornernet = CornerNet_Saccade() 94 | # CornerNet_Saccade is ready to use 95 | 96 | image = cv2.imread('/path/to/your/image') 97 | bboxes = cornernet(image) 98 | ``` 99 | 100 | If you want to train or evaluate the detectors on COCO, please move on to the following steps. 101 | 102 | ## Training and Evaluation 103 | 104 | ### Installing MS COCO APIs 105 | ``` 106 | mkdir -p /data 107 | cd /data 108 | git clone git@github.com:cocodataset/cocoapi.git coco 109 | cd /data/coco/PythonAPI 110 | make install 111 | ``` 112 | 113 | ### Downloading MS COCO Data 114 | - Download the training/validation split we use in our paper from [here](https://drive.google.com/file/d/1dop4188xo5lXDkGtOZUzy2SHOD_COXz4/view?usp=sharing) (originally from [Faster R-CNN](https://github.com/rbgirshick/py-faster-rcnn/tree/master/data)) 115 | - Unzip the file and place `annotations` under `/data/coco` 116 | - Download the images (2014 Train, 2014 Val, 2017 Test) from [here](http://cocodataset.org/#download) 117 | - Create 3 directories, `trainval2014`, `minival2014` and `testdev2017`, under `/data/coco/images/` 118 | - Copy the training/validation/testing images to the corresponding directories according to the annotation files 119 | 120 | To train and evaluate a network, you will need to create a configuration file, which defines the hyperparameters, and a model file, which defines the network architecture. The configuration file should be in JSON format and placed in `/configs/`. Each configuration file should have a corresponding model file in `/core/models/`. i.e. If there is a `.json` in `/configs/`, there should be a `.py` in `/core/models/`. There is only one exception which we will mention later. 121 | 122 | ### Training and Evaluating a Model 123 | To train a model: 124 | ``` 125 | python train.py 126 | ``` 127 | 128 | We provide the configuration files and the model files for CornerNet-Saccade, CornerNet-Squeeze and CornerNet in this repo. Please check the configuration files in `/configs/`. 129 | 130 | To train CornerNet-Saccade: 131 | ``` 132 | python train.py CornerNet_Saccade 133 | ``` 134 | Please adjust the batch size in `CornerNet_Saccade.json` to accommodate the number of GPUs that are available to you. 135 | 136 | To evaluate the trained model: 137 | ``` 138 | python evaluate.py CornerNet_Saccade --testiter 500000 --split 139 | ``` 140 | 141 | If you want to test different hyperparameters during evaluation and do not want to overwrite the original configuration file, you can do so by creating a configuration file with a suffix (`-.json`). There is no need to create `-.py` in `/core/models/`. 142 | 143 | To use the new configuration file: 144 | ``` 145 | python evaluate.py --testiter --split --suffix 146 | ``` 147 | 148 | We also include a configuration file for CornerNet under multi-scale setting, which is `CornerNet-multi_scale.json`, in this repo. 149 | 150 | To use the multi-scale configuration file: 151 | ``` 152 | python evaluate.py CornerNet --testiter --split --suffix multi_scale 153 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .core.detectors import CornerNet, CornerNet_Squeeze, CornerNet_Saccade 2 | from .core.vis_utils import draw_bboxes 3 | -------------------------------------------------------------------------------- /conda_packagelist.txt: -------------------------------------------------------------------------------- 1 | # This file may be used to create an environment using: 2 | # $ conda create --name --file 3 | # platform: linux-64 4 | blas=1.0=mkl 5 | bzip2=1.0.6=h14c3975_5 6 | ca-certificates=2018.12.5=0 7 | cairo=1.14.12=h8948797_3 8 | certifi=2018.11.29=py37_0 9 | cffi=1.11.5=py37he75722e_1 10 | cuda100=1.0=0 11 | cycler=0.10.0=py37_0 12 | cython=0.28.5=py37hf484d3e_0 13 | dbus=1.13.2=h714fa37_1 14 | expat=2.2.6=he6710b0_0 15 | ffmpeg=4.0=hcdf2ecd_0 16 | fontconfig=2.13.0=h9420a91_0 17 | freeglut=3.0.0=hf484d3e_5 18 | freetype=2.9.1=h8a8886c_1 19 | glib=2.56.2=hd408876_0 20 | graphite2=1.3.12=h23475e2_2 21 | gst-plugins-base=1.14.0=hbbd80ab_1 22 | gstreamer=1.14.0=hb453b48_1 23 | harfbuzz=1.8.8=hffaf4a1_0 24 | hdf5=1.10.2=hba1933b_1 25 | icu=58.2=h9c2bf20_1 26 | intel-openmp=2019.0=118 27 | jasper=2.0.14=h07fcdf6_1 28 | jpeg=9b=h024ee3a_2 29 | kiwisolver=1.0.1=py37hf484d3e_0 30 | libedit=3.1.20170329=h6b74fdf_2 31 | libffi=3.2.1=hd88cf55_4 32 | libgcc-ng=8.2.0=hdf63c60_1 33 | libgfortran-ng=7.3.0=hdf63c60_0 34 | libglu=9.0.0=hf484d3e_1 35 | libopencv=3.4.2=hb342d67_1 36 | libopus=1.2.1=hb9ed12e_0 37 | libpng=1.6.35=hbc83047_0 38 | libstdcxx-ng=8.2.0=hdf63c60_1 39 | libtiff=4.0.9=he85c1e1_2 40 | libuuid=1.0.3=h1bed415_2 41 | libvpx=1.7.0=h439df22_0 42 | libxcb=1.13=h1bed415_1 43 | libxml2=2.9.8=h26e45fe_1 44 | matplotlib=3.0.2=py37h5429711_0 45 | mkl=2018.0.3=1 46 | mkl_fft=1.0.6=py37h7dd41cf_0 47 | mkl_random=1.0.1=py37h4414c95_1 48 | ncurses=6.1=hf484d3e_0 49 | ninja=1.8.2=py37h6bb024c_1 50 | numpy=1.15.4=py37h1d66e8a_0 51 | numpy-base=1.15.4=py37h81de0dd_0 52 | olefile=0.46=py37_0 53 | opencv=3.4.2=py37h6fd60c2_1 54 | openssl=1.1.1a=h7b6447c_0 55 | pcre=8.42=h439df22_0 56 | pillow=5.2.0=py37heded4f4_0 57 | pip=10.0.1=py37_0 58 | pixman=0.34.0=hceecf20_3 59 | py-opencv=3.4.2=py37hb342d67_1 60 | pycparser=2.18=py37_1 61 | pyparsing=2.2.0=py37_1 62 | pyqt=5.9.2=py37h05f1152_2 63 | python=3.7.1=h0371630_3 64 | python-dateutil=2.7.3=py37_0 65 | pytorch=1.0.0=py3.7_cuda10.0.130_cudnn7.4.1_1 66 | pytz=2018.5=py37_0 67 | qt=5.9.7=h5867ecd_1 68 | readline=7.0=h7b6447c_5 69 | scikit-learn=0.19.1=py37hedc7406_0 70 | scipy=1.1.0=py37hfa4b5c9_1 71 | setuptools=40.2.0=py37_0 72 | sip=4.19.8=py37hf484d3e_0 73 | six=1.11.0=py37_1 74 | sqlite=3.25.3=h7b6447c_0 75 | tk=8.6.8=hbc83047_0 76 | torchvision=0.2.1=py37_1 77 | tornado=5.1=py37h14c3975_0 78 | tqdm=4.25.0=py37h28b3542_0 79 | wheel=0.31.1=py37_0 80 | xz=5.2.4=h14c3975_4 81 | zlib=1.2.11=ha838bed_2 82 | -------------------------------------------------------------------------------- /configs/CornerNet-multi_scale.json: -------------------------------------------------------------------------------- 1 | { 2 | "system": { 3 | "dataset": "DAGM", 4 | "batch_size": 49, 5 | "sampling_function": "cornernet", 6 | 7 | "train_split": "trainval", 8 | "val_split": "minival", 9 | 10 | "learning_rate": 0.00025, 11 | "decay_rate": 10, 12 | 13 | "val_iter": 100, 14 | 15 | "opt_algo": "adam", 16 | "prefetch_size": 5, 17 | 18 | "max_iter": 500000, 19 | "stepsize": 450000, 20 | "snapshot": 5000, 21 | 22 | "chunk_sizes": [4, 5, 5, 5, 5, 5, 5, 5, 5, 5], 23 | 24 | "data_dir": "./data" 25 | }, 26 | 27 | "db": { 28 | "rand_scale_min": 0.6, 29 | "rand_scale_max": 1.4, 30 | "rand_scale_step": 0.1, 31 | "rand_scales": null, 32 | 33 | "rand_crop": true, 34 | "rand_color": true, 35 | 36 | "border": 128, 37 | "gaussian_bump": true, 38 | 39 | "input_size": [511, 511], 40 | "output_sizes": [[128, 128]], 41 | 42 | "test_scales": [0.5, 0.75, 1, 1.25, 1.5], 43 | 44 | "top_k": 100, 45 | "categories": 10, 46 | "ae_threshold": 0.5, 47 | "nms_threshold": 0.5, 48 | 49 | "merge_bbox": true, 50 | "weight_exp": 10, 51 | 52 | "max_per_image": 100 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /configs/CornerNet.json: -------------------------------------------------------------------------------- 1 | { 2 | "system": { 3 | "dataset": "DAGM", 4 | "batch_size": 4, 5 | "sampling_function": "cornernet", 6 | 7 | "train_split": "traindagm", 8 | "val_split": "testdagm", 9 | 10 | "learning_rate": 0.00025, 11 | "decay_rate": 10, 12 | 13 | "val_iter": 100, 14 | 15 | "opt_algo": "adam", 16 | "prefetch_size": 5, 17 | 18 | "max_iter": 5000, 19 | "stepsize": 800, 20 | "snapshot": 400, 21 | 22 | "chunk_sizes": [4], 23 | 24 | "data_dir": "./data" #数据集的根目录 25 | }, 26 | 27 | "db": { 28 | "rand_scale_min": 0.6,#随机裁剪比例[0.6,0.7,...,1.4] 29 | "rand_scale_max": 1.4, 30 | "rand_scale_step": 0.1, 31 | "rand_scales": null, 32 | 33 | "rand_crop": true, #随机裁剪 34 | "rand_color": true, #色彩抖动 35 | 36 | "border": 128, #随机裁剪默认裁剪块的尺寸 37 | "gaussian_bump": true, 38 | "gaussian_iou": 0.3, 39 | 40 | "input_size": [511, 511], 41 | "output_sizes": [[128, 128]], 42 | 43 | "test_scales": [1], #测试图片的缩放比例 44 | 45 | "top_k": 100, 46 | "categories": 10, 47 | "ae_threshold": 0.5, 48 | "nms_threshold": 0.5, 49 | 50 | "max_per_image": 100 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /configs/CornerNet_Saccade.json: -------------------------------------------------------------------------------- 1 | { 2 | "system": { 3 | "dataset": "myData", 4 | "batch_size": 32, 5 | "sampling_function": "cornernet_saccade", 6 | "train_split": "train", 7 | "val_split": "valid", 8 | "learning_rate": 0.00025, 9 | "decay_rate": 10, 10 | "val_iter": 100, 11 | "opt_algo": "adam", 12 | "prefetch_size": 5, 13 | "max_iter": 100000, 14 | "stepsize": 200, 15 | "snapshot": 5000, 16 | "chunk_sizes": [ 17 | 32 18 | ], 19 | "pretrain":"./pretrain/CornerNet_Saccade_500000.pkl" 20 | }, 21 | "db": { 22 | "rand_scale_min": 0.5, 23 | "rand_scale_max": 1.1, 24 | "rand_scale_step": 0.1, 25 | "rand_scales": null, 26 | "rand_full_crop": true, 27 | "gaussian_bump": true, 28 | "gaussian_iou": 0.5, 29 | "min_scale": 16, 30 | "view_sizes": [], 31 | "height_mult": 31, 32 | "width_mult": 31, 33 | "input_size": [ 34 | 255, 35 | 255 36 | ], 37 | "output_sizes": [ 38 | [ 39 | 64, 40 | 64 41 | ] 42 | ], 43 | "att_max_crops": 30, 44 | "att_scales": [ 45 | [ 46 | 1, 47 | 2, 48 | 4 49 | ] 50 | ], 51 | "att_thresholds": [ 52 | 0.3 53 | ], 54 | "top_k": 12, 55 | "num_dets": 12, 56 | "categories": 21, 57 | "ae_threshold": 0.3, 58 | "nms_threshold": 0.5, 59 | "max_per_image": 100 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /configs/CornerNet_Saccade_anno.json: -------------------------------------------------------------------------------- 1 | { 2 | "system": { 3 | "dataset": "myData", #数据集 4 | "batch_size": 4, #batch_size 5 | "sampling_function": "cornernet_saccade", #数据增强策略 6 | "train_split": "traindagm", #训练集 7 | "val_split": "testdagm", #验证集 8 | "learning_rate": 0.00025, #初始学习率 9 | "decay_rate": 10, #学习率衰减因子 10 | "val_iter": 100,#每迭代val_iter计算一次val loss 11 | "opt_algo": "adam",#优化器 12 | "prefetch_size": 5,#队列预取数据量 13 | "max_iter": 8000,#训练迭代的总次数 14 | "stepsize": 200, #训练时每迭代stepsize次学习率衰减为原来的1/decay_rate 15 | "snapshot": 1000, #训练每迭代snapshot次保存一次模型参数 16 | "chunk_sizes": [ 17 | 4 18 | ] ## 每块GPU上处理的图片数,其和等于batch_size 19 | }, 20 | "db": { 21 | "rand_scale_min": 0.5, #随机裁减比例[0.5,0.6,0.7,...,1.1] 22 | "rand_scale_max": 1.1, 23 | "rand_scale_step": 0.1, 24 | "rand_scales": null, 25 | "rand_full_crop": true,# 随机裁剪 26 | "gaussian_bump": true, #是否使用二维高斯给出惩罚减少量 27 | "gaussian_iou": 0.5, #高斯半径的大小根据object尺寸得到,bounding box 和 gt box 至少0.5 IoU 28 | "min_scale": 16, 29 | "view_sizes": [], 30 | "height_mult": 31, 31 | "width_mult": 31, 32 | "input_size": [ 33 | 255, 34 | 255 35 | ], #网络输入图片的size 36 | "output_sizes": [ 37 | [ 38 | 64, 39 | 64 40 | ] 41 | ],#网络输出图片的size 42 | "att_max_crops": 30, #和attention map相关的参数设置 43 | "att_scales": [ 44 | [ 45 | 1, 46 | 2, 47 | 4 48 | ] 49 | ], 50 | "att_thresholds": [ 51 | 0.3 52 | ], #概率>0.3的location被筛出来 53 | "top_k": 12, ## maximum number of crops to process 54 | "num_dets": 12, 55 | "categories": 10, #类别数 56 | "ae_threshold": 0.3, #测试时,仅处理attention maps上 score > as_threshold=0.3 的locations 57 | "nms_threshold": 0.5, #nms的阈值 58 | "max_per_image": 100 ## maximum number of objects to predict on a single image 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /configs/CornerNet_Squeeze.json: -------------------------------------------------------------------------------- 1 | { 2 | "system": { 3 | "dataset": "DAGM", 4 | "batch_size": 4, 5 | "sampling_function": "cornernet", 6 | 7 | "train_split": "traindagm", 8 | "val_split": "testdagm", 9 | 10 | "learning_rate": 0.00025, 11 | "decay_rate": 10, 12 | 13 | "val_iter": 100, 14 | 15 | "opt_algo": "adam", 16 | "prefetch_size": 5, 17 | 18 | "max_iter": 500000, 19 | "stepsize": 45000, 20 | "snapshot": 5000, 21 | 22 | "chunk_sizes": [4], 23 | 24 | "data_dir": "./data" 25 | }, 26 | 27 | "db": { 28 | "rand_scale_min": 0.6, 29 | "rand_scale_max": 1.4, 30 | "rand_scale_step": 0.1, 31 | "rand_scales": null, 32 | 33 | "rand_crop": true, 34 | "rand_color": true, 35 | 36 | "border": 128, 37 | "gaussian_bump": true, 38 | "gaussian_iou": 0.3, 39 | 40 | "input_size": [511, 511], 41 | "output_sizes": [[64, 64]], 42 | 43 | "test_scales": [1], 44 | "test_flipped": false, 45 | 46 | "top_k": 20, 47 | "num_dets": 100, 48 | "categories": 10, 49 | "ae_threshold": 0.5, 50 | "nms_threshold": 0.5, 51 | 52 | "max_per_image": 100 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataXujing/CornerNet-Lite-Pytorch/35d491b153c715d40174717ccafd89e77e33b743/core/__init__.py -------------------------------------------------------------------------------- /core/base.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from .nnet.py_factory import NetworkFactory 4 | 5 | 6 | class Base(object): 7 | def __init__(self, db, nnet, func, model=None): 8 | super(Base, self).__init__() 9 | 10 | self._db = db 11 | self._nnet = nnet 12 | self._func = func 13 | 14 | if model is not None: 15 | self._nnet.load_pretrained_params(model) 16 | 17 | self._nnet.cuda() 18 | self._nnet.eval_mode() 19 | 20 | def _inference(self, image, *args, **kwargs): 21 | return self._func(self._db, self._nnet, image.copy(), *args, **kwargs) 22 | 23 | def __call__(self, image, *args, **kwargs): 24 | categories = self._db.configs["categories"] 25 | bboxes = self._inference(image, *args, **kwargs) 26 | return {self._db.cls2name(j): bboxes[j] for j in range(1, categories + 1)} 27 | 28 | 29 | def load_cfg(cfg_file): 30 | with open(cfg_file, "r") as f: 31 | cfg = json.load(f) 32 | 33 | cfg_sys = cfg["system"] 34 | cfg_db = cfg["db"] 35 | return cfg_sys, cfg_db 36 | 37 | 38 | def load_nnet(cfg_sys, model): 39 | return NetworkFactory(cfg_sys, model) 40 | -------------------------------------------------------------------------------- /core/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | 4 | class SystemConfig(object): ## 默认设置,通过SystemConfig().update_config()更新 5 | def __init__(self): 6 | self._configs = {} 7 | self._configs["dataset"] = None 8 | self._configs["sampling_function"] = "coco_detection" 9 | 10 | # Training Config 11 | self._configs["display"] = 5 #训练时每迭代display次打印一次los 12 | self._configs["snapshot"] = 400 13 | self._configs["stepsize"] = 5000 14 | self._configs["learning_rate"] = 0.001 15 | self._configs["decay_rate"] = 10 16 | self._configs["max_iter"] = 100000 17 | self._configs["val_iter"] = 20 18 | self._configs["batch_size"] = 1 19 | self._configs["snapshot_name"] = None #模型名字 20 | self._configs["prefetch_size"] = 100 21 | self._configs["pretrain"] = None #是否存在预训练的模型 22 | self._configs["opt_algo"] = "adam" 23 | self._configs["chunk_sizes"] = None 24 | 25 | # Directories #文件夹根目录 26 | self._configs["data_dir"] = "./data" 27 | self._configs["cache_dir"] = "./cache" 28 | self._configs["config_dir"] = "./config" 29 | self._configs["result_dir"] = "./results" 30 | 31 | # Split 对应训练,验证和测试集 32 | self._configs["train_split"] = "train" 33 | self._configs["val_split"] = "valid" 34 | self._configs["test_split"] = "test" 35 | 36 | # Rng 37 | # 使用RandomState获得随机数生成器,只要随机种子seed相同,产生的随机数序列就相同 38 | self._configs["data_rng"] = np.random.RandomState(123) #随机数,数据增强中会用到 39 | self._configs["nnet_rng"] = np.random.RandomState(317) 40 | 41 | @property 42 | def chunk_sizes(self): 43 | return self._configs["chunk_sizes"] 44 | 45 | @property 46 | def train_split(self): 47 | return self._configs["train_split"] 48 | 49 | @property 50 | def val_split(self): 51 | return self._configs["val_split"] 52 | 53 | @property 54 | def test_split(self): 55 | return self._configs["test_split"] 56 | 57 | @property 58 | def full(self): 59 | return self._configs 60 | 61 | @property 62 | def sampling_function(self): 63 | return self._configs["sampling_function"] 64 | 65 | @property 66 | def data_rng(self): 67 | return self._configs["data_rng"] 68 | 69 | @property 70 | def nnet_rng(self): 71 | return self._configs["nnet_rng"] 72 | 73 | @property 74 | def opt_algo(self): 75 | return self._configs["opt_algo"] 76 | 77 | @property 78 | def prefetch_size(self): 79 | return self._configs["prefetch_size"] 80 | 81 | @property 82 | def pretrain(self): 83 | return self._configs["pretrain"] 84 | 85 | @property 86 | def result_dir(self): 87 | result_dir = os.path.join(self._configs["result_dir"], self.snapshot_name) 88 | if not os.path.exists(result_dir): 89 | os.makedirs(result_dir) 90 | return result_dir 91 | 92 | @property 93 | def dataset(self): 94 | return self._configs["dataset"] 95 | 96 | @property 97 | def snapshot_name(self): 98 | return self._configs["snapshot_name"] 99 | 100 | @property 101 | def snapshot_dir(self): 102 | snapshot_dir = os.path.join(self.cache_dir, "nnet", self.snapshot_name) 103 | 104 | if not os.path.exists(snapshot_dir): 105 | os.makedirs(snapshot_dir) 106 | return snapshot_dir 107 | 108 | @property 109 | def snapshot_file(self): 110 | snapshot_file = os.path.join(self.snapshot_dir, self.snapshot_name + "_{}.pkl") 111 | return snapshot_file 112 | 113 | @property 114 | def config_dir(self): 115 | return self._configs["config_dir"] 116 | 117 | @property 118 | def batch_size(self): 119 | return self._configs["batch_size"] 120 | 121 | @property 122 | def max_iter(self): 123 | return self._configs["max_iter"] 124 | 125 | @property 126 | def learning_rate(self): 127 | return self._configs["learning_rate"] 128 | 129 | @property 130 | def decay_rate(self): 131 | return self._configs["decay_rate"] 132 | 133 | @property 134 | def stepsize(self): 135 | return self._configs["stepsize"] 136 | 137 | @property 138 | def snapshot(self): 139 | return self._configs["snapshot"] 140 | 141 | @property 142 | def display(self): 143 | return self._configs["display"] 144 | 145 | @property 146 | def val_iter(self): 147 | return self._configs["val_iter"] 148 | 149 | @property 150 | def data_dir(self): 151 | return self._configs["data_dir"] 152 | 153 | @property 154 | def cache_dir(self): 155 | if not os.path.exists(self._configs["cache_dir"]): 156 | os.makedirs(self._configs["cache_dir"]) 157 | return self._configs["cache_dir"] 158 | 159 | def update_config(self, new): 160 | for key in new: 161 | if key in self._configs: 162 | self._configs[key] = new[key] 163 | return self 164 | -------------------------------------------------------------------------------- /core/dbs/__init__.py: -------------------------------------------------------------------------------- 1 | from .coco import COCO 2 | # from .dagm import DAGM 3 | from .myData import myData 4 | 5 | # 数据库名字 6 | datasets = { 7 | "COCO": COCO, 8 | # "DAGM": DAGM, 9 | "myData":myData 10 | } 11 | 12 | -------------------------------------------------------------------------------- /core/dbs/base.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | 4 | # 数据库的基本模型 5 | 6 | class BASE(object): 7 | def __init__(self): 8 | self._split = None # 不晓得啥 9 | self._db_inds = [] # 10 | self._image_ids = [] # 图片id 11 | 12 | self._mean = np.zeros((3, ), dtype=np.float32) # 初始化均值 13 | self._std = np.ones((3, ), dtype=np.float32) # 初始化方差 14 | self._eig_val = np.ones((3, ), dtype=np.float32) # 15 | self._eig_vec = np.zeros((3, 3), dtype=np.float32) # 16 | 17 | self._configs = {} # 参数 18 | self._configs["data_aug"] = True 19 | 20 | self._data_rng = None 21 | 22 | @property 23 | def configs(self): 24 | return self._configs 25 | 26 | @property 27 | def mean(self): 28 | return self._mean 29 | 30 | @property 31 | def std(self): 32 | return self._std 33 | 34 | @property 35 | def eig_val(self): 36 | return self._eig_val 37 | 38 | @property 39 | def eig_vec(self): 40 | return self._eig_vec 41 | 42 | @property 43 | def db_inds(self): 44 | return self._db_inds 45 | 46 | @property 47 | def split(self): 48 | return self._split 49 | 50 | # 更新参数 51 | def update_config(self, new): 52 | for key in new: 53 | if key in self._configs: 54 | self._configs[key] = new[key] 55 | 56 | # 获得特定图片id 57 | def image_ids(self, ind): 58 | return self._image_ids[ind] 59 | 60 | # 图片路径(看起来是用作基类) 61 | def image_path(self, ind): 62 | pass 63 | 64 | # 写结果(看起来是用作基类) 65 | def write_result(self, ind, all_bboxes, all_scores): 66 | pass 67 | 68 | # 评价 69 | def evaluate(self, name): 70 | pass 71 | 72 | 73 | def shuffle_inds(self, quiet=False): 74 | if self._data_rng is None: 75 | self._data_rng = np.random.RandomState(os.getpid()) 76 | 77 | if not quiet: 78 | print("shuffling indices...") 79 | rand_perm = self._data_rng.permutation(len(self._db_inds)) 80 | self._db_inds = self._db_inds[rand_perm] 81 | -------------------------------------------------------------------------------- /core/dbs/coco.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import numpy as np 4 | 5 | from .detection import DETECTION 6 | from ..paths import get_file_path 7 | 8 | 9 | # COCO bounding boxes are 0-indexed 10 | 11 | class COCO(DETECTION): 12 | def __init__(self, db_config, split=None, sys_config=None): 13 | assert split is None or sys_config is not None 14 | super(COCO, self).__init__(db_config) 15 | 16 | self._mean = np.array([0.40789654, 0.44719302, 0.47026115], dtype=np.float32) 17 | self._std = np.array([0.28863828, 0.27408164, 0.27809835], dtype=np.float32) 18 | self._eig_val = np.array([0.2141788, 0.01817699, 0.00341571], dtype=np.float32) 19 | self._eig_vec = np.array([ 20 | [-0.58752847, -0.69563484, 0.41340352], 21 | [-0.5832747, 0.00994535, -0.81221408], 22 | [-0.56089297, 0.71832671, 0.41158938] 23 | ], dtype=np.float32) 24 | 25 | self._coco_cls_ids = [ 26 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 27 | 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 28 | 24, 25, 27, 28, 31, 32, 33, 34, 35, 36, 29 | 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 30 | 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 31 | 58, 59, 60, 61, 62, 63, 64, 65, 67, 70, 32 | 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 33 | 82, 84, 85, 86, 87, 88, 89, 90 34 | ] 35 | 36 | self._coco_cls_names = [ 37 | 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 38 | 'bus', 'train', 'truck', 'boat', 'traffic light', 39 | 'fire hydrant', 'stop sign', 'parking meter', 'bench', 40 | 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 41 | 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 42 | 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 43 | 'snowboard', 'sports ball', 'kite', 'baseball bat', 44 | 'baseball glove', 'skateboard', 'surfboard', 45 | 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 46 | 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 47 | 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 48 | 'donut', 'cake', 'chair', 'couch', 'potted plant', 49 | 'bed', 'dining table', 'toilet', 'tv', 'laptop', 50 | 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 51 | 'oven', 'toaster', 'sink', 'refrigerator', 'book', 52 | 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 53 | 'toothbrush' 54 | ] 55 | # 里面放的(ind:类别,..) 最后类似(1:1,2:2,3:3,...) 56 | self._cls2coco = {ind + 1: coco_id for ind, coco_id in enumerate(self._coco_cls_ids)} 57 | # 里面放的是图片 得了和上面反过来了 [类别id:index] 58 | self._coco2cls = {coco_id: cls_id for cls_id, coco_id in self._cls2coco.items()} 59 | # 把class和名称一一对应起来了 60 | self._coco2name = {cls_id: cls_name for cls_id, cls_name in zip(self._coco_cls_ids, self._coco_cls_names)} 61 | # 相似的 这里是 名称:class 62 | self._name2coco = {cls_name: cls_id for cls_name, cls_id in self._coco2name.items()} 63 | 64 | if split is not None: 65 | coco_dir = os.path.join(sys_config.data_dir, "coco") 66 | # 这边应该是要读取的数据集 67 | self._split = { 68 | "trainval": "trainval2014", 69 | "minival": "minival2014", 70 | "testdev": "testdev2017" 71 | }[split] 72 | 73 | # 数据集路径和标签路径 74 | self._data_dir = os.path.join(coco_dir, "images", self._split) 75 | self._anno_file = os.path.join(coco_dir, "annotations", "instances_{}.json".format(self._split)) 76 | 77 | # 返回 [图片名称:[[5x1的检测图]数组]] [图片名称:图片id] 78 | self._detections, self._eval_ids = self._load_coco_annos() 79 | self._image_ids = list(self._detections.keys()) 80 | self._db_inds = np.arange(len(self._image_ids)) 81 | 82 | def _load_coco_annos(self): 83 | """ 84 | :return:detections, eval_ids 85 | 86 | 返回 [图片名称:[[5x1的检测图]数组]] [图片名称:图片id] 87 | """ 88 | from pycocotools.coco import COCO 89 | 90 | coco = COCO(self._anno_file) 91 | self._coco = coco 92 | 93 | # 获取class和图片id 94 | # 当然getCatIds()函数可以指定catNms=[], supNms=[], catIds=[],获取特定的名称 95 | class_ids = coco.getCatIds() 96 | # 当然getImgIds()函数也可以指定imgIds=[], catIds=[] ,获取特定的图片数据的index列表[因为只返回keys()啊] 97 | image_ids = coco.getImgIds() 98 | 99 | eval_ids = {} 100 | detections = {} 101 | 102 | # 根据上面的图片id依次载入图片 103 | for image_id in image_ids: 104 | image = coco.loadImgs(image_id)[0] 105 | dets = [] 106 | 107 | # 创建[图片名称:图片id]的列表 108 | eval_ids[image["file_name"]] = image_id 109 | for class_id in class_ids: 110 | # 获取这一类下面的所有图片的标签数据的id列表 111 | annotation_ids = coco.getAnnIds(imgIds=image["id"], catIds=class_id) 112 | # 再读取这其中的额标签数据 113 | annotations = coco.loadAnns(annotation_ids) 114 | # 通过类别id来获取我们的定义的类index 115 | category = self._coco2cls[class_id] 116 | # 对标签数据进行遍历 117 | for annotation in annotations: 118 | # 现在det是一个[x1,y1,x2,y2,class]的5x1数组 119 | det = annotation["bbox"] + [category] 120 | # 不晓得为啥要加 121 | det[2] += det[0] 122 | det[3] += det[1] 123 | # 添加到list里 124 | dets.append(det) 125 | # 获得图片名字 126 | file_name = image["file_name"] 127 | # 如果列表为空就返回一个[0x5]的空数组 128 | if len(dets) == 0: 129 | detections[file_name] = np.zeros((0, 5), dtype=np.float32) 130 | # 反之就把特征数组对应给文件 131 | else: 132 | detections[file_name] = np.array(dets, dtype=np.float32) 133 | return detections, eval_ids 134 | 135 | def image_path(self, ind): 136 | if self._data_dir is None: 137 | raise ValueError("Data directory is not set") 138 | 139 | db_ind = self._db_inds[ind] 140 | file_name = self._image_ids[db_ind] 141 | return os.path.join(self._data_dir, file_name) 142 | 143 | def detections(self, ind): 144 | """ 145 | :param ind: 146 | :return: self._detections[file_name].copy() 147 | 148 | 返回 [图片名称:[[5x1的检测图]数组]] 解除包装后的 149 | 即对应图片的 特征数组 [[5x1数据结构(两点坐标+类别)]] 150 | """ 151 | db_ind = self._db_inds[ind] 152 | file_name = self._image_ids[db_ind] 153 | return self._detections[file_name].copy() 154 | 155 | def cls2name(self, cls): 156 | coco = self._cls2coco[cls] 157 | return self._coco2name[coco] 158 | 159 | def _to_float(self, x): 160 | return float("{:.2f}".format(x)) 161 | 162 | def convert_to_coco(self, all_bboxes): 163 | detections = [] 164 | for image_id in all_bboxes: 165 | coco_id = self._eval_ids[image_id] 166 | for cls_ind in all_bboxes[image_id]: 167 | category_id = self._cls2coco[cls_ind] 168 | for bbox in all_bboxes[image_id][cls_ind]: 169 | bbox[2] -= bbox[0] 170 | bbox[3] -= bbox[1] 171 | 172 | score = bbox[4] 173 | bbox = list(map(self._to_float, bbox[0:4])) 174 | 175 | detection = { 176 | "image_id": coco_id, 177 | "category_id": category_id, 178 | "bbox": bbox, 179 | "score": float("{:.2f}".format(score)) 180 | } 181 | 182 | detections.append(detection) 183 | return detections 184 | 185 | def evaluate(self, result_json, cls_ids, image_ids): 186 | from pycocotools.cocoeval import COCOeval 187 | 188 | if self._split == "testdev": 189 | return None 190 | 191 | coco = self._coco 192 | 193 | eval_ids = [self._eval_ids[image_id] for image_id in image_ids] 194 | cat_ids = [self._cls2coco[cls_id] for cls_id in cls_ids] 195 | 196 | coco_dets = coco.loadRes(result_json) 197 | coco_eval = COCOeval(coco, coco_dets, "bbox") 198 | coco_eval.params.imgIds = eval_ids 199 | coco_eval.params.catIds = cat_ids 200 | coco_eval.evaluate() 201 | coco_eval.accumulate() 202 | coco_eval.summarize() 203 | return coco_eval.stats[0], coco_eval.stats[12:] 204 | -------------------------------------------------------------------------------- /core/dbs/dagm.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import numpy as np 5 | 6 | from .detection import DETECTION 7 | from ..paths import get_file_path 8 | 9 | 10 | # DAGM bounding boxes are 0-indexed 11 | 12 | class DAGM(DETECTION): 13 | def __init__(self, db_config, split=None, sys_config=None): 14 | assert split is None or sys_config is not None 15 | super(DAGM, self).__init__(db_config) 16 | 17 | self._mean = np.array([0.40789654, 0.44719302, 0.47026115], dtype=np.float32) 18 | self._std = np.array([0.28863828, 0.27408164, 0.27809835], dtype=np.float32) 19 | self._eig_val = np.array([0.2141788, 0.01817699, 0.00341571], dtype=np.float32) 20 | self._eig_vec = np.array([ 21 | [-0.58752847, -0.69563484, 0.41340352], 22 | [-0.5832747, 0.00994535, -0.81221408], 23 | [-0.56089297, 0.71832671, 0.41158938] 24 | ], dtype=np.float32) 25 | 26 | self._dagm_cls_ids = [ 27 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 28 | ] 29 | 30 | self._dagm_cls_names = [ 31 | 'Class01', 'Class02', 'Class03', 'Class04', 'Class05', 'Class06', 'Class07', 'Class08', 'Class09', 'Class10' 32 | ] 33 | # 里面放的(ind:类别,..) 最后类似(1:1,2:2,3:3,...) 34 | self._cls2dagm = {ind + 1: dagm_id for ind, dagm_id in enumerate(self._dagm_cls_ids)} 35 | # 里面放的是图片 得了和上面反过来了 [类别id:index] 36 | self._dagm2cls = {dagm_id: cls_id for cls_id, dagm_id in self._cls2dagm.items()} 37 | # 把class和名称一一对应起来了 38 | self._dagm2name = {cls_id: cls_name for cls_id, cls_name in zip(self._dagm_cls_ids, self._dagm_cls_names)} 39 | # 相似的 这里是 名称:class 40 | self._name2dagm = {cls_name: cls_id for cls_name, cls_id in self._dagm2name.items()} 41 | 42 | if split is not None: 43 | dagm_dir = os.path.join(sys_config.data_dir, "dagm") 44 | # 这边应该是要读取的数据集 45 | self._split = { 46 | "traindagm": "traindagm2007", 47 | "testdagm": "testdagm2007" 48 | }[split] 49 | 50 | # 数据集路径和标签路径 51 | self._data_dir = os.path.join(dagm_dir, "images", self._split) 52 | # self._anno_file = os.path.join(dagm_dir, "annotations", "instances_{}.json".format(self._split)) 53 | self._anno_file = os.path.join(dagm_dir, "annotations", "{}.json".format(self._split)) 54 | 55 | # 返回 [图片名称:[[5x1的检测图]数组]] [图片名称:图片id] 56 | self._detections, self._eval_ids = self._load_dagm_annos() 57 | self._image_ids = list(self._detections.keys()) 58 | self._db_inds = np.arange(len(self._image_ids)) 59 | 60 | def _load_dagm_annos(self): 61 | """ 62 | :return:detections, eval_ids 63 | 64 | 返回 [图片名称:[[5x1的检测图]数组]] [图片名称:图片id] 65 | """ 66 | from pydagmtools.dagm import DAGM 67 | 68 | dagm = DAGM(self._anno_file) 69 | self._dagm = dagm 70 | 71 | # 获取class和图片id 72 | # 当然getCatIds()函数可以指定catNms=[], supNms=[], catIds=[],获取特定的名称 73 | class_ids = dagm.getCatIds() 74 | # 当然getImgIds()函数也可以指定imgIds=[], catIds=[] ,获取特定的图片数据的index列表[因为只返回keys()啊] 75 | image_ids = dagm.getImgIds() 76 | 77 | eval_ids = {} 78 | detections = {} 79 | 80 | # 根据上面的图片id依次载入图片 81 | for image_id in image_ids: 82 | image = dagm.loadImgs(image_id)[0] 83 | dets = [] 84 | 85 | # 创建[图片名称:图片id]的列表 86 | eval_ids[image["file_name"]] = image_id 87 | for class_id in class_ids: 88 | # 获取这一类下面的所有图片的标签数据的id列表 89 | annotation_ids = dagm.getAnnIds(imgIds=image["id"], catIds=class_id) 90 | # 再读取这其中的额标签数据 91 | annotations = dagm.loadAnns(annotation_ids) 92 | # 通过类别id来获取我们的定义的类index 93 | category = self._dagm2cls[class_id] 94 | # 对标签数据进行遍历 95 | for annotation in annotations: 96 | # 现在det是一个[x1,y1,x2,y2,class]的5x1数组 97 | det = annotation["bbox"] + [category] 98 | # 不晓得为啥要加 99 | det[2] += det[0] 100 | det[3] += det[1] 101 | # 添加到list里 102 | dets.append(det) 103 | # 获得图片名字 104 | file_name = image["file_name"] 105 | # 如果列表为空就返回一个[0x5]的空数组 106 | if len(dets) == 0: 107 | detections[file_name] = np.zeros((0, 5), dtype=np.float32) 108 | # 反之就把特征数组对应给文件 109 | else: 110 | detections[file_name] = np.array(dets, dtype=np.float32) 111 | return detections, eval_ids 112 | 113 | def image_path(self, ind): 114 | if self._data_dir is None: 115 | raise ValueError("Data directory is not set") 116 | 117 | # print("----------------------------------------------------------") 118 | db_ind = self._db_inds[ind] 119 | file_name = self._image_ids[db_ind] 120 | cat_name = file_name.split("_", 1)[0] 121 | # print(cat_name) 122 | 123 | # breakpoint() 124 | return os.path.join(self._data_dir, cat_name, file_name) 125 | 126 | def detections(self, ind): 127 | """ 128 | :param ind: 129 | :return: self._detections[file_name].copy() 130 | 131 | 返回 [图片名称:[[5x1的检测图]数组]] 解除包装后的 132 | 即对应图片的 特征数组 [[5x1数据结构(两点坐标+类别)]] 133 | """ 134 | db_ind = self._db_inds[ind] 135 | file_name = self._image_ids[db_ind] 136 | return self._detections[file_name].copy() 137 | 138 | def cls2name(self, cls): 139 | dagm = self._cls2dagm[cls] 140 | return self._dagm2name[dagm] 141 | 142 | def _to_float(self, x): 143 | return float("{:.2f}".format(x)) 144 | 145 | def convert_to_dagm(self, all_bboxes): 146 | print("\033[0;33m " + "现在位置:{}/{}/.{}".format(os.getcwd(), os.path.basename(__file__), 147 | sys._getframe().f_code.co_name) + "\033[0m") 148 | 149 | detections = [] 150 | for image_id in all_bboxes: 151 | dagm_id = self._eval_ids[image_id] 152 | for cls_ind in all_bboxes[image_id]: 153 | category_id = self._cls2dagm[cls_ind] 154 | for bbox in all_bboxes[image_id][cls_ind]: 155 | bbox[2] -= bbox[0] 156 | bbox[3] -= bbox[1] 157 | 158 | score = bbox[4] 159 | bbox = list(map(self._to_float, bbox[0:4])) 160 | 161 | detection = { 162 | "image_id": dagm_id, 163 | "category_id": category_id, 164 | "bbox": bbox, 165 | "score": float("{:.2f}".format(score)) 166 | } 167 | detections.append(detection) 168 | return detections 169 | 170 | def evaluate(self, result_json, cls_ids, image_ids): 171 | from pydagmtools.dagmeval import DAGMeval 172 | 173 | print("\033[0;33m " + "现在位置:{}/{}/.{}".format(os.getcwd(), os.path.basename(__file__), 174 | sys._getframe().f_code.co_name) + "\033[0m") 175 | 176 | if self._split == "testdagm": 177 | return None 178 | 179 | dagm = self._dagm 180 | 181 | eval_ids = [self._eval_ids[image_id] for image_id in image_ids] 182 | cat_ids = [self._cls2dagm[cls_id] for cls_id in cls_ids] 183 | 184 | print("\033[0;36m " + "eval_ids():" + "\033[0m") 185 | print(eval_ids) 186 | print("\033[0;36m " + "cat_ids(类别ids):" + "\033[0m") 187 | print(cat_ids) 188 | 189 | dagm_dets = dagm.loadRes(result_json) 190 | print("\033[0;36m " + "dagm_dets(太大不输出):" + "\033[0m") 191 | print(dagm_dets) 192 | 193 | dagm_eval = DAGMeval(dagm, dagm_dets, "bbox") 194 | dagm_eval.params.imgIds = eval_ids 195 | dagm_eval.params.catIds = cat_ids 196 | dagm_eval.evaluate() 197 | dagm_eval.accumulate() 198 | dagm_eval.summarize() 199 | return dagm_eval.stats[0], dagm_eval.stats[12:] 200 | -------------------------------------------------------------------------------- /core/dbs/detection.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from .base import BASE 4 | 5 | # 继承于BASE类 6 | class DETECTION(BASE): 7 | 8 | # 只重写了初始化函数 9 | def __init__(self, db_config): 10 | super(DETECTION, self).__init__() 11 | 12 | # Configs for training 13 | 14 | # 训练时参数[类别、一般的尺度、最小尺度、最大尺度、步长] 15 | self._configs["categories"] = 10 16 | self._configs["rand_scales"] = [1] 17 | self._configs["rand_scale_min"] = 0.8 18 | self._configs["rand_scale_max"] = 1.4 19 | self._configs["rand_scale_step"] = 0.2 20 | 21 | 22 | 23 | # Configs for both training and testing 24 | # [输入尺寸、输出尺寸] 25 | self._configs["input_size"] = [383, 383] 26 | self._configs["output_sizes"] = [[96, 96], [48, 48], [24, 24], [12, 12]] 27 | 28 | # [阈值] 29 | self._configs["score_threshold"] = 0.05 30 | self._configs["nms_threshold"] = 0.7 31 | self._configs["max_per_set"] = 40 32 | self._configs["max_per_image"] = 100 33 | self._configs["top_k"] = 20 34 | self._configs["ae_threshold"] = 1 35 | self._configs["nms_kernel"] = 3 # 3x3 maxpool代替nms,意味着9留1 36 | self._configs["num_dets"] = 1000 ## 挑选scores最大的num_dets个框保留下来 37 | 38 | self._configs["nms_algorithm"] = "exp_soft_nms" #执行nms选用的算法 39 | self._configs["weight_exp"] = 8 #NMS中用到的weight 40 | self._configs["merge_bbox"] = False ## soft_nms_merge,test时可选 41 | # self._configs["merge_bbox"] = True 42 | 43 | self._configs["data_aug"] = True #数据增强 44 | self._configs["lighting"] = True 45 | 46 | self._configs["border"] = 64 47 | self._configs["gaussian_bump"] = False 48 | self._configs["gaussian_iou"] = 0.7 49 | self._configs["gaussian_radius"] = -1 # 为-1时计算高斯半径,否则该值就是所设置的高斯半径 50 | self._configs["rand_crop"] = False 51 | self._configs["rand_color"] = False 52 | self._configs["rand_center"] = True #中心裁剪 53 | 54 | self._configs["init_sizes"] = [192, 255] #downsize image to two scales(longer side = 192 or 255 pixels) 55 | self._configs["view_sizes"] = [] 56 | 57 | self._configs["min_scale"] = 16 #目标的框的长边是否大于min_scale 58 | self._configs["max_scale"] = 32 59 | 60 | self._configs["att_sizes"] = [[16, 16], [32, 32], [64, 64]] ## attention mask的size 61 | self._configs["att_ranges"] = [[96, 256], [32, 96], [0, 32]] ## 分别对应大中小目标的long side 62 | self._configs["att_ratios"] = [16, 8, 4] ## 生成mask时,分别对应大中小目标的缩小比例 63 | self._configs["att_scales"] = [1, 1.5, 2] ## 对不同尺寸的目标进行不同倍数的zoom in 64 | self._configs["att_thresholds"] = [0.3, 0.3, 0.3, 0.3] ## test时使用的attention map thresholds 65 | self._configs["att_nms_ks"] = [3, 3, 3] ## attention map nms时maxpool的核尺寸 66 | self._configs["att_max_crops"] = 8 67 | self._configs["ref_dets"] = True 68 | 69 | # Configs for testing 70 | self._configs["test_scales"] = [1] #测试数据缩放比例 71 | self._configs["test_flipped"] = True #测试翻转 72 | 73 | self.update_config(db_config) 74 | 75 | if self._configs["rand_scales"] is None: 76 | self._configs["rand_scales"] = np.arange( 77 | self._configs["rand_scale_min"], 78 | self._configs["rand_scale_max"], 79 | self._configs["rand_scale_step"] 80 | ) 81 | -------------------------------------------------------------------------------- /core/dbs/myData.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import numpy as np 4 | 5 | from .detection import DETECTION 6 | from ..paths import get_file_path 7 | 8 | 9 | import xml.etree.ElementTree as ET 10 | import json 11 | 12 | 13 | 14 | class myData(DETECTION): 15 | 16 | def __init__(self, db_config, split=None, sys_config=None): 17 | assert split is None or sys_config is not None 18 | super(myData, self).__init__(db_config) 19 | # 训练数据存放的基路径 20 | self.base_data_path = '/home/myuser/xujing/CornerNet_lite_traffic/data/' 21 | #ImageNet的mean,std 22 | self._mean = np.array([0.40789654, 0.44719302, 0.47026115], dtype=np.float32) 23 | self._std = np.array([0.28863828, 0.27408164, 0.27809835], dtype=np.float32) 24 | self._eig_val = np.array([0.2141788, 0.01817699, 0.00341571], dtype=np.float32) 25 | self._eig_vec = np.array([ 26 | [-0.58752847, -0.69563484, 0.41340352], 27 | [-0.5832747, 0.00994535, -0.81221408], 28 | [-0.56089297, 0.71832671, 0.41158938] 29 | ], dtype=np.float32) 30 | 31 | #class id #替换成自己的 32 | self._myData_cls_ids = [ 33 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,12, 13, 34 | 14, 15, 16, 17, 18, 19, 20, 21 35 | ] 36 | 37 | # class name替换成自己的 38 | self._myData_cls_names = [ 39 | 'class_1', 'class_2', 'class_3', 'class_4', 'class_5', 'class_6', 'class_7', 40 | 'class_8', 'class_9', 'class_10','class_11', 'class_12', 41 | 'class_13', 'class_14', 'class_15','class_16', 'class_17', 'class_18', 42 | 'class_19', 'class_20','class_0' 43 | 44 | ] 45 | 46 | self._cls2myData = {ind + 1: myData_id for ind, myData_id in enumerate(self._myData_cls_ids)} #{1:0,2:1,...} 47 | self._myData2cls = {myData_id: cls_id for cls_id, myData_id in self._cls2myData.items()} #{0:1,1:2,...} 48 | self._myData2name = {cls_id: cls_name for cls_id, cls_name in zip(self._myData_cls_ids, self._myData_cls_names)} #{0:"aeroplane"} 49 | self._name2myData = {cls_name: cls_id for cls_id, cls_name in self._myData2name.items()} #{"aeroplane":0} 50 | 51 | if split is not None: 52 | # myData的根路径 53 | myData_dir = os.path.join(self.base_data_path, "myData") # 路径换成自己的 54 | 55 | # 数据集的分割 56 | self._split = { 57 | "train": "train", 58 | "valid": "valid", 59 | "test": "test" 60 | }[split] 61 | 62 | # image文件存放的文件夹 63 | self._data_dir = os.path.join(myData_dir, 'JPEGImages',self._split) 64 | # Annotations文件存放的文件夹 65 | self.xml_path = os.path.join(myData_dir, "Annotations",self._split) 66 | 67 | self._detections, self._eval_ids = self._load_myData_annos() 68 | self._image_ids = list(self._detections.keys()) 69 | self._db_inds = np.arange(len(self._image_ids)) 70 | 71 | 72 | 73 | def _load_myData_annos(self): 74 | eval_ids = {} 75 | detections = {} 76 | i = 0 77 | xml_path=self.xml_path #路径换成自己的 78 | for f in os.listdir(xml_path): 79 | res = [] 80 | if not f.endswith('.xml'): 81 | continue 82 | name = f.rstrip('.xml') + str('.jpg') 83 | eval_ids[name] = i 84 | i = i + 1 85 | 86 | bndbox = dict() 87 | size = dict() 88 | current_image_id = None 89 | current_category_id = None 90 | file_name = None 91 | size['width'] = None 92 | size['height'] = None 93 | size['depth'] = None 94 | 95 | xml_file = os.path.join(xml_path, f) 96 | # print(xml_file) 97 | 98 | tree = ET.parse(xml_file) 99 | root = tree.getroot() 100 | if root.tag != 'annotation': 101 | raise Exception('pascal voc xml root element should be annotation, rather than {}'.format(root.tag)) 102 | 103 | # elem is , , , 104 | for elem in root: 105 | current_parent = elem.tag 106 | current_sub = None 107 | object_name = None 108 | 109 | if elem.tag == 'folder': 110 | continue 111 | 112 | if elem.tag == 'filename': 113 | file_name = elem.text 114 | 115 | for subelem in elem: 116 | bndbox['xmin'] = None 117 | bndbox['xmax'] = None 118 | bndbox['ymin'] = None 119 | bndbox['ymax'] = None 120 | 121 | current_sub = subelem.tag 122 | if current_parent == 'object' and subelem.tag == 'name': 123 | object_name = subelem.text 124 | 125 | 126 | elif current_parent == 'size': 127 | if size[subelem.tag] is not None: 128 | raise Exception('xml structure broken at size tag.') 129 | size[subelem.tag] = int(subelem.text) 130 | 131 | # option is , , , , when subelem is 132 | for option in subelem: 133 | if current_sub == 'bndbox': 134 | if bndbox[option.tag] is not None: 135 | raise Exception('xml structure corrupted at bndbox tag.') 136 | bndbox[option.tag] = int(float(option.text)) 137 | 138 | # only after parse the tag 139 | if bndbox['xmin'] is not None: 140 | bbox = [] 141 | 142 | # x 143 | bbox.append(bndbox['xmin']) 144 | # y 145 | bbox.append(bndbox['ymin']) 146 | # w 147 | bbox.append(bndbox['xmax']) 148 | # h 149 | bbox.append(bndbox['ymax']) 150 | category = self._name2myData[object_name] 151 | bbox.append(category) 152 | res.append(bbox) 153 | # print(res) 154 | if len(res) == 0: 155 | detections[name] = np.zeros((0, 5), dtype=np.float32) 156 | else: 157 | detections[name] = np.array(res, dtype=np.float32) 158 | return detections, eval_ids 159 | 160 | def image_path(self, ind): 161 | file_name = self._image_ids[ind] 162 | return os.path.join(self._data_dir, file_name) 163 | 164 | def detections(self, ind): 165 | file_name = self._image_ids[ind] 166 | return self._detections[file_name].copy() 167 | 168 | def cls2name(self, cls): 169 | myData = self._cls2myData[cls] 170 | return self._myData2name[myData] 171 | 172 | 173 | if __name__ == '__main__': 174 | 175 | detections, eval_ids = _load_myData_annos(xml_path) 176 | print(detections, eval_ids) 177 | -------------------------------------------------------------------------------- /core/detectors.py: -------------------------------------------------------------------------------- 1 | from .base import Base, load_cfg, load_nnet 2 | from .paths import get_file_path 3 | from .config import SystemConfig 4 | # from .dbs.coco import COCO 5 | # from .dbs.dagm import DAGM as COCO 6 | from .dbs.myData import myData as COCO 7 | 8 | 9 | class CornerNet(Base): 10 | def __init__(self): 11 | from .test.cornernet import cornernet_inference 12 | from .models.CornerNet import model 13 | 14 | cfg_path = get_file_path("..", "configs", "CornerNet.json") 15 | model_path = get_file_path("..", "cache", "nnet", "CornerNet", "CornerNet_500000.pkl") 16 | 17 | cfg_sys, cfg_db = load_cfg(cfg_path) 18 | sys_cfg = SystemConfig().update_config(cfg_sys) 19 | coco = COCO(cfg_db) 20 | 21 | cornernet = load_nnet(sys_cfg, model()) 22 | super(CornerNet, self).__init__(coco, cornernet, cornernet_inference, model=model_path) 23 | 24 | 25 | class CornerNet_Squeeze(Base): 26 | def __init__(self): 27 | from .test.cornernet import cornernet_inference 28 | from .models.CornerNet_Squeeze import model 29 | 30 | cfg_path = get_file_path("..", "configs", "CornerNet_Squeeze.json") 31 | model_path = get_file_path("..", "cache", "nnet", "CornerNet_Squeeze", "CornerNet_Squeeze_1000.pkl") 32 | 33 | cfg_sys, cfg_db = load_cfg(cfg_path) 34 | sys_cfg = SystemConfig().update_config(cfg_sys) 35 | coco = COCO(cfg_db) 36 | 37 | cornernet = load_nnet(sys_cfg, model()) 38 | super(CornerNet_Squeeze, self).__init__(coco, cornernet, cornernet_inference, model=model_path) 39 | 40 | 41 | class CornerNet_Saccade(Base): 42 | def __init__(self): 43 | from .test.cornernet_saccade import cornernet_saccade_inference 44 | from .models.CornerNet_Saccade import model 45 | 46 | cfg_path = get_file_path("..", "configs", "CornerNet_Saccade.json") 47 | model_path = get_file_path("..", "cache", "nnet", "CornerNet_Saccade", "CornerNet_Saccade_30000.pkl") 48 | 49 | cfg_sys, cfg_db = load_cfg(cfg_path) 50 | sys_cfg = SystemConfig().update_config(cfg_sys) 51 | coco = COCO(cfg_db) 52 | # print(cfg_db) 53 | 54 | cornernet = load_nnet(sys_cfg, model()) 55 | super(CornerNet_Saccade, self).__init__(coco, cornernet, cornernet_saccade_inference, model=model_path) 56 | -------------------------------------------------------------------------------- /core/external/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | python setup.py build_ext --inplace 3 | rm -rf build 4 | -------------------------------------------------------------------------------- /core/external/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataXujing/CornerNet-Lite-Pytorch/35d491b153c715d40174717ccafd89e77e33b743/core/external/__init__.py -------------------------------------------------------------------------------- /core/external/bbox.cpython-35m-x86_64-linux-gnu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataXujing/CornerNet-Lite-Pytorch/35d491b153c715d40174717ccafd89e77e33b743/core/external/bbox.cpython-35m-x86_64-linux-gnu.so -------------------------------------------------------------------------------- /core/external/bbox.cpython-37m-x86_64-linux-gnu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataXujing/CornerNet-Lite-Pytorch/35d491b153c715d40174717ccafd89e77e33b743/core/external/bbox.cpython-37m-x86_64-linux-gnu.so -------------------------------------------------------------------------------- /core/external/bbox.pyx: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------- 2 | # Fast R-CNN 3 | # Copyright (c) 2015 Microsoft 4 | # Licensed under The MIT License [see LICENSE for details] 5 | # Written by Sergey Karayev 6 | # -------------------------------------------------------- 7 | 8 | cimport cython 9 | import numpy as np 10 | cimport numpy as np 11 | 12 | DTYPE = np.float 13 | ctypedef np.float_t DTYPE_t 14 | 15 | def bbox_overlaps( 16 | np.ndarray[DTYPE_t, ndim=2] boxes, 17 | np.ndarray[DTYPE_t, ndim=2] query_boxes): 18 | """ 19 | Parameters 20 | ---------- 21 | boxes: (N, 4) ndarray of float 22 | query_boxes: (K, 4) ndarray of float 23 | Returns 24 | ------- 25 | overlaps: (N, K) ndarray of overlap between boxes and query_boxes 26 | """ 27 | cdef unsigned int N = boxes.shape[0] 28 | cdef unsigned int K = query_boxes.shape[0] 29 | cdef np.ndarray[DTYPE_t, ndim=2] overlaps = np.zeros((N, K), dtype=DTYPE) 30 | cdef DTYPE_t iw, ih, box_area 31 | cdef DTYPE_t ua 32 | cdef unsigned int k, n 33 | for k in range(K): 34 | box_area = ( 35 | (query_boxes[k, 2] - query_boxes[k, 0] + 1) * 36 | (query_boxes[k, 3] - query_boxes[k, 1] + 1) 37 | ) 38 | for n in range(N): 39 | iw = ( 40 | min(boxes[n, 2], query_boxes[k, 2]) - 41 | max(boxes[n, 0], query_boxes[k, 0]) + 1 42 | ) 43 | if iw > 0: 44 | ih = ( 45 | min(boxes[n, 3], query_boxes[k, 3]) - 46 | max(boxes[n, 1], query_boxes[k, 1]) + 1 47 | ) 48 | if ih > 0: 49 | ua = float( 50 | (boxes[n, 2] - boxes[n, 0] + 1) * 51 | (boxes[n, 3] - boxes[n, 1] + 1) + 52 | box_area - iw * ih 53 | ) 54 | overlaps[n, k] = iw * ih / ua 55 | return overlaps 56 | -------------------------------------------------------------------------------- /core/external/nms.cpython-35m-x86_64-linux-gnu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataXujing/CornerNet-Lite-Pytorch/35d491b153c715d40174717ccafd89e77e33b743/core/external/nms.cpython-35m-x86_64-linux-gnu.so -------------------------------------------------------------------------------- /core/external/nms.cpython-37m-x86_64-linux-gnu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataXujing/CornerNet-Lite-Pytorch/35d491b153c715d40174717ccafd89e77e33b743/core/external/nms.cpython-37m-x86_64-linux-gnu.so -------------------------------------------------------------------------------- /core/external/nms.pyx: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------- 2 | # Fast R-CNN 3 | # Copyright (c) 2015 Microsoft 4 | # Licensed under The MIT License [see LICENSE for details] 5 | # Written by Ross Girshick 6 | # -------------------------------------------------------- 7 | 8 | import numpy as np 9 | cimport numpy as np 10 | 11 | cdef inline np.float32_t max(np.float32_t a, np.float32_t b): 12 | return a if a >= b else b 13 | 14 | cdef inline np.float32_t min(np.float32_t a, np.float32_t b): 15 | return a if a <= b else b 16 | 17 | def nms(np.ndarray[np.float32_t, ndim=2] dets, np.float thresh): 18 | cdef np.ndarray[np.float32_t, ndim=1] x1 = dets[:, 0] 19 | cdef np.ndarray[np.float32_t, ndim=1] y1 = dets[:, 1] 20 | cdef np.ndarray[np.float32_t, ndim=1] x2 = dets[:, 2] 21 | cdef np.ndarray[np.float32_t, ndim=1] y2 = dets[:, 3] 22 | cdef np.ndarray[np.float32_t, ndim=1] scores = dets[:, 4] 23 | 24 | cdef np.ndarray[np.float32_t, ndim=1] areas = (x2 - x1 + 1) * (y2 - y1 + 1) 25 | cdef np.ndarray[np.int_t, ndim=1] order = scores.argsort()[::-1] 26 | 27 | cdef int ndets = dets.shape[0] 28 | cdef np.ndarray[np.int_t, ndim=1] suppressed = \ 29 | np.zeros((ndets), dtype=np.int) 30 | 31 | # nominal indices 32 | cdef int _i, _j 33 | # sorted indices 34 | cdef int i, j 35 | # temp variables for box i's (the box currently under consideration) 36 | cdef np.float32_t ix1, iy1, ix2, iy2, iarea 37 | # variables for computing overlap with box j (lower scoring box) 38 | cdef np.float32_t xx1, yy1, xx2, yy2 39 | cdef np.float32_t w, h 40 | cdef np.float32_t inter, ovr 41 | 42 | keep = [] 43 | for _i in range(ndets): 44 | i = order[_i] 45 | if suppressed[i] == 1: 46 | continue 47 | keep.append(i) 48 | ix1 = x1[i] 49 | iy1 = y1[i] 50 | ix2 = x2[i] 51 | iy2 = y2[i] 52 | iarea = areas[i] 53 | for _j in range(_i + 1, ndets): 54 | j = order[_j] 55 | if suppressed[j] == 1: 56 | continue 57 | xx1 = max(ix1, x1[j]) 58 | yy1 = max(iy1, y1[j]) 59 | xx2 = min(ix2, x2[j]) 60 | yy2 = min(iy2, y2[j]) 61 | w = max(0.0, xx2 - xx1 + 1) 62 | h = max(0.0, yy2 - yy1 + 1) 63 | inter = w * h 64 | ovr = inter / (iarea + areas[j] - inter) 65 | if ovr >= thresh: 66 | suppressed[j] = 1 67 | 68 | return keep 69 | 70 | def soft_nms(np.ndarray[float, ndim=2] boxes, float sigma=0.5, float Nt=0.3, float threshold=0.001, unsigned int method=0): 71 | cdef unsigned int N = boxes.shape[0] 72 | cdef float iw, ih, box_area 73 | cdef float ua 74 | cdef int pos = 0 75 | cdef float maxscore = 0 76 | cdef int maxpos = 0 77 | cdef float x1,x2,y1,y2,tx1,tx2,ty1,ty2,ts,area,weight,ov 78 | 79 | for i in range(N): 80 | maxscore = boxes[i, 4] 81 | maxpos = i 82 | 83 | tx1 = boxes[i,0] 84 | ty1 = boxes[i,1] 85 | tx2 = boxes[i,2] 86 | ty2 = boxes[i,3] 87 | ts = boxes[i,4] 88 | 89 | pos = i + 1 90 | # get max box 91 | while pos < N: 92 | if maxscore < boxes[pos, 4]: 93 | maxscore = boxes[pos, 4] 94 | maxpos = pos 95 | pos = pos + 1 96 | 97 | # add max box as a detection 98 | boxes[i,0] = boxes[maxpos,0] 99 | boxes[i,1] = boxes[maxpos,1] 100 | boxes[i,2] = boxes[maxpos,2] 101 | boxes[i,3] = boxes[maxpos,3] 102 | boxes[i,4] = boxes[maxpos,4] 103 | 104 | # swap ith box with position of max box 105 | boxes[maxpos,0] = tx1 106 | boxes[maxpos,1] = ty1 107 | boxes[maxpos,2] = tx2 108 | boxes[maxpos,3] = ty2 109 | boxes[maxpos,4] = ts 110 | 111 | tx1 = boxes[i,0] 112 | ty1 = boxes[i,1] 113 | tx2 = boxes[i,2] 114 | ty2 = boxes[i,3] 115 | ts = boxes[i,4] 116 | 117 | pos = i + 1 118 | # NMS iterations, note that N changes if detection boxes fall below threshold 119 | while pos < N: 120 | x1 = boxes[pos, 0] 121 | y1 = boxes[pos, 1] 122 | x2 = boxes[pos, 2] 123 | y2 = boxes[pos, 3] 124 | s = boxes[pos, 4] 125 | 126 | area = (x2 - x1 + 1) * (y2 - y1 + 1) 127 | iw = (min(tx2, x2) - max(tx1, x1) + 1) 128 | if iw > 0: 129 | ih = (min(ty2, y2) - max(ty1, y1) + 1) 130 | if ih > 0: 131 | ua = float((tx2 - tx1 + 1) * (ty2 - ty1 + 1) + area - iw * ih) 132 | ov = iw * ih / ua #iou between max box and detection box 133 | 134 | if method == 1: # linear 135 | if ov > Nt: 136 | weight = 1 - ov 137 | else: 138 | weight = 1 139 | elif method == 2: # gaussian 140 | weight = np.exp(-(ov * ov)/sigma) 141 | else: # original NMS 142 | if ov > Nt: 143 | weight = 0 144 | else: 145 | weight = 1 146 | 147 | boxes[pos, 4] = weight*boxes[pos, 4] 148 | 149 | # if box score falls below threshold, discard the box by swapping with last box 150 | # update N 151 | if boxes[pos, 4] < threshold: 152 | boxes[pos,0] = boxes[N-1, 0] 153 | boxes[pos,1] = boxes[N-1, 1] 154 | boxes[pos,2] = boxes[N-1, 2] 155 | boxes[pos,3] = boxes[N-1, 3] 156 | boxes[pos,4] = boxes[N-1, 4] 157 | N = N - 1 158 | pos = pos - 1 159 | 160 | pos = pos + 1 161 | 162 | keep = [i for i in range(N)] 163 | return keep 164 | 165 | def soft_nms_merge(np.ndarray[float, ndim=2] boxes, float sigma=0.5, float Nt=0.3, float threshold=0.001, unsigned int method=0, float weight_exp=6): 166 | cdef unsigned int N = boxes.shape[0] 167 | cdef float iw, ih, box_area 168 | cdef float ua 169 | cdef int pos = 0 170 | cdef float maxscore = 0 171 | cdef int maxpos = 0 172 | cdef float x1,x2,y1,y2,tx1,tx2,ty1,ty2,ts,area,weight,ov 173 | cdef float mx1,mx2,my1,my2,mts,mbs,mw 174 | 175 | for i in range(N): 176 | maxscore = boxes[i, 4] 177 | maxpos = i 178 | 179 | tx1 = boxes[i,0] 180 | ty1 = boxes[i,1] 181 | tx2 = boxes[i,2] 182 | ty2 = boxes[i,3] 183 | ts = boxes[i,4] 184 | 185 | pos = i + 1 186 | # get max box 187 | while pos < N: 188 | if maxscore < boxes[pos, 4]: 189 | maxscore = boxes[pos, 4] 190 | maxpos = pos 191 | pos = pos + 1 192 | 193 | # add max box as a detection 194 | boxes[i,0] = boxes[maxpos,0] 195 | boxes[i,1] = boxes[maxpos,1] 196 | boxes[i,2] = boxes[maxpos,2] 197 | boxes[i,3] = boxes[maxpos,3] 198 | boxes[i,4] = boxes[maxpos,4] 199 | 200 | mx1 = boxes[i, 0] * boxes[i, 5] 201 | my1 = boxes[i, 1] * boxes[i, 5] 202 | mx2 = boxes[i, 2] * boxes[i, 6] 203 | my2 = boxes[i, 3] * boxes[i, 6] 204 | mts = boxes[i, 5] 205 | mbs = boxes[i, 6] 206 | 207 | # swap ith box with position of max box 208 | boxes[maxpos,0] = tx1 209 | boxes[maxpos,1] = ty1 210 | boxes[maxpos,2] = tx2 211 | boxes[maxpos,3] = ty2 212 | boxes[maxpos,4] = ts 213 | 214 | tx1 = boxes[i,0] 215 | ty1 = boxes[i,1] 216 | tx2 = boxes[i,2] 217 | ty2 = boxes[i,3] 218 | ts = boxes[i,4] 219 | 220 | pos = i + 1 221 | # NMS iterations, note that N changes if detection boxes fall below threshold 222 | while pos < N: 223 | x1 = boxes[pos, 0] 224 | y1 = boxes[pos, 1] 225 | x2 = boxes[pos, 2] 226 | y2 = boxes[pos, 3] 227 | s = boxes[pos, 4] 228 | 229 | area = (x2 - x1 + 1) * (y2 - y1 + 1) 230 | iw = (min(tx2, x2) - max(tx1, x1) + 1) 231 | if iw > 0: 232 | ih = (min(ty2, y2) - max(ty1, y1) + 1) 233 | if ih > 0: 234 | ua = float((tx2 - tx1 + 1) * (ty2 - ty1 + 1) + area - iw * ih) 235 | ov = iw * ih / ua #iou between max box and detection box 236 | 237 | if method == 1: # linear 238 | if ov > Nt: 239 | weight = 1 - ov 240 | else: 241 | weight = 1 242 | elif method == 2: # gaussian 243 | weight = np.exp(-(ov * ov)/sigma) 244 | else: # original NMS 245 | if ov > Nt: 246 | weight = 0 247 | else: 248 | weight = 1 249 | 250 | mw = (1 - weight) ** weight_exp 251 | mx1 = mx1 + boxes[pos, 0] * boxes[pos, 5] * mw 252 | my1 = my1 + boxes[pos, 1] * boxes[pos, 5] * mw 253 | mx2 = mx2 + boxes[pos, 2] * boxes[pos, 6] * mw 254 | my2 = my2 + boxes[pos, 3] * boxes[pos, 6] * mw 255 | mts = mts + boxes[pos, 5] * mw 256 | mbs = mbs + boxes[pos, 6] * mw 257 | 258 | boxes[pos, 4] = weight*boxes[pos, 4] 259 | 260 | # if box score falls below threshold, discard the box by swapping with last box 261 | # update N 262 | if boxes[pos, 4] < threshold: 263 | boxes[pos,0] = boxes[N-1, 0] 264 | boxes[pos,1] = boxes[N-1, 1] 265 | boxes[pos,2] = boxes[N-1, 2] 266 | boxes[pos,3] = boxes[N-1, 3] 267 | boxes[pos,4] = boxes[N-1, 4] 268 | N = N - 1 269 | pos = pos - 1 270 | 271 | pos = pos + 1 272 | 273 | boxes[i, 0] = mx1 / mts 274 | boxes[i, 1] = my1 / mts 275 | boxes[i, 2] = mx2 / mbs 276 | boxes[i, 3] = my2 / mbs 277 | 278 | keep = [i for i in range(N)] 279 | return keep 280 | -------------------------------------------------------------------------------- /core/external/setup.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | from distutils.core import setup 3 | from distutils.extension import Extension 4 | from Cython.Build import cythonize 5 | 6 | extensions = [ 7 | Extension( 8 | "bbox", 9 | ["bbox.pyx"], 10 | extra_compile_args=["-Wno-cpp", "-Wno-unused-function"] 11 | ), 12 | Extension( 13 | "nms", 14 | ["nms.pyx"], 15 | extra_compile_args=["-Wno-cpp", "-Wno-unused-function"] 16 | ) 17 | ] 18 | 19 | setup( 20 | name="coco", 21 | ext_modules=cythonize(extensions), 22 | include_dirs=[numpy.get_include()] 23 | ) 24 | -------------------------------------------------------------------------------- /core/font_lib/Microsoft-Yahei-UI-Light.ttc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataXujing/CornerNet-Lite-Pytorch/35d491b153c715d40174717ccafd89e77e33b743/core/font_lib/Microsoft-Yahei-UI-Light.ttc -------------------------------------------------------------------------------- /core/models/CornerNet.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | from .py_utils import TopPool, BottomPool, LeftPool, RightPool 5 | 6 | from .py_utils.utils import convolution, residual, corner_pool 7 | from .py_utils.losses import CornerNet_Loss 8 | from .py_utils.modules import hg_module, hg, hg_net 9 | 10 | 11 | # 做池化层 12 | def make_pool_layer(dim): 13 | return nn.Sequential() 14 | 15 | 16 | # 做hg层 [看样子和残差有关],modules是模块数目 17 | def make_hg_layer(inp_dim, out_dim, modules): 18 | layers = [residual(inp_dim, out_dim, stride=2)] 19 | layers += [residual(out_dim, out_dim) for _ in range(1, modules)] 20 | return nn.Sequential(*layers) 21 | 22 | 23 | class model(hg_net): 24 | def _pred_mod(self, dim): 25 | return nn.Sequential( 26 | convolution(3, 256, 256, with_bn=False), 27 | nn.Conv2d(256, dim, (1, 1)) 28 | ) 29 | 30 | def _merge_mod(self): 31 | return nn.Sequential( 32 | nn.Conv2d(256, 256, (1, 1), bias=False), 33 | nn.BatchNorm2d(256) 34 | ) 35 | 36 | def __init__(self): 37 | stacks = 2 38 | pre = nn.Sequential( 39 | # 为啥卷积核选择7啊 40 | convolution(7, 3, 128, stride=2), 41 | residual(128, 256, stride=2) 42 | ) 43 | hg_mods = nn.ModuleList([ 44 | hg_module( 45 | 5, [256, 256, 384, 384, 384, 512], [2, 2, 2, 2, 2, 4], 46 | # 这里初始化了一个空nn序列 47 | make_pool_layer=make_pool_layer, 48 | # 赋值上面的函数 49 | make_hg_layer=make_hg_layer 50 | ) for _ in range(stacks) 51 | ]) 52 | # 依次出n个卷基层,n-1残差层 53 | cnvs = nn.ModuleList([convolution(3, 256, 256) for _ in range(stacks)]) 54 | inters = nn.ModuleList([residual(256, 256) for _ in range(stacks - 1)]) 55 | # n-1 归一化层 56 | cnvs_ = nn.ModuleList([self._merge_mod() for _ in range(stacks - 1)]) 57 | inters_ = nn.ModuleList([self._merge_mod() for _ in range(stacks - 1)]) 58 | 59 | # 上面几个模块我们依次放下来做forward 60 | hgs = hg(pre, hg_mods, cnvs, inters, cnvs_, inters_) 61 | 62 | # 新建细节的模块[corner的池化层] 63 | tl_modules = nn.ModuleList([corner_pool(256, TopPool, LeftPool) for _ in range(stacks)]) 64 | br_modules = nn.ModuleList([corner_pool(256, BottomPool, RightPool) for _ in range(stacks)]) 65 | 66 | tl_heats = nn.ModuleList([self._pred_mod(80) for _ in range(stacks)]) 67 | br_heats = nn.ModuleList([self._pred_mod(80) for _ in range(stacks)]) 68 | for tl_heat, br_heat in zip(tl_heats, br_heats): 69 | torch.nn.init.constant_(tl_heat[-1].bias, -2.19) 70 | torch.nn.init.constant_(br_heat[-1].bias, -2.19) 71 | 72 | tl_tags = nn.ModuleList([self._pred_mod(1) for _ in range(stacks)]) 73 | br_tags = nn.ModuleList([self._pred_mod(1) for _ in range(stacks)]) 74 | 75 | tl_offs = nn.ModuleList([self._pred_mod(2) for _ in range(stacks)]) 76 | br_offs = nn.ModuleList([self._pred_mod(2) for _ in range(stacks)]) 77 | 78 | super(model, self).__init__( 79 | hgs, tl_modules, br_modules, tl_heats, br_heats, 80 | tl_tags, br_tags, tl_offs, br_offs 81 | ) 82 | 83 | self.loss = CornerNet_Loss(pull_weight=1e-1, push_weight=1e-1) 84 | -------------------------------------------------------------------------------- /core/models/CornerNet_Saccade.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | from .py_utils import TopPool, BottomPool, LeftPool, RightPool 5 | 6 | from .py_utils.utils import convolution, residual, corner_pool 7 | from .py_utils.losses import CornerNet_Saccade_Loss 8 | from .py_utils.modules import saccade_net, saccade_module, saccade 9 | 10 | 11 | def make_pool_layer(dim): 12 | return nn.Sequential() 13 | 14 | 15 | def make_hg_layer(inp_dim, out_dim, modules): 16 | layers = [residual(inp_dim, out_dim, stride=2)] 17 | layers += [residual(out_dim, out_dim) for _ in range(1, modules)] 18 | return nn.Sequential(*layers) 19 | 20 | 21 | class model(saccade_net): 22 | def _pred_mod(self, dim): 23 | return nn.Sequential( 24 | convolution(3, 256, 256, with_bn=False), 25 | nn.Conv2d(256, dim, (1, 1)) 26 | ) 27 | 28 | def _merge_mod(self): 29 | return nn.Sequential( 30 | nn.Conv2d(256, 256, (1, 1), bias=False), 31 | nn.BatchNorm2d(256) 32 | ) 33 | 34 | def __init__(self): 35 | stacks = 3 36 | pre = nn.Sequential( 37 | convolution(7, 3, 128, stride=2), 38 | residual(128, 256, stride=2) 39 | ) 40 | hg_mods = nn.ModuleList([ 41 | saccade_module( 42 | 3, [256, 384, 384, 512], [1, 1, 1, 1], 43 | make_pool_layer=make_pool_layer, 44 | make_hg_layer=make_hg_layer 45 | ) for _ in range(stacks) 46 | ]) 47 | cnvs = nn.ModuleList([convolution(3, 256, 256) for _ in range(stacks)]) 48 | inters = nn.ModuleList([residual(256, 256) for _ in range(stacks - 1)]) 49 | cnvs_ = nn.ModuleList([self._merge_mod() for _ in range(stacks - 1)]) 50 | inters_ = nn.ModuleList([self._merge_mod() for _ in range(stacks - 1)]) 51 | 52 | att_mods = nn.ModuleList([ 53 | nn.ModuleList([ 54 | nn.Sequential( 55 | convolution(3, 384, 256, with_bn=False), 56 | nn.Conv2d(256, 1, (1, 1)) 57 | ), 58 | nn.Sequential( 59 | convolution(3, 384, 256, with_bn=False), 60 | nn.Conv2d(256, 1, (1, 1)) 61 | ), 62 | nn.Sequential( 63 | convolution(3, 256, 256, with_bn=False), 64 | nn.Conv2d(256, 1, (1, 1)) 65 | ) 66 | ]) for _ in range(stacks) 67 | ]) 68 | for att_mod in att_mods: 69 | for att in att_mod: 70 | torch.nn.init.constant_(att[-1].bias, -2.19) 71 | 72 | hgs = saccade(pre, hg_mods, cnvs, inters, cnvs_, inters_) 73 | 74 | tl_modules = nn.ModuleList([corner_pool(256, TopPool, LeftPool) for _ in range(stacks)]) 75 | br_modules = nn.ModuleList([corner_pool(256, BottomPool, RightPool) for _ in range(stacks)]) 76 | # 具体的类别数要修改!!! 77 | tl_heats = nn.ModuleList([self._pred_mod(21) for _ in range(stacks)]) 78 | br_heats = nn.ModuleList([self._pred_mod(21) for _ in range(stacks)]) 79 | for tl_heat, br_heat in zip(tl_heats, br_heats): 80 | torch.nn.init.constant_(tl_heat[-1].bias, -2.19) 81 | torch.nn.init.constant_(br_heat[-1].bias, -2.19) 82 | 83 | tl_tags = nn.ModuleList([self._pred_mod(1) for _ in range(stacks)]) 84 | br_tags = nn.ModuleList([self._pred_mod(1) for _ in range(stacks)]) 85 | 86 | tl_offs = nn.ModuleList([self._pred_mod(2) for _ in range(stacks)]) 87 | br_offs = nn.ModuleList([self._pred_mod(2) for _ in range(stacks)]) 88 | 89 | super(model, self).__init__( 90 | hgs, tl_modules, br_modules, tl_heats, br_heats, 91 | tl_tags, br_tags, tl_offs, br_offs, att_mods 92 | ) 93 | 94 | self.loss = CornerNet_Saccade_Loss(pull_weight=1e-1, push_weight=1e-1) 95 | -------------------------------------------------------------------------------- /core/models/CornerNet_Squeeze.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | from .py_utils import TopPool, BottomPool, LeftPool, RightPool 5 | 6 | from .py_utils.utils import convolution, corner_pool, residual 7 | from .py_utils.losses import CornerNet_Loss 8 | from .py_utils.modules import hg_module, hg, hg_net 9 | 10 | class fire_module(nn.Module): 11 | def __init__(self, inp_dim, out_dim, sr=2, stride=1): 12 | super(fire_module, self).__init__() 13 | self.conv1 = nn.Conv2d(inp_dim, out_dim // sr, kernel_size=1, stride=1, bias=False) 14 | self.bn1 = nn.BatchNorm2d(out_dim // sr) 15 | self.conv_1x1 = nn.Conv2d(out_dim // sr, out_dim // 2, kernel_size=1, stride=stride, bias=False) 16 | self.conv_3x3 = nn.Conv2d(out_dim // sr, out_dim // 2, kernel_size=3, padding=1, 17 | stride=stride, groups=out_dim // sr, bias=False) 18 | self.bn2 = nn.BatchNorm2d(out_dim) 19 | self.skip = (stride == 1 and inp_dim == out_dim) 20 | self.relu = nn.ReLU(inplace=True) 21 | 22 | def forward(self, x): 23 | conv1 = self.conv1(x) 24 | bn1 = self.bn1(conv1) 25 | conv2 = torch.cat((self.conv_1x1(bn1), self.conv_3x3(bn1)), 1) 26 | bn2 = self.bn2(conv2) 27 | if self.skip: 28 | return self.relu(bn2 + x) 29 | else: 30 | return self.relu(bn2) 31 | 32 | def make_pool_layer(dim): 33 | return nn.Sequential() 34 | 35 | def make_unpool_layer(dim): 36 | return nn.ConvTranspose2d(dim, dim, kernel_size=4, stride=2, padding=1) 37 | 38 | def make_layer(inp_dim, out_dim, modules): 39 | layers = [fire_module(inp_dim, out_dim)] 40 | layers += [fire_module(out_dim, out_dim) for _ in range(1, modules)] 41 | return nn.Sequential(*layers) 42 | 43 | def make_layer_revr(inp_dim, out_dim, modules): 44 | layers = [fire_module(inp_dim, inp_dim) for _ in range(modules - 1)] 45 | layers += [fire_module(inp_dim, out_dim)] 46 | return nn.Sequential(*layers) 47 | 48 | def make_hg_layer(inp_dim, out_dim, modules): 49 | layers = [fire_module(inp_dim, out_dim, stride=2)] 50 | layers += [fire_module(out_dim, out_dim) for _ in range(1, modules)] 51 | return nn.Sequential(*layers) 52 | 53 | class model(hg_net): 54 | def _pred_mod(self, dim): 55 | return nn.Sequential( 56 | convolution(1, 256, 256, with_bn=False), 57 | nn.Conv2d(256, dim, (1, 1)) 58 | ) 59 | 60 | def _merge_mod(self): 61 | return nn.Sequential( 62 | nn.Conv2d(256, 256, (1, 1), bias=False), 63 | nn.BatchNorm2d(256) 64 | ) 65 | 66 | def __init__(self): 67 | stacks = 2 68 | pre = nn.Sequential( 69 | convolution(7, 3, 128, stride=2), 70 | residual(128, 256, stride=2), 71 | residual(256, 256, stride=2) 72 | ) 73 | hg_mods = nn.ModuleList([ 74 | hg_module( 75 | 4, [256, 256, 384, 384, 512], [2, 2, 2, 2, 4], 76 | make_pool_layer=make_pool_layer, 77 | make_unpool_layer=make_unpool_layer, 78 | make_up_layer=make_layer, 79 | make_low_layer=make_layer, 80 | make_hg_layer_revr=make_layer_revr, 81 | make_hg_layer=make_hg_layer 82 | ) for _ in range(stacks) 83 | ]) 84 | cnvs = nn.ModuleList([convolution(3, 256, 256) for _ in range(stacks)]) 85 | inters = nn.ModuleList([residual(256, 256) for _ in range(stacks - 1)]) 86 | cnvs_ = nn.ModuleList([self._merge_mod() for _ in range(stacks - 1)]) 87 | inters_ = nn.ModuleList([self._merge_mod() for _ in range(stacks - 1)]) 88 | 89 | hgs = hg(pre, hg_mods, cnvs, inters, cnvs_, inters_) 90 | 91 | tl_modules = nn.ModuleList([corner_pool(256, TopPool, LeftPool) for _ in range(stacks)]) 92 | br_modules = nn.ModuleList([corner_pool(256, BottomPool, RightPool) for _ in range(stacks)]) 93 | 94 | tl_heats = nn.ModuleList([self._pred_mod(10) for _ in range(stacks)]) 95 | br_heats = nn.ModuleList([self._pred_mod(10) for _ in range(stacks)]) 96 | for tl_heat, br_heat in zip(tl_heats, br_heats): 97 | torch.nn.init.constant_(tl_heat[-1].bias, -2.19) 98 | torch.nn.init.constant_(br_heat[-1].bias, -2.19) 99 | 100 | tl_tags = nn.ModuleList([self._pred_mod(1) for _ in range(stacks)]) 101 | br_tags = nn.ModuleList([self._pred_mod(1) for _ in range(stacks)]) 102 | 103 | tl_offs = nn.ModuleList([self._pred_mod(2) for _ in range(stacks)]) 104 | br_offs = nn.ModuleList([self._pred_mod(2) for _ in range(stacks)]) 105 | 106 | super(model, self).__init__( 107 | hgs, tl_modules, br_modules, tl_heats, br_heats, 108 | tl_tags, br_tags, tl_offs, br_offs 109 | ) 110 | 111 | self.loss = CornerNet_Loss(pull_weight=1e-1, push_weight=1e-1) 112 | -------------------------------------------------------------------------------- /core/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataXujing/CornerNet-Lite-Pytorch/35d491b153c715d40174717ccafd89e77e33b743/core/models/__init__.py -------------------------------------------------------------------------------- /core/models/py_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._cpools import TopPool, BottomPool, LeftPool, RightPool 2 | -------------------------------------------------------------------------------- /core/models/py_utils/_cpools/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | cpools.egg-info/ 3 | dist/ 4 | -------------------------------------------------------------------------------- /core/models/py_utils/_cpools/.idea/_cpools.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /core/models/py_utils/_cpools/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /core/models/py_utils/_cpools/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /core/models/py_utils/_cpools/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /core/models/py_utils/_cpools/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 65 | 66 | 71 | 72 | 73 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |