mirror of
https://github.com/opencv/opencv.git
synced 2026-07-30 07:43:03 +04:00
Merge pull request #28121 from abhishek-gola:loop_layer_add
Add Loop layer to new DNN engine #28121 Addition of loop layer in 5.x for issue: https://github.com/opencv/opencv/issues/26179 and https://github.com/opencv/opencv/issues/26141 and https://github.com/opencv/opencv/issues/25200 OpenCV Extra: https://github.com/opencv/opencv_extra/pull/1335 ### 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 - [x] 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
This commit is contained in:
@@ -1209,6 +1209,14 @@ void broadcast(InputArray _src, InputArray _shape, OutputArray _dst) {
|
||||
}
|
||||
// other cases
|
||||
int max_ndims = std::max(dims_src, dims_shape);
|
||||
if (max_ndims < 2 && src.total() == 1) {
|
||||
const char* p_src = src.ptr<const char>();
|
||||
char* p_dst = dst.ptr<char>();
|
||||
size_t esz = src.elemSize();
|
||||
for (size_t j = 0; j < dst.total(); j++)
|
||||
std::memcpy(p_dst + j * esz, p_src, esz);
|
||||
return;
|
||||
}
|
||||
const int all_ndims[2] = {src.dims, dst.dims};
|
||||
const int* orig_shapes[2] = {src.size.p, dst.size.p};
|
||||
cv::AutoBuffer<size_t> buff(max_ndims * 4);
|
||||
|
||||
@@ -749,7 +749,7 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
static Ptr<RequantizeLayer> create(const LayerParams ¶ms);
|
||||
};
|
||||
|
||||
// Forward declaration for computational Graph used by IfLayer
|
||||
// Forward declaration for computational Graph used by If/Loop layers
|
||||
class Graph;
|
||||
|
||||
class CV_EXPORTS IfLayer : public Layer
|
||||
@@ -761,6 +761,20 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
static Ptr<IfLayer> create(const LayerParams& params);
|
||||
};
|
||||
|
||||
class CV_EXPORTS LoopLayer : public Layer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Evaluate loop condition tensor as a boolean flag.
|
||||
*
|
||||
* The input tensor must contain exactly one element of an integral or floating type.
|
||||
*/
|
||||
virtual bool cond(InputArray arr) const = 0;
|
||||
|
||||
/** Factory: creates a LoopLayer implementation. */
|
||||
static Ptr<LoopLayer> create(const LayerParams& params);
|
||||
};
|
||||
|
||||
class CV_EXPORTS ConcatLayer : public Layer
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -288,8 +288,15 @@ struct BufferAllocator
|
||||
shareBuffer(outarg, elseOutarg);
|
||||
}
|
||||
|
||||
// Isolate subgraph buffers: prevent parent's freed buffers from
|
||||
// being reused here, which causes overwrites on re-execution.
|
||||
std::vector<int> saved_freebufs = freebufs;
|
||||
freebufs.clear();
|
||||
assign(thenBranch);
|
||||
freebufs.clear();
|
||||
assign(elseBranch);
|
||||
freebufs = saved_freebufs;
|
||||
|
||||
for (size_t i = 0; i < noutputs; i++) {
|
||||
Arg thenOutarg = thenOutargs[i];
|
||||
Arg elseOutarg = elseOutargs[i];
|
||||
@@ -320,9 +327,9 @@ struct BufferAllocator
|
||||
CV_Assert(body_noutputs == noutputs+1);
|
||||
CV_Assert(n_state_vars >= 0 && n_accums >= 0);
|
||||
Arg inp0 = inputs[0];
|
||||
if (inp0.idx > 0 && usecounts[inp0.idx] > 0) {
|
||||
CV_Assert(!netimpl->isConstArg(inp0));
|
||||
if (!netimpl->isConstArg(trip_count))
|
||||
if (inp0.idx > 0 && usecounts[inp0.idx] > 0 &&
|
||||
!netimpl->isConstArg(inp0)) {
|
||||
if (!netimpl->isConstArg(trip_count) && bufidxs[trip_count.idx] >= 0)
|
||||
shareBuffer(trip_count, inputs[0]);
|
||||
else
|
||||
bufidxs.at(inputs[0].idx) = getFreeBuffer();
|
||||
@@ -335,7 +342,7 @@ struct BufferAllocator
|
||||
Arg v_out = i >= 0 ? outputs[i] : Arg();
|
||||
if (inparg.idx > 0 && usecounts[inparg.idx] > 0) {
|
||||
CV_Assert(!netimpl->isConstArg(inparg));
|
||||
if (!netimpl->isConstArg(v_inp))
|
||||
if (!netimpl->isConstArg(v_inp) && v_inp.idx > 0 && bufidxs[v_inp.idx] >= 0)
|
||||
shareBuffer(v_inp, inparg);
|
||||
else
|
||||
bufidxs[inparg.idx] = getFreeBuffer();
|
||||
@@ -346,9 +353,10 @@ struct BufferAllocator
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> saved_freebufs = freebufs;
|
||||
freebufs.clear();
|
||||
assign(body);
|
||||
for (auto body_out: body_outputs)
|
||||
releaseBuffer(bufidxs.at(body_out.idx));
|
||||
freebufs = saved_freebufs;
|
||||
}
|
||||
|
||||
for (auto out: outputs) {
|
||||
|
||||
@@ -63,6 +63,7 @@ struct ConstFolding
|
||||
if (processGraph(g))
|
||||
modified = true;
|
||||
}
|
||||
newprog.push_back(layer);
|
||||
continue;
|
||||
}
|
||||
const std::vector<Arg>& inputs = layer->inputs;
|
||||
|
||||
@@ -73,7 +73,7 @@ int Subgraph::getInputNodeId(const Ptr<ImportGraphWrapper>& net,
|
||||
return i;
|
||||
}
|
||||
}
|
||||
CV_Error(Error::StsParseError, "Input node with name " + name + " not found");
|
||||
return -1; // not found in this graph — input comes from outer scope (valid for subgraphs)
|
||||
}
|
||||
|
||||
bool Subgraph::match(const Ptr<ImportGraphWrapper>& net, int nodeId,
|
||||
@@ -139,18 +139,24 @@ bool Subgraph::match(const Ptr<ImportGraphWrapper>& net, int nodeId,
|
||||
if (inputNodes.size() != 2)
|
||||
CV_Error(Error::StsNotImplemented, "Commutative op fusion with more than 2 inputs");
|
||||
|
||||
int inp0 = getInputNodeId(net, node, 0);
|
||||
int inp1 = getInputNodeId(net, node, 1);
|
||||
auto newMatchings = makePtr<std::map<int, int>>(*matchings);
|
||||
matchCandidates.push_back(newMatchings);
|
||||
state.matchings.push_back(newMatchings);
|
||||
states.push({getInputNodeId(net, node, 0), inputNodes[0], state.matchings});
|
||||
states.push({getInputNodeId(net, node, 1), inputNodes[1], state.matchings});
|
||||
if (inp0 >= 0)
|
||||
states.push({inp0, inputNodes[0], state.matchings});
|
||||
if (inp1 >= 0)
|
||||
states.push({inp1, inputNodes[1], state.matchings});
|
||||
state.matchings.pop_back();
|
||||
|
||||
newMatchings = makePtr<std::map<int, int>>(*matchings);
|
||||
matchCandidates.push_back(newMatchings);
|
||||
state.matchings.push_back(newMatchings);
|
||||
states.push({getInputNodeId(net, node, 0), inputNodes[1], state.matchings});
|
||||
states.push({getInputNodeId(net, node, 1), inputNodes[0], state.matchings});
|
||||
if (inp0 >= 0)
|
||||
states.push({inp0, inputNodes[1], state.matchings});
|
||||
if (inp1 >= 0)
|
||||
states.push({inp1, inputNodes[0], state.matchings});
|
||||
state.matchings.pop_back();
|
||||
}
|
||||
else
|
||||
@@ -158,6 +164,8 @@ bool Subgraph::match(const Ptr<ImportGraphWrapper>& net, int nodeId,
|
||||
for (int j = 0; j < inputNodes.size(); ++j)
|
||||
{
|
||||
nodeId = getInputNodeId(net, node, j);
|
||||
if (nodeId < 0)
|
||||
continue;
|
||||
states.push({nodeId, inputNodes[j], state.matchings});
|
||||
}
|
||||
}
|
||||
@@ -264,8 +272,10 @@ void simplifySubgraphs(const Ptr<ImportGraphWrapper>& net,
|
||||
std::string inpName = node->getInputName(i);
|
||||
if (inpName.empty())
|
||||
continue;
|
||||
CV_Assert(nodeIds.find(inpName) != nodeIds.end());
|
||||
refcounts[nodeIds[inpName]] += 1;
|
||||
auto it = nodeIds.find(inpName);
|
||||
if (it == nodeIds.end())
|
||||
continue;
|
||||
refcounts[it->second] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +287,10 @@ void simplifySubgraphs(const Ptr<ImportGraphWrapper>& net,
|
||||
auto node = net->getNode(nodeId);
|
||||
for (int i = 0; i < node->getNumInputs(); ++i) {
|
||||
std::string inpName = node->getInputName(i);
|
||||
refcounts[nodeIds[inpName]] -= 1;
|
||||
auto it = nodeIds.find(inpName);
|
||||
if (it == nodeIds.end())
|
||||
continue;
|
||||
refcounts[it->second] -= 1;
|
||||
}
|
||||
net->removeNode(nodeId);
|
||||
refcounts[nodeId] = -1; // Same node cannot be removed twice
|
||||
|
||||
@@ -85,6 +85,7 @@ void initializeLayerFactory()
|
||||
#endif
|
||||
|
||||
CV_DNN_REGISTER_LAYER_CLASS(If, IfLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Loop, LoopLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Concat, ConcatLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Concat2, Concat2Layer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(ConstantOfShape, ConstantOfShapeLayer);
|
||||
|
||||
@@ -271,12 +271,18 @@ void tensorToIntVec(const Mat& tensor, std::vector<int>& vec)
|
||||
} else {
|
||||
int type = tensor.type();
|
||||
CV_Assert(type == CV_32S || type == CV_64S);
|
||||
CV_Assert(tensor.dims <= 1);
|
||||
// Accept tensors of any dimensionality; treat them as a flat vector.
|
||||
CV_Assert(tensor.isContinuous());
|
||||
int size = (int)tensor.total();
|
||||
vec.resize(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
vec[i] = type == CV_32S ? tensor.at<int>(i) :
|
||||
saturate_cast<int>(tensor.at<int64_t>(i));
|
||||
if (type == CV_32S) {
|
||||
const int* p = tensor.ptr<int>();
|
||||
for (int i = 0; i < size; i++)
|
||||
vec[i] = p[i];
|
||||
} else {
|
||||
const int64_t* p = tensor.ptr<int64_t>();
|
||||
for (int i = 0; i < size; i++)
|
||||
vec[i] = saturate_cast<int>(p[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,12 +295,18 @@ void tensorToFloatVec(const Mat& tensor, std::vector<float>& vec)
|
||||
int type = tensor.type();
|
||||
MatShape shape = tensor.shape();
|
||||
CV_Assert(type == CV_32F || type == CV_16F);
|
||||
CV_Assert(shape.dims <= 1);
|
||||
// Accept tensors of any dimensionality; treat them as a flat vector.
|
||||
CV_Assert(tensor.isContinuous());
|
||||
int size = (int)shape.total();
|
||||
vec.resize(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
vec[i] = type == CV_32F ? tensor.at<float>(i) :
|
||||
(float)tensor.at<hfloat>(i);
|
||||
if (type == CV_32F) {
|
||||
const float* p = tensor.ptr<float>();
|
||||
for (int i = 0; i < size; i++)
|
||||
vec[i] = p[i];
|
||||
} else {
|
||||
const hfloat* p = tensor.ptr<hfloat>();
|
||||
for (int i = 0; i < size; i++)
|
||||
vec[i] = (float)p[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// 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 "../net_impl.hpp"
|
||||
#include "layers_common.hpp"
|
||||
#include <opencv2/dnn.hpp>
|
||||
|
||||
namespace cv { namespace dnn {
|
||||
CV__DNN_INLINE_NS_BEGIN
|
||||
|
||||
class LoopLayerImpl CV_FINAL : public LoopLayer
|
||||
{
|
||||
public:
|
||||
explicit LoopLayerImpl(const LayerParams& params) { setParamsFrom(params); }
|
||||
virtual ~LoopLayerImpl() = default;
|
||||
|
||||
// Single subgraph: the loop body
|
||||
std::vector<Ptr<Graph> >* subgraphs() const CV_OVERRIDE { return &body_; }
|
||||
|
||||
bool getMemoryShapes(const std::vector<MatShape>&,
|
||||
const int requiredOutputs,
|
||||
std::vector<MatShape>& outputs,
|
||||
std::vector<MatShape>& internals) const CV_OVERRIDE
|
||||
{
|
||||
outputs.assign(std::max(1, requiredOutputs), MatShape());
|
||||
internals.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dynamicOutputShapes() const CV_OVERRIDE { return true; }
|
||||
|
||||
// OPTIMIZATION: Reduced to a simple numeric check via double
|
||||
bool cond(InputArray arr) const CV_OVERRIDE
|
||||
{
|
||||
Mat buf, *inp;
|
||||
if (arr.kind() == _InputArray::MAT) {
|
||||
inp = (Mat*)arr.getObj();
|
||||
} else {
|
||||
buf = arr.getMat();
|
||||
inp = &buf;
|
||||
}
|
||||
CV_Assert(inp->total() == 1u);
|
||||
bool flag;
|
||||
switch (inp->depth())
|
||||
{
|
||||
case CV_8U: case CV_8S: case CV_Bool:
|
||||
flag = *inp->ptr<char>() != 0; break;
|
||||
case CV_16U: case CV_16S:
|
||||
flag = *inp->ptr<short>() != 0; break;
|
||||
case CV_16F:
|
||||
case CV_16BF:
|
||||
flag = *inp->ptr<hfloat>() != 0; break;
|
||||
case CV_32U: case CV_32S:
|
||||
flag = *inp->ptr<int>() != 0; break;
|
||||
case CV_32F:
|
||||
flag = *inp->ptr<float>() != 0; break;
|
||||
case CV_64U: case CV_64S:
|
||||
flag = *inp->ptr<long long>() != 0; break;
|
||||
case CV_64F:
|
||||
flag = *inp->ptr<double>() != 0; break;
|
||||
default:
|
||||
CV_Error_(Error::StsBadArg,
|
||||
("Loop condition: unsupported tensor type %s",
|
||||
typeToString(inp->type()).c_str()));
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::vector<Ptr<Graph> > body_;
|
||||
};
|
||||
|
||||
Ptr<LoopLayer> LoopLayer::create(const LayerParams& params)
|
||||
{
|
||||
return makePtr<LoopLayerImpl>(params);
|
||||
}
|
||||
CV__DNN_INLINE_NS_END
|
||||
}} // namespace cv::dnn
|
||||
@@ -1188,6 +1188,7 @@ void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
|
||||
}
|
||||
else {
|
||||
Ptr<IfLayer> iflayer = layer.dynamicCast<IfLayer>();
|
||||
Ptr<LoopLayer> loopLayer = layer.dynamicCast<LoopLayer>();
|
||||
if (iflayer) {
|
||||
int branch = iflayer->branch(inpMats[0]);
|
||||
Ptr<Graph> subgraph = subgraphs->at(branch);
|
||||
@@ -1196,6 +1197,89 @@ void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
|
||||
branchInputs.assign(inpMats.begin() + 1, inpMats.end());
|
||||
forwardGraph(subgraph, branchInputs, outMats, false);
|
||||
}
|
||||
else if (loopLayer) {
|
||||
CV_Assert(subgraphs->size() == 1);
|
||||
Ptr<Graph> body = subgraphs->at(0);
|
||||
|
||||
int n_in = (int)inpMats.size();
|
||||
int n_state = n_in - 2;
|
||||
int n_accum = (int)body->outputs().size() - n_state - 1;
|
||||
CV_Assert(n_in >= 2 && n_state >= 0 && n_accum >= 0);
|
||||
|
||||
Mat mIter, mCond, tmp;
|
||||
mIter.create(1, 1, CV_64S);
|
||||
if (inpMats[1].empty())
|
||||
{
|
||||
mCond.create(1, 1, CV_8U);
|
||||
mCond.at<uchar>(0) = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
mCond = inpMats[1];
|
||||
}
|
||||
int64 iter = 0;
|
||||
int64 max_iter = inpMats[0].empty() ? -1 :
|
||||
(inpMats[0].convertTo(tmp, CV_64S), *tmp.ptr<int64>());
|
||||
bool active = loopLayer->cond(mCond);
|
||||
|
||||
std::vector<Mat> state(inpMats.begin() + 2, inpMats.end());
|
||||
std::vector<std::vector<Mat> > history(n_accum);
|
||||
std::vector<Mat> inputs(n_in), outputs;
|
||||
|
||||
while (active && (max_iter < 0 || iter < max_iter)) {
|
||||
mIter.at<int64>(0) = iter++;
|
||||
inputs[0] = mIter;
|
||||
inputs[1] = mCond;
|
||||
std::copy(state.begin(), state.end(), inputs.begin() + 2);
|
||||
|
||||
forwardGraph(body, inputs, outputs, false);
|
||||
|
||||
mCond = outputs[0];
|
||||
active = loopLayer->cond(mCond);
|
||||
|
||||
for (int i = 0; i < n_state; i++)
|
||||
state[i] = outputs[1 + i];
|
||||
for (int i = 0; i < n_accum; i++)
|
||||
history[i].push_back(outputs[1 + n_state + i].clone());
|
||||
}
|
||||
|
||||
outMats.assign(state.begin(), state.end());
|
||||
outMats.resize(n_state + n_accum);
|
||||
|
||||
for (int i = 0; i < n_accum; ++i) {
|
||||
const std::vector<Mat>& perIter = history[i];
|
||||
if (perIter.empty()) {
|
||||
outMats[n_state + i] = Mat();
|
||||
continue;
|
||||
}
|
||||
|
||||
const Mat& first = perIter[0];
|
||||
MatShape elemShape = first.shape();
|
||||
int ndims = elemShape.dims;
|
||||
CV_Assert(ndims >= 0);
|
||||
|
||||
MatShape outShape(ndims + 1);
|
||||
outShape[0] = (int)perIter.size();
|
||||
for (int d = 0; d < ndims; ++d)
|
||||
outShape[d + 1] = elemShape[d];
|
||||
|
||||
Mat stacked;
|
||||
stacked.create(outShape, first.type());
|
||||
|
||||
for (size_t k = 0; k < perIter.size(); ++k)
|
||||
{
|
||||
const Mat& src = perIter[k];
|
||||
CV_Assert(src.type() == first.type());
|
||||
CV_Assert(src.total() == first.total());
|
||||
|
||||
Mat dst = stacked.reshape(0, (int)perIter.size())
|
||||
.row((int)k)
|
||||
.reshape(first.channels(), elemShape.dims, &elemShape[0]);
|
||||
src.copyTo(dst);
|
||||
}
|
||||
outMats[n_state + i] = stacked;
|
||||
}
|
||||
}
|
||||
else {
|
||||
CV_Error_(Error::StsNotImplemented,
|
||||
("unknown layer type '%s' with subgraphs", layer->type.c_str()));
|
||||
|
||||
@@ -180,6 +180,7 @@ protected:
|
||||
void parseCastLike (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseClip (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseConcat (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseLoop (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseIf (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseConstant (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseConstantOfShape (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
@@ -1499,10 +1500,6 @@ void ONNXImporter2::parseGather(LayerParams& layerParams, const opencv_onnx::Nod
|
||||
{
|
||||
layerParams.type = "Gather2";
|
||||
CV_CheckEQ(node_proto.input_size(), 2, "");
|
||||
// Diagnostics: log axis used by this Gather node (attribute may be absent -> default 0)
|
||||
int axis = layerParams.get<int>("axis", 0);
|
||||
const std::string node_name = node_proto.has_name() ? node_proto.name() : std::string();
|
||||
CV_LOG_WARNING(NULL, "DNN/ONNX: Gather node '" << node_name << "' axis=" << axis << ", outputs=" << (node_proto.output_size() > 0 ? node_proto.output(0) : std::string("")));
|
||||
addLayer(layerParams, node_proto);
|
||||
}
|
||||
|
||||
@@ -1519,6 +1516,34 @@ void ONNXImporter2::parseConcat(LayerParams& layerParams, const opencv_onnx::Nod
|
||||
addLayer(layerParams, node_proto);
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseLoop(LayerParams& layerParams,
|
||||
const opencv_onnx::NodeProto& node_proto)
|
||||
{
|
||||
// ONNX Loop: inputs = [M, cond, v0, v1, ...]; attribute "body" is a GraphProto.
|
||||
CV_Assert(node_proto.input_size() >= 2);
|
||||
layerParams.type = "Loop";
|
||||
|
||||
// Create Loop layer node in the current graph.
|
||||
addLayer(layerParams, node_proto);
|
||||
|
||||
std::vector<Ptr<Graph> > subgraphs(1);
|
||||
for (int i = 0; i < node_proto.attribute_size(); ++i)
|
||||
{
|
||||
const auto& attr = node_proto.attribute(i);
|
||||
if (attr.name() == "body")
|
||||
{
|
||||
opencv_onnx::GraphProto body = attr.g();
|
||||
Ptr<Graph> graph = parseGraph(&body, false);
|
||||
subgraphs[0] = graph;
|
||||
}
|
||||
}
|
||||
|
||||
CV_Assert(!subgraphs[0].empty());
|
||||
|
||||
Ptr<Layer>& loopLayer = curr_prog.back();
|
||||
*loopLayer->subgraphs() = subgraphs;
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseIf(LayerParams& layerParams,
|
||||
const opencv_onnx::NodeProto& node_proto)
|
||||
{
|
||||
@@ -2694,6 +2719,7 @@ void ONNXImporter2::buildDispatchMap_ONNX_AI()
|
||||
dispatch["Gather"] = &ONNXImporter2::parseGather;
|
||||
dispatch["GatherElements"] = &ONNXImporter2::parseGatherElements;
|
||||
dispatch["Concat"] = &ONNXImporter2::parseConcat;
|
||||
dispatch["Loop"] = &ONNXImporter2::parseLoop;
|
||||
dispatch["If"] = &ONNXImporter2::parseIf;
|
||||
dispatch["Resize"] = &ONNXImporter2::parseResize2;
|
||||
dispatch["Size"] = &ONNXImporter2::parseSize;
|
||||
|
||||
@@ -978,4 +978,117 @@ TEST_P(Reproducibility_ResNet50_QDQ_ONNX, Accuracy)
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_ResNet50_QDQ_ONNX,
|
||||
testing::ValuesIn(getAvailableTargets(DNN_BACKEND_OPENCV)));
|
||||
|
||||
typedef testing::TestWithParam<Target> Reproducibility_MobileNetSSD_ONNX;
|
||||
TEST_P(Reproducibility_MobileNetSSD_ONNX, Accuracy)
|
||||
{
|
||||
Target targetId = GetParam();
|
||||
auto engine_forced = static_cast<EngineType>(
|
||||
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO));
|
||||
if (engine_forced == ENGINE_CLASSIC)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
|
||||
return;
|
||||
}
|
||||
|
||||
applyTestTag(targetId == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_512MB : CV_TEST_TAG_MEMORY_1GB);
|
||||
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
|
||||
|
||||
std::string modelname = _tf("onnx/models/ssd_mobilenet_v1_12.onnx", false);
|
||||
Net net = readNetFromONNX(modelname);
|
||||
|
||||
net.setPreferableBackend(DNN_BACKEND_OPENCV);
|
||||
net.setPreferableTarget(targetId);
|
||||
|
||||
if (targetId == DNN_TARGET_CPU_FP16)
|
||||
net.enableWinograd(false);
|
||||
|
||||
std::string imgname = _tf("dog_orig_size.png");
|
||||
Mat image = imread(imgname);
|
||||
ASSERT_TRUE(!image.empty());
|
||||
Mat input;
|
||||
resize(image, input, Size(300, 300));
|
||||
int imsize[] = {1, input.rows, input.cols, 3};
|
||||
Mat input8dim4(4, imsize, CV_8U, input.data);
|
||||
|
||||
std::vector<String> outNames = net.getUnconnectedOutLayersNames();
|
||||
std::vector<Mat> outs;
|
||||
double min_t = 0;
|
||||
const int niters =
|
||||
#ifdef _DEBUG
|
||||
1;
|
||||
#else
|
||||
30;
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < niters; i++) {
|
||||
double t = (double)getTickCount();
|
||||
net.setInput(input8dim4);
|
||||
net.forward(outs, outNames);
|
||||
t = (double)getTickCount() - t;
|
||||
min_t = i == 0 ? t : std::min(min_t, t);
|
||||
}
|
||||
printf("run time = %.2fms\n", min_t*1000./getTickFrequency());
|
||||
|
||||
// Model outputs: detection_boxes [1,N,4], detection_classes [1,N],
|
||||
// detection_scores [1,N], num_detections [1]
|
||||
ASSERT_EQ(outs.size(), (size_t)4);
|
||||
|
||||
Mat boxes, classes, scores, numDet;
|
||||
for (size_t i = 0; i < outs.size(); i++) {
|
||||
if (outs[i].dims == 3 && outs[i].size[2] == 4)
|
||||
boxes = outs[i];
|
||||
else if (outs[i].total() == 1)
|
||||
numDet = outs[i];
|
||||
else if (outs[i].dims == 2) {
|
||||
float first = outs[i].at<float>(0, 0);
|
||||
if (first == std::round(first) && first > 0)
|
||||
classes = outs[i];
|
||||
else
|
||||
scores = outs[i];
|
||||
}
|
||||
}
|
||||
ASSERT_FALSE(boxes.empty());
|
||||
ASSERT_FALSE(scores.empty());
|
||||
ASSERT_FALSE(classes.empty());
|
||||
ASSERT_FALSE(numDet.empty());
|
||||
|
||||
int ndet = (int)numDet.at<float>(0);
|
||||
printf("num_detections = %d\n", ndet);
|
||||
ASSERT_GT(ndet, 0);
|
||||
|
||||
// Build test detection vectors from model outputs
|
||||
// Model boxes are normalized (y1, x1, y2, x2) — convert to Rect2d(x, y, w, h)
|
||||
std::vector<int> testClassIds;
|
||||
std::vector<float> testScores;
|
||||
std::vector<Rect2d> testBoxes;
|
||||
for (int j = 0; j < ndet; j++) {
|
||||
testClassIds.push_back((int)classes.at<float>(0, j));
|
||||
testScores.push_back(scores.at<float>(0, j));
|
||||
float y1 = boxes.at<float>(0, j, 0);
|
||||
float x1 = boxes.at<float>(0, j, 1);
|
||||
float y2 = boxes.at<float>(0, j, 2);
|
||||
float x2 = boxes.at<float>(0, j, 3);
|
||||
testBoxes.push_back(Rect2d(x1, y1, x2 - x1, y2 - y1));
|
||||
}
|
||||
|
||||
// Reference detections for dog_orig_size.png
|
||||
// COCO 1-indexed: 2=bicycle, 3=car, 18=dog
|
||||
std::vector<int> refClassIds = {2, 18, 3};
|
||||
std::vector<float> refScores = {0.944377f, 0.877805f, 0.787824f};
|
||||
std::vector<Rect2d> refBoxes = {
|
||||
Rect2d(0.157917, 0.219984, 0.742909 - 0.157917, 0.739280 - 0.219984), // bicycle
|
||||
Rect2d(0.168082, 0.360803, 0.426304 - 0.168082, 0.919625 - 0.360803), // dog
|
||||
Rect2d(0.600506, 0.114612, 0.899101 - 0.600506, 0.298757 - 0.114612), // car
|
||||
};
|
||||
|
||||
float confThreshold = 0.5f;
|
||||
double scoreDiff = 0.1;
|
||||
double iouDiff = 0.05;
|
||||
normAssertDetections(refClassIds, refScores, refBoxes,
|
||||
testClassIds, testScores, testBoxes,
|
||||
"", confThreshold, scoreDiff, iouDiff);
|
||||
}
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_MobileNetSSD_ONNX,
|
||||
testing::ValuesIn(getAvailableTargets(DNN_BACKEND_OPENCV)));
|
||||
|
||||
}} // namespace
|
||||
|
||||
@@ -1435,7 +1435,7 @@ CASE(test_logsoftmax_negative_axis_expanded)
|
||||
CASE(test_logsoftmax_negative_axis_expanded_ver18)
|
||||
SKIP;
|
||||
CASE(test_loop11)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_loop13_seq)
|
||||
// no filter
|
||||
CASE(test_loop16_seq_none)
|
||||
|
||||
@@ -600,7 +600,8 @@
|
||||
"test_rotary_embedding_with_interleaved_rotary_dim",
|
||||
"test_rotary_embedding_with_interleaved_rotary_dim_expanded",
|
||||
"test_rotary_embedding_with_rotary_dim",
|
||||
"test_rotary_embedding_with_rotary_dim_expanded","test_attention_3d",
|
||||
"test_rotary_embedding_with_rotary_dim_expanded",
|
||||
"test_attention_3d",
|
||||
"test_attention_3d_attn_mask",
|
||||
"test_attention_3d_causal",
|
||||
"test_attention_3d_diff_heads_sizes",
|
||||
@@ -733,3 +734,4 @@
|
||||
"test_reduce_sum_square_empty_set",
|
||||
"test_reduce_sum_square_empty_set_expanded",
|
||||
"test_reduce_log_sum_exp_empty_set_expanded",
|
||||
"test_loop11",
|
||||
|
||||
@@ -331,9 +331,8 @@
|
||||
"test_l1normalization_axis_last",
|
||||
"test_l2normalization_axis_0",
|
||||
"test_l2normalization_axis_1",
|
||||
"test_loop11", // Issue::'Graph' is not supported in function 'getLayerParams'
|
||||
"test_loop13_seq", // Issue::typeProto.has_tensor_type() in function 'populateNet'
|
||||
"test_loop16_seq_none", // Issue::Failed to allocate 179812654996800 bytes in function 'OutOfMemoryError'
|
||||
"test_loop13_seq", // Loop with tensor sequences output, not yet supported in OpenCV
|
||||
"test_loop16_seq_none", // Loop with optional tensor sequences, not yet supported in OpenCV
|
||||
"test_lpnormalization_default",
|
||||
"test_lppool_1d_default",
|
||||
"test_lppool_2d_default",
|
||||
|
||||
Reference in New Issue
Block a user