Merge pull request #18902 from mpashchenkov:mp/onnx-const-input
G-API: ONNX. Const input * Added const input for ONNX backend * Returned initMatrixRandu, added some comments, rebase
This commit is contained in:
parent
d3bc563c6e
commit
3eaeca58da
@ -263,14 +263,6 @@ struct IEUnit {
|
|||||||
// Still, constant data is to set only once.
|
// Still, constant data is to set only once.
|
||||||
this_request.SetBlob(p.first, wrapIE(p.second.first, p.second.second));
|
this_request.SetBlob(p.first, wrapIE(p.second.first, p.second.second));
|
||||||
}
|
}
|
||||||
// Bind const data to infer request
|
|
||||||
for (auto &&p : params.const_inputs) {
|
|
||||||
// FIXME: SetBlob is known to be inefficient,
|
|
||||||
// it is worth to make a customizable "initializer" and pass the
|
|
||||||
// cv::Mat-wrapped blob there to support IE's optimal "GetBlob idiom"
|
|
||||||
// Still, constant data is to set only once.
|
|
||||||
this_request.SetBlob(p.first, wrapIE(p.second.first, p.second.second));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {this_plugin, this_network, this_request};
|
return {this_plugin, this_network, this_request};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
#include <opencv2/gapi/gframe.hpp>
|
#include <opencv2/gapi/gframe.hpp>
|
||||||
|
|
||||||
#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK!
|
#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK!
|
||||||
|
#include "logger.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
struct ONNXCallContext;
|
struct ONNXCallContext;
|
||||||
@ -30,12 +31,35 @@ enum TensorPosition : int {
|
|||||||
OUTPUT
|
OUTPUT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::string pdims(const std::vector<int64_t> &dims) {
|
||||||
|
std::stringstream ss;
|
||||||
|
auto it = dims.begin();
|
||||||
|
ss << *it++;
|
||||||
|
for (; it != dims.end(); ++it) {
|
||||||
|
ss << '/' << *it;
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
struct TensorInfo {
|
struct TensorInfo {
|
||||||
TensorInfo() = default;
|
TensorInfo() = default;
|
||||||
explicit TensorInfo(const Ort::TensorTypeAndShapeInfo& info)
|
explicit TensorInfo(const Ort::TensorTypeAndShapeInfo& info)
|
||||||
: dims(info.GetShape())
|
: dims(info.GetShape())
|
||||||
, type(info.GetElementType())
|
, type(info.GetElementType())
|
||||||
, is_dynamic(std::find(dims.begin(), dims.end(), -1) != dims.end()) {
|
, is_dynamic(std::find(dims.begin(), dims.end(), -1) != dims.end()) {
|
||||||
|
|
||||||
|
// Double-check if the tensor is really dynamic
|
||||||
|
// Allow N to be -1
|
||||||
|
if (is_dynamic
|
||||||
|
&& dims[0] == -1
|
||||||
|
&& dims.size() > 1
|
||||||
|
&& std::find(dims.begin() + 1, dims.end(), -1) == dims.end()) {
|
||||||
|
|
||||||
|
GAPI_LOG_WARNING(NULL, "Promoting N=-1 to N=1 for tensor " << pdims(dims));
|
||||||
|
dims[0] = 1;
|
||||||
|
is_dynamic = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!is_dynamic) {
|
if (!is_dynamic) {
|
||||||
size = std::accumulate(dims.begin(),
|
size = std::accumulate(dims.begin(),
|
||||||
dims.end(),
|
dims.end(),
|
||||||
@ -81,6 +105,7 @@ class ONNXCompiled {
|
|||||||
std::vector<TensorInfo> in_tensor_info;
|
std::vector<TensorInfo> in_tensor_info;
|
||||||
std::vector<TensorInfo> out_tensor_info;
|
std::vector<TensorInfo> out_tensor_info;
|
||||||
bool is_dynamic = false;
|
bool is_dynamic = false;
|
||||||
|
bool is_postproc = false;
|
||||||
|
|
||||||
// G-API <Net> description
|
// G-API <Net> description
|
||||||
gapi::onnx::detail::ParamDesc params;
|
gapi::onnx::detail::ParamDesc params;
|
||||||
@ -95,6 +120,7 @@ class ONNXCompiled {
|
|||||||
void Run(const std::vector<cv::Mat>& ins,
|
void Run(const std::vector<cv::Mat>& ins,
|
||||||
const std::vector<cv::Mat>& outs);
|
const std::vector<cv::Mat>& outs);
|
||||||
|
|
||||||
|
std::vector<std::string> in_names_without_const;
|
||||||
public:
|
public:
|
||||||
explicit ONNXCompiled(const gapi::onnx::detail::ParamDesc &pp);
|
explicit ONNXCompiled(const gapi::onnx::detail::ParamDesc &pp);
|
||||||
|
|
||||||
@ -142,6 +168,7 @@ inline int toCV(ONNXTensorElementDataType prec) {
|
|||||||
switch (prec) {
|
switch (prec) {
|
||||||
case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8: return CV_8U;
|
case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8: return CV_8U;
|
||||||
case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT: return CV_32F;
|
case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT: return CV_32F;
|
||||||
|
case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: return CV_32S;
|
||||||
default: GAPI_Assert(false && "Unsupported data type");
|
default: GAPI_Assert(false && "Unsupported data type");
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
@ -308,6 +335,8 @@ inline Ort::Value createTensor(const Ort::MemoryInfo& memory_info,
|
|||||||
return createTensor<uint8_t>(memory_info, tensor_params, data);
|
return createTensor<uint8_t>(memory_info, tensor_params, data);
|
||||||
case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
|
case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
|
||||||
return createTensor<float>(memory_info, tensor_params, data);
|
return createTensor<float>(memory_info, tensor_params, data);
|
||||||
|
case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
|
||||||
|
return createTensor<int32_t>(memory_info, tensor_params, data);
|
||||||
default:
|
default:
|
||||||
GAPI_Assert(false && "Unsupported data type");
|
GAPI_Assert(false && "Unsupported data type");
|
||||||
}
|
}
|
||||||
@ -523,7 +552,6 @@ namespace onnx {
|
|||||||
|
|
||||||
ONNXCompiled::ONNXCompiled(const gapi::onnx::detail::ParamDesc &pp)
|
ONNXCompiled::ONNXCompiled(const gapi::onnx::detail::ParamDesc &pp)
|
||||||
: params(pp) {
|
: params(pp) {
|
||||||
|
|
||||||
// Validate input parameters before allocating any resources
|
// Validate input parameters before allocating any resources
|
||||||
if (params.num_in > 1u && params.num_in != params.input_names.size()) {
|
if (params.num_in > 1u && params.num_in != params.input_names.size()) {
|
||||||
cv::util::throw_error(std::logic_error("Please specify input layer names for "
|
cv::util::throw_error(std::logic_error("Please specify input layer names for "
|
||||||
@ -553,6 +581,7 @@ ONNXCompiled::ONNXCompiled(const gapi::onnx::detail::ParamDesc &pp)
|
|||||||
"Please provide a custom post-processing function "
|
"Please provide a custom post-processing function "
|
||||||
"(.cfgPostProc) in network parameters"));
|
"(.cfgPostProc) in network parameters"));
|
||||||
}
|
}
|
||||||
|
is_postproc = (params.custom_post_proc != nullptr);
|
||||||
|
|
||||||
// Update parameters based on session information
|
// Update parameters based on session information
|
||||||
if (params.num_in == 1u && params.input_names.empty()) {
|
if (params.num_in == 1u && params.input_names.empty()) {
|
||||||
@ -563,8 +592,6 @@ ONNXCompiled::ONNXCompiled(const gapi::onnx::detail::ParamDesc &pp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate what is supported currently
|
// Validate what is supported currently
|
||||||
GAPI_Assert(params.const_inputs.empty()
|
|
||||||
&& "Const inputs are not currently supported");
|
|
||||||
GAPI_Assert(std::all_of(in_tensor_info.begin(),
|
GAPI_Assert(std::all_of(in_tensor_info.begin(),
|
||||||
in_tensor_info.end(),
|
in_tensor_info.end(),
|
||||||
[](const cv::gimpl::onnx::TensorInfo &p) {
|
[](const cv::gimpl::onnx::TensorInfo &p) {
|
||||||
@ -593,6 +620,17 @@ ONNXCompiled::ONNXCompiled(const gapi::onnx::detail::ParamDesc &pp)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!params.const_inputs.empty()) {
|
||||||
|
// Form input names order without const input names
|
||||||
|
in_names_without_const.clear();
|
||||||
|
std::copy_if(params.input_names.begin(), params.input_names.end(),
|
||||||
|
std::back_inserter(in_names_without_const),
|
||||||
|
[&](const std::string& name) {
|
||||||
|
const auto it = params.const_inputs.find(name);
|
||||||
|
return it == params.const_inputs.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Pre-allocate vectors (not buffers) for runtime info
|
// Pre-allocate vectors (not buffers) for runtime info
|
||||||
in_data.resize(params.num_in);
|
in_data.resize(params.num_in);
|
||||||
out_data.resize(params.num_out);
|
out_data.resize(params.num_out);
|
||||||
@ -626,9 +664,9 @@ std::vector<TensorInfo> ONNXCompiled::getTensorInfo(TensorPosition pos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cv::GMatDesc ONNXCompiled::outMeta(int idx) const {
|
cv::GMatDesc ONNXCompiled::outMeta(int idx) const {
|
||||||
if (is_dynamic) {
|
if (is_dynamic || is_postproc) {
|
||||||
GAPI_Assert(!params.out_metas.empty()
|
GAPI_Assert(!params.out_metas.empty()
|
||||||
&& "Metadata must be specified if NN has dynamic inputs!");
|
&& "Metadata must be specified if NN has dynamic inputs or post-processing function is used!");
|
||||||
return params.out_metas.at(idx);
|
return params.out_metas.at(idx);
|
||||||
}
|
}
|
||||||
const auto ort_idx = getIdxByName(out_tensor_info, params.output_names[idx]);
|
const auto ort_idx = getIdxByName(out_tensor_info, params.output_names[idx]);
|
||||||
@ -678,9 +716,12 @@ void ONNXCompiled::Run(const std::vector<cv::Mat>& ins,
|
|||||||
const std::vector<cv::Mat>& outs) {
|
const std::vector<cv::Mat>& outs) {
|
||||||
std::vector<Ort::Value> in_tensors, out_tensors;
|
std::vector<Ort::Value> in_tensors, out_tensors;
|
||||||
|
|
||||||
auto in_run_names = getCharNames(params.input_names);
|
// Layer names order for run
|
||||||
|
auto input_names = (in_names_without_const.empty() && params.const_inputs.empty())
|
||||||
for (const auto it : ade::util::indexed(params.input_names)) {
|
? params.input_names
|
||||||
|
: in_names_without_const;
|
||||||
|
// Creates tensors for unique names that don't contain constant input
|
||||||
|
for (const auto it : ade::util::indexed(input_names)) {
|
||||||
auto i = ade::util::index(it);
|
auto i = ade::util::index(it);
|
||||||
auto in_name = ade::util::value(it);
|
auto in_name = ade::util::value(it);
|
||||||
const auto idx = getIdxByName(in_tensor_info, in_name);
|
const auto idx = getIdxByName(in_tensor_info, in_name);
|
||||||
@ -689,7 +730,19 @@ void ONNXCompiled::Run(const std::vector<cv::Mat>& ins,
|
|||||||
ins[i]));
|
ins[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_dynamic) {
|
for (auto &&c_in_pair : params.const_inputs) {
|
||||||
|
const auto idx = getIdxByName(in_tensor_info, c_in_pair.first);
|
||||||
|
in_tensors.emplace_back(createTensor(this_memory_info,
|
||||||
|
in_tensor_info[idx],
|
||||||
|
c_in_pair.second.first));
|
||||||
|
// Puts const input names in sequence for Run
|
||||||
|
// ONNXRuntime can match input tensors to CNN inputs by names
|
||||||
|
input_names.emplace_back(c_in_pair.first);
|
||||||
|
}
|
||||||
|
GAPI_Assert(input_names.size() == this_session.GetInputCount());
|
||||||
|
|
||||||
|
auto in_run_names = getCharNames(input_names);
|
||||||
|
if (!is_dynamic && !is_postproc) {
|
||||||
// Easy path - just run the session which is bound to G-API's
|
// Easy path - just run the session which is bound to G-API's
|
||||||
// internal data
|
// internal data
|
||||||
for (auto i : ade::util::iota(params.output_names.size())) {
|
for (auto i : ade::util::iota(params.output_names.size())) {
|
||||||
@ -701,7 +754,7 @@ void ONNXCompiled::Run(const std::vector<cv::Mat>& ins,
|
|||||||
this_session.Run(Ort::RunOptions{nullptr},
|
this_session.Run(Ort::RunOptions{nullptr},
|
||||||
in_run_names.data(),
|
in_run_names.data(),
|
||||||
&in_tensors.front(),
|
&in_tensors.front(),
|
||||||
params.input_names.size(),
|
input_names.size(),
|
||||||
out_run_names.data(),
|
out_run_names.data(),
|
||||||
&out_tensors.front(),
|
&out_tensors.front(),
|
||||||
params.output_names.size());
|
params.output_names.size());
|
||||||
@ -716,7 +769,7 @@ void ONNXCompiled::Run(const std::vector<cv::Mat>& ins,
|
|||||||
auto outputs = this_session.Run(Ort::RunOptions{nullptr},
|
auto outputs = this_session.Run(Ort::RunOptions{nullptr},
|
||||||
in_run_names.data(),
|
in_run_names.data(),
|
||||||
&in_tensors.front(),
|
&in_tensors.front(),
|
||||||
params.input_names.size(),
|
input_names.size(),
|
||||||
out_names.data(),
|
out_names.data(),
|
||||||
out_names.size());
|
out_names.size());
|
||||||
std::unordered_map<std::string, cv::Mat> onnx_outputs;
|
std::unordered_map<std::string, cv::Mat> onnx_outputs;
|
||||||
|
|||||||
@ -81,8 +81,24 @@ cv::Mat initMatrixRandU(const int type, const cv::Size& sz_in) {
|
|||||||
namespace opencv_test
|
namespace opencv_test
|
||||||
{
|
{
|
||||||
namespace {
|
namespace {
|
||||||
|
void initTestDataPath()
|
||||||
|
{
|
||||||
|
#ifndef WINRT
|
||||||
|
static bool initialized = false;
|
||||||
|
if (!initialized)
|
||||||
|
{
|
||||||
|
// Since G-API has no own test data (yet), it is taken from the common space
|
||||||
|
const char* testDataPath = getenv("OPENCV_TEST_DATA_PATH");
|
||||||
|
if (testDataPath) {
|
||||||
|
cvtest::addDataSearchPath(testDataPath);
|
||||||
|
}
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
#endif // WINRT
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: taken from the DNN module
|
// FIXME: taken from the DNN module
|
||||||
void normAssert(const cv::InputArray& ref, const cv::InputArray& test,
|
void normAssert(cv::InputArray& ref, cv::InputArray& test,
|
||||||
const char *comment /*= ""*/,
|
const char *comment /*= ""*/,
|
||||||
const double l1 = 0.00001, const double lInf = 0.0001) {
|
const double l1 = 0.00001, const double lInf = 0.0001) {
|
||||||
const double normL1 = cvtest::norm(ref, test, cv::NORM_L1) / ref.getMat().total();
|
const double normL1 = cvtest::norm(ref, test, cv::NORM_L1) / ref.getMat().total();
|
||||||
@ -109,6 +125,7 @@ inline int toCV(const ONNXTensorElementDataType prec) {
|
|||||||
switch (prec) {
|
switch (prec) {
|
||||||
case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8: return CV_8U;
|
case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8: return CV_8U;
|
||||||
case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT: return CV_32F;
|
case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT: return CV_32F;
|
||||||
|
case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: return CV_32S;
|
||||||
default: GAPI_Assert(false && "Unsupported data type");
|
default: GAPI_Assert(false && "Unsupported data type");
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
@ -126,46 +143,97 @@ inline std::vector<const char*> getCharNames(const std::vector<std::string>& nam
|
|||||||
return out_vec;
|
return out_vec;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void copyToOut(const cv::Mat& in, cv::Mat& out) {
|
template<typename T>
|
||||||
GAPI_Assert(in.depth() == CV_32F);
|
void copyToOut(const cv::Mat& in, cv::Mat& out) {
|
||||||
GAPI_Assert(in.size == out.size);
|
const size_t size = std::min(out.total(), in.total());
|
||||||
const float* const inptr = in.ptr<float>();
|
std::copy(in.begin<T>(), in.begin<T>() + size, out.begin<T>());
|
||||||
float* const optr = out.ptr<float>();
|
if (size < out.total()) {
|
||||||
const int size = in.total();
|
T* const optr = out.ptr<T>();
|
||||||
for (int i = 0; i < size; ++i) {
|
optr[size] = static_cast<T>(-1); // end data mark
|
||||||
optr[i] = inptr[i];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void remapYolo(const std::unordered_map<std::string, cv::Mat> &onnx,
|
void remapYolo(const std::unordered_map<std::string, cv::Mat> &onnx,
|
||||||
std::unordered_map<std::string, cv::Mat> &gapi) {
|
std::unordered_map<std::string, cv::Mat> &gapi) {
|
||||||
GAPI_Assert(onnx.size() == 1u);
|
GAPI_Assert(onnx.size() == 1u);
|
||||||
GAPI_Assert(gapi.size() == 1u);
|
GAPI_Assert(gapi.size() == 1u);
|
||||||
// Result from Run method
|
// Result from Run method
|
||||||
const cv::Mat& in = onnx.begin()->second;
|
const cv::Mat& in = onnx.begin()->second;
|
||||||
|
GAPI_Assert(in.depth() == CV_32F);
|
||||||
// Configured output
|
// Configured output
|
||||||
cv::Mat& out = gapi.begin()->second;
|
cv::Mat& out = gapi.begin()->second;
|
||||||
// Simple copy
|
// Simple copy
|
||||||
copyToOut(in, out);
|
copyToOut<float>(in, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
void remapSsdPorts(const std::unordered_map<std::string, cv::Mat> &onnx,
|
void remapYoloV3(const std::unordered_map<std::string, cv::Mat> &onnx,
|
||||||
std::unordered_map<std::string, cv::Mat> &gapi) {
|
std::unordered_map<std::string, cv::Mat> &gapi) {
|
||||||
// Result from Run method
|
|
||||||
const cv::Mat& in_num = onnx.at("num_detections:0");
|
|
||||||
const cv::Mat& in_boxes = onnx.at("detection_boxes:0");
|
|
||||||
const cv::Mat& in_scores = onnx.at("detection_scores:0");
|
|
||||||
const cv::Mat& in_classes = onnx.at("detection_classes:0");
|
|
||||||
// Configured outputs
|
|
||||||
cv::Mat& out_boxes = gapi.at("out1");
|
|
||||||
cv::Mat& out_classes = gapi.at("out2");
|
|
||||||
cv::Mat& out_scores = gapi.at("out3");
|
|
||||||
cv::Mat& out_num = gapi.at("out4");
|
|
||||||
// Simple copy for outputs
|
// Simple copy for outputs
|
||||||
copyToOut(in_num, out_num);
|
const cv::Mat& in_boxes = onnx.at("yolonms_layer_1/ExpandDims_1:0");
|
||||||
copyToOut(in_boxes, out_boxes);
|
const cv::Mat& in_scores = onnx.at("yolonms_layer_1/ExpandDims_3:0");
|
||||||
copyToOut(in_scores, out_scores);
|
const cv::Mat& in_indices = onnx.at("yolonms_layer_1/concat_2:0");
|
||||||
copyToOut(in_classes, out_classes);
|
GAPI_Assert(in_boxes.depth() == CV_32F);
|
||||||
|
GAPI_Assert(in_scores.depth() == CV_32F);
|
||||||
|
GAPI_Assert(in_indices.depth() == CV_32S);
|
||||||
|
|
||||||
|
cv::Mat& out_boxes = gapi.at("out1");
|
||||||
|
cv::Mat& out_scores = gapi.at("out2");
|
||||||
|
cv::Mat& out_indices = gapi.at("out3");
|
||||||
|
|
||||||
|
copyToOut<float>(in_boxes, out_boxes);
|
||||||
|
copyToOut<float>(in_scores, out_scores);
|
||||||
|
copyToOut<int32_t>(in_indices, out_indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remapToIESSDOut(const std::vector<cv::Mat> &detections,
|
||||||
|
cv::Mat &ssd_output) {
|
||||||
|
for (const auto &det_el : detections) {
|
||||||
|
GAPI_Assert(det_el.depth() == CV_32F);
|
||||||
|
GAPI_Assert(!det_el.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSD-MobilenetV1 structure check
|
||||||
|
ASSERT_EQ(detections[0].total(), 1u);
|
||||||
|
ASSERT_EQ(detections[2].total(), detections[0].total() * 100);
|
||||||
|
ASSERT_EQ(detections[2].total(), detections[3].total());
|
||||||
|
ASSERT_EQ((detections[2].total() * 4), detections[1].total());
|
||||||
|
|
||||||
|
const int num_objects = static_cast<int>(detections[0].ptr<float>()[0]);
|
||||||
|
GAPI_Assert(num_objects <= (ssd_output.size[2] - 1));
|
||||||
|
const float *in_boxes = detections[1].ptr<float>();
|
||||||
|
const float *in_scores = detections[2].ptr<float>();
|
||||||
|
const float *in_classes = detections[3].ptr<float>();
|
||||||
|
float *ptr = ssd_output.ptr<float>();
|
||||||
|
|
||||||
|
for (int i = 0; i < num_objects; i++) {
|
||||||
|
ptr[0] = 0.f; // "image_id"
|
||||||
|
ptr[1] = in_classes[i]; // "label"
|
||||||
|
ptr[2] = in_scores[i]; // "confidence"
|
||||||
|
ptr[3] = in_boxes[4 * i + 1]; // left
|
||||||
|
ptr[4] = in_boxes[4 * i + 0]; // top
|
||||||
|
ptr[5] = in_boxes[4 * i + 3]; // right
|
||||||
|
ptr[6] = in_boxes[4 * i + 2]; // bottom
|
||||||
|
|
||||||
|
ptr += 7;
|
||||||
|
in_boxes += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_objects < ssd_output.size[2] - 1) {
|
||||||
|
// put a -1 mark at the end of output blob if there is space left
|
||||||
|
ptr[0] = -1.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void remapSSDPorts(const std::unordered_map<std::string, cv::Mat> &onnx,
|
||||||
|
std::unordered_map<std::string, cv::Mat> &gapi) {
|
||||||
|
// Assemble ONNX-processed outputs back to a single 1x1x200x7 blob
|
||||||
|
// to preserve compatibility with OpenVINO-based SSD pipeline
|
||||||
|
const cv::Mat &num_detections = onnx.at("num_detections:0");
|
||||||
|
const cv::Mat &detection_boxes = onnx.at("detection_boxes:0");
|
||||||
|
const cv::Mat &detection_scores = onnx.at("detection_scores:0");
|
||||||
|
const cv::Mat &detection_classes = onnx.at("detection_classes:0");
|
||||||
|
cv::Mat &ssd_output = gapi.at("detection_output");
|
||||||
|
remapToIESSDOut({num_detections, detection_boxes, detection_scores, detection_classes}, ssd_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ONNXtest : public ::testing::Test {
|
class ONNXtest : public ::testing::Test {
|
||||||
@ -177,18 +245,17 @@ public:
|
|||||||
cv::Mat in_mat1;
|
cv::Mat in_mat1;
|
||||||
|
|
||||||
ONNXtest() {
|
ONNXtest() {
|
||||||
|
initTestDataPath();
|
||||||
env = Ort::Env(ORT_LOGGING_LEVEL_WARNING, "test");
|
env = Ort::Env(ORT_LOGGING_LEVEL_WARNING, "test");
|
||||||
memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
|
memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
|
||||||
out_gapi.resize(1);
|
out_gapi.resize(1);
|
||||||
out_onnx.resize(1);
|
out_onnx.resize(1);
|
||||||
// FIXME: All tests chek "random" image
|
// FIXME: It should be an image from own (gapi) directory in opencv extra
|
||||||
// Ideally it should be a real image
|
in_mat1 = cv::imread(findDataFile("cv/dpm/cat.png"));
|
||||||
in_mat1 = initMatrixRandU(CV_8UC3, cv::Size{640, 480});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void infer(const std::vector<cv::Mat>& ins,
|
void infer(const std::vector<cv::Mat>& ins, std::vector<cv::Mat>& outs) {
|
||||||
std::vector<cv::Mat>& outs) {
|
|
||||||
// Prepare session
|
// Prepare session
|
||||||
session = Ort::Session(env, model_path.data(), session_options);
|
session = Ort::Session(env, model_path.data(), session_options);
|
||||||
num_in = session.GetInputCount();
|
num_in = session.GetInputCount();
|
||||||
@ -241,10 +308,15 @@ public:
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
void infer(const cv::Mat& in, cv::Mat& out) {
|
void infer(const cv::Mat& in, cv::Mat& out) {
|
||||||
std::vector<cv::Mat> result;
|
std::vector<cv::Mat> result;
|
||||||
infer<T>({in}, result);
|
infer<T>(std::vector<cv::Mat>{in}, result);
|
||||||
GAPI_Assert(result.size() == 1u);
|
GAPI_Assert(result.size() == 1u);
|
||||||
out = result.front();
|
out = result.front();
|
||||||
}
|
}
|
||||||
|
// One input overload
|
||||||
|
template<typename T>
|
||||||
|
void infer(const cv::Mat& in, std::vector<cv::Mat>& outs) {
|
||||||
|
infer<T>(std::vector<cv::Mat>{in}, outs);
|
||||||
|
}
|
||||||
|
|
||||||
void validate() {
|
void validate() {
|
||||||
GAPI_Assert(!out_gapi.empty() && !out_onnx.empty());
|
GAPI_Assert(!out_gapi.empty() && !out_onnx.empty());
|
||||||
@ -275,6 +347,12 @@ public:
|
|||||||
const cv::Scalar mean = { 0.485, 0.456, 0.406 };
|
const cv::Scalar mean = { 0.485, 0.456, 0.406 };
|
||||||
const cv::Scalar std = { 0.229, 0.224, 0.225 };
|
const cv::Scalar std = { 0.229, 0.224, 0.225 };
|
||||||
|
|
||||||
|
// Rois for InferList, InferList2
|
||||||
|
const std::vector<cv::Rect> rois = {
|
||||||
|
cv::Rect(cv::Point{ 0, 0}, cv::Size{80, 120}),
|
||||||
|
cv::Rect(cv::Point{50, 100}, cv::Size{250, 360}),
|
||||||
|
};
|
||||||
|
|
||||||
void preprocess(const cv::Mat& src, cv::Mat& dst) {
|
void preprocess(const cv::Mat& src, cv::Mat& dst) {
|
||||||
const int new_h = 224;
|
const int new_h = 224;
|
||||||
const int new_w = 224;
|
const int new_w = 224;
|
||||||
@ -317,6 +395,55 @@ public:
|
|||||||
dst = dst.reshape(1, {1, 1, new_h, new_w});
|
dst = dst.reshape(1, {1, 1, new_h, new_w});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ONNXWithRemap : public ONNXtest {
|
||||||
|
public:
|
||||||
|
// You can specify any size of the outputs, since we don't know infer result
|
||||||
|
// Tests validate a range with results and don't compare empty space
|
||||||
|
void validate() {
|
||||||
|
GAPI_Assert(!out_gapi.empty() && !out_onnx.empty());
|
||||||
|
ASSERT_EQ(out_gapi.size(), out_onnx.size());
|
||||||
|
const auto size = out_onnx.size();
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
float* op = out_onnx.at(i).ptr<float>();
|
||||||
|
float* gp = out_gapi.at(i).ptr<float>();
|
||||||
|
const auto out_size = std::min(out_onnx.at(i).total(), out_gapi.at(i).total());
|
||||||
|
GAPI_Assert(out_size != 0u);
|
||||||
|
for (size_t d_idx = 0; d_idx < out_size; ++d_idx) {
|
||||||
|
if (gp[d_idx] == -1) {
|
||||||
|
break; // end of detections
|
||||||
|
}
|
||||||
|
ASSERT_EQ(op[d_idx], gp[d_idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ONNXYoloV3MultiInput : public ONNXWithRemap {
|
||||||
|
public:
|
||||||
|
std::vector<cv::Mat> ins;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void SetUp() {
|
||||||
|
const int yolo_in_h = 416;
|
||||||
|
const int yolo_in_w = 416;
|
||||||
|
cv::Mat yolov3_input, shape, prep_mat;
|
||||||
|
cv::resize(in_mat1, yolov3_input, cv::Size(yolo_in_w, yolo_in_h));
|
||||||
|
shape.create(cv::Size(2, 1), CV_32F);
|
||||||
|
float* ptr = shape.ptr<float>();
|
||||||
|
ptr[0] = in_mat1.cols;
|
||||||
|
ptr[1] = in_mat1.rows;
|
||||||
|
preprocess(yolov3_input, prep_mat);
|
||||||
|
ins = {prep_mat, shape};
|
||||||
|
}
|
||||||
|
|
||||||
|
void preprocess(const cv::Mat& src, cv::Mat& dst) {
|
||||||
|
cv::Mat cvt;
|
||||||
|
src.convertTo(cvt, CV_32F, 1.f / 255.f);
|
||||||
|
toCHW(cvt, dst);
|
||||||
|
dst = dst.reshape(1, {1, 3, 416, 416});
|
||||||
|
}
|
||||||
|
};
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
TEST_F(ONNXClassificationTest, Infer)
|
TEST_F(ONNXClassificationTest, Infer)
|
||||||
@ -341,15 +468,12 @@ TEST_F(ONNXClassificationTest, Infer)
|
|||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ONNXtest, InferTensor)
|
TEST_F(ONNXClassificationTest, InferTensor)
|
||||||
{
|
{
|
||||||
useModel("classification/squeezenet/model/squeezenet1.0-9");
|
useModel("classification/squeezenet/model/squeezenet1.0-9");
|
||||||
// Create tensor
|
// Create tensor
|
||||||
// FIXME: Test cheks "random" image
|
cv::Mat tensor;
|
||||||
// Ideally it should be a real image
|
preprocess(in_mat1, tensor);
|
||||||
const cv::Mat rand_mat = initMatrixRandU(CV_32FC3, cv::Size{224, 224});
|
|
||||||
const std::vector<int> dims = {1, rand_mat.channels(), rand_mat.rows, rand_mat.cols};
|
|
||||||
const cv::Mat tensor(dims, CV_32F, rand_mat.data);
|
|
||||||
// ONNX_API code
|
// ONNX_API code
|
||||||
infer<float>(tensor, out_onnx.front());
|
infer<float>(tensor, out_onnx.front());
|
||||||
// G_API code
|
// G_API code
|
||||||
@ -368,7 +492,7 @@ TEST_F(ONNXtest, InferTensor)
|
|||||||
TEST_F(ONNXClassificationTest, InferROI)
|
TEST_F(ONNXClassificationTest, InferROI)
|
||||||
{
|
{
|
||||||
useModel("classification/squeezenet/model/squeezenet1.0-9");
|
useModel("classification/squeezenet/model/squeezenet1.0-9");
|
||||||
const cv::Rect ROI(cv::Point{0, 0}, cv::Size{250, 250});
|
const auto ROI = rois.at(1);
|
||||||
// ONNX_API code
|
// ONNX_API code
|
||||||
cv::Mat roi_mat;
|
cv::Mat roi_mat;
|
||||||
preprocess(in_mat1(ROI), roi_mat);
|
preprocess(in_mat1(ROI), roi_mat);
|
||||||
@ -392,10 +516,6 @@ TEST_F(ONNXClassificationTest, InferROI)
|
|||||||
TEST_F(ONNXClassificationTest, InferROIList)
|
TEST_F(ONNXClassificationTest, InferROIList)
|
||||||
{
|
{
|
||||||
useModel("classification/squeezenet/model/squeezenet1.0-9");
|
useModel("classification/squeezenet/model/squeezenet1.0-9");
|
||||||
const std::vector<cv::Rect> rois = {
|
|
||||||
cv::Rect(cv::Point{ 0, 0}, cv::Size{80, 120}),
|
|
||||||
cv::Rect(cv::Point{50, 100}, cv::Size{250, 360}),
|
|
||||||
};
|
|
||||||
// ONNX_API code
|
// ONNX_API code
|
||||||
out_onnx.resize(rois.size());
|
out_onnx.resize(rois.size());
|
||||||
for (size_t i = 0; i < rois.size(); ++i) {
|
for (size_t i = 0; i < rois.size(); ++i) {
|
||||||
@ -422,10 +542,6 @@ TEST_F(ONNXClassificationTest, InferROIList)
|
|||||||
TEST_F(ONNXClassificationTest, Infer2ROIList)
|
TEST_F(ONNXClassificationTest, Infer2ROIList)
|
||||||
{
|
{
|
||||||
useModel("classification/squeezenet/model/squeezenet1.0-9");
|
useModel("classification/squeezenet/model/squeezenet1.0-9");
|
||||||
const std::vector<cv::Rect> rois = {
|
|
||||||
cv::Rect(cv::Point{ 0, 0}, cv::Size{80, 120}),
|
|
||||||
cv::Rect(cv::Point{50, 100}, cv::Size{250, 360}),
|
|
||||||
};
|
|
||||||
// ONNX_API code
|
// ONNX_API code
|
||||||
out_onnx.resize(rois.size());
|
out_onnx.resize(rois.size());
|
||||||
for (size_t i = 0; i < rois.size(); ++i) {
|
for (size_t i = 0; i < rois.size(); ++i) {
|
||||||
@ -449,27 +565,26 @@ TEST_F(ONNXClassificationTest, Infer2ROIList)
|
|||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ONNXtest, InferDynamicInputTensor)
|
TEST_F(ONNXWithRemap, InferDynamicInputTensor)
|
||||||
{
|
{
|
||||||
useModel("object_detection_segmentation/tiny-yolov2/model/tinyyolov2-8");
|
useModel("object_detection_segmentation/tiny-yolov2/model/tinyyolov2-8");
|
||||||
// Create tensor
|
// Create tensor
|
||||||
// FIXME: Test cheks "random" image
|
cv::Mat cvt, rsz, tensor;
|
||||||
// Ideally it should be a real image
|
cv::resize(in_mat1, rsz, cv::Size{416, 416});
|
||||||
const cv::Mat rand_mat = initMatrixRandU(CV_32FC3, cv::Size{416, 416});
|
rsz.convertTo(cvt, CV_32F, 1.f / 255.f);
|
||||||
const std::vector<int> dims = {1, rand_mat.channels(), rand_mat.rows, rand_mat.cols};
|
toCHW(cvt, tensor);
|
||||||
cv::Mat tensor(dims, CV_32F, rand_mat.data);
|
tensor = tensor.reshape(1, {1, 3, 416, 416});
|
||||||
const cv::Mat in_tensor = tensor / 255.f;
|
|
||||||
// ONNX_API code
|
// ONNX_API code
|
||||||
infer<float>(in_tensor, out_onnx.front());
|
infer<float>(tensor, out_onnx.front());
|
||||||
// G_API code
|
// G_API code
|
||||||
G_API_NET(YoloNet, <cv::GMat(cv::GMat)>, "YoloNet");
|
G_API_NET(YoloNet, <cv::GMat(cv::GMat)>, "YoloNet");
|
||||||
cv::GMat in;
|
cv::GMat in;
|
||||||
cv::GMat out = cv::gapi::infer<YoloNet>(in);
|
cv::GMat out = cv::gapi::infer<YoloNet>(in);
|
||||||
cv::GComputation comp(cv::GIn(in), cv::GOut(out));
|
cv::GComputation comp(cv::GIn(in), cv::GOut(out));
|
||||||
auto net = cv::gapi::onnx::Params<YoloNet>{model_path}
|
auto net = cv::gapi::onnx::Params<YoloNet>{ model_path }
|
||||||
.cfgPostProc({cv::GMatDesc{CV_32F, {1, 125, 13, 13}}}, remapYolo)
|
.cfgPostProc({cv::GMatDesc{CV_32F, {1, 125, 13, 13}}}, remapYolo)
|
||||||
.cfgOutputLayers({"out"});
|
.cfgOutputLayers({"out"});
|
||||||
comp.apply(cv::gin(in_tensor),
|
comp.apply(cv::gin(tensor),
|
||||||
cv::gout(out_gapi.front()),
|
cv::gout(out_gapi.front()),
|
||||||
cv::compile_args(cv::gapi::networks(net)));
|
cv::compile_args(cv::gapi::networks(net)));
|
||||||
// Validate
|
// Validate
|
||||||
@ -497,28 +612,26 @@ TEST_F(ONNXGRayScaleTest, InferImage)
|
|||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ONNXtest, InferMultOutput)
|
TEST_F(ONNXWithRemap, InferMultiOutput)
|
||||||
{
|
{
|
||||||
useModel("object_detection_segmentation/ssd-mobilenetv1/model/ssd_mobilenet_v1_10");
|
useModel("object_detection_segmentation/ssd-mobilenetv1/model/ssd_mobilenet_v1_10");
|
||||||
// ONNX_API code
|
// ONNX_API code
|
||||||
const auto prep_mat = in_mat1.reshape(1, {1, in_mat1.rows, in_mat1.cols, in_mat1.channels()});
|
const auto prep_mat = in_mat1.reshape(1, {1, in_mat1.rows, in_mat1.cols, in_mat1.channels()});
|
||||||
infer<uint8_t>({prep_mat}, out_onnx);
|
infer<uint8_t>(prep_mat, out_onnx);
|
||||||
|
cv::Mat onnx_conv_out({1, 1, 200, 7}, CV_32F);
|
||||||
|
remapToIESSDOut({out_onnx[3], out_onnx[0], out_onnx[2], out_onnx[1]}, onnx_conv_out);
|
||||||
|
out_onnx.clear();
|
||||||
|
out_onnx.push_back(onnx_conv_out);
|
||||||
// G_API code
|
// G_API code
|
||||||
using SSDOut = std::tuple<cv::GMat, cv::GMat, cv::GMat, cv::GMat>;
|
G_API_NET(MobileNet, <cv::GMat(cv::GMat)>, "ssd_mobilenet");
|
||||||
G_API_NET(MobileNet, <SSDOut(cv::GMat)>, "ssd_mobilenet");
|
|
||||||
cv::GMat in;
|
cv::GMat in;
|
||||||
cv::GMat out1, out2, out3, out4;
|
cv::GMat out = cv::gapi::infer<MobileNet>(in);
|
||||||
std::tie(out1, out2, out3, out4) = cv::gapi::infer<MobileNet>(in);
|
cv::GComputation comp(cv::GIn(in), cv::GOut(out));
|
||||||
cv::GComputation comp(cv::GIn(in), cv::GOut(out1, out2, out3, out4));
|
auto net = cv::gapi::onnx::Params<MobileNet>{ model_path }
|
||||||
auto net = cv::gapi::onnx::Params<MobileNet>{model_path}
|
.cfgOutputLayers({"detection_output"})
|
||||||
.cfgOutputLayers({"out1", "out2", "out3", "out4"})
|
.cfgPostProc({cv::GMatDesc{CV_32F, {1, 1, 200, 7}}}, remapSSDPorts);
|
||||||
.cfgPostProc({cv::GMatDesc{CV_32F, {1, 100, 4}},
|
|
||||||
cv::GMatDesc{CV_32F, {1, 100}},
|
|
||||||
cv::GMatDesc{CV_32F, {1, 100}},
|
|
||||||
cv::GMatDesc{CV_32F, {1, 1}}}, remapSsdPorts);
|
|
||||||
out_gapi.resize(num_out);
|
|
||||||
comp.apply(cv::gin(in_mat1),
|
comp.apply(cv::gin(in_mat1),
|
||||||
cv::gout(out_gapi[0], out_gapi[1], out_gapi[2], out_gapi[3]),
|
cv::gout(out_gapi.front()),
|
||||||
cv::compile_args(cv::gapi::networks(net)));
|
cv::compile_args(cv::gapi::networks(net)));
|
||||||
// Validate
|
// Validate
|
||||||
validate();
|
validate();
|
||||||
@ -733,6 +846,71 @@ TEST_F(ONNXMediaFrameTest, InferList2YUV)
|
|||||||
// Validate
|
// Validate
|
||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ONNXYoloV3MultiInput, InferConstInput)
|
||||||
|
{
|
||||||
|
useModel("object_detection_segmentation/yolov3/model/yolov3-10");
|
||||||
|
// ONNX_API code
|
||||||
|
infer<float>(ins, out_onnx);
|
||||||
|
// G_API code
|
||||||
|
using OUT = std::tuple<cv::GMat, cv::GMat, cv::GMat>;
|
||||||
|
G_API_NET(YoloNet, <OUT(cv::GMat)>, "yolov3");
|
||||||
|
auto net = cv::gapi::onnx::Params<YoloNet>{model_path}
|
||||||
|
.constInput("image_shape", ins[1])
|
||||||
|
.cfgInputLayers({"input_1"})
|
||||||
|
.cfgOutputLayers({"out1", "out2", "out3"})
|
||||||
|
.cfgPostProc({cv::GMatDesc{CV_32F, {1, 10000, 4}},
|
||||||
|
cv::GMatDesc{CV_32F, {1, 80, 10000}},
|
||||||
|
cv::GMatDesc{CV_32S, {5, 3}}}, remapYoloV3);
|
||||||
|
cv::GMat in, out1, out2, out3;
|
||||||
|
std::tie(out1, out2, out3) = cv::gapi::infer<YoloNet>(in);
|
||||||
|
cv::GComputation comp(cv::GIn(in), cv::GOut(out1, out2, out3));
|
||||||
|
out_gapi.resize(num_out);
|
||||||
|
comp.apply(cv::gin(ins[0]),
|
||||||
|
cv::gout(out_gapi[0], out_gapi[1], out_gapi[2]),
|
||||||
|
cv::compile_args(cv::gapi::networks(net)));
|
||||||
|
// Validate
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ONNXYoloV3MultiInput, InferBSConstInput)
|
||||||
|
{
|
||||||
|
// This test checks the case when a const input is used
|
||||||
|
// and all input layer names are specified.
|
||||||
|
// Const input has the advantage. It is expected behavior.
|
||||||
|
useModel("object_detection_segmentation/yolov3/model/yolov3-10");
|
||||||
|
// Tensor with incorrect image size
|
||||||
|
// is used for check case when InputLayers and constInput have same names
|
||||||
|
cv::Mat bad_shape;
|
||||||
|
bad_shape.create(cv::Size(2, 1), CV_32F);
|
||||||
|
float* ptr = bad_shape.ptr<float>();
|
||||||
|
ptr[0] = 590;
|
||||||
|
ptr[1] = 12;
|
||||||
|
// ONNX_API code
|
||||||
|
infer<float>(ins, out_onnx);
|
||||||
|
// G_API code
|
||||||
|
using OUT = std::tuple<cv::GMat, cv::GMat, cv::GMat>;
|
||||||
|
G_API_NET(YoloNet, <OUT(cv::GMat, cv::GMat)>, "yolov3");
|
||||||
|
auto net = cv::gapi::onnx::Params<YoloNet>{model_path}
|
||||||
|
// Data from const input will be used to infer
|
||||||
|
.constInput("image_shape", ins[1])
|
||||||
|
// image_shape - const_input has same name
|
||||||
|
.cfgInputLayers({"input_1", "image_shape"})
|
||||||
|
.cfgOutputLayers({"out1", "out2", "out3"})
|
||||||
|
.cfgPostProc({cv::GMatDesc{CV_32F, {1, 10000, 4}},
|
||||||
|
cv::GMatDesc{CV_32F, {1, 80, 10000}},
|
||||||
|
cv::GMatDesc{CV_32S, {5, 3}}}, remapYoloV3);
|
||||||
|
cv::GMat in1, in2, out1, out2, out3;
|
||||||
|
std::tie(out1, out2, out3) = cv::gapi::infer<YoloNet>(in1, in2);
|
||||||
|
cv::GComputation comp(cv::GIn(in1, in2), cv::GOut(out1, out2, out3));
|
||||||
|
out_gapi.resize(num_out);
|
||||||
|
comp.apply(cv::gin(ins[0], bad_shape),
|
||||||
|
cv::gout(out_gapi[0], out_gapi[1], out_gapi[2]),
|
||||||
|
cv::compile_args(cv::gapi::networks(net)));
|
||||||
|
// Validate
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace opencv_test
|
} // namespace opencv_test
|
||||||
|
|
||||||
#endif // HAVE_ONNX
|
#endif // HAVE_ONNX
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user