giou_loss.py 5.4 KB
Newer Older
littletomatodonkey's avatar
littletomatodonkey 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
# Copyright (c) 2019 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.

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import numpy as np

from paddle import fluid
from ppdet.core.workspace import register, serializable

__all__ = ['GiouLoss']


@register
@serializable
class GiouLoss(object):
    '''
    Generalized Intersection over Union, see https://arxiv.org/abs/1902.09630
    Args:
        loss_weight (float): diou loss weight, default as 10 in faster-rcnn
        is_cls_agnostic (bool): flag of class-agnostic
        num_classes (int): class num
W
wangguanzhong 已提交
36 37
        do_average (bool): whether to average the loss
        use_class_weight(bool): whether to use class weight
littletomatodonkey's avatar
littletomatodonkey 已提交
38 39 40
    '''
    __shared__ = ['num_classes']

W
wangguanzhong 已提交
41 42 43 44 45 46
    def __init__(self,
                 loss_weight=10.,
                 is_cls_agnostic=False,
                 num_classes=81,
                 do_average=True,
                 use_class_weight=True):
littletomatodonkey's avatar
littletomatodonkey 已提交
47 48 49 50
        super(GiouLoss, self).__init__()
        self.loss_weight = loss_weight
        self.is_cls_agnostic = is_cls_agnostic
        self.num_classes = num_classes
W
wangguanzhong 已提交
51 52 53
        self.do_average = do_average
        self.class_weight = 2 if is_cls_agnostic else num_classes
        self.use_class_weight = use_class_weight
littletomatodonkey's avatar
littletomatodonkey 已提交
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

    # deltas: NxMx4
    def bbox_transform(self, deltas, weights):
        wx, wy, ww, wh = weights

        deltas = fluid.layers.reshape(deltas, shape=(0, -1, 4))

        dx = fluid.layers.slice(deltas, axes=[2], starts=[0], ends=[1]) * wx
        dy = fluid.layers.slice(deltas, axes=[2], starts=[1], ends=[2]) * wy
        dw = fluid.layers.slice(deltas, axes=[2], starts=[2], ends=[3]) * ww
        dh = fluid.layers.slice(deltas, axes=[2], starts=[3], ends=[4]) * wh

        dw = fluid.layers.clip(dw, -1.e10, np.log(1000. / 16))
        dh = fluid.layers.clip(dh, -1.e10, np.log(1000. / 16))

        pred_ctr_x = dx
        pred_ctr_y = dy
        pred_w = fluid.layers.exp(dw)
        pred_h = fluid.layers.exp(dh)

        x1 = pred_ctr_x - 0.5 * pred_w
        y1 = pred_ctr_y - 0.5 * pred_h
        x2 = pred_ctr_x + 0.5 * pred_w
        y2 = pred_ctr_y + 0.5 * pred_h

        x1 = fluid.layers.reshape(x1, shape=(-1, ))
        y1 = fluid.layers.reshape(y1, shape=(-1, ))
        x2 = fluid.layers.reshape(x2, shape=(-1, ))
        y2 = fluid.layers.reshape(y2, shape=(-1, ))

        return x1, y1, x2, y2

    def __call__(self,
                 x,
                 y,
                 inside_weight=None,
                 outside_weight=None,
W
wangguanzhong 已提交
91 92
                 bbox_reg_weight=[0.1, 0.1, 0.2, 0.2],
                 use_transform=True):
littletomatodonkey's avatar
littletomatodonkey 已提交
93
        eps = 1.e-10
W
wangguanzhong 已提交
94 95 96 97 98 99
        if use_transform:
            x1, y1, x2, y2 = self.bbox_transform(x, bbox_reg_weight)
            x1g, y1g, x2g, y2g = self.bbox_transform(y, bbox_reg_weight)
        else:
            x1, y1, x2, y2 = fluid.layers.split(x, num_or_sections=4, dim=1)
            x1g, y1g, x2g, y2g = fluid.layers.split(y, num_or_sections=4, dim=1)
littletomatodonkey's avatar
littletomatodonkey 已提交
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118

        x2 = fluid.layers.elementwise_max(x1, x2)
        y2 = fluid.layers.elementwise_max(y1, y2)

        xkis1 = fluid.layers.elementwise_max(x1, x1g)
        ykis1 = fluid.layers.elementwise_max(y1, y1g)
        xkis2 = fluid.layers.elementwise_min(x2, x2g)
        ykis2 = fluid.layers.elementwise_min(y2, y2g)

        xc1 = fluid.layers.elementwise_min(x1, x1g)
        yc1 = fluid.layers.elementwise_min(y1, y1g)
        xc2 = fluid.layers.elementwise_max(x2, x2g)
        yc2 = fluid.layers.elementwise_max(y2, y2g)

        intsctk = (xkis2 - xkis1) * (ykis2 - ykis1)
        intsctk = intsctk * fluid.layers.greater_than(
            xkis2, xkis1) * fluid.layers.greater_than(ykis2, ykis1)
        unionk = (x2 - x1) * (y2 - y1) + (x2g - x1g) * (y2g - y1g
                                                        ) - intsctk + eps
W
wangguanzhong 已提交
119

littletomatodonkey's avatar
littletomatodonkey 已提交
120 121 122 123 124 125 126 127 128 129 130 131 132 133
        iouk = intsctk / unionk

        area_c = (xc2 - xc1) * (yc2 - yc1) + eps
        miouk = iouk - ((area_c - unionk) / area_c)

        iou_weights = 1
        if inside_weight is not None and outside_weight is not None:
            inside_weight = fluid.layers.reshape(inside_weight, shape=(-1, 4))
            outside_weight = fluid.layers.reshape(outside_weight, shape=(-1, 4))

            inside_weight = fluid.layers.reduce_mean(inside_weight, dim=1)
            outside_weight = fluid.layers.reduce_mean(outside_weight, dim=1)

            iou_weights = inside_weight * outside_weight
W
wangguanzhong 已提交
134 135 136 137 138 139 140 141 142 143 144 145
        elif outside_weight is not None:
            iou_weights = outside_weight

        if self.do_average:
            miouk = fluid.layers.reduce_mean((1 - miouk) * iou_weights)
        else:
            iou_distance = fluid.layers.elementwise_mul(
                1 - miouk, iou_weights, axis=0)
            miouk = fluid.layers.reduce_sum(iou_distance)

        if self.use_class_weight:
            miouk = miouk * self.class_weight
littletomatodonkey's avatar
littletomatodonkey 已提交
146 147

        return miouk * self.loss_weight