diff --git a/paddle/fluid/API.spec b/paddle/fluid/API.spec index 1289c1e3735d1dd52fd396ecc97c87f3707cf0c0..acf4e1ff101d8d9c86911336d0e91707ba352d75 100644 --- a/paddle/fluid/API.spec +++ b/paddle/fluid/API.spec @@ -318,7 +318,7 @@ paddle.fluid.layers.iou_similarity ArgSpec(args=['x', 'y', 'name'], varargs=None paddle.fluid.layers.box_coder ArgSpec(args=['prior_box', 'prior_box_var', 'target_box', 'code_type', 'box_normalized', 'name'], varargs=None, keywords=None, defaults=('encode_center_size', True, None)) paddle.fluid.layers.polygon_box_transform ArgSpec(args=['input', 'name'], varargs=None, keywords=None, defaults=(None,)) paddle.fluid.layers.yolov3_loss ArgSpec(args=['x', 'gtbox', 'gtlabel', 'anchors', 'class_num', 'ignore_thresh', 'loss_weight_xy', 'loss_weight_wh', 'loss_weight_conf_target', 'loss_weight_conf_notarget', 'loss_weight_class', 'name'], varargs=None, keywords=None, defaults=(None, None, None, None, None, None)) -paddle.fluid.layers.multiclass_nms ArgSpec(args=['bboxes', 'scores', 'score_threshold', 'nms_top_k', 'nms_threshold', 'keep_top_k', 'normalized', 'nms_eta', 'background_label'], varargs=None, keywords=None, defaults=(True, 1.0, 0)) +paddle.fluid.layers.multiclass_nms ArgSpec(args=['bboxes', 'scores', 'score_threshold', 'nms_top_k', 'nms_threshold', 'keep_top_k', 'normalized', 'nms_eta', 'background_label', 'name'], varargs=None, keywords=None, defaults=(True, 1.0, 0, None)) paddle.fluid.layers.accuracy ArgSpec(args=['input', 'label', 'k', 'correct', 'total'], varargs=None, keywords=None, defaults=(1, None, None)) paddle.fluid.layers.auc ArgSpec(args=['input', 'label', 'curve', 'num_thresholds', 'topk', 'slide_steps'], varargs=None, keywords=None, defaults=('ROC', 4095, 1, 1)) paddle.fluid.layers.exponential_decay ArgSpec(args=['learning_rate', 'decay_steps', 'decay_rate', 'staircase'], varargs=None, keywords=None, defaults=(False,)) diff --git a/paddle/fluid/operators/detection/bbox_util.h b/paddle/fluid/operators/detection/bbox_util.h index 0270ca77f3ee2e342ebad93731c3f1b228b6700e..6abeca1da443248d6ad3c1bcc64dd775d77f4ed8 100644 --- a/paddle/fluid/operators/detection/bbox_util.h +++ b/paddle/fluid/operators/detection/bbox_util.h @@ -93,25 +93,5 @@ void BboxOverlaps(const framework::Tensor& r_boxes, } } -template -void SliceOneClass(const platform::DeviceContext& ctx, - const framework::Tensor& items, const int class_id, - framework::Tensor* one_class_item) { - T* item_data = one_class_item->mutable_data(ctx.GetPlace()); - const T* items_data = items.data(); - const int64_t num_item = items.dims()[0]; - const int class_num = items.dims()[1]; - int item_size = 1; - if (items.dims().size() == 3) { - item_size = items.dims()[2]; - } - for (int i = 0; i < num_item; ++i) { - for (int j = 0; j < item_size; ++j) { - item_data[i * item_size + j] = - items_data[i * class_num * item_size + class_id * item_size + j]; - } - } -} - } // namespace operators } // namespace paddle diff --git a/paddle/fluid/operators/detection/multiclass_nms_op.cc b/paddle/fluid/operators/detection/multiclass_nms_op.cc index c61e3e1338dc39939480db9d267455e80fd0d216..43d6382280024cc707e8c39cfd1370c6e1d28a69 100644 --- a/paddle/fluid/operators/detection/multiclass_nms_op.cc +++ b/paddle/fluid/operators/detection/multiclass_nms_op.cc @@ -1,8 +1,11 @@ /* Copyright (c) 2018 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. @@ -10,7 +13,6 @@ limitations under the License. */ #include #include "paddle/fluid/framework/op_registry.h" -#include "paddle/fluid/operators/detection/bbox_util.h" #include "paddle/fluid/operators/detection/poly_util.h" namespace paddle { @@ -136,12 +138,9 @@ static inline T JaccardOverlap(const T* box1, const T* box2, const T inter_ymin = std::max(box1[1], box2[1]); const T inter_xmax = std::min(box1[2], box2[2]); const T inter_ymax = std::min(box1[3], box2[3]); - T inter_w = inter_xmax - inter_xmin; - T inter_h = inter_ymax - inter_ymin; - if (!normalized) { - inter_w += 1; - inter_h += 1; - } + T norm = normalized ? static_cast(0.) : static_cast(1.); + T inter_w = inter_xmax - inter_xmin + norm; + T inter_h = inter_ymax - inter_ymin + norm; const T inter_area = inter_w * inter_h; const T bbox1_area = BBoxArea(box1, normalized); const T bbox2_area = BBoxArea(box2, normalized); @@ -164,6 +163,25 @@ T PolyIoU(const T* box1, const T* box2, const size_t box_size, } } +template +void SliceOneClass(const platform::DeviceContext& ctx, + const framework::Tensor& items, const int class_id, + framework::Tensor* one_class_item) { + T* item_data = one_class_item->mutable_data(ctx.GetPlace()); + const T* items_data = items.data(); + const int64_t num_item = items.dims()[0]; + const int class_num = items.dims()[1]; + int item_size = 1; + if (items.dims().size() == 3) { + item_size = items.dims()[2]; + } + for (int i = 0; i < num_item; ++i) { + std::memcpy(item_data + i * item_size, + items_data + i * class_num * item_size + class_id * item_size, + sizeof(T) * item_size); + } +} + template class MultiClassNMSKernel : public framework::OpKernel { public: @@ -237,33 +255,26 @@ class MultiClassNMSKernel : public framework::OpKernel { auto& dev_ctx = ctx.template device_context(); int num_det = 0; - int64_t box_num = 0, class_num = 0, predict_dim = 0; - if (scores_size == 3) { - class_num = scores.dims()[0]; - predict_dim = scores.dims()[1]; - for (int64_t c = 0; c < class_num; ++c) { - if (c == background_label) continue; - Tensor score = scores.Slice(c, c + 1); - NMSFast(bboxes, score, score_threshold, nms_threshold, nms_eta, - nms_top_k, &((*indices)[c]), normalized); - num_det += (*indices)[c].size(); + + int64_t class_num = scores_size == 3 ? scores.dims()[0] : scores.dims()[1]; + Tensor bbox_slice, score_slice; + for (int64_t c = 0; c < class_num; ++c) { + if (c == background_label) continue; + if (scores_size == 3) { + score_slice = scores.Slice(c, c + 1); + bbox_slice = bboxes; + } else { + score_slice.Resize({scores.dims()[0], 1}); + bbox_slice.Resize({scores.dims()[0], 4}); + SliceOneClass(dev_ctx, scores, c, &score_slice); + SliceOneClass(dev_ctx, bboxes, c, &bbox_slice); } - } else { - box_num = scores.dims()[0]; - class_num = scores.dims()[1]; - Tensor score; - score.Resize({box_num, 1}); - Tensor bbox; - bbox.Resize({box_num, 4}); - for (int64_t c = 0; c < class_num; ++c) { - if (c == background_label) continue; - SliceOneClass(dev_ctx, scores, c, &score); - SliceOneClass(dev_ctx, bboxes, c, &bbox); - NMSFast(bbox, score, score_threshold, nms_threshold, nms_eta, nms_top_k, - &((*indices)[c]), normalized); + NMSFast(bbox_slice, score_slice, score_threshold, nms_threshold, nms_eta, + nms_top_k, &((*indices)[c]), normalized); + if (scores_size == 2) { std::stable_sort((*indices)[c].begin(), (*indices)[c].end()); - num_det += (*indices)[c].size(); } + num_det += (*indices)[c].size(); } *num_nmsed_out = num_det; @@ -274,12 +285,11 @@ class MultiClassNMSKernel : public framework::OpKernel { for (const auto& it : *indices) { int label = it.first; if (scores_size == 3) { - sdata = scores_data + label * predict_dim; + sdata = scores_data + label * scores.dims()[1]; } else { - Tensor score; - score.Resize({box_num, 1}); - SliceOneClass(dev_ctx, scores, label, &score); - sdata = score.data(); + score_slice.Resize({scores.dims()[0], 1}); + SliceOneClass(dev_ctx, scores, label, &score_slice); + sdata = score_slice.data(); } const std::vector& label_indices = it.second; for (size_t j = 0; j < label_indices.size(); ++j) { @@ -362,43 +372,33 @@ class MultiClassNMSKernel : public framework::OpKernel { auto* outs = ctx.Output("Out"); auto score_dims = scores->dims(); - int64_t class_num = score_dims[1]; + auto score_size = score_dims.size(); auto& dev_ctx = ctx.template device_context(); std::vector>> all_indices; std::vector batch_starts = {0}; int64_t batch_size = score_dims[0]; - int64_t predict_dim = 0; int64_t box_dim = boxes->dims()[2]; int64_t out_dim = box_dim + 2; int num_nmsed_out = 0; - if (score_dims.size() == 3) { - predict_dim = score_dims[2]; - for (int64_t i = 0; i < batch_size; ++i) { - Tensor ins_score = scores->Slice(i, i + 1); - ins_score.Resize({class_num, predict_dim}); - - Tensor ins_boxes = boxes->Slice(i, i + 1); - ins_boxes.Resize({predict_dim, box_dim}); - - std::map> indices; - MultiClassNMS(ctx, ins_score, ins_boxes, score_dims.size(), &indices, - &num_nmsed_out); - all_indices.push_back(indices); - batch_starts.push_back(batch_starts.back() + num_nmsed_out); - } - } else { - auto boxes_lod = boxes->lod().back(); - int64_t n = static_cast(boxes_lod.size() - 1); - for (int i = 0; i < n; ++i) { - Tensor boxes_slice = boxes->Slice(boxes_lod[i], boxes_lod[i + 1]); - Tensor scores_slice = scores->Slice(boxes_lod[i], boxes_lod[i + 1]); - std::map> indices; - MultiClassNMS(ctx, scores_slice, boxes_slice, score_dims.size(), - &indices, &num_nmsed_out); - all_indices.push_back(indices); - batch_starts.push_back(batch_starts.back() + num_nmsed_out); + Tensor boxes_slice, scores_slice; + int n = score_size == 3 ? batch_size : boxes->lod().back().size() - 1; + for (int i = 0; i < n; ++i) { + if (score_size == 3) { + scores_slice = scores->Slice(i, i + 1); + scores_slice.Resize({score_dims[1], score_dims[2]}); + boxes_slice = boxes->Slice(i, i + 1); + boxes_slice.Resize({score_dims[2], box_dim}); + } else { + auto boxes_lod = boxes->lod().back(); + scores_slice = scores->Slice(boxes_lod[i], boxes_lod[i + 1]); + boxes_slice = boxes->Slice(boxes_lod[i], boxes_lod[i + 1]); } + std::map> indices; + MultiClassNMS(ctx, scores_slice, boxes_slice, score_size, &indices, + &num_nmsed_out); + all_indices.push_back(indices); + batch_starts.push_back(batch_starts.back() + num_nmsed_out); } int num_kept = batch_starts.back(); @@ -408,35 +408,23 @@ class MultiClassNMSKernel : public framework::OpKernel { batch_starts = {0, 1}; } else { outs->mutable_data({num_kept, out_dim}, ctx.GetPlace()); - if (score_dims.size() == 3) { - for (int64_t i = 0; i < batch_size; ++i) { - Tensor ins_score = scores->Slice(i, i + 1); - ins_score.Resize({class_num, predict_dim}); - - Tensor ins_boxes = boxes->Slice(i, i + 1); - ins_boxes.Resize({predict_dim, box_dim}); - - int64_t s = batch_starts[i]; - int64_t e = batch_starts[i + 1]; - if (e > s) { - Tensor out = outs->Slice(s, e); - MultiClassOutput(dev_ctx, ins_score, ins_boxes, all_indices[i], - score_dims.size(), &out); - } + for (int i = 0; i < n; ++i) { + if (score_size == 3) { + scores_slice = scores->Slice(i, i + 1); + boxes_slice = boxes->Slice(i, i + 1); + scores_slice.Resize({score_dims[1], score_dims[2]}); + boxes_slice.Resize({score_dims[2], box_dim}); + } else { + auto boxes_lod = boxes->lod().back(); + scores_slice = scores->Slice(boxes_lod[i], boxes_lod[i + 1]); + boxes_slice = boxes->Slice(boxes_lod[i], boxes_lod[i + 1]); } - } else { - auto boxes_lod = boxes->lod().back(); - int64_t n = static_cast(boxes_lod.size() - 1); - for (int i = 0; i < n; ++i) { - Tensor boxes_slice = boxes->Slice(boxes_lod[i], boxes_lod[i + 1]); - Tensor scores_slice = scores->Slice(boxes_lod[i], boxes_lod[i + 1]); - int64_t s = batch_starts[i]; - int64_t e = batch_starts[i + 1]; - if (e > s) { - Tensor out = outs->Slice(s, e); - MultiClassOutput(dev_ctx, scores_slice, boxes_slice, all_indices[i], - score_dims.size(), &out); - } + int64_t s = batch_starts[i]; + int64_t e = batch_starts[i + 1]; + if (e > s) { + Tensor out = outs->Slice(s, e); + MultiClassOutput(dev_ctx, scores_slice, boxes_slice, all_indices[i], + score_dims.size(), &out); } } } @@ -458,17 +446,18 @@ class MultiClassNMSOpMaker : public framework::OpProtoAndCheckerMaker { "predicted locations of M bounding bboxes, N is the batch size. " "Each bounding box has four coordinate values and the layout is " "[xmin, ymin, xmax, ymax], when box size equals to 4." - "2. (LoDTensor) A 3-D Tensor with shape [N, M, 4]" - "N is the number of boxes, M is the class number"); + "2. (LoDTensor) A 3-D Tensor with shape [M, C, 4]" + "M is the number of bounding boxes, C is the class number"); AddInput("Scores", "Two types of scores are supported:" "1. (Tensor) A 3-D Tensor with shape [N, C, M] represents the " "predicted confidence predictions. N is the batch size, C is the " "class number, M is number of bounding boxes. For each category " "there are total M scores which corresponding M bounding boxes. " - " Please note, M is equal to the 1st dimension of BBoxes. " - "2. (LoDTensor) A 2-D LoDTensor with shape" - "[N, num_class]. N is the number of bbox"); + " Please note, M is equal to the 2nd dimension of BBoxes. " + "2. (LoDTensor) A 2-D LoDTensor with shape [M, C]. " + "M is the number of bbox, C is the class number. In this case, " + "Input BBoxes should be the second case with shape [M, C, 4]."); AddAttr( "background_label", "(int, defalut: 0) " @@ -528,8 +517,8 @@ independently for each class. The outputs is a 2-D LoDTenosr, for each image, the offsets in first dimension of LoDTensor are called LoD, the number of offset is N + 1, where N is the batch size. If LoD[i + 1] - LoD[i] == 0, means there is no detected bbox for this image. If there is no detected boxes -for all images, all the elements in LoD are 0, and the Out only contains one -value which is -1. +for all images, all the elements in LoD are set to {0,1}, and the Out only +contains one value which is -1. )DOC"); } }; diff --git a/python/paddle/fluid/layers/detection.py b/python/paddle/fluid/layers/detection.py index e8ce0c1d90db430d6fe320fd6493a2179c5584f9..3d0896850eb085f0e5e50d600469a7379e8ccd4a 100644 --- a/python/paddle/fluid/layers/detection.py +++ b/python/paddle/fluid/layers/detection.py @@ -1821,8 +1821,88 @@ def multiclass_nms(bboxes, keep_top_k, normalized=True, nms_eta=1., - background_label=0): + background_label=0, + name=None): """ + **Multiclass NMS** + + This operator is to do multi-class non maximum suppression (NMS) on + boxes and scores. + + In the NMS step, this operator greedily selects a subset of detection bounding + boxes that have high scores larger than score_threshold, if providing this + threshold, then selects the largest nms_top_k confidences scores if nms_top_k + is larger than -1. Then this operator pruns away boxes that have high IOU + (intersection over union) overlap with already selected boxes by adaptive + threshold NMS based on parameters of nms_threshold and nms_eta. + + Aftern NMS step, at most keep_top_k number of total bboxes are to be kept + per image if keep_top_k is larger than -1. + + Args: + bboxes (Variable): Two types of bboxes are supported: + 1. (Tensor) A 3-D Tensor with shape + [N, M, 4 or 8 16 24 32] represents the + predicted locations of M bounding bboxes, + N is the batch size. Each bounding box has four + coordinate values and the layout is + [xmin, ymin, xmax, ymax], when box size equals to 4. + 2. (LoDTensor) A 3-D Tensor with shape [M, C, 4] + M is the number of bounding boxes, C is the + class number + scores (Variable): Two types of scores are supported: + 1. (Tensor) A 3-D Tensor with shape [N, C, M] + represents the predicted confidence predictions. + N is the batch size, C is the class number, M is + number of bounding boxes. For each category there + are total M scores which corresponding M bounding + boxes. Please note, M is equal to the 2nd dimension + of BBoxes. + 2. (LoDTensor) A 2-D LoDTensor with shape [M, C]. + M is the number of bbox, C is the class number. + In this case, input BBoxes should be the second + case with shape [M, C, 4]. + background_label (int): The index of background label, the background + label will be ignored. If set to -1, then all + categories will be considered. Default: 0 + score_threshold (float): Threshold to filter out bounding boxes with + low confidence score. If not provided, + consider all boxes. + nms_top_k (int): Maximum number of detections to be kept according to + the confidences aftern the filtering detections based + on score_threshold. + nms_threshold (float): The threshold to be used in NMS. Default: 0.3 + nms_eta (float): The threshold to be used in NMS. Default: 1.0 + keep_top_k (int): Number of total bboxes to be kept per image after NMS + step. -1 means keeping all bboxes after NMS step. + normalized (bool): Whether detections are normalized. Default: True + name(str): Name of the multiclass nms op. Default: None. + + Returns: + Out: A 2-D LoDTensor with shape [No, 6] represents the detections. + Each row has 6 values: [label, confidence, xmin, ymin, xmax, ymax] + or A 2-D LoDTensor with shape [No, 10] represents the detections. + Each row has 10 values: + [label, confidence, x1, y1, x2, y2, x3, y3, x4, y4]. No is the + total number of detections. If there is no detected boxes for all + images, lod will be set to {0, 1} and Out only contains one value + which is -1. + + Examples: + .. code-block:: python + + boxes = fluid.layers.data(name='bboxes', shape=[81, 4], + dtype='float32', lod_level=1) + scores = fluid.layers.data(name='scores', shape=[81], + dtype='float32', lod_level=1) + out = fluid.layers.multiclass_nms(bboxes=boxes, + scores=scores, + background_label=0, + score_threshold=0.5, + nms_top_k=400, + nms_threshold=0.3, + keep_top_k=200, + normalized=False) """ helper = LayerHelper('multiclass_nms', **locals())