mirror of
https://github.com/opencv/opencv.git
synced 2025-07-30 17:37:05 +08:00

Added int support for OpenVINO dnn backend #25458 Modified dnn OpenVINO integration to support type inference and int operations. Added OpenVINO support to Cast, CumSum, Expand, Gather, GatherElements, Scatter, ScatterND, Tile layers. I tried to add Reduce layer, but looks like OpenVINO uses float values inside Reduce operation so it can't pass our int tests. OpenVINO uses int32 precision for int64 operations, so I've modified input values for int64 tests when backend is OpenVINO. OpenVINO has a strange behavior with custom layers and int64 values. After model compilation OpenVINO may change types, so the model can have different output type. That's why these tests were disabled: - Test_ArgMax_Int.random/0, where GetParam() = (4, NGRAPH/CPU) - Test_ArgMax_Int.random/6, where GetParam() = (11, NGRAPH/CPU) - Test_Reduce_Int.random/6, where GetParam() = (11, NGRAPH/CPU) - Test_Reduce_Int.two_axes/6, where GetParam() = (11, NGRAPH/CPU) Also these tests were temporary disabled, they didn't work on both 4.x and 5.x branches: - Test_Caffe_layers.layer_prelu_fc/0, where GetParam() = NGRAPH/CPU - Test_ONNX_layers.LSTM_Activations/0, where GetParam() = NGRAPH/CPU - Test_ONNX_layers.Quantized_Convolution/0, where GetParam() = NGRAPH/CPU - Test_ONNX_layers.Quantized_Eltwise_Scalar/0, where GetParam() = NGRAPH/CPU - Test_TFLite.EfficientDet_int8/0, where GetParam() = NGRAPH/CPU ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
181 lines
7.4 KiB
C++
181 lines
7.4 KiB
C++
// This file is part of OpenCV project.
|
|
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
|
// of this distribution and at http://opencv.org/license.html.
|
|
|
|
#include "../precomp.hpp"
|
|
#include "../op_inf_engine.hpp"
|
|
#include "../ie_ngraph.hpp"
|
|
#include <opencv2/dnn/shape_utils.hpp>
|
|
|
|
namespace cv { namespace dnn {
|
|
|
|
class ExpandLayerImpl CV_FINAL : public ExpandLayer
|
|
{
|
|
public:
|
|
ExpandLayerImpl(const LayerParams ¶ms) {
|
|
setParamsFrom(params);
|
|
|
|
// shape as param
|
|
CV_CheckTrue(params.has("shape"), "DNN/Expand: shape is required in Expand layer initialization");
|
|
DictValue param_shape = params.get("shape");
|
|
int ndims_shape = param_shape.size();
|
|
CV_CheckGT(ndims_shape, 0, "DNN/Expand: ndims of shape must be > 0");
|
|
target_shape.resize(ndims_shape);
|
|
for (int i = 0; i < ndims_shape; i++) {
|
|
target_shape[i] = param_shape.get<int>(i);
|
|
}
|
|
|
|
// FIXME: remove when 0d/1d mat is available
|
|
const_input_1d = params.get("const_input_1d", false);
|
|
}
|
|
|
|
virtual bool supportBackend(int backendId) CV_OVERRIDE
|
|
{
|
|
return backendId == DNN_BACKEND_OPENCV ||
|
|
backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH;
|
|
}
|
|
|
|
virtual bool getMemoryShapes(const std::vector<MatShape> &inputs,
|
|
const int requiredOutputs,
|
|
std::vector<MatShape> &outputs,
|
|
std::vector<MatShape> &internals) const CV_OVERRIDE {
|
|
CV_CheckGE(inputs.size(), static_cast<size_t>(1), "DNN/Expand: one input at least");
|
|
CV_CheckLE(inputs.size(), static_cast<size_t>(2), "DNN/Expand: two input at most");
|
|
CV_CheckFalse(target_shape.empty(), "DNN/Expand: shape must known before memory is set");
|
|
|
|
MatShape input_shape = inputs[0]; // 1d tensor is represented as 2d mat, e.g. [3] -> [3, 1]
|
|
if (const_input_1d) {
|
|
input_shape = {inputs[0][0]};
|
|
}
|
|
|
|
auto& moreDimension = input_shape.size() > target_shape.size() ? input_shape : target_shape;
|
|
auto& lessDimension = input_shape.size() <= target_shape.size() ? input_shape : target_shape;
|
|
|
|
/* Example:
|
|
i = 3
|
|
|
|
|
moreDimension: 1 2 3 4 5, assign non-aligned dimensions to output shape
|
|
lessDimension: 1 1 5, when dimension is aligned, check valid dimension (either equal or one of them is 1) and assign bigger one
|
|
|
|
|
j = 0 = i - (moreDimension.size() - lessDimension.size());
|
|
*/
|
|
MatShape outputShape(moreDimension.size(), 1);
|
|
for (int i = 0; i < moreDimension.size(); i++) {
|
|
int d = moreDimension[i];
|
|
int j = i - (moreDimension.size() - lessDimension.size());
|
|
if (j >= 0) {
|
|
if (d == 1 || lessDimension[j] == 1 || // broadcast
|
|
d == lessDimension[j]) { // plain copy
|
|
outputShape[i] = std::max(d, lessDimension[j]);
|
|
} else {
|
|
CV_Error(Error::StsBadSize, cv::format("DNN/Expand: invalid dimension, d (%d) != d (%d)", moreDimension[i], lessDimension[j]));
|
|
}
|
|
} else {
|
|
outputShape[i] = d;
|
|
}
|
|
}
|
|
outputs.assign(1, outputShape);
|
|
return false;
|
|
}
|
|
|
|
void getTypes(const std::vector<MatType>& inputs,
|
|
const int requiredOutputs,
|
|
const int requiredInternals,
|
|
std::vector<MatType>& outputs,
|
|
std::vector<MatType>& internals) const CV_OVERRIDE
|
|
{
|
|
CV_Assert(inputs.size());
|
|
for (auto input : inputs)
|
|
CV_CheckType(input, input == CV_32F || input == CV_16F || input == CV_8S || input == CV_32S || input == CV_64S, "");
|
|
|
|
outputs.assign(requiredOutputs, inputs[0]);
|
|
}
|
|
|
|
|
|
virtual void finalize(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr) CV_OVERRIDE {
|
|
std::vector<Mat> inputs;
|
|
inputs_arr.getMatVector(inputs);
|
|
|
|
const auto &input = inputs[0];
|
|
auto input_shape = shape(input);
|
|
if (const_input_1d) {
|
|
input_shape = {input_shape[0]};
|
|
}
|
|
|
|
auto& moreDimension = input_shape.size() > target_shape.size() ? input_shape : target_shape;
|
|
auto& lessDimension = input_shape.size() <= target_shape.size() ? input_shape : target_shape;
|
|
|
|
MatShape final_target_shape(moreDimension.size(), 1);
|
|
for (int i = 0; i < moreDimension.size(); i++) {
|
|
int d = moreDimension[i];
|
|
int j = i - (moreDimension.size() - lessDimension.size());
|
|
if (j >= 0) {
|
|
final_target_shape[i] = std::max(lessDimension[j], d);
|
|
} else {
|
|
final_target_shape[i] = d;
|
|
}
|
|
}
|
|
target_shape.clear();
|
|
target_shape = std::move(final_target_shape);
|
|
}
|
|
|
|
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE {
|
|
CV_TRACE_FUNCTION();
|
|
CV_TRACE_ARG_VALUE(name, "name", name.c_str());
|
|
|
|
std::vector<Mat> inputs, outputs;
|
|
inputs_arr.getMatVector(inputs);
|
|
outputs_arr.getMatVector(outputs);
|
|
|
|
int target_shape_total = std::accumulate(target_shape.begin(), target_shape.end(), 1, std::multiplies<int>());
|
|
if (target_shape_total == inputs[0].total()) {
|
|
const char *data = inputs[0].ptr<const char>();
|
|
char *output = outputs[0].ptr<char>();
|
|
int step = target_shape_total * outputs[0].elemSize();
|
|
std::memcpy(output, data, step);
|
|
return;
|
|
}
|
|
|
|
if (const_input_1d) {
|
|
const char *data = inputs[0].ptr<const char>();
|
|
char *output = outputs[0].ptr<char>();
|
|
int step = target_shape.back() * outputs[0].elemSize();
|
|
int total = std::accumulate(target_shape.begin(), target_shape.end() - 1, 1, std::multiplies<int>());
|
|
for (int i = 0; i < total; i++) {
|
|
std::memcpy(output + i * step, data, step);
|
|
}
|
|
} else {
|
|
cv::broadcast(inputs[0], target_shape, outputs[0]);
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_DNN_NGRAPH
|
|
virtual Ptr<BackendNode> initNgraph(const std::vector<Ptr<BackendWrapper> >& inputs,
|
|
const std::vector<Ptr<BackendNode> >& nodes) CV_OVERRIDE
|
|
{
|
|
auto input_shape = nodes[0].dynamicCast<InfEngineNgraphNode>()->node.get_shape();
|
|
CV_CheckGE(target_shape.size(), input_shape.size(), "");
|
|
|
|
std::vector<int32_t> output_shape(target_shape.begin(), target_shape.end());
|
|
for (int i = 1; i < input_shape.size() + 1; ++i)
|
|
output_shape[output_shape.size() - i] = std::max(
|
|
(int32_t)input_shape[input_shape.size() - i],
|
|
output_shape[output_shape.size() - i]);
|
|
|
|
auto shape_node = std::make_shared<ov::op::v0::Constant>(ov::element::i32, ov::Shape{output_shape.size()}, output_shape.data());
|
|
auto expand = std::make_shared<ov::op::v3::Broadcast>(nodes[0].dynamicCast<InfEngineNgraphNode>()->node, shape_node);
|
|
return Ptr<BackendNode>(new InfEngineNgraphNode(expand));
|
|
}
|
|
#endif // HAVE_DNN_NGRAPH
|
|
|
|
private:
|
|
MatShape target_shape;
|
|
bool const_input_1d;
|
|
};
|
|
|
|
Ptr<ExpandLayer> ExpandLayer::create(const LayerParams ¶ms) {
|
|
return makePtr<ExpandLayerImpl>(params);
|
|
}
|
|
|
|
}} // cv::dnn
|