converter.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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. import paddle.fluid as fluid
  16. import os
  17. import sys
  18. import paddlex as pdx
  19. import paddlex.utils.logging as logging
  20. __all__ = ['export_onnx']
  21. def export_onnx(model_dir, save_dir, fixed_input_shape):
  22. assert len(fixed_input_shape) == 2, "len of fixed input shape must == 2"
  23. model = pdx.load_model(model_dir, fixed_input_shape)
  24. model_name = os.path.basename(model_dir.strip('/')).split('/')[-1]
  25. export_onnx_model(model, save_dir)
  26. class MultiClassNMS4OpenVINO():
  27. """
  28. Convert the paddle multiclass_nms to onnx op.
  29. This op is get the select boxes from origin boxes.
  30. """
  31. @classmethod
  32. def opset_10(cls, graph, node, **kw):
  33. from paddle2onnx.constant import dtypes
  34. import numpy as np
  35. result_name = node.output('Out', 0)
  36. background = node.attr('background_label')
  37. normalized = node.attr('normalized')
  38. if normalized == False:
  39. logging.warn(
  40. "The parameter normalized of multiclass_nms OP of Paddle is False, which has diff with ONNX." \
  41. " Please set normalized=True in multiclass_nms of Paddle, see doc Q1 in" \
  42. " https://github.com/PaddlePaddle/paddle2onnx/blob/develop/FAQ.md")
  43. #convert the paddle attribute to onnx tensor
  44. node_score_threshold = graph.make_node(
  45. 'Constant',
  46. inputs=[],
  47. dtype=dtypes.ONNX.FLOAT,
  48. value=[float(node.attr('score_threshold'))])
  49. node_iou_threshold = graph.make_node(
  50. 'Constant',
  51. inputs=[],
  52. dtype=dtypes.ONNX.FLOAT,
  53. value=[float(node.attr('nms_threshold'))])
  54. node_keep_top_k = graph.make_node(
  55. 'Constant',
  56. inputs=[],
  57. dtype=dtypes.ONNX.INT64,
  58. value=[np.int64(node.attr('keep_top_k'))])
  59. node_keep_top_k_2D = graph.make_node(
  60. 'Constant',
  61. inputs=[],
  62. dtype=dtypes.ONNX.INT64,
  63. dims=[1, 1],
  64. value=[node.attr('keep_top_k')])
  65. # the paddle data format is x1,y1,x2,y2
  66. kwargs = {'center_point_box': 0}
  67. node_select_nms= graph.make_node(
  68. 'NonMaxSuppression',
  69. inputs=[node.input('BBoxes', 0), node.input('Scores', 0), node_keep_top_k,\
  70. node_iou_threshold, node_score_threshold])
  71. # step 1 nodes select the nms class
  72. # create some const value to use
  73. node_const_value = [result_name+"@const_0",
  74. result_name+"@const_1",\
  75. result_name+"@const_2",\
  76. result_name+"@const_-1"]
  77. value_const_value = [0, 1, 2, -1]
  78. for name, value in zip(node_const_value, value_const_value):
  79. graph.make_node(
  80. 'Constant',
  81. layer_name=name,
  82. inputs=[],
  83. outputs=[name],
  84. dtype=dtypes.ONNX.INT64,
  85. value=[value])
  86. # In this code block, we will deocde the raw score data, reshape N * C * M to 1 * N*C*M
  87. # and the same time, decode the select indices to 1 * D, gather the select_indices
  88. node_gather_1 = graph.make_node(
  89. 'Gather',
  90. inputs=[node_select_nms, result_name + "@const_1"],
  91. axis=1)
  92. node_gather_1 = graph.make_node(
  93. 'Unsqueeze', inputs=[node_gather_1], axes=[0])
  94. node_gather_2 = graph.make_node(
  95. 'Gather',
  96. inputs=[node_select_nms, result_name + "@const_2"],
  97. axis=1)
  98. node_gather_2 = graph.make_node(
  99. 'Unsqueeze', inputs=[node_gather_2], axes=[0])
  100. # reshape scores N * C * M to (N*C*M) * 1
  101. node_reshape_scores_rank1 = graph.make_node(
  102. "Reshape",
  103. inputs=[node.input('Scores', 0), result_name + "@const_-1"])
  104. # get the shape of scores
  105. node_shape_scores = graph.make_node(
  106. 'Shape', inputs=node.input('Scores'))
  107. # gather the index: 2 shape of scores
  108. node_gather_scores_dim1 = graph.make_node(
  109. 'Gather',
  110. inputs=[node_shape_scores, result_name + "@const_2"],
  111. axis=0)
  112. # mul class * M
  113. node_mul_classnum_boxnum = graph.make_node(
  114. 'Mul', inputs=[node_gather_1, node_gather_scores_dim1])
  115. # add class * M * index
  116. node_add_class_M_index = graph.make_node(
  117. 'Add', inputs=[node_mul_classnum_boxnum, node_gather_2])
  118. # Squeeze the indices to 1 dim
  119. node_squeeze_select_index = graph.make_node(
  120. 'Squeeze', inputs=[node_add_class_M_index], axes=[0, 2])
  121. # gather the data from flatten scores
  122. node_gather_select_scores = graph.make_node(
  123. 'Gather',
  124. inputs=[node_reshape_scores_rank1, node_squeeze_select_index],
  125. axis=0)
  126. # get nums to input TopK
  127. node_shape_select_num = graph.make_node(
  128. 'Shape', inputs=[node_gather_select_scores])
  129. node_gather_select_num = graph.make_node(
  130. 'Gather',
  131. inputs=[node_shape_select_num, result_name + "@const_0"],
  132. axis=0)
  133. node_unsqueeze_select_num = graph.make_node(
  134. 'Unsqueeze', inputs=[node_gather_select_num], axes=[0])
  135. node_concat_topK_select_num = graph.make_node(
  136. 'Concat',
  137. inputs=[node_unsqueeze_select_num, node_keep_top_k_2D],
  138. axis=0)
  139. node_cast_concat_topK_select_num = graph.make_node(
  140. 'Cast', inputs=[node_concat_topK_select_num], to=6)
  141. # get min(topK, num_select)
  142. node_compare_topk_num_select = graph.make_node(
  143. 'ReduceMin', inputs=[node_cast_concat_topK_select_num], keepdims=0)
  144. # unsqueeze the indices to 1D tensor
  145. node_unsqueeze_topk_select_indices = graph.make_node(
  146. 'Unsqueeze', inputs=[node_compare_topk_num_select], axes=[0])
  147. # cast the indices to INT64
  148. node_cast_topk_indices = graph.make_node(
  149. 'Cast', inputs=[node_unsqueeze_topk_select_indices], to=7)
  150. # select topk scores indices
  151. outputs_topk_select_topk_indices = [result_name + "@topk_select_topk_values",\
  152. result_name + "@topk_select_topk_indices"]
  153. node_topk_select_topk_indices = graph.make_node(
  154. 'TopK',
  155. inputs=[node_gather_select_scores, node_cast_topk_indices],
  156. outputs=outputs_topk_select_topk_indices)
  157. # gather topk label, scores, boxes
  158. node_gather_topk_scores = graph.make_node(
  159. 'Gather',
  160. inputs=[
  161. node_gather_select_scores, outputs_topk_select_topk_indices[1]
  162. ],
  163. axis=0)
  164. node_gather_topk_class = graph.make_node(
  165. 'Gather',
  166. inputs=[
  167. node_gather_1, outputs_topk_select_topk_indices[1]
  168. ],
  169. axis=1)
  170. # gather the boxes need to gather the boxes id, then get boxes
  171. node_gather_topk_boxes_id = graph.make_node(
  172. 'Gather',
  173. inputs=[
  174. node_gather_2, outputs_topk_select_topk_indices[1]
  175. ],
  176. axis=1)
  177. # squeeze the gather_topk_boxes_id to 1 dim
  178. node_squeeze_topk_boxes_id = graph.make_node(
  179. 'Squeeze', inputs=[node_gather_topk_boxes_id], axes=[0, 2])
  180. node_gather_select_boxes = graph.make_node(
  181. 'Gather',
  182. inputs=[node.input('BBoxes', 0), node_squeeze_topk_boxes_id],
  183. axis=1)
  184. # concat the final result
  185. # before concat need to cast the class to float
  186. node_cast_topk_class = graph.make_node(
  187. 'Cast', inputs=[node_gather_topk_class], to=1)
  188. node_unsqueeze_topk_scores = graph.make_node(
  189. 'Unsqueeze', inputs=[node_gather_topk_scores], axes=[0, 2])
  190. inputs_concat_final_results = [node_cast_topk_class, node_unsqueeze_topk_scores, \
  191. node_gather_select_boxes]
  192. node_sort_by_socre_results = graph.make_node(
  193. 'Concat', inputs=inputs_concat_final_results, axis=2)
  194. # select topk classes indices
  195. node_squeeze_cast_topk_class = graph.make_node(
  196. 'Squeeze', inputs=[node_cast_topk_class], axes=[0, 2])
  197. node_neg_squeeze_cast_topk_class = graph.make_node(
  198. 'Neg', inputs=[node_squeeze_cast_topk_class])
  199. outputs_topk_select_classes_indices = [result_name + "@topk_select_topk_classes_scores",\
  200. result_name + "@topk_select_topk_classes_indices"]
  201. node_topk_select_topk_indices = graph.make_node(
  202. 'TopK',
  203. inputs=[node_neg_squeeze_cast_topk_class, node_cast_topk_indices],
  204. outputs=outputs_topk_select_classes_indices)
  205. node_concat_final_results = graph.make_node(
  206. 'Gather',
  207. inputs=[
  208. node_sort_by_socre_results,
  209. outputs_topk_select_classes_indices[1]
  210. ],
  211. axis=1)
  212. node_concat_final_results = graph.make_node(
  213. 'Squeeze',
  214. inputs=[node_concat_final_results],
  215. outputs=[node.output('Out', 0)],
  216. axes=[0])
  217. if node.type == 'multiclass_nms2':
  218. graph.make_node(
  219. 'Squeeze',
  220. inputs=[node_gather_2],
  221. outputs=node.output('Index'),
  222. axes=[0])
  223. def export_onnx_model(model, save_dir, opset_version=10):
  224. if model.__class__.__name__ == "FastSCNN" or (
  225. model.model_type == "detector" and
  226. model.__class__.__name__ != "YOLOv3"):
  227. logging.error(
  228. "Only image classifier models, detection models(YOLOv3) and semantic segmentation models(except FastSCNN) are supported to export to ONNX"
  229. )
  230. try:
  231. import paddle2onnx
  232. except:
  233. logging.error(
  234. "You need to install paddle2onnx first, pip install paddle2onnx")
  235. import paddle2onnx as p2o
  236. if opset_version == 10 and model.__class__.__name__ == "YOLOv3":
  237. logging.warning(
  238. "Export for openVINO by default, the output of multiclass_nms exported to onnx will contains background. If you need onnx completely consistent with paddle, please use paddle2onnx to export"
  239. )
  240. p2o.register_op_mapper('multiclass_nms', MultiClassNMS4OpenVINO)
  241. p2o.program2onnx(
  242. model.test_prog,
  243. save_dir,
  244. scope=model.scope,
  245. opset_version=opset_version)