yolof_head.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. # Copyright (c) 2022 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. import math
  15. import numpy as np
  16. import paddle
  17. import paddle.nn as nn
  18. import paddle.nn.functional as F
  19. from paddle import ParamAttr
  20. from paddle.regularizer import L2Decay
  21. from paddle.nn.initializer import Normal, Constant
  22. from ppdet.modeling.layers import MultiClassNMS
  23. from ppdet.core.workspace import register
  24. from ppdet.modeling.bbox_utils import delta2bbox_v2
  25. __all__ = ['YOLOFHead']
  26. INF = 1e8
  27. def reduce_mean(tensor):
  28. world_size = paddle.distributed.get_world_size()
  29. if world_size == 1:
  30. return tensor
  31. paddle.distributed.all_reduce(tensor)
  32. return tensor / world_size
  33. def find_inside_anchor(feat_size, stride, num_anchors, im_shape):
  34. feat_h, feat_w = feat_size[:2]
  35. im_h, im_w = im_shape[:2]
  36. inside_h = min(int(np.ceil(im_h / stride)), feat_h)
  37. inside_w = min(int(np.ceil(im_w / stride)), feat_w)
  38. inside_mask = paddle.zeros([feat_h, feat_w], dtype=paddle.bool)
  39. inside_mask[:inside_h, :inside_w] = True
  40. inside_mask = inside_mask.unsqueeze(-1).expand(
  41. [feat_h, feat_w, num_anchors])
  42. return inside_mask.reshape([-1])
  43. @register
  44. class YOLOFFeat(nn.Layer):
  45. def __init__(self,
  46. feat_in=256,
  47. feat_out=256,
  48. num_cls_convs=2,
  49. num_reg_convs=4,
  50. norm_type='bn'):
  51. super(YOLOFFeat, self).__init__()
  52. assert norm_type == 'bn', "YOLOFFeat only support BN now."
  53. self.feat_in = feat_in
  54. self.feat_out = feat_out
  55. self.num_cls_convs = num_cls_convs
  56. self.num_reg_convs = num_reg_convs
  57. self.norm_type = norm_type
  58. cls_subnet, reg_subnet = [], []
  59. for i in range(self.num_cls_convs):
  60. feat_in = self.feat_in if i == 0 else self.feat_out
  61. cls_subnet.append(
  62. nn.Conv2D(
  63. feat_in,
  64. self.feat_out,
  65. 3,
  66. stride=1,
  67. padding=1,
  68. weight_attr=ParamAttr(initializer=Normal(
  69. mean=0.0, std=0.01)),
  70. bias_attr=ParamAttr(initializer=Constant(value=0.0))))
  71. cls_subnet.append(
  72. nn.BatchNorm2D(
  73. self.feat_out,
  74. weight_attr=ParamAttr(regularizer=L2Decay(0.0)),
  75. bias_attr=ParamAttr(regularizer=L2Decay(0.0))))
  76. cls_subnet.append(nn.ReLU())
  77. for i in range(self.num_reg_convs):
  78. feat_in = self.feat_in if i == 0 else self.feat_out
  79. reg_subnet.append(
  80. nn.Conv2D(
  81. feat_in,
  82. self.feat_out,
  83. 3,
  84. stride=1,
  85. padding=1,
  86. weight_attr=ParamAttr(initializer=Normal(
  87. mean=0.0, std=0.01)),
  88. bias_attr=ParamAttr(initializer=Constant(value=0.0))))
  89. reg_subnet.append(
  90. nn.BatchNorm2D(
  91. self.feat_out,
  92. weight_attr=ParamAttr(regularizer=L2Decay(0.0)),
  93. bias_attr=ParamAttr(regularizer=L2Decay(0.0))))
  94. reg_subnet.append(nn.ReLU())
  95. self.cls_subnet = nn.Sequential(*cls_subnet)
  96. self.reg_subnet = nn.Sequential(*reg_subnet)
  97. def forward(self, fpn_feat):
  98. cls_feat = self.cls_subnet(fpn_feat)
  99. reg_feat = self.reg_subnet(fpn_feat)
  100. return cls_feat, reg_feat
  101. @register
  102. class YOLOFHead(nn.Layer):
  103. __shared__ = ['num_classes', 'trt', 'exclude_nms']
  104. __inject__ = [
  105. 'conv_feat', 'anchor_generator', 'bbox_assigner', 'loss_class',
  106. 'loss_bbox', 'nms'
  107. ]
  108. def __init__(self,
  109. num_classes=80,
  110. conv_feat='YOLOFFeat',
  111. anchor_generator='AnchorGenerator',
  112. bbox_assigner='UniformAssigner',
  113. loss_class='FocalLoss',
  114. loss_bbox='GIoULoss',
  115. ctr_clip=32.0,
  116. delta_mean=[0.0, 0.0, 0.0, 0.0],
  117. delta_std=[1.0, 1.0, 1.0, 1.0],
  118. nms='MultiClassNMS',
  119. prior_prob=0.01,
  120. nms_pre=1000,
  121. use_inside_anchor=False,
  122. trt=False,
  123. exclude_nms=False):
  124. super(YOLOFHead, self).__init__()
  125. self.num_classes = num_classes
  126. self.conv_feat = conv_feat
  127. self.anchor_generator = anchor_generator
  128. self.na = self.anchor_generator.num_anchors
  129. self.bbox_assigner = bbox_assigner
  130. self.loss_class = loss_class
  131. self.loss_bbox = loss_bbox
  132. self.ctr_clip = ctr_clip
  133. self.delta_mean = delta_mean
  134. self.delta_std = delta_std
  135. self.nms = nms
  136. self.nms_pre = nms_pre
  137. self.use_inside_anchor = use_inside_anchor
  138. if isinstance(self.nms, MultiClassNMS) and trt:
  139. self.nms.trt = trt
  140. self.exclude_nms = exclude_nms
  141. bias_init_value = -math.log((1 - prior_prob) / prior_prob)
  142. self.cls_score = self.add_sublayer(
  143. 'cls_score',
  144. nn.Conv2D(
  145. in_channels=conv_feat.feat_out,
  146. out_channels=self.num_classes * self.na,
  147. kernel_size=3,
  148. stride=1,
  149. padding=1,
  150. weight_attr=ParamAttr(initializer=Normal(
  151. mean=0.0, std=0.01)),
  152. bias_attr=ParamAttr(initializer=Constant(
  153. value=bias_init_value))))
  154. self.bbox_pred = self.add_sublayer(
  155. 'bbox_pred',
  156. nn.Conv2D(
  157. in_channels=conv_feat.feat_out,
  158. out_channels=4 * self.na,
  159. kernel_size=3,
  160. stride=1,
  161. padding=1,
  162. weight_attr=ParamAttr(initializer=Normal(
  163. mean=0.0, std=0.01)),
  164. bias_attr=ParamAttr(initializer=Constant(value=0))))
  165. self.object_pred = self.add_sublayer(
  166. 'object_pred',
  167. nn.Conv2D(
  168. in_channels=conv_feat.feat_out,
  169. out_channels=self.na,
  170. kernel_size=3,
  171. stride=1,
  172. padding=1,
  173. weight_attr=ParamAttr(initializer=Normal(
  174. mean=0.0, std=0.01)),
  175. bias_attr=ParamAttr(initializer=Constant(value=0))))
  176. def forward(self, feats, targets=None):
  177. assert len(feats) == 1, "YOLOF only has one level feature."
  178. conv_cls_feat, conv_reg_feat = self.conv_feat(feats[0])
  179. cls_logits = self.cls_score(conv_cls_feat)
  180. objectness = self.object_pred(conv_reg_feat)
  181. bboxes_reg = self.bbox_pred(conv_reg_feat)
  182. N, C, H, W = paddle.shape(cls_logits)[:]
  183. cls_logits = cls_logits.reshape((N, self.na, self.num_classes, H, W))
  184. objectness = objectness.reshape((N, self.na, 1, H, W))
  185. norm_cls_logits = cls_logits + objectness - paddle.log(
  186. 1.0 + paddle.clip(
  187. cls_logits.exp(), max=INF) + paddle.clip(
  188. objectness.exp(), max=INF))
  189. norm_cls_logits = norm_cls_logits.reshape((N, C, H, W))
  190. anchors = self.anchor_generator([norm_cls_logits])
  191. if self.training:
  192. yolof_losses = self.get_loss(
  193. [anchors[0], norm_cls_logits, bboxes_reg], targets)
  194. return yolof_losses
  195. else:
  196. return anchors[0], norm_cls_logits, bboxes_reg
  197. def get_loss(self, head_outs, targets):
  198. anchors, cls_logits, bbox_preds = head_outs
  199. feat_size = cls_logits.shape[-2:]
  200. cls_logits = cls_logits.transpose([0, 2, 3, 1])
  201. cls_logits = cls_logits.reshape([0, -1, self.num_classes])
  202. bbox_preds = bbox_preds.transpose([0, 2, 3, 1])
  203. bbox_preds = bbox_preds.reshape([0, -1, 4])
  204. num_pos_list = []
  205. cls_pred_list, cls_tar_list = [], []
  206. reg_pred_list, reg_tar_list = [], []
  207. # find and gather preds and targets in each image
  208. for cls_logit, bbox_pred, gt_bbox, gt_class, im_shape in zip(
  209. cls_logits, bbox_preds, targets['gt_bbox'], targets['gt_class'],
  210. targets['im_shape']):
  211. if self.use_inside_anchor:
  212. inside_mask = find_inside_anchor(
  213. feat_size, self.anchor_generator.strides[0], self.na,
  214. im_shape.tolist())
  215. cls_logit = cls_logit[inside_mask]
  216. bbox_pred = bbox_pred[inside_mask]
  217. anchors = anchors[inside_mask]
  218. bbox_pred = delta2bbox_v2(
  219. bbox_pred,
  220. anchors,
  221. self.delta_mean,
  222. self.delta_std,
  223. ctr_clip=self.ctr_clip)
  224. bbox_pred = bbox_pred.reshape([-1, bbox_pred.shape[-1]])
  225. # -2:ignore, -1:neg, >=0:pos
  226. match_labels, pos_bbox_pred, pos_bbox_tar = self.bbox_assigner(
  227. bbox_pred, anchors, gt_bbox)
  228. pos_mask = (match_labels >= 0)
  229. neg_mask = (match_labels == -1)
  230. chosen_mask = paddle.logical_or(pos_mask, neg_mask)
  231. gt_class = gt_class.reshape([-1])
  232. bg_class = paddle.to_tensor(
  233. [self.num_classes], dtype=gt_class.dtype)
  234. # a trick to assign num_classes to negative targets
  235. gt_class = paddle.concat([gt_class, bg_class], axis=-1)
  236. match_labels = paddle.where(
  237. neg_mask,
  238. paddle.full_like(match_labels, gt_class.size - 1), match_labels)
  239. num_pos_list.append(max(1.0, pos_mask.sum().item()))
  240. cls_pred_list.append(cls_logit[chosen_mask])
  241. cls_tar_list.append(gt_class[match_labels[chosen_mask]])
  242. reg_pred_list.append(pos_bbox_pred)
  243. reg_tar_list.append(pos_bbox_tar)
  244. num_tot_pos = paddle.to_tensor(sum(num_pos_list))
  245. num_tot_pos = reduce_mean(num_tot_pos).item()
  246. num_tot_pos = max(1.0, num_tot_pos)
  247. cls_pred = paddle.concat(cls_pred_list)
  248. cls_tar = paddle.concat(cls_tar_list)
  249. cls_loss = self.loss_class(
  250. cls_pred, cls_tar, reduction='sum') / num_tot_pos
  251. reg_pred_list = [_ for _ in reg_pred_list if _ is not None]
  252. reg_tar_list = [_ for _ in reg_tar_list if _ is not None]
  253. if len(reg_pred_list) == 0:
  254. reg_loss = bbox_preds.sum() * 0.0
  255. else:
  256. reg_pred = paddle.concat(reg_pred_list)
  257. reg_tar = paddle.concat(reg_tar_list)
  258. reg_loss = self.loss_bbox(reg_pred, reg_tar).sum() / num_tot_pos
  259. yolof_losses = {
  260. 'loss': cls_loss + reg_loss,
  261. 'loss_cls': cls_loss,
  262. 'loss_reg': reg_loss,
  263. }
  264. return yolof_losses
  265. def get_bboxes_single(self,
  266. anchors,
  267. cls_scores,
  268. bbox_preds,
  269. im_shape,
  270. scale_factor,
  271. rescale=True):
  272. assert len(cls_scores) == len(bbox_preds)
  273. mlvl_bboxes = []
  274. mlvl_scores = []
  275. for anchor, cls_score, bbox_pred in zip(anchors, cls_scores,
  276. bbox_preds):
  277. cls_score = cls_score.reshape([-1, self.num_classes])
  278. bbox_pred = bbox_pred.reshape([-1, 4])
  279. if self.nms_pre is not None and cls_score.shape[0] > self.nms_pre:
  280. max_score = cls_score.max(axis=1)
  281. _, topk_inds = max_score.topk(self.nms_pre)
  282. bbox_pred = bbox_pred.gather(topk_inds)
  283. anchor = anchor.gather(topk_inds)
  284. cls_score = cls_score.gather(topk_inds)
  285. bbox_pred = delta2bbox_v2(
  286. bbox_pred,
  287. anchor,
  288. self.delta_mean,
  289. self.delta_std,
  290. max_shape=im_shape,
  291. ctr_clip=self.ctr_clip).squeeze()
  292. mlvl_bboxes.append(bbox_pred)
  293. mlvl_scores.append(F.sigmoid(cls_score))
  294. mlvl_bboxes = paddle.concat(mlvl_bboxes)
  295. mlvl_bboxes = paddle.squeeze(mlvl_bboxes)
  296. if rescale:
  297. mlvl_bboxes = mlvl_bboxes / paddle.concat(
  298. [scale_factor[::-1], scale_factor[::-1]])
  299. mlvl_scores = paddle.concat(mlvl_scores)
  300. mlvl_scores = mlvl_scores.transpose([1, 0])
  301. return mlvl_bboxes, mlvl_scores
  302. def decode(self, anchors, cls_scores, bbox_preds, im_shape, scale_factor):
  303. batch_bboxes = []
  304. batch_scores = []
  305. for img_id in range(cls_scores[0].shape[0]):
  306. num_lvls = len(cls_scores)
  307. cls_score_list = [cls_scores[i][img_id] for i in range(num_lvls)]
  308. bbox_pred_list = [bbox_preds[i][img_id] for i in range(num_lvls)]
  309. bboxes, scores = self.get_bboxes_single(
  310. anchors, cls_score_list, bbox_pred_list, im_shape[img_id],
  311. scale_factor[img_id])
  312. batch_bboxes.append(bboxes)
  313. batch_scores.append(scores)
  314. batch_bboxes = paddle.stack(batch_bboxes, 0)
  315. batch_scores = paddle.stack(batch_scores, 0)
  316. return batch_bboxes, batch_scores
  317. def post_process(self, head_outs, im_shape, scale_factor):
  318. anchors, cls_scores, bbox_preds = head_outs
  319. cls_scores = cls_scores.transpose([0, 2, 3, 1])
  320. bbox_preds = bbox_preds.transpose([0, 2, 3, 1])
  321. pred_bboxes, pred_scores = self.decode(
  322. [anchors], [cls_scores], [bbox_preds], im_shape, scale_factor)
  323. if self.exclude_nms:
  324. # `exclude_nms=True` just use in benchmark
  325. return pred_bboxes.sum(), pred_scores.sum()
  326. else:
  327. bbox_pred, bbox_num, _ = self.nms(pred_bboxes, pred_scores)
  328. return bbox_pred, bbox_num