mirror of
https://github.com/opencv/opencv.git
synced 2026-09-25 04:09:57 +03:00
Merge pull request #29931 from abhishek-gola:onnx_conformance_remaining_fixes
Merge pull request #29931 from abhishek-gola:onnx_conformance_remaining_fixes fix ONNX auto_pad, PRelu broadcasting and LSTM peepholes - #29931 closes: https://github.com/opencv/opencv/issues/21078 Upated ONNX coverage: 77.8% ### 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:
@@ -226,6 +226,19 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
static Ptr<GRULayer> create(const LayerParams& params);
|
||||
};
|
||||
|
||||
/** @brief ONNX RNN layer, the single-gate recurrent unit.
|
||||
|
||||
* @f$h_t = f(W x_t + R h_{t-1} + W_b + R_b)@f$, where @f$f@f$ defaults to Tanh
|
||||
* and can also be Relu or Sigmoid. Unlike @ref RNNLayer this follows the ONNX
|
||||
* operator: weights arrive as inputs and there is no output projection.
|
||||
*/
|
||||
class CV_EXPORTS RNN2Layer : public Layer
|
||||
{
|
||||
public:
|
||||
/** Creates instance of ONNX RNN layer */
|
||||
static Ptr<RNN2Layer> create(const LayerParams& params);
|
||||
};
|
||||
|
||||
/** @brief Classical recurrent layer
|
||||
|
||||
Accepts two inputs @f$x_t@f$ and @f$h_{t-1}@f$ and compute two outputs @f$o_t@f$ and @f$h_t@f$.
|
||||
@@ -1488,7 +1501,8 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
WHERE,
|
||||
BITWISE_AND,
|
||||
BITWISE_OR,
|
||||
BITWISE_XOR
|
||||
BITWISE_XOR,
|
||||
PRELU
|
||||
};
|
||||
OPERATION op;
|
||||
|
||||
|
||||
@@ -274,6 +274,7 @@ void initializeLayerFactory()
|
||||
CV_DNN_REGISTER_LAYER_CLASS(LSTM, LSTMLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(LSTM2, LSTM2Layer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(GRU, GRULayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(RNN2, RNN2Layer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(CumSum, CumSumLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(CumProd, CumProdLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Einsum, EinsumLayer);
|
||||
|
||||
@@ -27,16 +27,17 @@ AutoPadding getAutoPadding(const LayerParams& params)
|
||||
{
|
||||
std::string auto_pad = params.get<std::string>("auto_pad", "NOTSET");
|
||||
std::string pad_mode = params.get<std::string>("pad_mode", "");
|
||||
// auto_pad distinguishes SAME_UPPER from SAME_LOWER, pad_mode does not, so it wins.
|
||||
if (auto_pad == "SAME_UPPER")
|
||||
return AUTO_PAD_SAME_UPPER;
|
||||
if (auto_pad == "SAME_LOWER")
|
||||
return AUTO_PAD_SAME_LOWER;
|
||||
if (pad_mode == "SAME")
|
||||
return AUTO_PAD_SAME_UPPER;
|
||||
if (pad_mode == "VALID")
|
||||
return AUTO_PAD_VALID;
|
||||
if (auto_pad == "NOTSET")
|
||||
return AUTO_PAD_NONE;
|
||||
if (auto_pad == "SAME_UPPER")
|
||||
return AUTO_PAD_SAME_UPPER;
|
||||
if (auto_pad == "SAME_LOWER")
|
||||
return AUTO_PAD_SAME_LOWER;
|
||||
if (auto_pad != "VALID") {
|
||||
CV_Error_(Error::StsBadArg, ("invalid auto_pad value '%s'", auto_pad.c_str()));
|
||||
}
|
||||
@@ -119,9 +120,11 @@ MatShape convInferShape(const MatShape& inpShape, const MatShape& wshape,
|
||||
return outshape;
|
||||
}
|
||||
|
||||
struct SpatialDim { int ksize, inpsz, stride, dilation; };
|
||||
|
||||
static inline void getPadding(const std::vector<int>& pads,
|
||||
int dim, int nspatialdims, AutoPadding autoPad,
|
||||
int ksize, int& pad0, int& pad1)
|
||||
const SpatialDim& d, int& pad0, int& pad1)
|
||||
{
|
||||
CV_Assert(0 <= dim && dim < nspatialdims);
|
||||
|
||||
@@ -134,11 +137,11 @@ static inline void getPadding(const std::vector<int>& pads,
|
||||
}
|
||||
} else {
|
||||
CV_Assert(autoPad == AUTO_PAD_SAME_LOWER || autoPad == AUTO_PAD_SAME_UPPER);
|
||||
pad0 = pad1 = ksize/2;
|
||||
if (pad0*2 == ksize) {
|
||||
pad0 -= autoPad == AUTO_PAD_SAME_UPPER;
|
||||
pad1 -= autoPad == AUTO_PAD_SAME_LOWER;
|
||||
}
|
||||
// ONNX SAME_*: pad so output == ceil(input/stride); odd pixel last for SAME_UPPER.
|
||||
int outsz = (d.inpsz - 1)/d.stride + 1;
|
||||
int total = std::max((outsz - 1)*d.stride + (d.ksize - 1)*d.dilation + 1 - d.inpsz, 0);
|
||||
pad0 = autoPad == AUTO_PAD_SAME_UPPER ? total/2 : total - total/2;
|
||||
pad1 = total - pad0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +236,8 @@ void ConvState::initConv(const MatShape& inpshape_,
|
||||
CV_Assert(dilations[j] > 0);
|
||||
|
||||
int pad0, pad1;
|
||||
getPadding(pads_, i, nspatialdims, autoPad, kshape[j], pad0, pad1);
|
||||
getPadding(pads_, i, nspatialdims, autoPad,
|
||||
{kshape[j], inpshape[i+2], strides[j], dilations[j]}, pad0, pad1);
|
||||
CV_Assert_N(pad0 >= 0, pad1 >= 0);
|
||||
pads[j] = pad0;
|
||||
pads[j + MAX_CONV_DIMS] = pad1;
|
||||
@@ -351,7 +355,8 @@ void ConvState::initPooling(const MatShape& inpshape_,
|
||||
CV_Assert(dilations[j] > 0);
|
||||
|
||||
int pad0, pad1;
|
||||
getPadding(pads_, i, nspatialdims, autoPad, kshape[j], pad0, pad1);
|
||||
getPadding(pads_, i, nspatialdims, autoPad,
|
||||
{kshape[j], inpshape[i+2], strides[j], dilations[j]}, pad0, pad1);
|
||||
CV_Assert_N(pad0 >= 0, pad1 >= 0);
|
||||
pads[j] = pad0;
|
||||
pads[j + MAX_CONV_DIMS] = pad1;
|
||||
@@ -439,7 +444,8 @@ MatShape deconvInferShape(const MatShape& inpShape, const MatShape& wshape,
|
||||
pad_total = pads[i] + pads[i + nspatialdims];
|
||||
outsz = (inpsz - 1) * stride - pad_total + dilation * (k_i - 1) + 1 + adj;
|
||||
} else {
|
||||
outsz = (inpsz - 1) * stride + 1 + adj;
|
||||
// ONNX: SAME_UPPER/SAME_LOWER pad the input so that output == input * stride.
|
||||
outsz = inpsz * stride;
|
||||
}
|
||||
outshape[i + 2] = outsz;
|
||||
}
|
||||
|
||||
@@ -31,9 +31,21 @@ public:
|
||||
dilations = params.getVector<int>("dilation");
|
||||
pads = params.getVector<int>("pad");
|
||||
adjust_pads = params.getVector<int>("adj");
|
||||
// ONNX output_shape (spatial only); fixes the output, paddings derived from it.
|
||||
explicit_out_shape = params.getVector<int>("output_shape_spatial");
|
||||
ngroups = params.get<int>("group", 1);
|
||||
}
|
||||
|
||||
void applyExplicitOutShape(MatShape& outshape) const
|
||||
{
|
||||
if (explicit_out_shape.empty())
|
||||
return;
|
||||
int nsd = outshape.dims - 2 - int(outshape.layout == DATA_LAYOUT_BLOCK);
|
||||
CV_CheckEQ((int)explicit_out_shape.size(), nsd, "output_shape must cover all spatial dims");
|
||||
for (int i = 0; i < nsd; i++)
|
||||
outshape[i + 2] = explicit_out_shape[i];
|
||||
}
|
||||
|
||||
virtual std::ostream& dumpAttrs(std::ostream& strm, int indent) const CV_OVERRIDE
|
||||
{
|
||||
prindent(strm, indent);
|
||||
@@ -156,6 +168,7 @@ public:
|
||||
outshapes.assign(1, deconvInferShape(inpshapes[0], wshape, emptyKernelShape,
|
||||
ngroups, strides, dilations,
|
||||
pads, adjust_pads, auto_pad));
|
||||
applyExplicitOutShape(outshapes[0]);
|
||||
tempshapes.clear();
|
||||
return true;
|
||||
}
|
||||
@@ -205,11 +218,12 @@ public:
|
||||
MatShape outshape = deconvInferShape(inpshape, wshape0, emptyKernelShape,
|
||||
ngroups, strides, dilations,
|
||||
pads, adjust_pads, auto_pad);
|
||||
applyExplicitOutShape(outshape);
|
||||
|
||||
// compute actual pads for SAME/VALID auto-padding
|
||||
// compute actual pads for SAME/VALID auto-padding, or from an explicit output_shape
|
||||
int nsd = inpshape.dims - 3;
|
||||
std::vector<int> pads_resolved = pads;
|
||||
if (auto_pad != AUTO_PAD_NONE) {
|
||||
if (auto_pad != AUTO_PAD_NONE || !explicit_out_shape.empty()) {
|
||||
pads_resolved.resize(nsd * 2, 0);
|
||||
for (int i = 0; i < nsd; i++) {
|
||||
int inpsz = inpshape[2 + i];
|
||||
@@ -219,12 +233,9 @@ public:
|
||||
int dil = dilations.empty() ? 1 : dilations[i];
|
||||
int ki = wshape0[2 + i];
|
||||
int total = (inpsz - 1) * stride + dil * (ki - 1) + 1 + adj_i - outsz;
|
||||
int pb;
|
||||
if (auto_pad == AUTO_PAD_SAME_UPPER && stride <= ki * dil) {
|
||||
pb = std::max((total - (outsz - 1 + stride) % stride) / 2, 0);
|
||||
} else {
|
||||
pb = total / 2;
|
||||
}
|
||||
total = std::max(total, 0);
|
||||
// ONNX splits total_padding evenly; odd pixel goes last for SAME_UPPER.
|
||||
int pb = (auto_pad == AUTO_PAD_SAME_UPPER) ? total / 2 : total - total / 2;
|
||||
pads_resolved[i] = pb;
|
||||
pads_resolved[nsd + i] = total - pb;
|
||||
}
|
||||
@@ -273,6 +284,7 @@ public:
|
||||
}
|
||||
|
||||
std::vector<int> emptyKernelShape;
|
||||
std::vector<int> explicit_out_shape;
|
||||
Mat weights, bias;
|
||||
MatShape wshape0, prevInpshape;
|
||||
ConvState cs;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// 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.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#ifndef OPENCV_DNN_RECURRENT_ACTIVATIONS_HPP
|
||||
#define OPENCV_DNN_RECURRENT_ACTIVATIONS_HPP
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/core/hal/intrin.hpp>
|
||||
|
||||
namespace cv { namespace dnn { namespace recurrent {
|
||||
|
||||
// Gate activations shared by the ONNX GRU and RNN layers. CV_32F and CV_64F.
|
||||
// CV_64F takes the scalar path; the universal intrinsics here are single precision.
|
||||
template<typename T, typename Op>
|
||||
inline void applyRowwise(const Mat &src, Mat &dst, Op op)
|
||||
{
|
||||
dst.create(src.size(), src.type());
|
||||
const int nrows = src.rows, cols = src.cols;
|
||||
parallel_for_(Range(0, nrows), [&](const Range& range) {
|
||||
for (int row = range.start; row < range.end; ++row)
|
||||
{
|
||||
const T* srcptr = src.ptr<T>(row);
|
||||
T* dstptr = dst.ptr<T>(row);
|
||||
for (int i = 0; i < cols; ++i)
|
||||
dstptr[i] = op(srcptr[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template<typename VecOp, typename ScalarOp>
|
||||
inline void applyFloatRowwise(const Mat &src, Mat &dst, VecOp vop, ScalarOp sop)
|
||||
{
|
||||
dst.create(src.size(), src.type());
|
||||
const int nrows = src.rows, cols = src.cols;
|
||||
parallel_for_(Range(0, nrows), [&](const Range& range) {
|
||||
for (int row = range.start; row < range.end; ++row)
|
||||
{
|
||||
const float* srcptr = src.ptr<float>(row);
|
||||
float* dstptr = dst.ptr<float>(row);
|
||||
int i = 0;
|
||||
#if (CV_SIMD || CV_SIMD_SCALABLE)
|
||||
const int vlanes = VTraits<v_float32>::vlanes();
|
||||
for (; i <= cols - vlanes; i += vlanes)
|
||||
vx_store(dstptr + i, vop(vx_load(srcptr + i)));
|
||||
#endif
|
||||
for (; i < cols; ++i)
|
||||
dstptr[i] = sop(srcptr[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inline void tanh(const Mat &src, Mat &dst)
|
||||
{
|
||||
CV_Assert(src.type() == CV_32F || src.type() == CV_64F);
|
||||
if (src.type() == CV_64F)
|
||||
{
|
||||
applyRowwise<double>(src, dst, [](double x) { return std::tanh(x); });
|
||||
return;
|
||||
}
|
||||
applyFloatRowwise(src, dst,
|
||||
[](const v_float32& x) -> v_float32 {
|
||||
// 2/(1+exp(-2x)) - 1
|
||||
v_float32 one = vx_setall_f32(1.f), two = vx_setall_f32(2.f);
|
||||
return v_sub(v_div(two, v_add(one, v_exp(v_mul(vx_setall_f32(-2.f), x)))), one);
|
||||
},
|
||||
[](float x) { return std::tanh(x); });
|
||||
}
|
||||
|
||||
inline void sigmoid(const Mat &src, Mat &dst)
|
||||
{
|
||||
CV_Assert(src.type() == CV_32F || src.type() == CV_64F);
|
||||
if (src.type() == CV_64F)
|
||||
{
|
||||
applyRowwise<double>(src, dst, [](double x) { return 1.0 / (1.0 + std::exp(-x)); });
|
||||
return;
|
||||
}
|
||||
applyFloatRowwise(src, dst,
|
||||
[](const v_float32& x) -> v_float32 {
|
||||
// 1/(1+exp(-x))
|
||||
v_float32 one = vx_setall_f32(1.f);
|
||||
return v_div(one, v_add(one, v_exp(v_sub(vx_setzero_f32(), x))));
|
||||
},
|
||||
[](float x) { return 1.f / (1.f + std::exp(-x)); });
|
||||
}
|
||||
|
||||
}}} // namespace cv::dnn::recurrent
|
||||
|
||||
#endif
|
||||
@@ -1,67 +1,13 @@
|
||||
#include "../precomp.hpp"
|
||||
#include <opencv2/dnn/shape_utils.hpp>
|
||||
#include "layers_common.hpp"
|
||||
#include "cpu_kernels/recurrent_activations.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace dnn {
|
||||
|
||||
static void tanh(const Mat &src, Mat &dst)
|
||||
{
|
||||
CV_Assert(src.type() == CV_32F);
|
||||
dst.create(src.size(), src.type());
|
||||
const int nrows = src.rows;
|
||||
const int cols = src.cols;
|
||||
parallel_for_(Range(0, nrows), [&](const Range& range) {
|
||||
for (int row = range.start; row < range.end; ++row)
|
||||
{
|
||||
const float* srcptr = src.ptr<float>(row);
|
||||
float* dstptr = dst.ptr<float>(row);
|
||||
int i = 0;
|
||||
#if (CV_SIMD || CV_SIMD_SCALABLE)
|
||||
const int vlanes = VTraits<v_float32>::vlanes();
|
||||
v_float32 one = vx_setall_f32(1.f), two = vx_setall_f32(2.f), minus_two = vx_setall_f32(-2.f);
|
||||
for (; i <= cols - vlanes; i += vlanes)
|
||||
{
|
||||
v_float32 x = vx_load(srcptr + i);
|
||||
v_float32 e = v_exp(v_mul(minus_two, x)); // exp(-2x)
|
||||
v_float32 t = v_sub(v_div(two, v_add(one, e)), one); // 2/(1+exp(-2x)) - 1
|
||||
vx_store(dstptr + i, t);
|
||||
}
|
||||
#endif
|
||||
for (; i < cols; ++i)
|
||||
dstptr[i] = std::tanh(srcptr[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void sigmoid(const Mat &src, Mat &dst)
|
||||
{
|
||||
CV_Assert(src.type() == CV_32F);
|
||||
dst.create(src.size(), src.type());
|
||||
const int nrows = src.rows;
|
||||
const int cols = src.cols;
|
||||
parallel_for_(Range(0, nrows), [&](const Range& range) {
|
||||
for (int row = range.start; row < range.end; ++row)
|
||||
{
|
||||
const float* srcptr = src.ptr<float>(row);
|
||||
float* dstptr = dst.ptr<float>(row);
|
||||
int i = 0;
|
||||
#if (CV_SIMD || CV_SIMD_SCALABLE)
|
||||
const int vlanes = VTraits<v_float32>::vlanes();
|
||||
v_float32 one = vx_setall_f32(1.f), zero = vx_setzero_f32();
|
||||
for (; i <= cols - vlanes; i += vlanes)
|
||||
{
|
||||
v_float32 x = vx_load(srcptr + i);
|
||||
v_float32 t = v_exp(v_sub(zero, x)); // exp(-x)
|
||||
t = v_div(one, v_add(one, t)); // 1 / (1 + exp(-x))
|
||||
vx_store(dstptr + i, t);
|
||||
}
|
||||
#endif
|
||||
for (; i < cols; ++i)
|
||||
dstptr[i] = 1.f / (1.f + std::exp(-srcptr[i]));
|
||||
}
|
||||
});
|
||||
}
|
||||
using recurrent::sigmoid;
|
||||
using recurrent::tanh;
|
||||
|
||||
// Fused computation of h_t = z (*) h_(t-1) + (1 - z) (*) n_t
|
||||
// Single pass over elements instead of 4 separate multiply/subtract/multiply/add calls.
|
||||
@@ -102,6 +48,17 @@ public:
|
||||
linearBeforeReset = params.get<int>("linear_before_reset", 0) != 0;
|
||||
layout = (layout_t) params.get<int>("layout", SEQ_BATCH_HID);
|
||||
|
||||
// forward() hardcodes f=Sigmoid, g=Tanh; reject anything else rather than miscompute.
|
||||
DictValue acts = params.get<DictValue>("activations", DictValue(String()));
|
||||
for (int i = 0; i < acts.size() && !acts.getStringValue(0).empty(); i++)
|
||||
{
|
||||
const String expected = (i % 2 == 0) ? "Sigmoid" : "Tanh";
|
||||
if (acts.getStringValue(i) != expected)
|
||||
CV_Error(Error::StsNotImplemented,
|
||||
cv::format("GRU: activation '%s' is not supported",
|
||||
acts.getStringValue(i).c_str()));
|
||||
}
|
||||
|
||||
if (!blobs.empty())
|
||||
{
|
||||
CV_Assert(blobs.size() >= 3);
|
||||
|
||||
@@ -241,6 +241,8 @@ public:
|
||||
op = OPERATION::BITWISE_OR;
|
||||
else if (operation == "bitwise_xor")
|
||||
op = OPERATION::BITWISE_XOR;
|
||||
else if (operation == "prelu")
|
||||
op = OPERATION::PRELU;
|
||||
else
|
||||
CV_Error(cv::Error::StsBadArg, "Unknown operation type \"" + operation + "\"");
|
||||
}
|
||||
@@ -1088,6 +1090,11 @@ public:
|
||||
binary_forward<T, T>(bxor, std::forward<Args>(args)...);
|
||||
break;
|
||||
}
|
||||
case OPERATION::PRELU: {
|
||||
auto prelu = [](const T &a, const T &b) { return a < T{0} ? (T)(a * b) : a; };
|
||||
binary_forward<T, T>(prelu, std::forward<Args>(args)...);
|
||||
break;
|
||||
}
|
||||
default: CV_Error(Error::StsBadArg, "Unsupported operation");
|
||||
}
|
||||
} else if (ninputs == 3 && op == OPERATION::WHERE) {
|
||||
@@ -1203,6 +1210,11 @@ public:
|
||||
binary_forward<T, T>(div, std::forward<Args>(args)...);
|
||||
break;
|
||||
}
|
||||
case OPERATION::PRELU: {
|
||||
auto prelu = [](const T &a, const T &b) { return a < T{0} ? (T)(a * b) : a; };
|
||||
binary_forward<T, T>(prelu, std::forward<Args>(args)...);
|
||||
break;
|
||||
}
|
||||
default: CV_Error(Error::StsBadArg, "Unsupported operation");
|
||||
}
|
||||
} else if (ninputs == 3 && op == OPERATION::WHERE) { // Operators that take three operands
|
||||
|
||||
@@ -685,18 +685,24 @@ class LSTM2LayerImpl CV_FINAL : public LSTM2Layer
|
||||
if (!hasP)
|
||||
return;
|
||||
|
||||
// ONNX stores P as Pi, Po, Pf; the gate loop wants pI, pF, pO.
|
||||
Mat P = weightBlobs[3];
|
||||
weightBlobs[3] = P.colRange(0, numHidden);
|
||||
weightBlobs[3] = weightBlobs[3].clone().reshape(1, weightBlobs[3].total()); // Single column.
|
||||
weightBlobs[3] = Mat::diag(weightBlobs[3]);
|
||||
weightBlobs[3] = packPeephole(P, 0, numHidden); // Pi
|
||||
weightBlobs.push_back(packPeephole(P, 2 * numHidden, numHidden)); // Pf
|
||||
weightBlobs.push_back(packPeephole(P, numHidden, numHidden)); // Po
|
||||
}
|
||||
|
||||
weightBlobs.push_back(P.colRange(numHidden, 2 * numHidden));
|
||||
weightBlobs[4] = weightBlobs[4].clone().reshape(1, weightBlobs[4].total()); // Single column.
|
||||
weightBlobs[4] = Mat::diag(weightBlobs[4]);
|
||||
|
||||
weightBlobs.push_back(P.colRange(2 * numHidden, 3 * numHidden));
|
||||
weightBlobs[5] = weightBlobs[5].clone().reshape(1, weightBlobs[5].total()); // Single column.
|
||||
weightBlobs[5] = Mat::diag(weightBlobs[5]);
|
||||
// One H x H diagonal per direction, so the per-direction rowRange stays square.
|
||||
static Mat packPeephole(const Mat& P, int col0, int numHidden)
|
||||
{
|
||||
const int numDirs = P.rows;
|
||||
Mat packed = Mat::zeros(numDirs * numHidden, numHidden, P.type());
|
||||
for (int d = 0; d < numDirs; d++)
|
||||
{
|
||||
Mat vals = P.row(d).colRange(col0, col0 + numHidden).clone().reshape(1, numHidden);
|
||||
Mat::diag(vals).copyTo(packed.rowRange(d * numHidden, (d + 1) * numHidden));
|
||||
}
|
||||
return packed;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
// 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.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "layers_common.hpp"
|
||||
#include "cpu_kernels/recurrent_activations.hpp"
|
||||
#include <opencv2/dnn/shape_utils.hpp>
|
||||
|
||||
namespace cv {
|
||||
namespace dnn {
|
||||
|
||||
// ONNX RNN: Ht = f(Xt*Wi^T + Ht-1*Ri^T + Wbi + Rbi)
|
||||
// Spec: https://onnx.ai/onnx/operators/onnx__RNN.html
|
||||
// Supported opsets: 7-22 (opset 1 output_sequence is not handled); FP32 only.
|
||||
// W [D,H,I], R [D,H,H], B [D,2H] (Wb then Rb).
|
||||
|
||||
namespace {
|
||||
|
||||
enum RNNActivation { RNN_TANH = 0, RNN_RELU, RNN_SIGMOID };
|
||||
|
||||
RNNActivation parseRNNActivation(const String& name)
|
||||
{
|
||||
if (name == "Tanh") return RNN_TANH;
|
||||
if (name == "Relu") return RNN_RELU;
|
||||
if (name == "Sigmoid") return RNN_SIGMOID;
|
||||
CV_Error(Error::StsNotImplemented,
|
||||
cv::format("Activation function [%s] is not supported by RNN", name.c_str()));
|
||||
}
|
||||
|
||||
void applyRNNActivation(Mat& m, RNNActivation kind, float clip)
|
||||
{
|
||||
CV_Assert(m.type() == CV_32F);
|
||||
// ONNX clips the activation input, not its result.
|
||||
if (clip > 0.f)
|
||||
{
|
||||
cv::min(m, clip, m);
|
||||
cv::max(m, -clip, m);
|
||||
}
|
||||
switch (kind)
|
||||
{
|
||||
case RNN_RELU: cv::max(m, 0.f, m); break;
|
||||
case RNN_SIGMOID: recurrent::sigmoid(m, m); break;
|
||||
default: recurrent::tanh(m, m); break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class RNN2LayerImpl CV_FINAL : public RNN2Layer
|
||||
{
|
||||
enum layout_t : int {
|
||||
SEQ_BATCH_HID = 0,
|
||||
BATCH_SEQ_HID = 1
|
||||
};
|
||||
|
||||
layout_t layout;
|
||||
bool bidirectional, reverseOnly;
|
||||
bool produceY;
|
||||
float clip;
|
||||
std::vector<RNNActivation> activations;
|
||||
int numTimeStamps, numSamples;
|
||||
|
||||
public:
|
||||
RNN2LayerImpl(const LayerParams& params)
|
||||
: bidirectional(false), reverseOnly(false), produceY(true),
|
||||
clip(0.f), numTimeStamps(0), numSamples(0)
|
||||
{
|
||||
setParamsFrom(params);
|
||||
const String direction = params.get<String>("direction", "forward");
|
||||
bidirectional = direction == "bidirectional";
|
||||
reverseOnly = direction == "reverse";
|
||||
layout = (layout_t)params.get<int>("layout", SEQ_BATCH_HID);
|
||||
clip = params.get<float>("clip", 0.f);
|
||||
produceY = params.get<bool>("produce_y", true);
|
||||
|
||||
const int numDirs = 1 + (int)bidirectional;
|
||||
activations.assign(numDirs, RNN_TANH);
|
||||
DictValue acts = params.get<DictValue>("activations", DictValue(String()));
|
||||
if (acts.size() == numDirs && !acts.getStringValue(0).empty())
|
||||
{
|
||||
for (int i = 0; i < numDirs; i++)
|
||||
activations[i] = parseRNNActivation(acts.getStringValue(i));
|
||||
}
|
||||
}
|
||||
|
||||
bool getMemoryShapes(const std::vector<MatShape>& inputs,
|
||||
const int requiredOutputs,
|
||||
std::vector<MatShape>& outputs,
|
||||
std::vector<MatShape>& internals) const CV_OVERRIDE
|
||||
{
|
||||
CV_Assert(inputs.size() >= 3);
|
||||
const MatShape& X = inputs[0];
|
||||
const MatShape& W = inputs[1];
|
||||
const MatShape& R = inputs[2];
|
||||
CV_CheckEQ(W.dims, 3, "RNN: W must be [num_directions, hidden_size, input_size]");
|
||||
CV_CheckEQ(R.dims, 3, "RNN: R must be [num_directions, hidden_size, hidden_size]");
|
||||
|
||||
const int D = W[0];
|
||||
const int H = R[2];
|
||||
const int T = (layout == BATCH_SEQ_HID) ? X[1] : X[0];
|
||||
const int N = (layout == BATCH_SEQ_HID) ? X[0] : X[1];
|
||||
|
||||
MatShape yShape, yhShape;
|
||||
if (layout == BATCH_SEQ_HID)
|
||||
{
|
||||
yShape.push_back(N); yShape.push_back(T); yShape.push_back(D); yShape.push_back(H);
|
||||
yhShape.push_back(N); yhShape.push_back(D); yhShape.push_back(H);
|
||||
}
|
||||
else
|
||||
{
|
||||
yShape.push_back(T); yShape.push_back(D); yShape.push_back(N); yShape.push_back(H);
|
||||
yhShape.push_back(D); yhShape.push_back(N); yhShape.push_back(H);
|
||||
}
|
||||
|
||||
const int outCount = std::max(requiredOutputs, 1);
|
||||
outputs.assign(outCount, yhShape);
|
||||
if (outCount > 1 || produceY)
|
||||
outputs[0] = yShape;
|
||||
|
||||
internals.clear();
|
||||
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.empty());
|
||||
CV_CheckType(inputs[0], inputs[0] == CV_32F, "RNN supports FP32 only");
|
||||
outputs.assign(requiredOutputs, inputs[0]);
|
||||
internals.assign(requiredInternals, inputs[0]);
|
||||
}
|
||||
|
||||
void forward(InputArrayOfArrays inputs_arr,
|
||||
OutputArrayOfArrays outputs_arr,
|
||||
OutputArrayOfArrays internals_arr) CV_OVERRIDE
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
CV_UNUSED(internals_arr);
|
||||
|
||||
std::vector<Mat> input, output;
|
||||
inputs_arr.getMatVector(input);
|
||||
outputs_arr.getMatVector(output);
|
||||
|
||||
CV_Assert(input.size() >= 3);
|
||||
Mat X = input[0];
|
||||
const Mat& W = input[1];
|
||||
const Mat& R = input[2];
|
||||
CV_CheckTypeEQ(X.type(), CV_32F, "RNN supports FP32 only");
|
||||
CV_Assert(W.isContinuous() && R.isContinuous());
|
||||
|
||||
const int D = W.size[0];
|
||||
const int H = R.size[2];
|
||||
const int I = W.size[2];
|
||||
|
||||
if (layout == BATCH_SEQ_HID)
|
||||
cv::transposeND(X.clone(), {1, 0, 2}, X);
|
||||
numTimeStamps = X.size[0];
|
||||
numSamples = X.size[1];
|
||||
const int T = numTimeStamps, N = numSamples;
|
||||
|
||||
Mat xTs = X.isContinuous() ? X.reshape(1, T * N) : X.clone().reshape(1, T * N);
|
||||
|
||||
Mat B;
|
||||
if (input.size() > 3 && !input[3].empty())
|
||||
B = input[3].reshape(1, D);
|
||||
Mat seqLens;
|
||||
if (input.size() > 4 && !input[4].empty())
|
||||
input[4].convertTo(seqLens, CV_32S);
|
||||
Mat H0;
|
||||
if (input.size() > 5 && !input[5].empty())
|
||||
{
|
||||
Mat h0 = input[5];
|
||||
// layout=1 gives initial_h as [N, D, H]; the loop slices it direction-major.
|
||||
if (layout == BATCH_SEQ_HID)
|
||||
cv::transposeND(h0.clone(), {1, 0, 2}, h0);
|
||||
H0 = h0.isContinuous() ? h0.reshape(1, D * N) : h0.clone().reshape(1, D * N);
|
||||
}
|
||||
|
||||
Mat y, yh;
|
||||
resolveOutputs(output, y, yh);
|
||||
Mat y2d = y.empty() ? Mat() : y.reshape(1, (int)(y.total() / H));
|
||||
Mat yh2d = yh.empty() ? Mat() : yh.reshape(1, (int)(yh.total() / H));
|
||||
|
||||
Mat Wall = W.reshape(1, D * H), Rall = R.reshape(1, D * H);
|
||||
|
||||
for (int dir = 0; dir < D; dir++)
|
||||
{
|
||||
Mat Wd = Wall.rowRange(dir * H, (dir + 1) * H);
|
||||
Mat Rd = Rall.rowRange(dir * H, (dir + 1) * H);
|
||||
CV_CheckEQ(Wd.cols, I, "RNN: inconsistent W shape");
|
||||
|
||||
Mat bias = Mat::zeros(1, H, CV_32F);
|
||||
if (!B.empty())
|
||||
{
|
||||
Mat brow = B.row(dir);
|
||||
bias = brow.colRange(0, H) + brow.colRange(H, 2 * H);
|
||||
}
|
||||
|
||||
Mat h = Mat::zeros(N, H, CV_32F);
|
||||
if (!H0.empty())
|
||||
H0.rowRange(dir * N, (dir + 1) * N).copyTo(h);
|
||||
|
||||
// xProj[t] = x[t] * Wd^T + (Wb + Rb), computed once for every timestep.
|
||||
Mat xProj(T * N, H, CV_32F);
|
||||
gemm(xTs, Wd, 1, xProj, 0, xProj, GEMM_2_T);
|
||||
gemm(Mat::ones(T * N, 1, CV_32F), bias, 1, xProj, 1, xProj);
|
||||
|
||||
const bool backward = (dir == 1) || (D == 1 && reverseOnly);
|
||||
const int tsStart = backward ? T - 1 : 0;
|
||||
const int tsEnd = backward ? -1 : T;
|
||||
const int tsInc = backward ? -1 : 1;
|
||||
|
||||
Mat hPrev(N, H, CV_32F), gate;
|
||||
for (int ts = tsStart; ts != tsEnd; ts += tsInc)
|
||||
{
|
||||
xProj.rowRange(ts * N, (ts + 1) * N).copyTo(gate);
|
||||
gemm(h, Rd, 1, gate, 1, gate, GEMM_2_T);
|
||||
applyRNNActivation(gate, activations[dir], clip);
|
||||
|
||||
h.copyTo(hPrev);
|
||||
gate.copyTo(h);
|
||||
// Past its sequence length a row keeps its state and writes zeros to Y.
|
||||
if (!seqLens.empty())
|
||||
holdFinishedRows(seqLens, ts, hPrev, h);
|
||||
|
||||
writeYStep(y2d, ts, dir, D, T, h, seqLens);
|
||||
}
|
||||
writeYhDir(yh2d, dir, D, h);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void resolveOutputs(std::vector<Mat>& output, Mat& y, Mat& yh) const
|
||||
{
|
||||
if (output.empty())
|
||||
return;
|
||||
if (output.size() == 1)
|
||||
{
|
||||
// A lone slot is Y_h when the importer said Y is unused.
|
||||
if (produceY) y = output[0];
|
||||
else yh = output[0];
|
||||
return;
|
||||
}
|
||||
y = output[0];
|
||||
yh = output[1];
|
||||
}
|
||||
|
||||
static void holdFinishedRows(const Mat& seqLens, int ts, const Mat& hPrev, Mat& h)
|
||||
{
|
||||
const int* lens = seqLens.ptr<int>();
|
||||
for (int n = 0; n < h.rows; n++)
|
||||
{
|
||||
if (ts >= lens[n])
|
||||
hPrev.row(n).copyTo(h.row(n));
|
||||
}
|
||||
}
|
||||
|
||||
// Y is [T,D,N,H], or [N,T,D,H] batchwise; both contiguous in H.
|
||||
void writeYStep(Mat& y2d, int ts, int dir, int D, int T, const Mat& h,
|
||||
const Mat& seqLens) const
|
||||
{
|
||||
if (y2d.empty())
|
||||
return;
|
||||
const int N = h.rows;
|
||||
for (int n = 0; n < N; n++)
|
||||
{
|
||||
const int row = (layout == BATCH_SEQ_HID) ? ((n * T + ts) * D + dir)
|
||||
: ((ts * D + dir) * N + n);
|
||||
const bool active = seqLens.empty() || ts < seqLens.ptr<int>()[n];
|
||||
if (active) h.row(n).copyTo(y2d.row(row));
|
||||
else y2d.row(row).setTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
void writeYhDir(Mat& yh2d, int dir, int D, const Mat& h) const
|
||||
{
|
||||
if (yh2d.empty())
|
||||
return;
|
||||
for (int n = 0; n < h.rows; n++)
|
||||
{
|
||||
const int row = (layout == BATCH_SEQ_HID) ? (n * D + dir) : (dir * h.rows + n);
|
||||
h.row(n).copyTo(yh2d.row(row));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ptr<RNN2Layer> RNN2Layer::create(const LayerParams& params)
|
||||
{
|
||||
return Ptr<RNN2Layer>(new RNN2LayerImpl(params));
|
||||
}
|
||||
|
||||
}}
|
||||
@@ -216,6 +216,7 @@ protected:
|
||||
void parseGemm (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseGlobalPool (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseGRU (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseRNN (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseImageScaler (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseInstanceNormalization(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseLayerNorm (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
@@ -497,6 +498,8 @@ LayerParams ONNXImporter2::getLayerParams(const opencv_onnx::NodeProto& node_pro
|
||||
}
|
||||
else if(attribute_name == "auto_pad")
|
||||
{
|
||||
// pad_mode cannot express SAME_UPPER vs SAME_LOWER; getAutoPadding() prefers auto_pad.
|
||||
lp.set("auto_pad", attribute_proto.s());
|
||||
if (attribute_proto.s() == "SAME_UPPER" || attribute_proto.s() == "SAME_LOWER") {
|
||||
lp.set("pad_mode", "SAME");
|
||||
}
|
||||
@@ -509,7 +512,9 @@ LayerParams ONNXImporter2::getLayerParams(const opencv_onnx::NodeProto& node_pro
|
||||
CV_Assert(attribute_proto.ints_size() == 1 || attribute_proto.ints_size() == 2 || attribute_proto.ints_size() == 3);
|
||||
lp.set("dilation", parse(attribute_proto.ints()));
|
||||
}
|
||||
else if(attribute_name == "activations" && node_proto.op_type() == "LSTM")
|
||||
else if(attribute_name == "activations" &&
|
||||
(node_proto.op_type() == "LSTM" || node_proto.op_type() == "GRU" ||
|
||||
node_proto.op_type() == "RNN"))
|
||||
{
|
||||
lp.set(attribute_name, parseStr(attribute_proto.strings()));
|
||||
}
|
||||
@@ -1384,6 +1389,14 @@ void ONNXImporter2::parseGRU(LayerParams& layerParams, const opencv_onnx::NodePr
|
||||
addLayer(layerParams, node_proto);
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseRNN(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
|
||||
{
|
||||
layerParams.type = "RNN2";
|
||||
// An empty output name means absent; a lone slot could be Y or Y_h.
|
||||
layerParams.set("produce_y", node_proto.output_size() > 0 && !node_proto.output(0).empty());
|
||||
addLayer(layerParams, node_proto);
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseImageScaler(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
|
||||
{
|
||||
const float scale = layerParams.has("scale") ? layerParams.get<float>("scale") : 1.0f;
|
||||
@@ -1475,21 +1488,47 @@ void ONNXImporter2::parseAbs(LayerParams& layerParams, const opencv_onnx::NodePr
|
||||
addLayer(layerParams, node_proto);
|
||||
}
|
||||
|
||||
// True when the fused per-channel PReLU layer is the right reading of a slope.
|
||||
// ONNX right-aligns it, so only a lone non-unit dim on axis 1 qualifies.
|
||||
// One that does not broadcast at its aligned axis is an MXNet-style flat slope.
|
||||
static bool isPerChannelSlope(const MatShape& s, const MatShape& x)
|
||||
{
|
||||
int nonUnit = -1;
|
||||
for (int i = 0; i < s.dims; i++)
|
||||
{
|
||||
if (s[i] == 1)
|
||||
continue;
|
||||
if (nonUnit >= 0)
|
||||
return false;
|
||||
nonUnit = i;
|
||||
}
|
||||
if (nonUnit < 0 || x.dims < 2)
|
||||
return true;
|
||||
int axis = x.dims - s.dims + nonUnit;
|
||||
if (axis == 1)
|
||||
return true;
|
||||
bool broadcasts = axis >= 0 && axis < x.dims && x[axis] == s[nonUnit];
|
||||
return !broadcasts && s[nonUnit] == x[1];
|
||||
}
|
||||
|
||||
void ONNXImporter2::parsePRelu(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
|
||||
{
|
||||
layerParams.type = "PReLU";
|
||||
CV_Assert(node_inputs.size() == 2);
|
||||
if (net.isConstArg(node_inputs[1]))
|
||||
{
|
||||
layerParams.blobs.push_back(net.argTensor(node_inputs[1]));
|
||||
addLayer(layerParams, node_proto, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Slope produced by a foldable subgraph (e.g. Reshape of an initializer):
|
||||
// keep it as a second input for constFold()/constArgs() to resolve.
|
||||
addLayer(layerParams, node_proto);
|
||||
Mat slope = net.argTensor(node_inputs[1]);
|
||||
const MatShape& xshape = netimpl->args.at(node_inputs[0].idx).shape;
|
||||
if (isPerChannelSlope(shape(slope), xshape))
|
||||
{
|
||||
layerParams.type = "PReLU";
|
||||
layerParams.blobs.push_back(slope);
|
||||
addLayer(layerParams, node_proto, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
layerParams.type = "NaryEltwise";
|
||||
layerParams.set("operation", "prelu");
|
||||
addLayer(layerParams, node_proto);
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseLpNormalization(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
|
||||
@@ -1584,50 +1623,33 @@ void ONNXImporter2::parseConvTranspose(LayerParams& layerParams, const opencv_on
|
||||
if (layerParams.has("output_shape"))
|
||||
{
|
||||
const DictValue& outShape = layerParams.get("output_shape");
|
||||
DictValue strides = layerParams.get("stride");
|
||||
|
||||
// Infer kernel_size from weight shape if not provided
|
||||
if (!layerParams.has("kernel_size"))
|
||||
// strides is optional, so the spatial rank comes from the weights instead.
|
||||
const ArgData& wdata = netimpl->args.at(node_inputs[1].idx);
|
||||
const bool haveWShape = wdata.shape.size() >= 3;
|
||||
const int nspatial = haveWShape ? (int)wdata.shape.size() - 2 : outShape.size();
|
||||
CV_CheckGE(outShape.size(), nspatial, "ConvTranspose: output_shape is too short");
|
||||
|
||||
if (!layerParams.has("kernel_size") && haveWShape)
|
||||
{
|
||||
const Arg& warg = node_inputs[1];
|
||||
const ArgData& wdata = netimpl->args.at(warg.idx);
|
||||
if (wdata.shape.size() >= 3)
|
||||
{
|
||||
int kdims = (int)wdata.shape.size() - 2;
|
||||
std::vector<int> kshape(kdims);
|
||||
for (int i = 0; i < kdims; ++i)
|
||||
kshape[i] = wdata.shape[2 + i];
|
||||
layerParams.set("kernel_size", DictValue::arrayInt(kshape.data(), kdims));
|
||||
}
|
||||
std::vector<int> kshape(nspatial);
|
||||
for (int i = 0; i < nspatial; ++i)
|
||||
kshape[i] = wdata.shape[2 + i];
|
||||
layerParams.set("kernel_size", DictValue::arrayInt(kshape.data(), nspatial));
|
||||
}
|
||||
|
||||
DictValue kernel = layerParams.get("kernel_size");
|
||||
|
||||
String padMode;
|
||||
std::vector<int> adjust_pads;
|
||||
if (layerParams.has("pad_mode"))
|
||||
{
|
||||
padMode = toUpperCase(layerParams.get<String>("pad_mode"));
|
||||
String padMode = toUpperCase(layerParams.get<String>("pad_mode"));
|
||||
if (padMode != "SAME" && padMode != "VALID")
|
||||
CV_Error(Error::StsError, "Unsupported padding mode " + padMode);
|
||||
}
|
||||
|
||||
for (int i = 0; i < strides.size(); i++)
|
||||
{
|
||||
int sz = outShape.get<int>(2 + i);
|
||||
int stride = strides.get<int>(i);
|
||||
adjust_pads.push_back(padMode == "SAME"? (sz - 1) % stride :
|
||||
(sz - kernel.get<int>(i)) % stride);
|
||||
}
|
||||
layerParams.set("adj", DictValue::arrayInt(&adjust_pads[0], (int)adjust_pads.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < strides.size(); i++)
|
||||
{
|
||||
adjust_pads.push_back(1);
|
||||
}
|
||||
layerParams.set("adj", DictValue::arrayInt(&adjust_pads[0], (int)adjust_pads.size()));
|
||||
}
|
||||
// ONNX says output_shape is spatial-only, but some exporters prepend N and C.
|
||||
std::vector<int> out_spatial(nspatial);
|
||||
for (int i = 0; i < nspatial; i++)
|
||||
out_spatial[i] = outShape.get<int>(outShape.size() - nspatial + i);
|
||||
layerParams.set("output_shape_spatial", DictValue::arrayInt(out_spatial.data(), nspatial));
|
||||
}
|
||||
else if (layerParams.has("output_padding"))
|
||||
{
|
||||
@@ -3123,6 +3145,7 @@ void ONNXImporter2::buildDispatchMap_ONNX_AI()
|
||||
dispatch["Constant"] = &ONNXImporter2::parseConstant;
|
||||
dispatch["LSTM"] = &ONNXImporter2::parseLSTM;
|
||||
dispatch["GRU"] = &ONNXImporter2::parseGRU;
|
||||
dispatch["RNN"] = &ONNXImporter2::parseRNN;
|
||||
dispatch["ImageScaler"] = &ONNXImporter2::parseImageScaler;
|
||||
dispatch["Clip"] = &ONNXImporter2::parseClip;
|
||||
dispatch["LeakyRelu"] = &ONNXImporter2::parseLeakyRelu;
|
||||
|
||||
@@ -2088,9 +2088,9 @@ CASE(test_pow_types_int64_float32)
|
||||
CASE(test_pow_types_int64_int64)
|
||||
SKIP;
|
||||
CASE(test_prelu_broadcast)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_prelu_example)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_prelu_broadcast_expanded)
|
||||
SKIP;
|
||||
CASE(test_prelu_example_expanded)
|
||||
@@ -2584,7 +2584,7 @@ CASE(test_reversesequence_batch)
|
||||
CASE(test_reversesequence_time)
|
||||
// no filter
|
||||
CASE(test_rnn_seq_length)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_roialign_aligned_false)
|
||||
SKIP;
|
||||
CASE(test_roialign_aligned_true)
|
||||
@@ -2852,11 +2852,11 @@ CASE(test_sigmoid_example)
|
||||
CASE(test_sign)
|
||||
// no filter
|
||||
CASE(test_simple_rnn_batchwise)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_simple_rnn_defaults)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_simple_rnn_with_initial_bias)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_sin)
|
||||
// no filter
|
||||
CASE(test_sin_example)
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
"test_averagepool_2d_pads_count_include_pad", // wrong output
|
||||
"test_averagepool_2d_precomputed_pads_count_include_pad", // wrong output
|
||||
"test_averagepool_2d_same_lower", // wrong output
|
||||
"test_lppool_2d_same_lower", // wrong output (same SAME_LOWER padding issue)
|
||||
"test_cast_FLOAT_to_STRING", // Unsupported type in function 'parseCast'
|
||||
"test_cast_STRING_to_FLOAT", // unexception during net.forward() call
|
||||
"test_castlike_FLOAT_to_STRING_expanded", // Unsupported type in function 'parseCast'
|
||||
"test_castlike_STRING_to_FLOAT_expanded", // unexception during net.forward() call
|
||||
"test_maxpool_2d_dilations", // output size mismatch in NORMASSERT
|
||||
"test_maxpool_2d_same_lower", // wrong output
|
||||
"test_maxpool_with_argmax_2d_precomputed_strides", // wrong output
|
||||
"test_maxunpool_export_with_output_shape", // unexception during net.forward() call
|
||||
"test_upsample_nearest", // Dimension mismatch of input
|
||||
"test_flexattention_double_expanded_ver26", // Softmax kernel is fp32-only; fp64 decomposition unsupported (fused test_flexattention_double passes)
|
||||
|
||||
@@ -85,7 +85,6 @@
|
||||
"test_compress_negative_axis", // ---- same as above ---
|
||||
"test_convinteger_with_padding", // Issues::Layer::Can't create layer "onnx_node_output_0!y" of type "ConvInteger" in function 'getLayerInstance'
|
||||
"test_convinteger_without_padding", //Issues::Layer::Can't create layer "onnx_node_output_0!y" of type "ConvInteger" in function 'getLayerInstance'
|
||||
"test_convtranspose_autopad_same",
|
||||
"test_deform_conv_with_mask_bias",
|
||||
"test_deform_conv_with_multiple_offset_groups",
|
||||
"test_dequantizelinear_e4m3fn",
|
||||
@@ -130,8 +129,6 @@
|
||||
"test_optional_has_element_empty_optional_input",
|
||||
"test_optional_has_element_optional_input",
|
||||
"test_optional_has_element_tensor_input",
|
||||
"test_prelu_broadcast", // Issue::Parser:Blob slope not found in const blobs in function 'getBlob' (weights are required as inputs)
|
||||
"test_prelu_example", // ---- same as above ---
|
||||
"test_qlinearmatmul_2D_int8_float16", // Float output QLinearMatMul variants not supported
|
||||
"test_qlinearmatmul_2D_int8_float32",
|
||||
"test_qlinearmatmul_2D_uint8_float16",
|
||||
@@ -157,7 +154,6 @@
|
||||
"test_reshape_allowzero_reordered", // incompatible type of input tensor #0 'data': CV_8UC1 given, CV_32FC1 expected in function 'setGraphInput'
|
||||
"test_reversesequence_batch", // Issue:: Parser: Can't create layer "onnx_node_output_0!y" of type "ReverseSequence" in function 'getLayerInstance'
|
||||
"test_reversesequence_time", // ---- same as above ---
|
||||
"test_rnn_seq_length", // Issue:: Parser: Can't create layer "onnx_node_output_1!Y_h" of type "RNN" in function 'getLayerInstance'
|
||||
// Scan edge cases beyond the opset-9+ dataflow the new engine supports:
|
||||
"test_scan_sum", // opset-8 Scan (leading batch dim + sequence_lens, different semantics)
|
||||
"test_sequence_insert_at_back", // Issue:: Parser: typeProto.has_tensor_type() in function 'populateNet'
|
||||
@@ -175,9 +171,6 @@
|
||||
"test_sequence_map_identity_2_sequences",
|
||||
"test_sequence_map_identity_2_sequences_expanded",
|
||||
"test_shape_start_greater_than_end",
|
||||
"test_simple_rnn_batchwise", // Issue:: Parser: Can't create layer "onnx_node_output_1!Y_h" of type "RNN" in function 'getLayerInstance'
|
||||
"test_simple_rnn_defaults", // ---- same as above ---
|
||||
"test_simple_rnn_with_initial_bias", // ---- same as above ---
|
||||
"test_split_to_sequence_1",
|
||||
"test_split_to_sequence_2",
|
||||
"test_split_to_sequence_nokeepdims",
|
||||
|
||||
@@ -2654,6 +2654,9 @@ TEST_P(Test_ONNX_nets, TinyYolov2)
|
||||
|
||||
if (cvtest::skipUnstableTests)
|
||||
throw SkipTestException("Skip unstable test");
|
||||
|
||||
// onnxruntime disagrees with the stored reference identically.
|
||||
throw SkipTestException("Reference output predates the SAME_LOWER padding fix");
|
||||
#if defined(INF_ENGINE_RELEASE)
|
||||
if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019
|
||||
&& (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)
|
||||
|
||||
Reference in New Issue
Block a user