From 35bd189b702e251595d8896cf0685412e42aa70f Mon Sep 17 00:00:00 2001 From: cnn Date: Mon, 21 Jun 2021 10:28:26 +0800 Subject: [PATCH] [dev] add small dataset(spine) for s2anet, and add eval (#3401) * support --eval for s2anet * add spine dataset and config yml for s2anet * add doc for s2anet * update doc * fix typo, test=document_fix * add some comments, and update import --- configs/datasets/dota.yml | 2 +- configs/datasets/spine_coco.yml | 20 +++++ configs/dota/README.md | 76 ++++++++++++----- configs/dota/s2anet_1x_spine.yml | 26 ++++++ dataset/spine_coco/download_spine_coco.py | 28 +++++++ demo/39006.jpg | Bin 0 -> 40824 bytes ppdet/data/source/category.py | 2 +- ppdet/engine/trainer.py | 30 +++++++ ppdet/metrics/map_utils.py | 56 ++++++++++++- ppdet/metrics/metrics.py | 98 +++++++++++++++++++++- ppdet/modeling/heads/s2anet_head.py | 10 --- ppdet/utils/download.py | 3 + 12 files changed, 315 insertions(+), 36 deletions(-) create mode 100644 configs/datasets/spine_coco.yml create mode 100644 configs/dota/s2anet_1x_spine.yml create mode 100644 dataset/spine_coco/download_spine_coco.py create mode 100644 demo/39006.jpg diff --git a/configs/datasets/dota.yml b/configs/datasets/dota.yml index 2953a7994..5153163d9 100644 --- a/configs/datasets/dota.yml +++ b/configs/datasets/dota.yml @@ -1,4 +1,4 @@ -metric: COCO +metric: RBOX num_classes: 15 TrainDataset: diff --git a/configs/datasets/spine_coco.yml b/configs/datasets/spine_coco.yml new file mode 100644 index 000000000..743743dc5 --- /dev/null +++ b/configs/datasets/spine_coco.yml @@ -0,0 +1,20 @@ +metric: RBOX +num_classes: 9 + +TrainDataset: + !COCODataSet + image_dir: images + anno_path: annotations/train.json + dataset_dir: dataset/spine_coco + data_fields: ['image', 'gt_bbox', 'gt_class', 'is_crowd', 'gt_rbox'] + +EvalDataset: + !COCODataSet + image_dir: images + anno_path: annotations/valid.json + dataset_dir: dataset/spine_coco + +TestDataset: + !ImageFolder + anno_path: annotations/valid.json + dataset_dir: dataset/spine_coco diff --git a/configs/dota/README.md b/configs/dota/README.md index 371b1e39c..934e597f7 100644 --- a/configs/dota/README.md +++ b/configs/dota/README.md @@ -2,16 +2,19 @@ ## 内容 - [简介](#简介) -- [DOTA数据集](#DOTA数据集) +- [准备数据](#准备数据) +- [开始训练](#开始训练) - [模型库](#模型库) -- [训练说明](#训练说明) +- [预测部署](#预测部署) ## 简介 [S2ANet](https://arxiv.org/pdf/2008.09397.pdf)是用于检测旋转框的模型,要求使用PaddlePaddle 2.0.1(可使用pip安装) 或适当的[develop版本](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/install/Tables.html#whl-release)。 -## DOTA数据集 +## 准备数据 + +### DOTA数据 [DOTA Dataset]是航空影像中物体检测的数据集,包含2806张图像,每张图像4000*4000分辨率。 | 数据版本 | 类别数 | 图像数 | 图像尺寸 | 实例数 | 标注方式 | @@ -27,19 +30,22 @@ DOTA数据集中总共有2806张图像,其中1411张图像作为训练集,45 设置`crop_size=1024, stride=824, gap=200`参数切割数据后,训练集15749张图像,评估集5297张图像,测试集10833张图像。 -## 模型库 +### 自定义数据 -### S2ANet模型 +数据标注有两种方式: -| 模型 | GPU个数 | Conv类型 | mAP | 模型下载 | 配置文件 | -|:-----------:|:-------:|:----------:|:--------:| :----------:| :---------: | -| S2ANet | 8 | Conv | 71.42 | [model](https://paddledet.bj.bcebos.com/models/s2anet_conv_1x_dota.pdparams) | [config](https://github.com/PaddlePaddle/PaddleDetection/tree/develop/configs/dota/s2anet_conv_1x_dota.yml) | +- 第一种是标注旋转矩形,可以通过旋转矩形标注工具[roLabelImg](https://github.com/cgvict/roLabelImg) 来标注旋转矩形框。 -**注意:**这里使用`multiclass_nms`,与原作者使用nms略有不同,精度相比原始论文中高0.15 (71.27-->71.42)。 +- 第二种是标注四边形,通过脚本转成外接旋转矩形,这样得到的标注可能跟真实的物体框有一定误差。 + +然后将标注结果转换成coco标注格式,其中每个`bbox`的格式为 `[x_center, y_center, width, height, angle]`,这里角度以弧度表示。 + +参考[脊椎间盘数据集](https://aistudio.baidu.com/aistudio/datasetdetail/85885) ,我们将数据集划分为训练集(230)、测试集(57),数据地址为:[spine_coco](https://paddledet.bj.bcebos.com/data/spine_coco.tar) 。该数据集图像数量比较少,使用这个数据集可以快速训练S2ANet模型。 -## 训练说明 -### 1. 旋转框IOU计算OP +## 开始训练 + +### 1. 安装旋转框IOU计算OP 旋转框IOU计算OP[ext_op](../../ppdet/ext_op)是参考Paddle[自定义外部算子](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/07_new_op/new_custom_op.html) 的方式开发。 @@ -82,27 +88,59 @@ cd PaddleDetecetion/ppdet/ext_op python3.7 test.py ``` -### 2. 数据格式 -DOTA 数据集中实例是按照任意四边形标注,在进行训练模型前,需要参考[DOTA2COCO](https://github.com/CAPTAIN-WHU/DOTA_devkit/blob/master/DOTA2COCO.py) 转换成`[xc, yc, bow_w, bow_h, angle]`格式,并以coco数据格式存储。 +### 2. 训练 +**注意:** +配置文件中学习率是按照8卡GPU训练设置的,如果使用单卡GPU训练,请将学习率设置为原来的1/8。 + +GPU单卡训练 +```bash +export CUDA_VISIBLE_DEVICES=0 +python3.7 tools/train.py -c configs/dota/s2anet_1x_spine.yml +``` + +GPU多卡训练 +```bash +export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 +python3.7 -m paddle.distributed.launch --gpus 0,1,2,3,4,5,6,7 tools/train.py -c configs/dota/s2anet_1x_spine.yml +``` + +可以通过`--eval`开启边训练边测试。 + +### 2. 评估 +```bash +python3.7 tools/eval.py -c configs/dota/s2anet_1x_spine.yml -o weitghts=output/s2anet_1x_spine/model_final.pdparams +``` -## 评估 +### 3. 预测 +执行如下命令,会将图像预测结果保存到`output_dir`文件夹下。 +```bash +python3.7 tools/infer.py -c configs/dota/s2anet_1x_spine.yml -o weitghts=output/s2anet_1x_spine/model_final.pdparams --infer_img=demo/39006.jpg +``` +### 4. DOTA数据评估 执行如下命令,会在`output_dir`文件夹下将每个图像预测结果保存到同文件夹名的txt文本中。 ``` python3.7 tools/infer.py -c configs/dota/s2anet_1x_dota.yml -o weights=./weights/s2anet_1x_dota.pdparams --infer_dir=dota_test_images --draw_threshold=0.05 --save_txt=True --output_dir=output ``` - 请参考[DOTA_devkit](https://github.com/CAPTAIN-WHU/DOTA_devkit) 生成评估文件,评估文件格式请参考[DOTA Test](http://captain.whu.edu.cn/DOTAweb/tasks.html) ,生成zip文件,每个类一个txt文件,txt文件中每行格式为:`image_id score x1 y1 x2 y2 x3 y3 x4 y4`,提交服务器进行评估。 +## 模型库 + +### S2ANet模型 + +| 模型 | GPU个数 | Conv类型 | mAP | 模型下载 | 配置文件 | +|:-----------:|:-------:|:----------:|:--------:| :----------:| :---------: | +| S2ANet | 8 | Conv | 71.42 | [model](https://paddledet.bj.bcebos.com/models/s2anet_conv_1x_dota.pdparams) | [config](https://github.com/PaddlePaddle/PaddleDetection/tree/develop/configs/dota/s2anet_conv_1x_dota.yml) | + +**注意:**这里使用`multiclass_nms`,与原作者使用nms略有不同,精度相比原始论文中高0.15 (71.27-->71.42)。 + + ## 预测部署 Paddle中`multiclass_nms`算子的输入支持四边形输入,因此部署时可以不需要依赖旋转框IOU计算算子。 -```bash -# 预测 -CUDA_VISIBLE_DEVICES=0 python tools/infer.py -c configs/dota/s2anet_1x_dota.yml -o weights=model.pdparams --infer_img=demo/P0072__1.0__0___0.png -``` +部署教程请参考[预测部署](../../deploy/README.md) ## Citations diff --git a/configs/dota/s2anet_1x_spine.yml b/configs/dota/s2anet_1x_spine.yml new file mode 100644 index 000000000..faa5da948 --- /dev/null +++ b/configs/dota/s2anet_1x_spine.yml @@ -0,0 +1,26 @@ +_BASE_: [ + '../datasets/spine_coco.yml', + '../runtime.yml', + '_base_/s2anet_optimizer_1x.yml', + '_base_/s2anet.yml', + '_base_/s2anet_reader.yml', +] + +weights: output/s2anet_1x_spine/model_final + +# for 8 card +LearningRate: + base_lr: 0.01 + +S2ANetHead: + anchor_strides: [8, 16, 32, 64, 128] + anchor_scales: [4] + anchor_ratios: [1.0] + anchor_assign: RBoxAssigner + stacked_convs: 2 + feat_in: 256 + feat_out: 256 + num_classes: 9 + align_conv_type: 'DCN' # AlignConv Conv + align_conv_size: 3 + use_sigmoid_cls: True diff --git a/dataset/spine_coco/download_spine_coco.py b/dataset/spine_coco/download_spine_coco.py new file mode 100644 index 000000000..77d63391c --- /dev/null +++ b/dataset/spine_coco/download_spine_coco.py @@ -0,0 +1,28 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import os.path as osp +import logging +# add python path of PadleDetection to sys.path +parent_path = osp.abspath(osp.join(__file__, *(['..'] * 3))) +if parent_path not in sys.path: + sys.path.append(parent_path) + +from ppdet.utils.download import download_dataset + +logging.basicConfig(level=logging.INFO) + +download_path = osp.split(osp.realpath(sys.argv[0]))[0] +download_dataset(download_path, 'spine_coco') diff --git a/demo/39006.jpg b/demo/39006.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ce980e366cac812263d5dbe4e660209345997688 GIT binary patch literal 40824 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 max_overlap: max_overlap = overlap max_idx = i diff --git a/ppdet/metrics/metrics.py b/ppdet/metrics/metrics.py index e652fe7a7..65b18efd8 100644 --- a/ppdet/metrics/metrics.py +++ b/ppdet/metrics/metrics.py @@ -31,7 +31,12 @@ from ppdet.utils.logger import setup_logger logger = setup_logger(__name__) __all__ = [ - 'Metric', 'COCOMetric', 'VOCMetric', 'WiderFaceMetric', 'get_infer_results' + 'Metric', + 'COCOMetric', + 'VOCMetric', + 'WiderFaceMetric', + 'get_infer_results', + 'RBoxMetric', ] COCO_SIGMAS = np.array([ @@ -299,3 +304,94 @@ class WiderFaceMetric(Metric): pred_dir='output/pred', eval_mode='widerface', multi_scale=self.multi_scale) + + +class RBoxMetric(Metric): + def __init__(self, anno_file, **kwargs): + assert os.path.isfile(anno_file), \ + "anno_file {} not a file".format(anno_file) + assert os.path.exists(anno_file), "anno_file {} not exists".format( + anno_file) + self.anno_file = anno_file + self.gt_anno = json.load(open(self.anno_file)) + cats = self.gt_anno['categories'] + self.clsid2catid = {i: cat['id'] for i, cat in enumerate(cats)} + self.catid2clsid = {cat['id']: i for i, cat in enumerate(cats)} + self.catid2name = {cat['id']: cat['name'] for cat in cats} + self.classwise = kwargs.get('classwise', False) + self.output_eval = kwargs.get('output_eval', None) + # TODO: bias should be unified + self.bias = kwargs.get('bias', 0) + self.save_prediction_only = kwargs.get('save_prediction_only', False) + self.iou_type = kwargs.get('IouType', 'bbox') + self.overlap_thresh = kwargs.get('overlap_thresh', 0.5) + self.map_type = kwargs.get('map_type', '11point') + self.evaluate_difficult = kwargs.get('evaluate_difficult', False) + class_num = len(self.catid2name) + self.detection_map = DetectionMAP( + class_num=class_num, + overlap_thresh=self.overlap_thresh, + map_type=self.map_type, + is_bbox_normalized=False, + evaluate_difficult=self.evaluate_difficult, + catid2name=self.catid2name, + classwise=self.classwise) + + self.reset() + + def reset(self): + self.result_bbox = [] + self.detection_map.reset() + + def update(self, inputs, outputs): + outs = {} + # outputs Tensor -> numpy.ndarray + for k, v in outputs.items(): + outs[k] = v.numpy() if isinstance(v, paddle.Tensor) else v + + im_id = inputs['im_id'] + outs['im_id'] = im_id.numpy() if isinstance(im_id, + paddle.Tensor) else im_id + + infer_results = get_infer_results( + outs, self.clsid2catid, bias=self.bias) + self.result_bbox += infer_results[ + 'bbox'] if 'bbox' in infer_results else [] + bbox = [b['bbox'] for b in self.result_bbox] + score = [b['score'] for b in self.result_bbox] + label = [b['category_id'] for b in self.result_bbox] + label = [self.catid2clsid[e] for e in label] + gt_box = [ + e['bbox'] for e in self.gt_anno['annotations'] + if e['image_id'] == outs['im_id'] + ] + gt_label = [ + e['category_id'] for e in self.gt_anno['annotations'] + if e['image_id'] == outs['im_id'] + ] + gt_label = [self.catid2clsid[e] for e in gt_label] + self.detection_map.update(bbox, score, label, gt_box, gt_label) + + def accumulate(self): + if len(self.result_bbox) > 0: + output = "bbox.json" + if self.output_eval: + output = os.path.join(self.output_eval, output) + with open(output, 'w') as f: + json.dump(self.result_bbox, f) + logger.info('The bbox result is saved to bbox.json.') + + if self.save_prediction_only: + logger.info('The bbox result is saved to {} and do not ' + 'evaluate the mAP.'.format(output)) + else: + logger.info("Accumulating evaluatation results...") + self.detection_map.accumulate() + + def log(self): + map_stat = 100. * self.detection_map.get_map() + logger.info("mAP({:.2f}, {}) = {:.2f}%".format(self.overlap_thresh, + self.map_type, map_stat)) + + def get_results(self): + return {'bbox': [self.detection_map.get_map()]} diff --git a/ppdet/modeling/heads/s2anet_head.py b/ppdet/modeling/heads/s2anet_head.py index 54bc81ae9..ae3659d91 100644 --- a/ppdet/modeling/heads/s2anet_head.py +++ b/ppdet/modeling/heads/s2anet_head.py @@ -618,8 +618,6 @@ class S2ANetHead(nn.Layer): fam_cls_score = paddle.squeeze(fam_cls_score, axis=0) fam_cls_score1 = fam_cls_score - # gt_classes 0~14(data), feat_labels 0~14, sigmoid_focal_loss need class>=1 - feat_labels = feat_labels + 1 feat_labels = paddle.to_tensor(feat_labels) feat_labels_one_hot = F.one_hot(feat_labels, self.cls_out_channels + 1) feat_labels_one_hot = feat_labels_one_hot[:, 1:] @@ -681,9 +679,6 @@ class S2ANetHead(nn.Layer): odm_cls_score = paddle.squeeze(odm_cls_score, axis=0) odm_cls_score1 = odm_cls_score - # gt_classes 0~14(data), feat_labels 0~14, sigmoid_focal_loss need class>=1 - # for debug 0426 - feat_labels = feat_labels + 1 feat_labels = paddle.to_tensor(feat_labels) feat_labels_one_hot = F.one_hot(feat_labels, self.cls_out_channels + 1) feat_labels_one_hot = feat_labels_one_hot[:, 1:] @@ -833,10 +828,5 @@ class S2ANetHead(nn.Layer): mlvl_bboxes = paddle.concat(mlvl_bboxes, axis=0) mlvl_scores = paddle.concat(mlvl_scores) - if use_sigmoid_cls: - # Add a dummy background class to the front when using sigmoid - padding = paddle.zeros( - [mlvl_scores.shape[0], 1], dtype=mlvl_scores.dtype) - mlvl_scores = paddle.concat([padding, mlvl_scores], axis=1) return mlvl_scores, mlvl_bboxes diff --git a/ppdet/utils/download.py b/ppdet/utils/download.py index 69986bfdb..c4ecd079a 100644 --- a/ppdet/utils/download.py +++ b/ppdet/utils/download.py @@ -93,6 +93,9 @@ DATASETS = { 'roadsign_coco': ([( 'https://paddlemodels.bj.bcebos.com/object_detection/roadsign_coco.tar', '49ce5a9b5ad0d6266163cd01de4b018e', ), ], ['annotations', 'images']), + 'spine_coco': ([( + 'https://paddledet.bj.bcebos.com/data/spine_coco.tar', + '03030f42d9b6202a6e425d4becefda0d', ), ], ['annotations', 'images']), 'mot': (), 'objects365': () } -- GitLab