iou_loss.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. # Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from __future__ import absolute_import
  15. from __future__ import division
  16. from __future__ import print_function
  17. import numpy as np
  18. import math
  19. import paddle
  20. from ppdet.core.workspace import register, serializable
  21. from ..bbox_utils import bbox_iou
  22. __all__ = ['IouLoss', 'GIoULoss', 'DIouLoss', 'SIoULoss']
  23. @register
  24. @serializable
  25. class IouLoss(object):
  26. """
  27. iou loss, see https://arxiv.org/abs/1908.03851
  28. loss = 1.0 - iou * iou
  29. Args:
  30. loss_weight (float): iou loss weight, default is 2.5
  31. max_height (int): max height of input to support random shape input
  32. max_width (int): max width of input to support random shape input
  33. ciou_term (bool): whether to add ciou_term
  34. loss_square (bool): whether to square the iou term
  35. """
  36. def __init__(self,
  37. loss_weight=2.5,
  38. giou=False,
  39. diou=False,
  40. ciou=False,
  41. loss_square=True):
  42. self.loss_weight = loss_weight
  43. self.giou = giou
  44. self.diou = diou
  45. self.ciou = ciou
  46. self.loss_square = loss_square
  47. def __call__(self, pbox, gbox):
  48. iou = bbox_iou(
  49. pbox, gbox, giou=self.giou, diou=self.diou, ciou=self.ciou)
  50. if self.loss_square:
  51. loss_iou = 1 - iou * iou
  52. else:
  53. loss_iou = 1 - iou
  54. loss_iou = loss_iou * self.loss_weight
  55. return loss_iou
  56. @register
  57. @serializable
  58. class GIoULoss(object):
  59. """
  60. Generalized Intersection over Union, see https://arxiv.org/abs/1902.09630
  61. Args:
  62. loss_weight (float): giou loss weight, default as 1
  63. eps (float): epsilon to avoid divide by zero, default as 1e-10
  64. reduction (string): Options are "none", "mean" and "sum". default as none
  65. """
  66. def __init__(self, loss_weight=1., eps=1e-10, reduction='none'):
  67. self.loss_weight = loss_weight
  68. self.eps = eps
  69. assert reduction in ('none', 'mean', 'sum')
  70. self.reduction = reduction
  71. def bbox_overlap(self, box1, box2, eps=1e-10):
  72. """calculate the iou of box1 and box2
  73. Args:
  74. box1 (Tensor): box1 with the shape (..., 4)
  75. box2 (Tensor): box1 with the shape (..., 4)
  76. eps (float): epsilon to avoid divide by zero
  77. Return:
  78. iou (Tensor): iou of box1 and box2
  79. overlap (Tensor): overlap of box1 and box2
  80. union (Tensor): union of box1 and box2
  81. """
  82. x1, y1, x2, y2 = box1
  83. x1g, y1g, x2g, y2g = box2
  84. xkis1 = paddle.maximum(x1, x1g)
  85. ykis1 = paddle.maximum(y1, y1g)
  86. xkis2 = paddle.minimum(x2, x2g)
  87. ykis2 = paddle.minimum(y2, y2g)
  88. w_inter = (xkis2 - xkis1).clip(0)
  89. h_inter = (ykis2 - ykis1).clip(0)
  90. overlap = w_inter * h_inter
  91. area1 = (x2 - x1) * (y2 - y1)
  92. area2 = (x2g - x1g) * (y2g - y1g)
  93. union = area1 + area2 - overlap + eps
  94. iou = overlap / union
  95. return iou, overlap, union
  96. def __call__(self, pbox, gbox, iou_weight=1., loc_reweight=None):
  97. x1, y1, x2, y2 = paddle.split(pbox, num_or_sections=4, axis=-1)
  98. x1g, y1g, x2g, y2g = paddle.split(gbox, num_or_sections=4, axis=-1)
  99. box1 = [x1, y1, x2, y2]
  100. box2 = [x1g, y1g, x2g, y2g]
  101. iou, overlap, union = self.bbox_overlap(box1, box2, self.eps)
  102. xc1 = paddle.minimum(x1, x1g)
  103. yc1 = paddle.minimum(y1, y1g)
  104. xc2 = paddle.maximum(x2, x2g)
  105. yc2 = paddle.maximum(y2, y2g)
  106. area_c = (xc2 - xc1) * (yc2 - yc1) + self.eps
  107. miou = iou - ((area_c - union) / area_c)
  108. if loc_reweight is not None:
  109. loc_reweight = paddle.reshape(loc_reweight, shape=(-1, 1))
  110. loc_thresh = 0.9
  111. giou = 1 - (1 - loc_thresh
  112. ) * miou - loc_thresh * miou * loc_reweight
  113. else:
  114. giou = 1 - miou
  115. if self.reduction == 'none':
  116. loss = giou
  117. elif self.reduction == 'sum':
  118. loss = paddle.sum(giou * iou_weight)
  119. else:
  120. loss = paddle.mean(giou * iou_weight)
  121. return loss * self.loss_weight
  122. @register
  123. @serializable
  124. class DIouLoss(GIoULoss):
  125. """
  126. Distance-IoU Loss, see https://arxiv.org/abs/1911.08287
  127. Args:
  128. loss_weight (float): giou loss weight, default as 1
  129. eps (float): epsilon to avoid divide by zero, default as 1e-10
  130. use_complete_iou_loss (bool): whether to use complete iou loss
  131. """
  132. def __init__(self, loss_weight=1., eps=1e-10, use_complete_iou_loss=True):
  133. super(DIouLoss, self).__init__(loss_weight=loss_weight, eps=eps)
  134. self.use_complete_iou_loss = use_complete_iou_loss
  135. def __call__(self, pbox, gbox, iou_weight=1.):
  136. x1, y1, x2, y2 = paddle.split(pbox, num_or_sections=4, axis=-1)
  137. x1g, y1g, x2g, y2g = paddle.split(gbox, num_or_sections=4, axis=-1)
  138. cx = (x1 + x2) / 2
  139. cy = (y1 + y2) / 2
  140. w = x2 - x1
  141. h = y2 - y1
  142. cxg = (x1g + x2g) / 2
  143. cyg = (y1g + y2g) / 2
  144. wg = x2g - x1g
  145. hg = y2g - y1g
  146. x2 = paddle.maximum(x1, x2)
  147. y2 = paddle.maximum(y1, y2)
  148. # A and B
  149. xkis1 = paddle.maximum(x1, x1g)
  150. ykis1 = paddle.maximum(y1, y1g)
  151. xkis2 = paddle.minimum(x2, x2g)
  152. ykis2 = paddle.minimum(y2, y2g)
  153. # A or B
  154. xc1 = paddle.minimum(x1, x1g)
  155. yc1 = paddle.minimum(y1, y1g)
  156. xc2 = paddle.maximum(x2, x2g)
  157. yc2 = paddle.maximum(y2, y2g)
  158. intsctk = (xkis2 - xkis1) * (ykis2 - ykis1)
  159. intsctk = intsctk * paddle.greater_than(
  160. xkis2, xkis1) * paddle.greater_than(ykis2, ykis1)
  161. unionk = (x2 - x1) * (y2 - y1) + (x2g - x1g) * (y2g - y1g
  162. ) - intsctk + self.eps
  163. iouk = intsctk / unionk
  164. # DIOU term
  165. dist_intersection = (cx - cxg) * (cx - cxg) + (cy - cyg) * (cy - cyg)
  166. dist_union = (xc2 - xc1) * (xc2 - xc1) + (yc2 - yc1) * (yc2 - yc1)
  167. diou_term = (dist_intersection + self.eps) / (dist_union + self.eps)
  168. # CIOU term
  169. ciou_term = 0
  170. if self.use_complete_iou_loss:
  171. ar_gt = wg / hg
  172. ar_pred = w / h
  173. arctan = paddle.atan(ar_gt) - paddle.atan(ar_pred)
  174. ar_loss = 4. / np.pi / np.pi * arctan * arctan
  175. alpha = ar_loss / (1 - iouk + ar_loss + self.eps)
  176. alpha.stop_gradient = True
  177. ciou_term = alpha * ar_loss
  178. diou = paddle.mean((1 - iouk + ciou_term + diou_term) * iou_weight)
  179. return diou * self.loss_weight
  180. @register
  181. @serializable
  182. class SIoULoss(GIoULoss):
  183. """
  184. see https://arxiv.org/pdf/2205.12740.pdf
  185. Args:
  186. loss_weight (float): siou loss weight, default as 1
  187. eps (float): epsilon to avoid divide by zero, default as 1e-10
  188. theta (float): default as 4
  189. reduction (str): Options are "none", "mean" and "sum". default as none
  190. """
  191. def __init__(self, loss_weight=1., eps=1e-10, theta=4., reduction='none'):
  192. super(SIoULoss, self).__init__(loss_weight=loss_weight, eps=eps)
  193. self.loss_weight = loss_weight
  194. self.eps = eps
  195. self.theta = theta
  196. self.reduction = reduction
  197. def __call__(self, pbox, gbox):
  198. x1, y1, x2, y2 = paddle.split(pbox, num_or_sections=4, axis=-1)
  199. x1g, y1g, x2g, y2g = paddle.split(gbox, num_or_sections=4, axis=-1)
  200. box1 = [x1, y1, x2, y2]
  201. box2 = [x1g, y1g, x2g, y2g]
  202. iou = bbox_iou(box1, box2)
  203. cx = (x1 + x2) / 2
  204. cy = (y1 + y2) / 2
  205. w = x2 - x1 + self.eps
  206. h = y2 - y1 + self.eps
  207. cxg = (x1g + x2g) / 2
  208. cyg = (y1g + y2g) / 2
  209. wg = x2g - x1g + self.eps
  210. hg = y2g - y1g + self.eps
  211. x2 = paddle.maximum(x1, x2)
  212. y2 = paddle.maximum(y1, y2)
  213. # A or B
  214. xc1 = paddle.minimum(x1, x1g)
  215. yc1 = paddle.minimum(y1, y1g)
  216. xc2 = paddle.maximum(x2, x2g)
  217. yc2 = paddle.maximum(y2, y2g)
  218. cw_out = xc2 - xc1
  219. ch_out = yc2 - yc1
  220. ch = paddle.maximum(cy, cyg) - paddle.minimum(cy, cyg)
  221. cw = paddle.maximum(cx, cxg) - paddle.minimum(cx, cxg)
  222. # angle cost
  223. dist_intersection = paddle.sqrt((cx - cxg)**2 + (cy - cyg)**2)
  224. sin_angle_alpha = ch / dist_intersection
  225. sin_angle_beta = cw / dist_intersection
  226. thred = paddle.pow(paddle.to_tensor(2), 0.5) / 2
  227. thred.stop_gradient = True
  228. sin_alpha = paddle.where(sin_angle_alpha > thred, sin_angle_beta,
  229. sin_angle_alpha)
  230. angle_cost = paddle.cos(paddle.asin(sin_alpha) * 2 - math.pi / 2)
  231. # distance cost
  232. gamma = 2 - angle_cost
  233. # gamma.stop_gradient = True
  234. beta_x = ((cxg - cx) / cw_out)**2
  235. beta_y = ((cyg - cy) / ch_out)**2
  236. dist_cost = 1 - paddle.exp(-gamma * beta_x) + 1 - paddle.exp(-gamma *
  237. beta_y)
  238. # shape cost
  239. omega_w = paddle.abs(w - wg) / paddle.maximum(w, wg)
  240. omega_h = paddle.abs(hg - h) / paddle.maximum(h, hg)
  241. omega = (1 - paddle.exp(-omega_w))**self.theta + (
  242. 1 - paddle.exp(-omega_h))**self.theta
  243. siou_loss = 1 - iou + (omega + dist_cost) / 2
  244. if self.reduction == 'mean':
  245. siou_loss = paddle.mean(siou_loss)
  246. elif self.reduction == 'sum':
  247. siou_loss = paddle.sum(siou_loss)
  248. return siou_loss * self.loss_weight