PINTO0309 / openvino2tensorflow

This script converts the ONNX/OpenVINO IR model to Tensorflow's saved_model, tflite, h5, tfjs, tftrt(TensorRT), CoreML, EdgeTPU, ONNX and pb. PyTorch (NCHW) -> ONNX (NCHW) -> OpenVINO (NCHW) -> openvino2tensorflow -> Tensorflow/Keras (NHWC/NCHW) -> TFLite (NHWC/NCHW). And the conversion from .pb to saved_model and from saved_model to .pb and from .pb to .tflite and saved_model to .tflite and saved_model to onnx. Support for building environments with Docker. It is possible to directly access the host PC GUI and the camera to verify the operation. NVIDIA GPU (dGPU) support. Intel iHD GPU (iGPU) support.
MIT License
334 stars 40 forks source link

[Yolact] Cannot convert ONNX model with NMS to tflite #51

Closed taisuke-tomida closed 3 years ago

taisuke-tomida commented 3 years ago

I introduced NMS to yolact's ONNX model in my own unique way.
However, after converting to OpenVINO, I am unable to convert to tflite.

os etc

windows10 cuda10.2

Version

python=3.6.8 tensorflow=2.5.0 onnx=1.9.0 openvino2tensorflow=1.15.3 pytorch=1.8.1+cu102

How to add an NMS

  1. Make the code modification as shown in this URL.
  2. Change the output of the Yolact class in the modified yolact.py to look like the following.
    return self.detect(pred_outs)
  3. The loc and priors in detection.py are gone, and decode_boxes is defined instead.
    # loc_data   = predictions['loc']
    conf_data  = predictions['conf']
    mask_data  = predictions['mask']
    # prior_data = predictions['priors']
    decoded_boxes = predictions['boxes'] # [1,19248,4]
  4. The continuation of number 3 was as follows.
    
    proto_data = predictions['proto'] if 'proto' in predictions else None
    inst_data  = predictions['inst']  if 'inst'  in predictions else None

out = []

with timer.env('Detect'): batch_size = 1 num_priors = 19248

conf_preds = conf_data.view(batch_size, num_priors, self.num_classes).transpose(2, 1).contiguous()

for batch_idx in range(batch_size):
    result = self.detect(batch_idx, conf_preds, decoded_boxes[0], mask_data, inst_data)

    if result is not None and proto_data is not None:
        result['proto'] = proto_data[batch_idx]

    out.append(result)

return out

5. Next, we move on to the NMS process. Here, I thought of adding NMS using torchvision.ops.nms
```python
def pytorch_nms(self, boxes, decode_boxes, masks, conf_scores, scores, iou_threshold:float=0.5, top_k:int=200):
    scores, classes = scores.max(dim=0)

    _, idx = scores.sort(0, descending=True)
    idx = idx[:top_k]

    id = (conf_scores > self.conf_thresh)
    conf_scores = conf_scores[id]
    decode_boxes = decode_boxes[id, :]

    keep = torchvision.ops.nms(boxes=decode_boxes, scores=conf_scores, iou_threshold=iou_threshold).to(torch.int64)

    return boxes[keep], masks[keep], classes[keep], scores[keep]
  1. Convert to ONNX.
    torch.onnx.export(net, dummy, "yolact_detect_pytorchnms10.onnx", opset_version=11, input_names=['input'], output_names=['boxes', 'masks', 'classes', 'scores', 'proto'])
    # If opset_version<=10, torchvision.ops.nms could not be converted.

Error in openvino2tensorflow

command

python openvino2tensorflow.py --model_path yolact_detect_pytorchnms11.xml --model_output_path yolact --output_saved_model --output_no_quant_float32_tflite --output_integer_quant_tflite --output_full_integer_quant_tflite

result

TensorFlow/Keras model building process starts ======================================
ERROR: Value passed to parameter 'indices' has DataType bool not in list of allowed values: int32, int64
ERROR: model_path  : yolact_detect_pytorchnms11.xml
ERROR: weights_path: yolact_detect_pytorchnms11.bin
ERROR: layer_id    : 646
ERROR: input_layer0 layer_id=642: KerasTensor(type_spec=TensorSpec(shape=(19248, 4), dtype=tf.float32, name=None), name='tf.compat.v1.squeeze_2/Squeeze:0', description="created by layer 'tf.compat.v1.squeeze_2'")
ERROR: input_layer1 layer_id=645: KerasTensor(type_spec=TensorSpec(shape=(None, 1), dtype=tf.bool, name=None), name='tf.compat.v1.transpose_12/transpose:0', description="created by layer 'tf.compat.v1.transpose_12'")
ERROR: The trace log is below.
Traceback (most recent call last):
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\ops\array_ops.py", line 5350, in gather_nd
    return params.gather_nd(indices, name=name)
AttributeError: 'KerasTensor' object has no attribute 'gather_nd'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\util\dispatch.py", line 206, in wrapper
    return target(*args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\ops\array_ops.py", line 5352, in gather_nd
    return gen_array_ops.gather_nd(params, indices, name=name)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\ops\gen_array_ops.py", line 3719, in gather_nd
    params, indices, name=name, ctx=_ctx)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\ops\gen_array_ops.py", line 3739, in gather_nd_eager_fallback
    _attr_Tparams, (params,) = _execute.args_to_matching_eager([params], ctx, [])
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\eager\execute.py", line 274, in args_to_matching_eager
    t, dtype, preferred_dtype=default_dtype, ctx=ctx)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\profiler\trace.py", line 163, in wrapped
    return func(*args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\framework\ops.py", line 1566, in convert_to_tensor
    ret = conversion_func(value, dtype=dtype, name=name, as_ref=as_ref)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\framework\constant_op.py", line 339, in _constant_tensor_conversion_function
    return constant(v, dtype=dtype, name=name)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\framework\constant_op.py", line 265, in constant
    allow_broadcast=True)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\framework\constant_op.py", line 276, in _constant_impl
    return _constant_eager_impl(ctx, value, dtype, shape, verify_shape)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\framework\constant_op.py", line 301, in _constant_eager_impl
    t = convert_to_eager_tensor(value, ctx, dtype)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\framework\constant_op.py", line 98, in convert_to_eager_tensor
    return ops.EagerTensor(value, ctx.device_name, dtype)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\engine\keras_tensor.py", line 255, in __array__
    'Cannot convert a symbolic Keras input/output to a numpy array. '
TypeError: Cannot convert a symbolic Keras input/output to a numpy array. This error may indicate that you're trying to pass a symbolic value to a NumPy call, which is not supported. Or, you may be trying to pass Keras symbolic inputs/outputs to a TF API that does not register dispatching, preventing Keras from automatically converting the API call to a lambda layer in the Functional Model.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\ops\array_ops.py", line 5350, in gather_nd
    return params.gather_nd(indices, name=name)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\framework\ops.py", line 401, in __getattr__
    self.__getattribute__(name)
AttributeError: 'Tensor' object has no attribute 'gather_nd'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\util\dispatch.py", line 206, in wrapper
    return target(*args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\ops\array_ops.py", line 5360, in gather_nd_v2
    return gather_nd(params, indices, name=name, batch_dims=batch_dims)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\util\dispatch.py", line 210, in wrapper
    result = dispatch(wrapper, args, kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\util\dispatch.py", line 126, in dispatch
    result = dispatcher.handle(op, args, kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\layers\core.py", line 1486, in handle
    return TFOpLambda(op)(*args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\engine\base_layer.py", line 970, in __call__
    input_list)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\engine\base_layer.py", line 1108, in _functional_construction_call
    inputs, input_masks, args, kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\engine\base_layer.py", line 840, in _keras_tensor_symbolic_call
    return self._infer_output_signature(inputs, args, kwargs, input_masks)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\engine\base_layer.py", line 880, in _infer_output_signature
    outputs = call_fn(inputs, *args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\layers\core.py", line 1363, in _call_wrapper
    return self._call_wrapper(*args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\layers\core.py", line 1395, in _call_wrapper
    result = self.function(*args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\util\dispatch.py", line 206, in wrapper
    return target(*args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\ops\array_ops.py", line 5352, in gather_nd
    return gen_array_ops.gather_nd(params, indices, name=name)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\ops\gen_array_ops.py", line 3724, in gather_nd
    "GatherNd", params=params, indices=indices, name=name)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\framework\op_def_library.py", line 630, in _apply_op_helper
    param_name=input_name)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\framework\op_def_library.py", line 63, in _SatisfiesTypeConstraint
    ", ".join(dtypes.as_dtype(x).name for x in allowed_list)))
TypeError: Value passed to parameter 'indices' has DataType bool not in list of allowed values: int32, int64

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\ops\array_ops.py", line 5350, in gather_nd
    return params.gather_nd(indices, name=name)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\framework\ops.py", line 401, in __getattr__
    self.__getattribute__(name)
AttributeError: 'Tensor' object has no attribute 'gather_nd'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "openvino2tensorflow.py", line 1362, in convert
    tf_layers_dict[layer_id] = tf.gather_nd(params, indices, batch_dims=batch_dims)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\util\dispatch.py", line 210, in wrapper
    result = dispatch(wrapper, args, kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\util\dispatch.py", line 126, in dispatch
    result = dispatcher.handle(op, args, kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\layers\core.py", line 1486, in handle
    return TFOpLambda(op)(*args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\engine\base_layer.py", line 970, in __call__
    input_list)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\engine\base_layer.py", line 1108, in _functional_construction_call
    inputs, input_masks, args, kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\engine\base_layer.py", line 840, in _keras_tensor_symbolic_call
    return self._infer_output_signature(inputs, args, kwargs, input_masks)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\engine\base_layer.py", line 880, in _infer_output_signature
    outputs = call_fn(inputs, *args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\layers\core.py", line 1363, in _call_wrapper
    return self._call_wrapper(*args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\keras\layers\core.py", line 1395, in _call_wrapper
    result = self.function(*args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\util\dispatch.py", line 206, in wrapper
    return target(*args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\ops\array_ops.py", line 5360, in gather_nd_v2
    return gather_nd(params, indices, name=name, batch_dims=batch_dims)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\util\dispatch.py", line 206, in wrapper
    return target(*args, **kwargs)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\ops\array_ops.py", line 5352, in gather_nd
    return gen_array_ops.gather_nd(params, indices, name=name)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\ops\gen_array_ops.py", line 3724, in gather_nd
    "GatherNd", params=params, indices=indices, name=name)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\framework\op_def_library.py", line 630, in _apply_op_helper
    param_name=input_name)
  File "C:\Users\20-0365\AppData\Local\Programs\Python\Python36\lib\site-packages\tensorflow\python\framework\op_def_library.py", line 63, in _SatisfiesTypeConstraint
    ", ".join(dtypes.as_dtype(x).name for x in allowed_list)))
TypeError: Value passed to parameter 'indices' has DataType bool not in list of allowed values: int32, int64

thanks

PINTO0309 commented 3 years ago

Fixed a bug in NonZero, and it now works properly until NonMaxSuppresion is processed. This is just a quick fix, so I haven't committed it yet.

### NonZero
elif layer.attrib['type'] == 'NonZero':
    output_type = tf.int64
    if not data is None and 'output_type' in data.attrib:
        output_type = cast_type_ov_tf[data.attrib['output_type']]

    try:
        # type_spec.dtype
        if tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)].type_spec.dtype != tf.bool:
            input_type = tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)].type_spec.dtype
            mask = tf.math.not_equal(
                tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)],
                tf.constant([0], dtype=input_type)
            )
            tf_layers_dict[layer_id] = tf.expand_dims(
                tf.boolean_mask(tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)], mask),
                axis=0
            )
        else:
            temp_op = tf.cast(tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)], output_type)
            mask = tf.math.not_equal(
                temp_op,
                tf.constant([0], dtype=output_type)
            )
            tf_layers_dict[layer_id] = tf.expand_dims(
                tf.boolean_mask(temp_op, mask),
                axis=0
            )
    except:
        # dtype
        if tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)].dtype != tf.bool:
            input_type = tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)].dtype
            mask = tf.math.not_equal(
                tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)],
                tf.constant([0], dtype=input_type)
            )
            tf_layers_dict[layer_id] = tf.expand_dims(
                tf.boolean_mask(tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)], mask),
                axis=0
            )
        else:
            temp_op = tf.cast(tf_layers_dict[get_tf_edges_from(tf_edges, layer_id, 0)], output_type)
            mask = tf.math.not_equal(
                temp_op,
                tf.constant([0], dtype=output_type)
            )
            tf_layers_dict[layer_id] = tf.expand_dims(
                tf.boolean_mask(temp_op, mask),
                axis=0
            )

There is a big problem here. The result of NonZero should exactly be a tensor of indefinite size, but OpenVINO appears to produce a fixed size output. The behavior specified in the specification does not match the specification of the actually generated model.

True = 1 False = 0

For example, NonZero( [True, False, False, True] ) should be [1, 2], but it produces output of size [1, 4]. In the first place, the number of True's is not constant, so the size can be all combinations of 0 or 1 or 2 or 3 or 4. The figure below suggests that NonZero( [19248 of True/False] ) results in [1, 19248]. However, the number of non-zero values should not be constant, so there is a problem with the specification. The NonZero result has an almost 100% probability of not returning 19248 values. It is inevitable that you will be confused. This is because the structure of the model you see does not match the internal workings of the framework. https://docs.openvinotoolkit.org/latest/openvino_docs_ops_condition_NonZero_3.html Screenshot 2021-08-04 09:45:26

As a result, TensorFlow is unable to predict the size of the tensor to be passed to NMS, resulting in the error shown in the figure below. scores.shape[0] is the scores parameter of NMS. NMS requires score, boxes, and class probabilities with matching dimensions, but because the number of dimensions is undefined, errors cannot be avoided in the subsequent transformation process.

ERROR: list index out of range
ERROR: model_path  : openvino/yolact_detect_pytorchnms11/550x550/FP32/yolact_detect_pytorchnms11.xml
ERROR: weights_path: openvino/yolact_detect_pytorchnms11/550x550/FP32/yolact_detect_pytorchnms11.bin
ERROR: layer_id    : 707
ERROR: The trace log is below.
Traceback (most recent call last):
  File "openvino2tensorflow.py", line 2098, in convert
    class_count = scores.shape[0]
  File "/usr/local/lib/python3.8/dist-packages/tensorflow/python/framework/tensor_shape.py", line 896, in __getitem__
    return self._dims[key].value
IndexError: list index out of range

Do you understand what I'm saying? This is the reason why I told you in my email that I don't want you to use NonZero a lot if possible. The post-processing implemented in PyTorch uses a lot of NonZero to mass produce processes with indeterminate tensor sizes, which is why I have not actively implemented NMS. This is because TensorFlow's default processing cannot handle it.

Therefore, you really need to consider whether you need to do the following,

keep = (conf_scores > self.conf_thresh)

and

id = (conf_scores > self.conf_thresh)

By the way, I don't think it's necessary.

taisuke-tomida commented 3 years ago

Thanks. @PINTO0309

If it says [True, False, False, True], it means you have to count only True. The score received by NMS is supposed to be variable, but it is received as [19248]. Indeed, it is not right. I understood.

Therefore, you really need to consider whether you need to do the following,

As I told you a little bit in my email, I was trying to figure out how to implement NMS without using Boolean. The changes are as follows

Non Boolean NMS

Change the detect function in detection.py. The default is to use a threshold to remove candidates before passing them to NMS.

keep = (conf_scores > self.conf_thresh)

This is the process. Because of this process, Booleans were being mass produced. In addition, the NMS function also uses a threshold value to eliminate candidates in the same way. So I removed and changed all of that process.

def detect(self, batch_idx, conf_preds, decoded_boxes, mask_data, inst_data):
    """ Perform nms for only the max scoring class that isn't background (class 0) """
    cur_scores = conf_preds[batch_idx, 1:, :] # [80, 19248]
    conf_scores, _ = torch.max(cur_scores, dim=0) # 列方向の最大値 [19248]

    masks4pynms = mask_data[batch_idx, :, :]

    boxes, masks, classes, scores = self.pytorch_nms(decoded_boxes, masks4pynms, conf_scores, cur_scores, self.nms_thresh, self.top_k)

    return {'box': boxes, 'mask': masks, 'class': classes, 'score': scores}

The NMS functions are

def pytorch_nms(self, decode_boxes, masks, conf_scores, scores, iou_threshold:float=0.2, top_k:int=200):
    print("use pytorch nms")

    scores, classes = scores.max(dim=0)

    keep = torchvision.ops.nms(boxes=decode_boxes, scores=conf_scores, iou_threshold=iou_threshold)

    return decode_boxes[keep], masks[keep], classes[keep], scores[keep]

It's pretty heavy-handed, but I made sure to pass all candidates to torchvision.ops.nms. I have attached the converted model (ONNX) to the email. With my understanding, I can only deal with this so much. Sorry.

Thanks.

PINTO0309 commented 3 years ago

Don't worry about it. I will respond again at midnight. Thank you for the perfect issue. :smile_cat:

taisuke-tomida commented 3 years ago

Please. :pray:

PINTO0309 commented 3 years ago

Only some type conversion bugs in NonZero and NonMaxSuppression have been fixed and versioned. Thanks to the sample you provided, the conversion is now successful up to NonMaxSuppression.

Commit: https://github.com/PINTO0309/openvino2tensorflow/commit/177bdaa52e141356e47435846fe1a265894b600f

https://github.com/PINTO0309/openvino2tensorflow/releases/tag/v1.15.5

At the moment, it is designed to remove the process after NonMaxSuppression, which causes an error in Gather immediately afterwards. The fundamental tool specification after the NonMaxSuppression conversion process needs to be revised significantly, so it will take a very long time to fix. Screenshot 2021-08-04 21:37:23

$ docker run -it --rm \
-v `pwd`:/home/user/workdir \
pinto0309/openvino2tensorflow:latest

$ cd workdir

$ MODEL=yolact_detect_pytorchnms11_non_boolean
$ H=550
$ W=550

$ python3 -m onnxsim ${MODEL}.onnx ${MODEL}_opt.onnx

$ $INTEL_OPENVINO_DIR/deployment_tools/model_optimizer/mo.py \
--input_model ${MODEL}_opt.onnx \
--data_type FP32 \
--output_dir openvino/${MODEL}/${H}x${W}/FP32

$ openvino2tensorflow \
--model_path openvino/${MODEL}/${H}x${W}/FP32/${MODEL}_opt.xml \
--output_saved_model \
--output_pb \
--output_no_quant_float32_tflite

I'm sorry to keep you waiting.

taisuke-tomida commented 3 years ago

Thanks!! @PINTO0309 I think it's a huge step forward that the NMS conversion worked out so well! Thank you very much for your prompt response.

Finally, I have one question.

If you can accept the value, then I should be able to handle it!

Thanks

PINTO0309 commented 3 years ago

Now, I force the three NMS outputs, selected_boxes, selected_class_idx, and selected_scores, to the fabricated outputs with layer IDs 99990, 99991, and 99992, respectively. This needs to be modified to connect to Gather and other layers according to the connection information of the model being converted. This is because I originally envisioned only the optimization of the object detection model for ease of use, so there was little need for processing after NMS. https://github.com/PINTO0309/openvino2tensorflow/blob/3211a57785cf88007efc18ebf737945d439a0316/openvino2tensorflow/openvino2tensorflow.py#L2180-L2200

For that reason, in order to be extremely optimized for TensorFlow, the NMS conversion process is replaced by a huge number of layers with my own tricks. https://github.com/PINTO0309/openvino2tensorflow/blob/3211a57785cf88007efc18ebf737945d439a0316/openvino2tensorflow/openvino2tensorflow.py#L2087-L2200

The current program is still able to obtain the converted tensors successfully, but it ignores the connections after NMS, so the program needs to be modified significantly. It's probably impossible to handle with a little work-around.

P.S. I think it is possible to get the values by deleting all layers after NMS. @taisuke-tomida

taisuke-tomida commented 3 years ago

Thank you for taking the time to help me with this issue.

I think it is possible to get the values by deleting all layers after NMS.

When you say delete this layer, do you mean delete it in the Pytorch implementation? So you are saying that the output of ONNX is the output of NMS?

PINTO0309 commented 3 years ago

There are three ways to do this.

  1. Remove the implementation after NonMaximumSuppresion from the PyTorch implementation and export ONNX. (NMS will remain.) It looks like PyTorch's NMS returns indicies instead of the values themselves, so it's not going to be easy.
    keep = torchvision.ops.nms(boxes=decode_boxes, scores=conf_scores, iou_threshold=iou_threshold)
    return decode_boxes[keep], masks[keep], classes[keep], scores[keep]

    or

  2. Use onnx2json and json2onnx to process .onnx directly or
  3. Process the OpenVINO IR file .xml in the IDE and delete the layers behind NMS by hand.

Just do one of the above and run the conversion again.

taisuke-tomida commented 3 years ago

Thank you very much for your kind attention. I will start working on it as soon as possible.

Just to confirm, are the values we can get at the moment BBox, Scores, and masks?

interpreter = tf.lite.Interpreter("./yolact_pynms_float32.tflite")
interpreter.allocate_tensors()

input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

input_name = input_details[0]['index']

output1_name = output_details[0]['index']
output2_name = output_details[1]['index']
output3_name = output_details[2]['index']

interpreter.set_tensor(input_name, batch)

start = time.time()
interpreter.invoke()
print("inference time:", time.time() - start)

output1 = interpreter.get_tensor(output1_name)
output2 = interpreter.get_tensor(output2_name)
output3 = interpreter.get_tensor(output3_name)

print(output1)
print(output2)
print(output3)
inference time: 1.8543763160705566
[[[ 0.27205527  0.5113899   0.39761117  0.72468936]
  [ 0.87256825  0.0971102   0.9575784   0.31370932]
  [ 0.5089483   0.70911205  0.62713844  0.8617991 ]
  ...
  [-0.00865072 -0.00755513  0.036653    0.06734621]
  [-0.00865072 -0.00755513  0.036653    0.06734621]
  [-0.00865072 -0.00755513  0.036653    0.06734621]]]
[[0 0 0 ... 0 0 0]]
[[0.8754336  0.612168   0.58365595 ... 0.         0.         0.        ]]
PINTO0309 commented 3 years ago

Maybe it's like this.

↓↓↓↓↓↓↓↓ boxes coordinates 
[[[ 0.27205527  0.5113899   0.39761117  0.72468936]
  [ 0.87256825  0.0971102   0.9575784   0.31370932]
  [ 0.5089483   0.70911205  0.62713844  0.8617991 ]
  ...
  [-0.00865072 -0.00755513  0.036653    0.06734621]
  [-0.00865072 -0.00755513  0.036653    0.06734621]
  [-0.00865072 -0.00755513  0.036653    0.06734621]]]

↓↓↓↓↓↓↓↓ class id
[[0 0 0 ... 0 0 0]]

↓↓↓↓↓↓↓↓ probability
[[0.8754336  0.612168   0.58365595 ... 0.         0.         0.        ]]

It looks like Mask is not being obtained because the conversion program has ignored the connection information of other OUTPUTs. There should be five outputs.

output1_name = output_details[0]['index']
output2_name = output_details[1]['index']
output3_name = output_details[2]['index']
output3_name = output_details[3]['index']
output3_name = output_details[4]['index']
taisuke-tomida commented 3 years ago

Maybe my conversion was bad, but there are only three outputs.... The model has not yet deleted any layers after NMS.

image

PINTO0309 commented 3 years ago

You misunderstand. It's just that my openvino2tensorflow is forcing me to remove the other outputs. This is a problem with the current specification of openvino2tensorflow.

taisuke-tomida commented 3 years ago

So you actually have five outputs. However, I understand that the specification does not allow you to see all the output.

PINTO0309 commented 3 years ago

Due to a problem with the current specification of openvino2tensorflow, all outputs after the fourth one are removed. Therefore, the current output of three is the correct behavior.

 tf_outputs.append(tf_layers_dict['99990']) 
 tf_outputs.append(tf_layers_dict['99991']) 
 tf_outputs.append(tf_layers_dict['99992']) 

There should be five outputs.

This statement of mine is after I have modified the program so that all the conversion behavior you expect can be done successfully.

taisuke-tomida commented 3 years ago

I understood. I will work on it so that I can receive the value I expect. Please let me know if there is anything else I can do for you.

Thanks @PINTO0309

PINTO0309 commented 3 years ago

Thank you very much for your patience. The entire process has been overhauled and now supports consolidation of processes after NMS. You can convert Yolact. https://github.com/PINTO0309/openvino2tensorflow/releases/tag/v1.19.4

However, there are some things to keep in mind.

Convertion script sample.

$ docker run -it --rm \
  -v `pwd`:/home/user/workdir \
  pinto0309/openvino2tensorflow:latest

$ MODEL=yolact_detect_pytorchnms11_non_boolean_opt
$ openvino2tensorflow \
  --model_path ${MODEL}.xml \
  --output_saved_model \
  --output_pb \
  --output_no_quant_float32_tflite \
  --weight_replacement_config replace.json

replace.json.zip

Check out this tutorial on how to extrapolate layers. https://github.com/PINTO0309/openvino2tensorflow/tree/v1.19.4#6-7-replace-weights-or-constant-values-in-const-op-and-add-transpose-or-reshape-or-cast-just-beforeafter-the-operation-specified-by-layer_id

After converting Yolact to Float32 tflite, the structure is as shown below. model_float32 tflite (1)

taisuke-tomida commented 3 years ago

Thank you very much. @PINTO0309

I haven't checked yet, but Thanks to this result, what was impossible became possible. I had to rely on him because of my lack of knowledge, but I didn't expect him to be so responsive.

I'll be back to find something no one else has ever done, so please do let me know when you do!

PINTO0309 commented 3 years ago

The purpose of the issue has been resolved, so I will close it once. If you have another issue, please post another issue.