bbox_head.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  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. import numpy as np
  15. import paddle
  16. import paddle.nn as nn
  17. import paddle.nn.functional as F
  18. from paddle.nn.initializer import Normal, XavierUniform, KaimingNormal
  19. from paddle.regularizer import L2Decay
  20. from paddlex.ppdet.core.workspace import register, create
  21. from paddlex.ppdet.modeling import ops
  22. from .roi_extractor import RoIAlign
  23. from ..shape_spec import ShapeSpec
  24. from ..bbox_utils import bbox2delta
  25. from paddlex.ppdet.modeling.layers import ConvNormLayer
  26. __all__ = ['TwoFCHead', 'XConvNormHead', 'BBoxHead']
  27. @register
  28. class TwoFCHead(nn.Layer):
  29. """
  30. RCNN bbox head with Two fc layers to extract feature
  31. Args:
  32. in_channel (int): Input channel which can be derived by from_config
  33. out_channel (int): Output channel
  34. resolution (int): Resolution of input feature map, default 7
  35. """
  36. def __init__(self, in_channel=256, out_channel=1024, resolution=7):
  37. super(TwoFCHead, self).__init__()
  38. self.in_channel = in_channel
  39. self.out_channel = out_channel
  40. fan = in_channel * resolution * resolution
  41. self.fc6 = nn.Linear(
  42. in_channel * resolution * resolution,
  43. out_channel,
  44. weight_attr=paddle.ParamAttr(
  45. initializer=XavierUniform(fan_out=fan)))
  46. self.fc6.skip_quant = True
  47. self.fc7 = nn.Linear(
  48. out_channel,
  49. out_channel,
  50. weight_attr=paddle.ParamAttr(initializer=XavierUniform()))
  51. self.fc7.skip_quant = True
  52. @classmethod
  53. def from_config(cls, cfg, input_shape):
  54. s = input_shape
  55. s = s[0] if isinstance(s, (list, tuple)) else s
  56. return {'in_channel': s.channels}
  57. @property
  58. def out_shape(self):
  59. return [ShapeSpec(channels=self.out_channel, )]
  60. def forward(self, rois_feat):
  61. rois_feat = paddle.flatten(rois_feat, start_axis=1, stop_axis=-1)
  62. fc6 = self.fc6(rois_feat)
  63. fc6 = F.relu(fc6)
  64. fc7 = self.fc7(fc6)
  65. fc7 = F.relu(fc7)
  66. return fc7
  67. @register
  68. class XConvNormHead(nn.Layer):
  69. __shared__ = ['norm_type', 'freeze_norm']
  70. """
  71. RCNN bbox head with serveral convolution layers
  72. Args:
  73. in_channel (int): Input channels which can be derived by from_config
  74. num_convs (int): The number of conv layers
  75. conv_dim (int): The number of channels for the conv layers
  76. out_channel (int): Output channels
  77. resolution (int): Resolution of input feature map
  78. norm_type (string): Norm type, bn, gn, sync_bn are available,
  79. default `gn`
  80. freeze_norm (bool): Whether to freeze the norm
  81. stage_name (string): Prefix name for conv layer, '' by default
  82. """
  83. def __init__(self,
  84. in_channel=256,
  85. num_convs=4,
  86. conv_dim=256,
  87. out_channel=1024,
  88. resolution=7,
  89. norm_type='gn',
  90. freeze_norm=False,
  91. stage_name=''):
  92. super(XConvNormHead, self).__init__()
  93. self.in_channel = in_channel
  94. self.num_convs = num_convs
  95. self.conv_dim = conv_dim
  96. self.out_channel = out_channel
  97. self.norm_type = norm_type
  98. self.freeze_norm = freeze_norm
  99. self.bbox_head_convs = []
  100. fan = conv_dim * 3 * 3
  101. initializer = KaimingNormal(fan_in=fan)
  102. for i in range(self.num_convs):
  103. in_c = in_channel if i == 0 else conv_dim
  104. head_conv_name = stage_name + 'bbox_head_conv{}'.format(i)
  105. head_conv = self.add_sublayer(
  106. head_conv_name,
  107. ConvNormLayer(
  108. ch_in=in_c,
  109. ch_out=conv_dim,
  110. filter_size=3,
  111. stride=1,
  112. norm_type=self.norm_type,
  113. freeze_norm=self.freeze_norm,
  114. initializer=initializer))
  115. self.bbox_head_convs.append(head_conv)
  116. fan = conv_dim * resolution * resolution
  117. self.fc6 = nn.Linear(
  118. conv_dim * resolution * resolution,
  119. out_channel,
  120. weight_attr=paddle.ParamAttr(
  121. initializer=XavierUniform(fan_out=fan)),
  122. bias_attr=paddle.ParamAttr(
  123. learning_rate=2., regularizer=L2Decay(0.)))
  124. @classmethod
  125. def from_config(cls, cfg, input_shape):
  126. s = input_shape
  127. s = s[0] if isinstance(s, (list, tuple)) else s
  128. return {'in_channel': s.channels}
  129. @property
  130. def out_shape(self):
  131. return [ShapeSpec(channels=self.out_channel, )]
  132. def forward(self, rois_feat):
  133. for i in range(self.num_convs):
  134. rois_feat = F.relu(self.bbox_head_convs[i](rois_feat))
  135. rois_feat = paddle.flatten(rois_feat, start_axis=1, stop_axis=-1)
  136. fc6 = F.relu(self.fc6(rois_feat))
  137. return fc6
  138. @register
  139. class BBoxHead(nn.Layer):
  140. __shared__ = ['num_classes']
  141. __inject__ = ['bbox_assigner', 'bbox_loss']
  142. """
  143. RCNN bbox head
  144. Args:
  145. head (nn.Layer): Extract feature in bbox head
  146. in_channel (int): Input channel after RoI extractor
  147. roi_extractor (object): The module of RoI Extractor
  148. bbox_assigner (object): The module of Box Assigner, label and sample the
  149. box.
  150. with_pool (bool): Whether to use pooling for the RoI feature.
  151. num_classes (int): The number of classes
  152. bbox_weight (List[float]): The weight to get the decode box
  153. """
  154. def __init__(self,
  155. head,
  156. in_channel,
  157. roi_extractor=RoIAlign().__dict__,
  158. bbox_assigner='BboxAssigner',
  159. with_pool=False,
  160. num_classes=80,
  161. bbox_weight=[10., 10., 5., 5.],
  162. bbox_loss=None):
  163. super(BBoxHead, self).__init__()
  164. self.head = head
  165. self.roi_extractor = roi_extractor
  166. if isinstance(roi_extractor, dict):
  167. self.roi_extractor = RoIAlign(**roi_extractor)
  168. self.bbox_assigner = bbox_assigner
  169. self.with_pool = with_pool
  170. self.num_classes = num_classes
  171. self.bbox_weight = bbox_weight
  172. self.bbox_loss = bbox_loss
  173. self.bbox_score = nn.Linear(
  174. in_channel,
  175. self.num_classes + 1,
  176. weight_attr=paddle.ParamAttr(initializer=Normal(
  177. mean=0.0, std=0.01)))
  178. self.bbox_score.skip_quant = True
  179. self.bbox_delta = nn.Linear(
  180. in_channel,
  181. 4 * self.num_classes,
  182. weight_attr=paddle.ParamAttr(initializer=Normal(
  183. mean=0.0, std=0.001)))
  184. self.bbox_delta.skip_quant = True
  185. self.assigned_label = None
  186. self.assigned_rois = None
  187. @classmethod
  188. def from_config(cls, cfg, input_shape):
  189. roi_pooler = cfg['roi_extractor']
  190. assert isinstance(roi_pooler, dict)
  191. kwargs = RoIAlign.from_config(cfg, input_shape)
  192. roi_pooler.update(kwargs)
  193. kwargs = {'input_shape': input_shape}
  194. head = create(cfg['head'], **kwargs)
  195. return {
  196. 'roi_extractor': roi_pooler,
  197. 'head': head,
  198. 'in_channel': head.out_shape[0].channels
  199. }
  200. def forward(self, body_feats=None, rois=None, rois_num=None, inputs=None):
  201. """
  202. body_feats (list[Tensor]): Feature maps from backbone
  203. rois (list[Tensor]): RoIs generated from RPN module
  204. rois_num (Tensor): The number of RoIs in each image
  205. inputs (dict{Tensor}): The ground-truth of image
  206. """
  207. if self.training:
  208. rois, rois_num, targets = self.bbox_assigner(rois, rois_num, inputs)
  209. self.assigned_rois = (rois, rois_num)
  210. self.assigned_targets = targets
  211. rois_feat = self.roi_extractor(body_feats, rois, rois_num)
  212. bbox_feat = self.head(rois_feat)
  213. if self.with_pool:
  214. feat = F.adaptive_avg_pool2d(bbox_feat, output_size=1)
  215. feat = paddle.squeeze(feat, axis=[2, 3])
  216. else:
  217. feat = bbox_feat
  218. scores = self.bbox_score(feat)
  219. deltas = self.bbox_delta(feat)
  220. if self.training:
  221. loss = self.get_loss(scores, deltas, targets, rois,
  222. self.bbox_weight)
  223. return loss, bbox_feat
  224. else:
  225. pred = self.get_prediction(scores, deltas)
  226. return pred, self.head
  227. def get_loss(self, scores, deltas, targets, rois, bbox_weight):
  228. """
  229. scores (Tensor): scores from bbox head outputs
  230. deltas (Tensor): deltas from bbox head outputs
  231. targets (list[List[Tensor]]): bbox targets containing tgt_labels, tgt_bboxes and tgt_gt_inds
  232. rois (List[Tensor]): RoIs generated in each batch
  233. """
  234. cls_name = 'loss_bbox_cls'
  235. reg_name = 'loss_bbox_reg'
  236. loss_bbox = {}
  237. # TODO: better pass args
  238. tgt_labels, tgt_bboxes, tgt_gt_inds = targets
  239. # bbox cls
  240. tgt_labels = paddle.concat(tgt_labels) if len(
  241. tgt_labels) > 1 else tgt_labels[0]
  242. valid_inds = paddle.nonzero(tgt_labels >= 0).flatten()
  243. if valid_inds.shape[0] == 0:
  244. loss_bbox[cls_name] = paddle.zeros([1], dtype='float32')
  245. else:
  246. tgt_labels = tgt_labels.cast('int64')
  247. tgt_labels.stop_gradient = True
  248. loss_bbox_cls = F.cross_entropy(
  249. input=scores, label=tgt_labels, reduction='mean')
  250. loss_bbox[cls_name] = loss_bbox_cls
  251. # bbox reg
  252. cls_agnostic_bbox_reg = deltas.shape[1] == 4
  253. fg_inds = paddle.nonzero(
  254. paddle.logical_and(tgt_labels >= 0, tgt_labels <
  255. self.num_classes)).flatten()
  256. if fg_inds.numel() == 0:
  257. loss_bbox[reg_name] = paddle.zeros([1], dtype='float32')
  258. return loss_bbox
  259. if cls_agnostic_bbox_reg:
  260. reg_delta = paddle.gather(deltas, fg_inds)
  261. else:
  262. fg_gt_classes = paddle.gather(tgt_labels, fg_inds)
  263. reg_row_inds = paddle.arange(fg_gt_classes.shape[0]).unsqueeze(1)
  264. reg_row_inds = paddle.tile(reg_row_inds, [1, 4]).reshape([-1, 1])
  265. reg_col_inds = 4 * fg_gt_classes.unsqueeze(1) + paddle.arange(4)
  266. reg_col_inds = reg_col_inds.reshape([-1, 1])
  267. reg_inds = paddle.concat([reg_row_inds, reg_col_inds], axis=1)
  268. reg_delta = paddle.gather(deltas, fg_inds)
  269. reg_delta = paddle.gather_nd(reg_delta, reg_inds).reshape([-1, 4])
  270. rois = paddle.concat(rois) if len(rois) > 1 else rois[0]
  271. tgt_bboxes = paddle.concat(tgt_bboxes) if len(
  272. tgt_bboxes) > 1 else tgt_bboxes[0]
  273. reg_target = bbox2delta(rois, tgt_bboxes, bbox_weight)
  274. reg_target = paddle.gather(reg_target, fg_inds)
  275. reg_target.stop_gradient = True
  276. if self.bbox_loss is not None:
  277. reg_delta = self.bbox_transform(reg_delta)
  278. reg_target = self.bbox_transform(reg_target)
  279. loss_bbox_reg = self.bbox_loss(
  280. reg_delta, reg_target).sum() / tgt_labels.shape[0]
  281. loss_bbox_reg *= self.num_classes
  282. else:
  283. loss_bbox_reg = paddle.abs(reg_delta - reg_target).sum(
  284. ) / tgt_labels.shape[0]
  285. loss_bbox[reg_name] = loss_bbox_reg
  286. return loss_bbox
  287. def bbox_transform(self, deltas, weights=[0.1, 0.1, 0.2, 0.2]):
  288. wx, wy, ww, wh = weights
  289. deltas = paddle.reshape(deltas, shape=(0, -1, 4))
  290. dx = paddle.slice(deltas, axes=[2], starts=[0], ends=[1]) * wx
  291. dy = paddle.slice(deltas, axes=[2], starts=[1], ends=[2]) * wy
  292. dw = paddle.slice(deltas, axes=[2], starts=[2], ends=[3]) * ww
  293. dh = paddle.slice(deltas, axes=[2], starts=[3], ends=[4]) * wh
  294. dw = paddle.clip(dw, -1.e10, np.log(1000. / 16))
  295. dh = paddle.clip(dh, -1.e10, np.log(1000. / 16))
  296. pred_ctr_x = dx
  297. pred_ctr_y = dy
  298. pred_w = paddle.exp(dw)
  299. pred_h = paddle.exp(dh)
  300. x1 = pred_ctr_x - 0.5 * pred_w
  301. y1 = pred_ctr_y - 0.5 * pred_h
  302. x2 = pred_ctr_x + 0.5 * pred_w
  303. y2 = pred_ctr_y + 0.5 * pred_h
  304. x1 = paddle.reshape(x1, shape=(-1, ))
  305. y1 = paddle.reshape(y1, shape=(-1, ))
  306. x2 = paddle.reshape(x2, shape=(-1, ))
  307. y2 = paddle.reshape(y2, shape=(-1, ))
  308. return paddle.concat([x1, y1, x2, y2])
  309. def get_prediction(self, score, delta):
  310. bbox_prob = F.softmax(score)
  311. return delta, bbox_prob
  312. def get_head(self, ):
  313. return self.head
  314. def get_assigned_targets(self, ):
  315. return self.assigned_targets
  316. def get_assigned_rois(self, ):
  317. return self.assigned_rois