-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Support exporting fpn #372
Changes from all commits
6064f00
94c7251
7d01723
6b3de8b
23234e4
abaa4b9
3558c35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,6 +50,7 @@ | |
from detectron.utils.logging import setup_logging | ||
from detectron.utils.model_convert_utils import convert_op_in_proto | ||
from detectron.utils.model_convert_utils import op_filter | ||
import detectron.utils.blob as blob_utils | ||
import detectron.core.test_engine as test_engine | ||
import detectron.utils.c2 as c2_utils | ||
import detectron.utils.model_convert_utils as mutils | ||
|
@@ -124,10 +125,41 @@ def unscope_name(name): | |
|
||
|
||
def reset_names(names): | ||
for i in range(0, len(names)): | ||
for i in range(len(names)): | ||
names[i] = unscope_name(names[i]) | ||
|
||
|
||
def convert_collect_and_distribute( | ||
op, blobs, | ||
roi_canonical_scale, | ||
roi_canonical_level, | ||
roi_max_level, | ||
roi_min_level, | ||
rpn_max_level, | ||
rpn_min_level, | ||
rpn_post_nms_topN, | ||
): | ||
print('Converting CollectAndDistributeFpnRpnProposals' | ||
' Python -> C++:\n{}'.format(op)) | ||
assert op.name.startswith('CollectAndDistributeFpnRpnProposalsOp'), \ | ||
'Not valid CollectAndDistributeFpnRpnProposalsOp' | ||
|
||
inputs = [x for x in op.input] | ||
ret = core.CreateOperator( | ||
'CollectAndDistributeFpnRpnProposals', | ||
inputs, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi, Iam trying to convert maskrcnn model from pkl to pb format. But Iam getting below error Input to the script is config file = e2e_mask_rcnn_R-101-FPN_1x.yaml and its pkl model There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Looks like you also need #449 PR to do what you want. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi, Iam able to convert the model from pkl to pb format with convert_pkl_to_pb.py with args.device = cpu. But when Iam viewing the network using netron (https://github.com/lutzroeder/Netron), mask_conv_body section is missing in the graph. I want to understand if this is the issue with convert_pkl_to_pb.py or with Netron. |
||
list(op.output), | ||
roi_canonical_scale=roi_canonical_scale, | ||
roi_canonical_level=roi_canonical_level, | ||
roi_max_level=roi_max_level, | ||
roi_min_level=roi_min_level, | ||
rpn_max_level=rpn_max_level, | ||
rpn_min_level=rpn_min_level, | ||
rpn_post_nms_topN=rpn_post_nms_topN, | ||
) | ||
return ret | ||
|
||
|
||
def convert_gen_proposals( | ||
op, blobs, | ||
rpn_pre_nms_topN, | ||
|
@@ -136,19 +168,22 @@ def convert_gen_proposals( | |
rpn_min_size, | ||
): | ||
print('Converting GenerateProposals Python -> C++:\n{}'.format(op)) | ||
assert op.name.startswith("GenerateProposalsOp"), "Not valid GenerateProposalsOp" | ||
assert op.name.startswith('GenerateProposalsOp'), 'Not valid GenerateProposalsOp' | ||
|
||
spatial_scale = mutils.get_op_arg_valf(op, "spatial_scale", None) | ||
spatial_scale = mutils.get_op_arg_valf(op, 'spatial_scale', None) | ||
assert spatial_scale is not None | ||
|
||
lvl = int(op.input[0][-1]) if op.input[0][-1].isdigit() else None | ||
|
||
inputs = [x for x in op.input] | ||
anchor_name = "anchor" | ||
anchor_name = 'anchor{}'.format(lvl) if lvl else 'anchor' | ||
inputs.append(anchor_name) | ||
blobs[anchor_name] = get_anchors(spatial_scale) | ||
anchor_sizes = (cfg.FPN.RPN_ANCHOR_START_SIZE * 2.**(lvl - cfg.FPN.RPN_MIN_LEVEL),) if lvl else cfg.RPN.SIZES | ||
blobs[anchor_name] = get_anchors(spatial_scale, anchor_sizes) | ||
print('anchors {}'.format(blobs[anchor_name])) | ||
|
||
ret = core.CreateOperator( | ||
"GenerateProposals", | ||
'GenerateProposals', | ||
inputs, | ||
list(op.output), | ||
spatial_scale=spatial_scale, | ||
|
@@ -158,14 +193,13 @@ def convert_gen_proposals( | |
min_size=rpn_min_size, | ||
correct_transform_coords=True, | ||
) | ||
|
||
return ret, anchor_name | ||
|
||
|
||
def get_anchors(spatial_scale): | ||
def get_anchors(spatial_scale, anchor_sizes): | ||
anchors = generate_anchors.generate_anchors( | ||
stride=1. / spatial_scale, | ||
sizes=cfg.RPN.SIZES, | ||
sizes=anchor_sizes, | ||
aspect_ratios=cfg.RPN.ASPECT_RATIOS).astype(np.float32) | ||
return anchors | ||
|
||
|
@@ -188,36 +222,78 @@ def convert_op_name(op): | |
reset_names(op.output) | ||
return [op] | ||
|
||
@op_filter(type="Python", inputs=['rpn_cls_probs', 'rpn_bbox_pred', 'im_info']) | ||
def convert_gen_proposal(op_in): | ||
gen_proposals_op, ext_input = convert_gen_proposals( | ||
op_in, blobs, | ||
rpn_min_size=float(cfg.TEST.RPN_MIN_SIZE), | ||
rpn_post_nms_topN=cfg.TEST.RPN_POST_NMS_TOP_N, | ||
rpn_pre_nms_topN=cfg.TEST.RPN_PRE_NMS_TOP_N, | ||
rpn_nms_thresh=cfg.TEST.RPN_NMS_THRESH, | ||
) | ||
net.external_input.extend([ext_input]) | ||
return [gen_proposals_op] | ||
@op_filter(type='Python') | ||
def convert_python(op): | ||
if op.name.startswith('GenerateProposalsOp'): | ||
gen_proposals_op, ext_input = convert_gen_proposals( | ||
op, blobs, | ||
rpn_min_size=float(cfg.TEST.RPN_MIN_SIZE), | ||
rpn_post_nms_topN=cfg.TEST.RPN_POST_NMS_TOP_N, | ||
rpn_pre_nms_topN=cfg.TEST.RPN_PRE_NMS_TOP_N, | ||
rpn_nms_thresh=cfg.TEST.RPN_NMS_THRESH, | ||
) | ||
net.external_input.extend([ext_input]) | ||
return [gen_proposals_op] | ||
elif op.name.startswith('CollectAndDistributeFpnRpnProposalsOp'): | ||
collect_dist_op = convert_collect_and_distribute( | ||
op, blobs, | ||
roi_canonical_scale=cfg.FPN.ROI_CANONICAL_SCALE, | ||
roi_canonical_level=cfg.FPN.ROI_CANONICAL_LEVEL, | ||
roi_max_level=cfg.FPN.ROI_MAX_LEVEL, | ||
roi_min_level=cfg.FPN.ROI_MIN_LEVEL, | ||
rpn_max_level=cfg.FPN.RPN_MAX_LEVEL, | ||
rpn_min_level=cfg.FPN.RPN_MIN_LEVEL, | ||
rpn_post_nms_topN=cfg.TEST.RPN_POST_NMS_TOP_N, | ||
) | ||
return [collect_dist_op] | ||
else: | ||
raise ValueError('Failed to convert Python op {}'.format( | ||
op.name)) | ||
|
||
# Only convert UpsampleNearest to ResizeNearest when converting to pb so that the existing models is unchanged | ||
# https://github.com/facebookresearch/Detectron/pull/372#issuecomment-410248561 | ||
@op_filter(type='UpsampleNearest') | ||
def convert_upsample_nearest(op): | ||
for arg in op.arg: | ||
if arg.name == 'scale': | ||
scale = arg.i | ||
break | ||
else: | ||
raise KeyError('No attribute "scale" in UpsampleNearest op') | ||
resize_nearest_op = core.CreateOperator('ResizeNearest', | ||
list(op.input), | ||
list(op.output), | ||
name=op.name, | ||
width_scale=float(scale), | ||
height_scale=float(scale)) | ||
return resize_nearest_op | ||
|
||
@op_filter(input_has='rois') | ||
@op_filter() | ||
def convert_rpn_rois(op): | ||
for j in range(0, len(op.input)): | ||
for j in range(len(op.input)): | ||
if op.input[j] == 'rois': | ||
print('Converting op {} input name: rois -> rpn_rois:\n{}'.format( | ||
op.type, op)) | ||
op.input[j] = 'rpn_rois' | ||
for j in range(len(op.output)): | ||
if op.output[j] == 'rois': | ||
print('Converting op {} output name: rois -> rpn_rois:\n{}'.format( | ||
op.type, op)) | ||
op.output[j] = 'rpn_rois' | ||
return [op] | ||
|
||
@op_filter(type_in=['StopGradient', 'Alias']) | ||
def convert_remove_op(op): | ||
print('Removing op {}:\n{}'.format(op.type, op)) | ||
return [] | ||
|
||
# We want to apply to all operators, including converted | ||
# so run separately | ||
convert_op_in_proto(net, convert_remove_op) | ||
convert_op_in_proto(net, convert_upsample_nearest) | ||
convert_op_in_proto(net, convert_python) | ||
convert_op_in_proto(net, convert_op_name) | ||
convert_op_in_proto(net, [ | ||
convert_gen_proposal, convert_rpn_rois, convert_remove_op | ||
]) | ||
convert_op_in_proto(net, convert_rpn_rois) | ||
|
||
reset_names(net.external_input) | ||
reset_names(net.external_output) | ||
|
@@ -272,6 +348,7 @@ def convert_model_gpu(args, net, init_net): | |
cdo_cpu = mutils.get_device_option_cpu() | ||
|
||
CPU_OPS = [ | ||
["CollectAndDistributeFpnRpnProposals", None], | ||
["GenerateProposals", None], | ||
["BBoxTransform", None], | ||
["BoxWithNMSLimit", None], | ||
|
@@ -424,10 +501,8 @@ def _prepare_blobs( | |
im = cv2.resize(im, None, None, fx=im_scale, fy=im_scale, | ||
interpolation=cv2.INTER_LINEAR) | ||
|
||
blob = np.zeros([1, im.shape[0], im.shape[1], 3], dtype=np.float32) | ||
blob[0, :, :, :] = im | ||
channel_swap = (0, 3, 1, 2) # swap channel to (k, c, h, w) | ||
blob = blob.transpose(channel_swap) | ||
# Reuse code in blob_utils and fit FPN | ||
blob = blob_utils.im_list_to_blob([im]) | ||
|
||
blobs = {} | ||
blobs['data'] = blob | ||
|
@@ -462,7 +537,7 @@ def run_model_pb(args, net, init_net, im, check_blobs): | |
) | ||
|
||
try: | ||
workspace.RunNet(net.Proto().name) | ||
workspace.RunNet(net) | ||
scores = workspace.FetchBlob('score_nms') | ||
classids = workspace.FetchBlob('class_nms') | ||
boxes = workspace.FetchBlob('bbox_nms') | ||
|
@@ -520,13 +595,16 @@ def main(): | |
merge_cfg_from_list(args.opts) | ||
cfg.NUM_GPUS = 1 | ||
assert_and_infer_cfg() | ||
logger.info('Conerting model with config:') | ||
logger.info('Converting model with config:') | ||
logger.info(pprint.pformat(cfg)) | ||
|
||
assert not cfg.MODEL.KEYPOINTS_ON, "Keypoint model not supported." | ||
assert not cfg.MODEL.MASK_ON, "Mask model not supported." | ||
assert not cfg.FPN.FPN_ON, "FPN not supported." | ||
assert not cfg.RETINANET.RETINANET_ON, "RetinaNet model not supported." | ||
# script will stop when it can't find an operator rather | ||
# than stopping based on these flags | ||
# | ||
# assert not cfg.MODEL.KEYPOINTS_ON, "Keypoint model not supported." | ||
# assert not cfg.MODEL.MASK_ON, "Mask model not supported." | ||
# assert not cfg.FPN.FPN_ON, "FPN not supported." | ||
# assert not cfg.RETINANET.RETINANET_ON, "RetinaNet model not supported." | ||
|
||
# load model from cfg | ||
model, blobs = load_model(args) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use
list(op.input)
? It seems to have the exact same output.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is from @orionr 's code. I have no idea whether it is better for me to change it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @daquexian,
Thanks for writing this. While running it to convert pkl model(FPN), I get an error at verify_model(args, [net, init_net], args.test_img)
Something like as shown below:
AssertionError:
Arrays are not almost equal to 3 decimals
result_boxes and result_boxes not matched. Max diff: 5.0
(mismatch 0.606060606061%)
x: array([[3.329e+02, 1.799e+01, 5.250e+02, 3.556e+02, 9.995e-01],
[0.000e+00, 2.073e+01, 2.308e+02, 3.685e+02, 9.926e-01],
[2.459e+01, 3.320e+01, 1.330e+02, 1.092e+02, 9.909e-01],...
y: array([[3.329e+02, 1.799e+01, 5.250e+02, 3.556e+02, 9.995e-01],
[0.000e+00, 2.073e+01, 2.308e+02, 3.685e+02, 9.926e-01],
[2.459e+01, 3.320e+01, 1.330e+02, 1.092e+02, 9.909e-01],...
I get this error when I use a test_img, otherwise model gets written in pb format.
How important it is to get correct boxes on test image??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daquexian Sure. I can share the model, config file and test image. How would you like me to share it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daquexian : I was able to convert pkl model(with fpn) to protobufs. However, when I try to run this net, I get following error:
RuntimeError: [enforce fail at utility_ops.h:275] . Check failed: output->dims() == Input(i).dims().Description: Input #1, input dimension:1 256 38 60 should match output dimension: 1 256 38 59 Error from operator:
input: "fpn_inner_res4_5_sum_lateral" input: "fpn_inner_res4_5_sum_topdown" output: "fpn_inner_res4_5_sum" name: "" type: "Sum" device_option { device_type: 1 cuda_gpu_id: 0 }
The error is typical of models with fpn layers. I never encountered this kind of issue before. Any pointers to what might be causing this would be of great help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daquexian : Yes, there is a method
blob_utils.im_list_to_blob([im])
inconvert_pkl_to_pb.py
. The size of original image is(377, 590, 3)
.The size of the blob while data prep becomes
(1, 3, 500, 782)
. Also, content ofim_info
isarray([[500. , 782. , 1.32626]], dtype=float32)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rohitbhio The error you encountered is caused by input size which is not a multiple of 32. When the size is not a multiple of 32, for example, the width is 36, after a certain layer, a feature map whose width=36/2/2=9 is produced, then after a conv layer whose stride=2 and a upsample2x layer, a feature map whose width=8 instead of 9 is produced, then an error occurs when summing these two feature maps whose widths are different with each other.
blob_utils.im_list_to_blob
pads the input so that its height and width will be a multiple of 32. You can try to print some messages to check whetherblob_utils.im_list_to_blob
works correctly.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daquexian: Thanks for the explanation. Your observation regarding
blob_utils.im_list_to_blob
method is correct. For some reason, the method was not adjusting image blob dimensions according toCOARSEST_STRIDE
. Now, the size of the blob while data prep becomes (1, 3, 512, 800) and I am able to make detections. Wow. Thanks.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rohitbhio Glad to see it :) Could you please tell me why the method doesn't adjust the dimensions? Is there anything wrong in my PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daquexian Your PR is awesome. I was trying to load these protobuf models without loading its corresponding config yaml file. Now,
blob_utils.im_list_to_blob
method ended up usingcfg.FPN.FPN_ON
andcfg.FPN.COARSEST_STRIDE
from default config file(config.py). In default config file,cfg.FPN.FPN_ON
is set toFalse
. For my purpose, I made a copy ofim_list_to_blob
method from blob.py and modified it so that I could explicitly passFPN_ON
andCOARSEST_STRIDE
as input parameters so that this method doesn't take global values.