Skip to content

Commit afb3b7a

Browse files
authored
Remove conditional block in RCNN export onnx (PaddlePaddle#5371)
* support rcnn onnx * clean code * update cascade rcnn * add todo for rpn proposals
1 parent 5715854 commit afb3b7a

File tree

7 files changed

+194
-134
lines changed

7 files changed

+194
-134
lines changed

ppdet/engine/export_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def _dump_infer_config(config, path, image_shape, model):
121121
setup_orderdict()
122122
use_dynamic_shape = True if image_shape[2] == -1 else False
123123
infer_cfg = OrderedDict({
124-
'mode': 'fluid',
124+
'mode': 'paddle',
125125
'draw_threshold': 0.5,
126126
'metric': config['metric'],
127127
'use_dynamic_shape': use_dynamic_shape

ppdet/modeling/architectures/cascade_rcnn.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ def _forward(self):
117117
return bbox_pred, bbox_num, None
118118
mask_out = self.mask_head(body_feats, bbox, bbox_num, self.inputs)
119119
origin_shape = self.bbox_post_process.get_origin_shape()
120-
mask_pred = self.mask_post_process(mask_out[:, 0, :, :], bbox_pred,
121-
bbox_num, origin_shape)
120+
mask_pred = self.mask_post_process(mask_out, bbox_pred, bbox_num,
121+
origin_shape)
122122
return bbox_pred, bbox_num, mask_pred
123123

124124
def get_loss(self, ):

ppdet/modeling/architectures/mask_rcnn.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ def _forward(self):
115115
bbox_pred = self.bbox_post_process.get_pred(bbox, bbox_num,
116116
im_shape, scale_factor)
117117
origin_shape = self.bbox_post_process.get_origin_shape()
118-
mask_pred = self.mask_post_process(mask_out[:, 0, :, :], bbox_pred,
119-
bbox_num, origin_shape)
118+
mask_pred = self.mask_post_process(mask_out, bbox_pred, bbox_num,
119+
origin_shape)
120120
return bbox_pred, bbox_num, mask_pred
121121

122122
def get_loss(self, ):

ppdet/modeling/heads/mask_head.py

+10-14
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def forward(self, feats):
103103

104104
@register
105105
class MaskHead(nn.Layer):
106-
__shared__ = ['num_classes']
106+
__shared__ = ['num_classes', 'export_onnx']
107107
__inject__ = ['mask_assigner']
108108
"""
109109
RCNN mask head
@@ -123,9 +123,11 @@ def __init__(self,
123123
roi_extractor=RoIAlign().__dict__,
124124
mask_assigner='MaskAssigner',
125125
num_classes=80,
126-
share_bbox_feat=False):
126+
share_bbox_feat=False,
127+
export_onnx=False):
127128
super(MaskHead, self).__init__()
128129
self.num_classes = num_classes
130+
self.export_onnx = export_onnx
129131

130132
self.roi_extractor = roi_extractor
131133
if isinstance(roi_extractor, dict):
@@ -206,7 +208,7 @@ def forward_test(self,
206208
rois_num (Tensor): The number of prediction for each batch
207209
scale_factor (Tensor): The scale factor from origin size to input size
208210
"""
209-
if rois.shape[0] == 0:
211+
if not self.export_onnx and rois.shape[0] == 0:
210212
mask_out = paddle.full([1, 1, 1, 1], -1)
211213
else:
212214
bbox = [rois[:, 2:]]
@@ -218,19 +220,13 @@ def forward_test(self,
218220

219221
mask_feat = self.head(rois_feat)
220222
mask_logit = self.mask_fcn_logits(mask_feat)
221-
mask_num_class = mask_logit.shape[1]
222-
if mask_num_class == 1:
223+
if self.num_classes == 1:
223224
mask_out = F.sigmoid(mask_logit)
224225
else:
225-
num_masks = mask_logit.shape[0]
226-
mask_out = []
227-
# TODO: need to optimize gather
228-
for i in range(mask_logit.shape[0]):
229-
pred_masks = paddle.unsqueeze(
230-
mask_logit[i, :, :, :], axis=0)
231-
mask = paddle.gather(pred_masks, labels[i], axis=1)
232-
mask_out.append(mask)
233-
mask_out = F.sigmoid(paddle.concat(mask_out))
226+
num_masks = paddle.shape(mask_logit)[0]
227+
index = paddle.arange(num_masks).cast('int32')
228+
mask_out = mask_logit[index, labels]
229+
mask_out = F.sigmoid(mask_out)
234230
return mask_out
235231

236232
def forward(self,

ppdet/modeling/layers.py

+26-25
Original file line numberDiff line numberDiff line change
@@ -363,58 +363,59 @@ def __call__(self, inputs, image):
363363
@register
364364
@serializable
365365
class RCNNBox(object):
366-
__shared__ = ['num_classes']
366+
__shared__ = ['num_classes', 'export_onnx']
367367

368368
def __init__(self,
369369
prior_box_var=[10., 10., 5., 5.],
370370
code_type="decode_center_size",
371371
box_normalized=False,
372-
num_classes=80):
372+
num_classes=80,
373+
export_onnx=False):
373374
super(RCNNBox, self).__init__()
374375
self.prior_box_var = prior_box_var
375376
self.code_type = code_type
376377
self.box_normalized = box_normalized
377378
self.num_classes = num_classes
379+
self.export_onnx = export_onnx
378380

379381
def __call__(self, bbox_head_out, rois, im_shape, scale_factor):
380382
bbox_pred = bbox_head_out[0]
381383
cls_prob = bbox_head_out[1]
382384
roi = rois[0]
383385
rois_num = rois[1]
384386

385-
origin_shape = paddle.floor(im_shape / scale_factor + 0.5)
386-
scale_list = []
387-
origin_shape_list = []
387+
if self.export_onnx:
388+
onnx_rois_num_per_im = rois_num[0]
389+
origin_shape = paddle.expand(im_shape[0, :],
390+
[onnx_rois_num_per_im, 2])
388391

389-
batch_size = 1
390-
if isinstance(roi, list):
391-
batch_size = len(roi)
392392
else:
393-
batch_size = paddle.slice(paddle.shape(im_shape), [0], [0], [1])
394-
# bbox_pred.shape: [N, C*4]
395-
for idx in range(batch_size):
396-
roi_per_im = roi[idx]
397-
rois_num_per_im = rois_num[idx]
398-
expand_im_shape = paddle.expand(im_shape[idx, :],
399-
[rois_num_per_im, 2])
400-
origin_shape_list.append(expand_im_shape)
393+
origin_shape_list = []
394+
if isinstance(roi, list):
395+
batch_size = len(roi)
396+
else:
397+
batch_size = paddle.slice(paddle.shape(im_shape), [0], [0], [1])
398+
399+
# bbox_pred.shape: [N, C*4]
400+
for idx in range(batch_size):
401+
rois_num_per_im = rois_num[idx]
402+
expand_im_shape = paddle.expand(im_shape[idx, :],
403+
[rois_num_per_im, 2])
404+
origin_shape_list.append(expand_im_shape)
401405

402-
origin_shape = paddle.concat(origin_shape_list)
406+
origin_shape = paddle.concat(origin_shape_list)
403407

404408
# bbox_pred.shape: [N, C*4]
405409
# C=num_classes in faster/mask rcnn(bbox_head), C=1 in cascade rcnn(cascade_head)
406410
bbox = paddle.concat(roi)
407-
if bbox.shape[0] == 0:
408-
bbox = paddle.zeros([0, bbox_pred.shape[1]], dtype='float32')
409-
else:
410-
bbox = delta2bbox(bbox_pred, bbox, self.prior_box_var)
411+
bbox = delta2bbox(bbox_pred, bbox, self.prior_box_var)
411412
scores = cls_prob[:, :-1]
412413

413414
# bbox.shape: [N, C, 4]
414415
# bbox.shape[1] must be equal to scores.shape[1]
415-
bbox_num_class = bbox.shape[1]
416-
if bbox_num_class == 1:
417-
bbox = paddle.tile(bbox, [1, self.num_classes, 1])
416+
total_num = bbox.shape[0]
417+
bbox_dim = bbox.shape[-1]
418+
bbox = paddle.expand(bbox, [total_num, self.num_classes, bbox_dim])
418419

419420
origin_h = paddle.unsqueeze(origin_shape[:, 0], axis=1)
420421
origin_w = paddle.unsqueeze(origin_shape[:, 1], axis=1)
@@ -1422,7 +1423,7 @@ def conv_mixer(
14221423
Seq, ActBn = nn.Sequential, lambda x: Seq(x, nn.GELU(), nn.BatchNorm2D(dim))
14231424
Residual = type('Residual', (Seq, ),
14241425
{'forward': lambda self, x: self[0](x) + x})
1425-
return Seq(*[
1426+
return Seq(* [
14261427
Seq(Residual(
14271428
ActBn(
14281429
nn.Conv2D(

ppdet/modeling/post_process.py

+78-59
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,16 @@
3434

3535
@register
3636
class BBoxPostProcess(nn.Layer):
37-
__shared__ = ['num_classes']
37+
__shared__ = ['num_classes', 'export_onnx']
3838
__inject__ = ['decode', 'nms']
3939

40-
def __init__(self, num_classes=80, decode=None, nms=None):
40+
def __init__(self, num_classes=80, decode=None, nms=None,
41+
export_onnx=False):
4142
super(BBoxPostProcess, self).__init__()
4243
self.num_classes = num_classes
4344
self.decode = decode
4445
self.nms = nms
46+
self.export_onnx = export_onnx
4547

4648
def forward(self, head_out, rois, im_shape, scale_factor):
4749
"""
@@ -52,6 +54,7 @@ def forward(self, head_out, rois, im_shape, scale_factor):
5254
rois (tuple): roi and rois_num of rpn_head output.
5355
im_shape (Tensor): The shape of the input image.
5456
scale_factor (Tensor): The scale factor of the input image.
57+
export_onnx (bool): whether export model to onnx
5558
Returns:
5659
bbox_pred (Tensor): The output prediction with shape [N, 6], including
5760
labels, scores and bboxes. The size of bboxes are corresponding
@@ -62,9 +65,20 @@ def forward(self, head_out, rois, im_shape, scale_factor):
6265
if self.nms is not None:
6366
bboxes, score = self.decode(head_out, rois, im_shape, scale_factor)
6467
bbox_pred, bbox_num, _ = self.nms(bboxes, score, self.num_classes)
68+
6569
else:
6670
bbox_pred, bbox_num = self.decode(head_out, rois, im_shape,
6771
scale_factor)
72+
73+
if self.export_onnx:
74+
# add fake box after postprocess when exporting onnx
75+
fake_bboxes = paddle.to_tensor(
76+
np.array(
77+
[[0., 0.0, 0.0, 0.0, 1.0, 1.0]], dtype='float32'))
78+
79+
bbox_pred = paddle.concat([bbox_pred, fake_bboxes])
80+
bbox_num = bbox_num + 1
81+
6882
return bbox_pred, bbox_num
6983

7084
def get_pred(self, bboxes, bbox_num, im_shape, scale_factor):
@@ -86,45 +100,55 @@ def get_pred(self, bboxes, bbox_num, im_shape, scale_factor):
86100
pred_result (Tensor): The final prediction results with shape [N, 6]
87101
including labels, scores and bboxes.
88102
"""
89-
90-
bboxes_list = []
91-
bbox_num_list = []
92-
id_start = 0
93-
fake_bboxes = paddle.to_tensor(
94-
np.array(
95-
[[-1, 0.0, 0.0, 0.0, 0.0, 0.0]], dtype='float32'))
96-
fake_bbox_num = paddle.to_tensor(np.array([1], dtype='int32'))
97-
98-
# add fake bbox when output is empty for each batch
99-
for i in range(bbox_num.shape[0]):
100-
if bbox_num[i] == 0:
101-
bboxes_i = fake_bboxes
102-
bbox_num_i = fake_bbox_num
103-
else:
104-
bboxes_i = bboxes[id_start:id_start + bbox_num[i], :]
105-
bbox_num_i = bbox_num[i]
106-
id_start += bbox_num[i]
107-
bboxes_list.append(bboxes_i)
108-
bbox_num_list.append(bbox_num_i)
109-
bboxes = paddle.concat(bboxes_list)
110-
bbox_num = paddle.concat(bbox_num_list)
103+
if not self.export_onnx:
104+
bboxes_list = []
105+
bbox_num_list = []
106+
id_start = 0
107+
fake_bboxes = paddle.to_tensor(
108+
np.array(
109+
[[0., 0.0, 0.0, 0.0, 1.0, 1.0]], dtype='float32'))
110+
fake_bbox_num = paddle.to_tensor(np.array([1], dtype='int32'))
111+
112+
# add fake bbox when output is empty for each batch
113+
for i in range(bbox_num.shape[0]):
114+
if bbox_num[i] == 0:
115+
bboxes_i = fake_bboxes
116+
bbox_num_i = fake_bbox_num
117+
else:
118+
bboxes_i = bboxes[id_start:id_start + bbox_num[i], :]
119+
bbox_num_i = bbox_num[i]
120+
id_start += bbox_num[i]
121+
bboxes_list.append(bboxes_i)
122+
bbox_num_list.append(bbox_num_i)
123+
bboxes = paddle.concat(bboxes_list)
124+
bbox_num = paddle.concat(bbox_num_list)
111125

112126
origin_shape = paddle.floor(im_shape / scale_factor + 0.5)
113127

114-
origin_shape_list = []
115-
scale_factor_list = []
116-
# scale_factor: scale_y, scale_x
117-
for i in range(bbox_num.shape[0]):
118-
expand_shape = paddle.expand(origin_shape[i:i + 1, :],
119-
[bbox_num[i], 2])
120-
scale_y, scale_x = scale_factor[i][0], scale_factor[i][1]
121-
scale = paddle.concat([scale_x, scale_y, scale_x, scale_y])
122-
expand_scale = paddle.expand(scale, [bbox_num[i], 4])
123-
origin_shape_list.append(expand_shape)
124-
scale_factor_list.append(expand_scale)
128+
if not self.export_onnx:
129+
origin_shape_list = []
130+
scale_factor_list = []
131+
# scale_factor: scale_y, scale_x
132+
for i in range(bbox_num.shape[0]):
133+
expand_shape = paddle.expand(origin_shape[i:i + 1, :],
134+
[bbox_num[i], 2])
135+
scale_y, scale_x = scale_factor[i][0], scale_factor[i][1]
136+
scale = paddle.concat([scale_x, scale_y, scale_x, scale_y])
137+
expand_scale = paddle.expand(scale, [bbox_num[i], 4])
138+
origin_shape_list.append(expand_shape)
139+
scale_factor_list.append(expand_scale)
140+
141+
self.origin_shape_list = paddle.concat(origin_shape_list)
142+
scale_factor_list = paddle.concat(scale_factor_list)
125143

126-
self.origin_shape_list = paddle.concat(origin_shape_list)
127-
scale_factor_list = paddle.concat(scale_factor_list)
144+
else:
145+
# simplify the computation for bs=1 when exporting onnx
146+
scale_y, scale_x = scale_factor[0][0], scale_factor[0][1]
147+
scale = paddle.concat(
148+
[scale_x, scale_y, scale_x, scale_y]).unsqueeze(0)
149+
self.origin_shape_list = paddle.expand(origin_shape,
150+
[bbox_num[0], 2])
151+
scale_factor_list = paddle.expand(scale, [bbox_num[0], 4])
128152

129153
# bboxes: [N, 6], label, score, bbox
130154
pred_label = bboxes[:, 0:1]
@@ -170,19 +194,20 @@ def paste_mask(self, masks, boxes, im_h, im_w):
170194
"""
171195
Paste the mask prediction to the original image.
172196
"""
173-
197+
x0_int, y0_int = 0, 0
198+
x1_int, y1_int = im_w, im_h
174199
x0, y0, x1, y1 = paddle.split(boxes, 4, axis=1)
175-
masks = paddle.unsqueeze(masks, [0, 1])
176-
img_y = paddle.arange(0, im_h, dtype='float32') + 0.5
177-
img_x = paddle.arange(0, im_w, dtype='float32') + 0.5
200+
N = masks.shape[0]
201+
img_y = paddle.arange(y0_int, y1_int) + 0.5
202+
img_x = paddle.arange(x0_int, x1_int) + 0.5
178203
img_y = (img_y - y0) / (y1 - y0) * 2 - 1
179204
img_x = (img_x - x0) / (x1 - x0) * 2 - 1
180-
img_x = paddle.unsqueeze(img_x, [1])
181-
img_y = paddle.unsqueeze(img_y, [2])
182-
N = boxes.shape[0]
205+
# img_x, img_y have shapes (N, w), (N, h)
183206

184-
gx = paddle.expand(img_x, [N, img_y.shape[1], img_x.shape[2]])
185-
gy = paddle.expand(img_y, [N, img_y.shape[1], img_x.shape[2]])
207+
gx = img_x[:, None, :].expand(
208+
[N, paddle.shape(img_y)[1], paddle.shape(img_x)[1]])
209+
gy = img_y[:, :, None].expand(
210+
[N, paddle.shape(img_y)[1], paddle.shape(img_x)[1]])
186211
grid = paddle.stack([gx, gy], axis=3)
187212
img_masks = F.grid_sample(masks, grid, align_corners=False)
188213
return img_masks[:, 0]
@@ -208,19 +233,13 @@ def __call__(self, mask_out, bboxes, bbox_num, origin_shape):
208233
# TODO: support bs > 1 and mask output dtype is bool
209234
pred_result = paddle.zeros(
210235
[num_mask, origin_shape[0][0], origin_shape[0][1]], dtype='int32')
211-
if bbox_num == 1 and bboxes[0][0] == -1:
212-
return pred_result
213-
214-
# TODO: optimize chunk paste
215-
pred_result = []
216-
for i in range(bboxes.shape[0]):
217-
im_h, im_w = origin_shape[i][0], origin_shape[i][1]
218-
pred_mask = self.paste_mask(mask_out[i], bboxes[i:i + 1, 2:], im_h,
219-
im_w)
220-
pred_mask = pred_mask >= self.binary_thresh
221-
pred_mask = paddle.cast(pred_mask, 'int32')
222-
pred_result.append(pred_mask)
223-
pred_result = paddle.concat(pred_result)
236+
237+
im_h, im_w = origin_shape[0][0], origin_shape[0][1]
238+
pred_mask = self.paste_mask(mask_out[:, None, :, :], bboxes[:, 2:],
239+
im_h, im_w)
240+
pred_mask = pred_mask >= self.binary_thresh
241+
pred_result = paddle.cast(pred_mask, 'int32')
242+
224243
return pred_result
225244

226245

0 commit comments

Comments
 (0)