1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 15:53:03 +04:00

Merge pull request #28839 from Prasadayus:Block-Layout-Support

Extend several operations to support block layout #28839

Requires opencv_extra: https://github.com/opencv/opencv_extra/pull/1347

After this PR, we observed the following improvements in the listed models:


Model | Before (ENGINE_NEW) | After (ENGINE_NEW) | ENGINE_ORT | % Improvement (Before v/s After)
-- | -- | -- | -- | --
Face_Paint | 514.68ms | 384.40ms | 394.38ms | 25.31%
BlazeFace | 1.03ms | 0.83ms | 0.66ms | 19.42%

**Device details:**
 Model name: Intel(R) Core(TM) i9-14900KS, x86_64, 32 Cores, Ubuntu 22.04.5 LTS

Operations covered:

- [x] Pad
- [x] Resize
- [x] Reshape
- [x] Shape
- [x] InstanceNorm
- [x] GroupNorm

### 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:
Prasad Ayush Kumar
2026-05-06 18:50:45 +05:30
committed by GitHub
parent 2760c0c08a
commit e519173241
10 changed files with 1432 additions and 57 deletions
+23
View File
@@ -449,6 +449,29 @@ PERF_TEST_P_(DNNTestNetwork, BEiT_Base_Patch16_224)
processNet("dnn/beit_base_patch16_224_Opset16.onnx", "", cv::Size(224, 224));
}
PERF_TEST_P_(DNNTestNetwork, BlazeFace)
{
Mat input(cv::Size(128, 128), CV_32FC3);
randu(input, 0.0f, 1.0f);
input = blobFromImage(input, 1.0 / 255.0, Size(128, 128));
const int oneDim[] = {1};
Mat conf(1, oneDim, CV_32F); conf.ptr<float>()[0] = 0.20f;
Mat iou(1, oneDim, CV_32F); iou.ptr<float>()[0] = 0.30f;
Mat maxDet(1, oneDim, CV_64S); maxDet.ptr<int64_t>()[0] = 25;
processNet("dnn/onnx/models/blazeface.onnx", "",
{std::make_tuple(input, "image"),
std::make_tuple(conf, "conf_threshold"),
std::make_tuple(iou, "iou_threshold"),
std::make_tuple(maxDet, "max_detections")});
}
PERF_TEST_P_(DNNTestNetwork, FacePaint)
{
processNet("dnn/onnx/models/face_paint_512_v2_0.onnx", "", cv::Size(512, 512));
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, DNNTestNetwork, dnnBackendsAndTargets());
} // namespace
+102
View File
@@ -130,6 +130,108 @@ struct ModelFusionBasic
}
}
}
// fuse Reshape + InstanceNorm(scale=ones,bias=zeros) + Reshape + Mul + Add
if (elemwise && elemwise->op == NaryEltwiseLayer::OPERATION::ADD &&
ninputs == 2) {
int mul_input_idx = -1;
Arg add_bias_arg;
for (int k = 0; k < 2; k++) {
int pidx = producer_of.at(inputs[k].idx);
NaryEltwiseLayer* mul = getLayer<NaryEltwiseLayer>(newprog, pidx);
if (mul && mul->op == NaryEltwiseLayer::OPERATION::PROD) {
mul_input_idx = k;
add_bias_arg = inputs[1 - k];
break;
}
}
if (mul_input_idx >= 0 && netimpl->isConstArg(add_bias_arg)) {
Arg mul_out = inputs[mul_input_idx];
int mul_idx = producer_of.at(mul_out.idx);
NaryEltwiseLayer* mul = getLayer<NaryEltwiseLayer>(newprog, mul_idx);
if (mul && mul->inputs.size() == 2 &&
usecounts.at(mul_out.idx) == 1) {
int reshape2_input_idx = -1;
Arg mul_scale_arg;
for (int k = 0; k < 2; k++) {
int pidx = producer_of.at(mul->inputs[k].idx);
if (getLayer<Reshape2Layer>(newprog, pidx)) {
reshape2_input_idx = k;
mul_scale_arg = mul->inputs[1 - k];
break;
}
}
if (reshape2_input_idx >= 0 && netimpl->isConstArg(mul_scale_arg)) {
Arg reshape2_out = mul->inputs[reshape2_input_idx];
int reshape2_idx = producer_of.at(reshape2_out.idx);
Reshape2Layer* reshape2_lyr = getLayer<Reshape2Layer>(newprog, reshape2_idx);
if (reshape2_lyr && reshape2_lyr->inputs.size() >= 1 &&
usecounts.at(reshape2_out.idx) == 1) {
Arg reshape2_inp = reshape2_lyr->inputs[0];
int instnorm_idx = producer_of.at(reshape2_inp.idx);
InstanceNormLayer* instnorm = getLayer<InstanceNormLayer>(newprog, instnorm_idx);
if (instnorm && instnorm->inputs.size() == 3 &&
usecounts.at(reshape2_inp.idx) == 1) {
Arg instnorm_inp = instnorm->inputs[0];
int reshape1_idx = producer_of.at(instnorm_inp.idx);
Reshape2Layer* reshape1_lyr = getLayer<Reshape2Layer>(newprog, reshape1_idx);
if (reshape1_lyr && reshape1_lyr->outputs.size() == 1 &&
usecounts.at(instnorm_inp.idx) == 1) {
Mat in_scale = netimpl->isConstArg(instnorm->inputs[1]) ?
netimpl->argTensor(instnorm->inputs[1]) : Mat();
Mat in_bias = netimpl->isConstArg(instnorm->inputs[2]) ?
netimpl->argTensor(instnorm->inputs[2]) : Mat();
bool valid = !in_scale.empty() && !in_bias.empty() &&
in_scale.type() == CV_32F && in_bias.type() == CV_32F;
if (valid) {
const float* sp = in_scale.ptr<float>();
const float* bp = in_bias.ptr<float>();
bool all_ones = true, all_zeros = true;
for (size_t k = 0; k < in_scale.total() && all_ones; k++)
all_ones = (std::abs(sp[k] - 1.f) < 1e-6f);
for (size_t k = 0; k < in_bias.total() && all_zeros; k++)
all_zeros = (std::abs(bp[k]) < 1e-6f);
if (all_ones && all_zeros) {
Arg orig_inp = reshape1_lyr->inputs[0];
Mat mul_scale_mat = netimpl->argTensor(mul_scale_arg);
if (in_scale.total() == mul_scale_mat.total()) {
// Channel dim preserved — fuse into InstanceNorm
instnorm->inputs[0] = orig_inp;
instnorm->inputs[1] = mul_scale_arg;
instnorm->inputs[2] = add_bias_arg;
} else {
// Channel dim changed (e.g. [1,C,H,W]->[1,1,C*H*W]):
// original is global norm + per-channel affine.
// Replace with GroupNorm(num_groups=reshaped_C).
int num_groups = (int)in_scale.total();
LayerParams gnparams;
gnparams.name = instnorm->name;
gnparams.type = "GroupNormalization";
gnparams.set("epsilon", instnorm->epsilon);
gnparams.set("num_groups", num_groups);
Ptr<Layer> gnlayer = GroupNormLayer::create(gnparams);
gnlayer->netimpl = netimpl;
gnlayer->inputs = {orig_inp, mul_scale_arg, add_bias_arg};
newprog[instnorm_idx] = gnlayer;
}
fused_layer_idx = instnorm_idx;
removed_args.push_back(instnorm_inp);
removed_args.push_back(reshape2_inp);
removed_args.push_back(reshape2_out);
removed_args.push_back(mul_out);
newprog[reshape1_idx] = Ptr<Layer>();
newprog[reshape2_idx] = Ptr<Layer>();
newprog[mul_idx] = Ptr<Layer>();
break;
}
}
}
}
}
}
}
}
}
break;
}
+280 -21
View File
@@ -4,6 +4,7 @@
#include "../../precomp.hpp"
#include "fast_norm.hpp"
#include <opencv2/core/hal/intrin.hpp>
namespace cv { namespace dnn {
@@ -162,14 +163,152 @@ void fastNorm(const Mat &input, const Mat &scale, const Mat &bias, Mat &output,
void fastNormChannel(const Mat &input, const Mat &scale, const Mat &bias, Mat &output, float epsilon) {
const auto input_shape = shape(input);
size_t N = input_shape[0], C = input_shape[1];
CV_CheckEQ(scale.total(), bias.total(), "fastNormChannel: scale and bias should have the same shape");
size_t C = input_shape.layout == DATA_LAYOUT_BLOCK ? (size_t)input_shape.C : input_shape[1];
CV_CheckEQ(scale.total(), C, "fastNormChannel: scale should be a 1d tensor and match the channel of input");
CV_CheckEQ(bias.total(), C, "fastNormChannel: bias should be a 1d tensor and match the channel of input");
if (input_shape.layout == DATA_LAYOUT_BLOCK) {
CV_Assert(input.dims == 5 && output.dims == 5);
CV_Assert(input.type() == CV_32F && output.type() == CV_32F);
CV_Assert(input.isContinuous() && output.isContinuous());
const int N = input.size[0];
const int C1 = input.size[1];
const int H = input.size[2];
const int W = input.size[3];
const int C0 = input.size[4];
const int Ci = (int)C;
const float* scale_data = scale.ptr<float>();
const float* bias_data = bias.ptr<float>();
const size_t inStep0 = input.step.p[0] / sizeof(float);
const size_t inStep1 = input.step.p[1] / sizeof(float);
const size_t inStep2 = input.step.p[2] / sizeof(float);
const size_t inStep3 = input.step.p[3] / sizeof(float);
const size_t outStep0 = output.step.p[0] / sizeof(float);
const size_t outStep1 = output.step.p[1] / sizeof(float);
const size_t outStep2 = output.step.p[2] / sizeof(float);
const size_t outStep3 = output.step.p[3] / sizeof(float);
const size_t norm_size = (size_t)H * (size_t)W;
const float inv_norm_size = 1.f / (float)norm_size;
#if CV_SIMD
const int VEC_SZ = VTraits<v_float32>::vlanes();
#endif
parallel_for_(Range(0, N * C1), [&](const Range& r) {
const float* inptr0 = (const float*)input.data;
float* outptr0 = (float*)output.data;
AutoBuffer<float> buf(C0 * 4);
float* sum = buf.data();
float* sqsum = sum + C0;
float* alpha = sqsum + C0;
float* beta = alpha + C0;
for (int i = r.start; i < r.end; ++i) {
int n = i / C1;
int c1 = i - n * C1;
int cbase = c1 * C0;
int validC0 = std::min(C0, std::max(0, Ci - cbase));
const float* inbase = inptr0 + n * inStep0 + c1 * inStep1;
float* outbase = outptr0 + n * outStep0 + c1 * outStep1;
int c0 = 0;
#if CV_SIMD
for (; c0 <= validC0 - VEC_SZ; c0 += VEC_SZ) {
v_float32 vsum = vx_setzero_f32();
v_float32 vsqsum = vx_setzero_f32();
for (int h = 0; h < H; ++h) {
const float* inrow = inbase + h * inStep2;
for (int w = 0; w < W; ++w) {
v_float32 v = vx_load(inrow + w * inStep3 + c0);
vsum = v_add(vsum, v);
vsqsum = v_fma(v, v, vsqsum);
}
}
vx_store(sum + c0, vsum);
vx_store(sqsum + c0, vsqsum);
}
#endif
for (; c0 < validC0; ++c0) {
float s = 0.f, sq = 0.f;
for (int h = 0; h < H; ++h) {
const float* inrow = inbase + h * inStep2;
for (int w = 0; w < W; ++w) {
float v = inrow[w * inStep3 + c0];
s += v;
sq += v * v;
}
}
sum[c0] = s;
sqsum[c0] = sq;
}
for (int c = 0; c < validC0; ++c) {
float mean = sum[c] * inv_norm_size;
float var = std::max(0.f, sqsum[c] * inv_norm_size - mean * mean);
float inv_stdev = 1.f / std::sqrt(var + epsilon);
alpha[c] = scale_data[cbase + c] * inv_stdev;
beta[c] = bias_data[cbase + c] - alpha[c] * mean;
}
c0 = 0;
#if CV_SIMD
for (; c0 <= validC0 - VEC_SZ; c0 += VEC_SZ) {
v_float32 va = vx_load(alpha + c0);
v_float32 vb = vx_load(beta + c0);
for (int h = 0; h < H; ++h) {
const float* inrow = inbase + h * inStep2;
float* outrow = outbase + h * outStep2;
for (int w = 0; w < W; ++w) {
v_float32 vin = vx_load(inrow + w * inStep3 + c0);
vx_store(outrow + w * outStep3 + c0, v_fma(vin, va, vb));
}
}
}
#endif
for (; c0 < validC0; ++c0) {
float a = alpha[c0], b = beta[c0];
for (int h = 0; h < H; ++h) {
const float* inrow = inbase + h * inStep2;
float* outrow = outbase + h * outStep2;
for (int w = 0; w < W; ++w)
outrow[w * outStep3 + c0] = inrow[w * inStep3 + c0] * a + b;
}
}
int c0_pad = validC0;
#if CV_SIMD
for (; c0_pad <= C0 - VEC_SZ; c0_pad += VEC_SZ) {
v_float32 vzero = vx_setzero_f32();
for (int h = 0; h < H; ++h) {
float* outrow = outbase + h * outStep2;
for (int w = 0; w < W; ++w)
vx_store(outrow + w * outStep3 + c0_pad, vzero);
}
}
#endif
for (; c0_pad < C0; ++c0_pad)
for (int h = 0; h < H; ++h) {
float* outrow = outbase + h * outStep2;
for (int w = 0; w < W; ++w)
outrow[w * outStep3 + c0_pad] = 0.f;
}
}
});
return;
}
size_t N = input_shape[0];
CV_CheckGE(input.dims, 3, "fastNormChannel: input dimension >= 3");
size_t loops = N * C,
norm_size = static_cast<size_t>(total(input_shape, 2));
float inv_norm_size = 1.0 / norm_size;
auto fn = [&](const Range &r) {
const auto *input_data = input.ptr<const float>();
@@ -180,16 +319,16 @@ void fastNormChannel(const Mat &input, const Mat &scale, const Mat &bias, Mat &o
const auto *x = input_data + norm_size * i;
auto *y = output_data + norm_size * i;
float mean = 0.f, mean_square = 0.f;
for (int j = 0; j < norm_size; j++) {
float v = x[j];
mean += v;
mean_square += v * v;
double dmean = 0., dmean_sq = 0.;
for (size_t j = 0; j < norm_size; j++) {
double v = (double)x[j];
dmean += v;
dmean_sq += v * v;
}
mean *= inv_norm_size;
mean_square = std::sqrt(std::max(0.f, mean_square * inv_norm_size - mean * mean) + epsilon);
float inv_stdev = 1.f / mean_square;
float mean = (float)(dmean / norm_size);
float var = (float)std::max(0., dmean_sq / norm_size - (double)mean * (double)mean);
float inv_stdev = 1.f / std::sqrt(var + epsilon);
size_t c = i % C;
float s = scale_data[c] * inv_stdev, b = bias_data[c];
@@ -204,16 +343,136 @@ void fastNormChannel(const Mat &input, const Mat &scale, const Mat &bias, Mat &o
void fastNormGroup(const Mat &input, const Mat &scale, const Mat &bias, Mat &output, float epsilon, size_t num_groups) {
const auto input_shape = shape(input);
size_t N = input_shape[0], C = input_shape[1];
size_t C = input_shape.layout == DATA_LAYOUT_BLOCK ? (size_t)input_shape.C : input_shape[1];
CV_CheckEQ(scale.total(), bias.total(), "fastNormGroup: scale and bias should have the same shape");
CV_CheckEQ(scale.total(), C, "fastNormGroup: scale should be a 1d tensor and match the channel of input");
// Block layout path: [N, C1, H, W, C0]
if (input_shape.layout == DATA_LAYOUT_BLOCK) {
CV_Assert(input.dims == 5 && output.dims == 5);
CV_Assert(input.type() == CV_32F && output.type() == CV_32F);
CV_Assert(input.isContinuous() && output.isContinuous());
const int N = input.size[0];
const int H = input.size[2];
const int W = input.size[3];
const int C0 = input.size[4];
const int Ci = (int)C;
const float* scale_data = scale.ptr<float>();
const float* bias_data = bias.ptr<float>();
const size_t inStep0 = input.step.p[0] / sizeof(float);
const size_t inStep1 = input.step.p[1] / sizeof(float);
const size_t inStep2 = input.step.p[2] / sizeof(float);
const size_t inStep3 = input.step.p[3] / sizeof(float);
const size_t outStep0 = output.step.p[0] / sizeof(float);
const size_t outStep1 = output.step.p[1] / sizeof(float);
const size_t outStep2 = output.step.p[2] / sizeof(float);
const size_t outStep3 = output.step.p[3] / sizeof(float);
const int channels_per_group = Ci / (int)num_groups;
const size_t norm_size = (size_t)channels_per_group * (size_t)H * (size_t)W;
const double inv_norm_size = 1.0 / (double)norm_size;
#if CV_SIMD
const int VEC_SZ = VTraits<v_float32>::vlanes();
#endif
parallel_for_(Range(0, N * (int)num_groups), [&](const Range& r) {
const float* inptr = (const float*)input.data;
float* outptr = (float*)output.data;
AutoBuffer<float> buf(C0 * 2);
float* alpha = buf.data();
float* beta = alpha + C0;
for (int i = r.start; i < r.end; ++i) {
int n = i / (int)num_groups;
int g = i - n * (int)num_groups;
int c_start = g * channels_per_group;
int c_end = c_start + channels_per_group;
double group_sum = 0., group_sqsum = 0.;
for (int c = c_start; c < c_end; c++) {
int c1 = c / C0;
int c0 = c % C0;
const float* inbase = inptr + n * inStep0 + c1 * inStep1;
for (int h = 0; h < H; ++h) {
const float* inrow = inbase + h * inStep2;
for (int w = 0; w < W; ++w) {
double v = (double)inrow[w * inStep3 + c0];
group_sum += v;
group_sqsum += v * v;
}
}
}
float mean = (float)(group_sum * inv_norm_size);
float var = std::max(0.f, (float)(group_sqsum * inv_norm_size - (double)mean * (double)mean));
float inv_stdev = 1.f / std::sqrt(var + epsilon);
for (int c1_start = c_start / C0, c1_end_idx = (c_end - 1) / C0 + 1,
c1 = c1_start; c1 < c1_end_idx; ++c1) {
int cbase = c1 * C0;
int c0_lo = std::max(0, c_start - cbase);
int c0_hi = std::min(C0, c_end - cbase);
int validC0 = std::min(C0, std::max(0, Ci - cbase));
for (int c0 = c0_lo; c0 < c0_hi; ++c0) {
alpha[c0] = scale_data[cbase + c0] * inv_stdev;
beta[c0] = bias_data[cbase + c0] - alpha[c0] * mean;
}
const float* inbase = inptr + n * inStep0 + c1 * inStep1;
float* outbase = outptr + n * outStep0 + c1 * outStep1;
int c0 = c0_lo;
#if CV_SIMD
for (; c0 <= c0_hi - VEC_SZ; c0 += VEC_SZ) {
v_float32 va = vx_load(alpha + c0);
v_float32 vb = vx_load(beta + c0);
for (int h = 0; h < H; ++h) {
const float* inrow = inbase + h * inStep2;
float* outrow = outbase + h * outStep2;
for (int w = 0; w < W; ++w) {
v_float32 vin = vx_load(inrow + w * inStep3 + c0);
vx_store(outrow + w * outStep3 + c0, v_fma(vin, va, vb));
}
}
}
#endif
for (; c0 < c0_hi; ++c0) {
float a = alpha[c0], b = beta[c0];
for (int h = 0; h < H; ++h) {
const float* inrow = inbase + h * inStep2;
float* outrow = outbase + h * outStep2;
for (int w = 0; w < W; ++w)
outrow[w * outStep3 + c0] = inrow[w * inStep3 + c0] * a + b;
}
}
for (int c0_pad = std::max(c0_hi, validC0); c0_pad < C0; ++c0_pad)
for (int h = 0; h < H; ++h) {
float* outrow = outbase + h * outStep2;
for (int w = 0; w < W; ++w)
outrow[w * outStep3 + c0_pad] = 0.f;
}
}
}
});
return;
}
// NCHW path
size_t N = input_shape[0];
CV_CheckGE(input.dims, 3, "fastNormGroup: input dimension >= 3");
size_t channels_per_group = C / num_groups;
size_t loops = N * num_groups;
size_t norm_size = static_cast<size_t>(total(input_shape, 2) * channels_per_group);
size_t step = norm_size / channels_per_group;
float inv_norm_size = 1.0 / norm_size;
auto fn = [&](const Range &r) {
const auto *input_data = input.ptr<const float>();
@@ -225,16 +484,16 @@ void fastNormGroup(const Mat &input, const Mat &scale, const Mat &bias, Mat &out
const auto *x = input_data + norm_size * i;
auto *y = output_data + norm_size * i;
float mean = 0.f, mean_square = 0.f;
for (int j = 0; j < norm_size; j++) {
float v = x[j];
mean += v;
mean_square += v * v;
double dmean = 0., dmean_sq = 0.;
for (size_t j = 0; j < norm_size; j++) {
double v = (double)x[j];
dmean += v;
dmean_sq += v * v;
}
mean *= inv_norm_size;
mean_square = std::sqrt(std::max(0.f, mean_square * inv_norm_size - mean * mean) + epsilon);
float inv_stdev = 1.f / mean_square;
float mean = (float)(dmean / norm_size);
float var = (float)std::max(0., dmean_sq / norm_size - (double)mean * (double)mean);
float inv_stdev = 1.f / std::sqrt(var + epsilon);
size_t group_idx = i % num_groups * channels_per_group;
for (size_t j = 0; j < norm_size; j++) {
+12 -1
View File
@@ -5,6 +5,7 @@
#include "../precomp.hpp"
#include <opencv2/dnn/shape_utils.hpp>
#include "./cpu_kernels/fast_norm.hpp"
#include "../net_impl.hpp"
// CUDA backend
#include "../op_cuda.hpp"
@@ -46,7 +47,7 @@ public:
const auto &bias = inputs[2];
CV_CheckGE(input.size(), static_cast<size_t>(3), "DNN/GroupNorm: input dimension >= 3 is required");
int C = input[1];
int C = input.layout == DATA_LAYOUT_BLOCK ? input.C : input[1];
int scale_dim = std::accumulate(scale.begin(), scale.end(), 1, std::multiplies<int>());
CV_CheckEQ(scale_dim, C, "DNN/InstanceNorm: scale must be a 1d tensor and match the channel of input");
int bias_dim = std::accumulate(bias.begin(), bias.end(), 1, std::multiplies<int>());
@@ -56,6 +57,16 @@ public:
return false;
}
int getLayouts(const std::vector<DataLayout>& actualInputs,
std::vector<DataLayout>& desiredInputs,
const int requiredOutputs,
std::vector<DataLayout>& outputs) const CV_OVERRIDE {
CV_Assert(!actualInputs.empty());
desiredInputs = actualInputs;
outputs.assign(requiredOutputs, actualInputs[0]);
return 0;
}
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE {
CV_TRACE_FUNCTION();
CV_TRACE_ARG_VALUE(name, "name", name.c_str());
+14 -1
View File
@@ -5,6 +5,7 @@
#include "../precomp.hpp"
#include <opencv2/dnn/shape_utils.hpp>
#include "./cpu_kernels/fast_norm.hpp"
#include <opencv2/core/hal/intrin.hpp>
// CANN backend
#include "../op_cann.hpp"
@@ -55,8 +56,10 @@ public:
const auto &scale = inputs[1];
const auto &bias = inputs[2];
CV_CheckGE(input.size(), static_cast<size_t>(3), "DNN/InstanceNorm: input dimension >= 3 is required");
if (input.layout == DATA_LAYOUT_BLOCK)
CV_CheckEQ(input.dims, 5, "DNN/InstanceNorm: only 5D block layout is supported");
int C = input[1];
int C = input.layout == DATA_LAYOUT_BLOCK ? input.C : input[1];
int scale_dim = std::accumulate(scale.begin(), scale.end(), 1, std::multiplies<int>());
CV_CheckEQ(scale_dim, C, "DNN/InstanceNorm: scale must be a 1d tensor and match the channel of input");
int bias_dim = std::accumulate(bias.begin(), bias.end(), 1, std::multiplies<int>());
@@ -66,6 +69,16 @@ public:
return false;
}
int getLayouts(const std::vector<DataLayout>& actualInputs,
std::vector<DataLayout>& desiredInputs,
const int requiredOutputs,
std::vector<DataLayout>& outputs) const CV_OVERRIDE {
CV_Assert(!actualInputs.empty());
desiredInputs = actualInputs;
outputs.assign(requiredOutputs, actualInputs[0]);
return 0;
}
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE {
CV_TRACE_FUNCTION();
CV_TRACE_ARG_VALUE(name, "name", name.c_str());
+472 -13
View File
@@ -152,6 +152,354 @@ static void pad(const Mat& inp, const std::vector<int>& pads_, int mode_, const
}
}
// Specialized semantic-padding path for block tensors with shape [N, C1, H, W, C0].
// It handles semantic channel-axis padding directly in blocked representation.
static void pad_block5d_semantic(const Mat& inp,
const std::vector<int>& semanticPads,
int mode_,
const Mat& value,
DataLayout origLayout,
Mat& out)
{
CV_Assert(inp.dims == 5 && out.dims == 5);
CV_Assert(inp.shape().layout == DATA_LAYOUT_BLOCK && out.shape().layout == DATA_LAYOUT_BLOCK);
CV_Assert(semanticPads.size() == 8);
CV_Assert(mode_ == BORDER_CONSTANT);
int inptype = inp.type();
CV_Assert(inp.type() == out.type());
CV_Assert(inp.isContinuous() && out.isContinuous());
double buf = 0;
Mat vbuf(1, 1, inptype, &buf);
if (!value.empty()) {
CV_Assert(value.dims <= 2 && value.total() == 1 && value.channels() == 1);
tensorToScalar(value, inptype, &buf);
}
const int C0 = inp.shape()[4];
const int semantic_Cin = inp.shape().C;
CV_Assert(C0 > 0);
// Physical layout is always [N, C1, H, W, C0] for BLOCK;
// semantic channel axis depends on original model layout.
const int semanticChannelAxis = (origLayout == DATA_LAYOUT_NCHW) ? 1 : 3;
const int semanticHAxis = (origLayout == DATA_LAYOUT_NCHW) ? 2 : 1;
const int semanticWAxis = (origLayout == DATA_LAYOUT_NCHW) ? 3 : 2;
const int cPadBefore = semanticPads[semanticChannelAxis];
const int N = inp.size[0];
const int Hin = inp.size[2];
const int Win = inp.size[3];
const int Nout = out.size[0];
const int C1out = out.size[1];
const int Hout = out.size[2];
const int Wout = out.size[3];
const int phys_Cout = C1out * C0;
std::vector<int> tabN(Nout), tabH(Hout), tabW(Wout), tabC(phys_Cout);
for (int i = 0; i < Nout; i++)
tabN[i] = borderInterpolate(i - semanticPads[0], N, mode_);
for (int i = 0; i < Hout; i++)
tabH[i] = borderInterpolate(i - semanticPads[semanticHAxis], Hin, mode_);
for (int i = 0; i < Wout; i++)
tabW[i] = borderInterpolate(i - semanticPads[semanticWAxis], Win, mode_);
for (int i = 0; i < phys_Cout; i++)
tabC[i] = borderInterpolate(i - cPadBefore, semantic_Cin, mode_);
std::vector<int> blockL(C1out), blockR(C1out), blockInStart(C1out);
std::vector<int> seg0OutOff(C1out), seg0Len(C1out), seg0InC1(C1out), seg0InC0(C1out);
std::vector<int> seg1OutOff(C1out), seg1Len(C1out), seg1InC1(C1out);
for (int oc1 = 0; oc1 < C1out; oc1++) {
int base = oc1 * C0;
int l = 0;
while (l < C0 && tabC[base + l] < 0)
l++;
int r = C0;
while (r > l && tabC[base + r - 1] < 0)
r--;
blockL[oc1] = l;
blockR[oc1] = r;
blockInStart[oc1] = (l < r) ? tabC[base + l] : 0;
int len = r - l;
if (len > 0) {
int in_c = blockInStart[oc1];
int in_c1 = in_c / C0;
int in_c0 = in_c - in_c1 * C0;
int len0 = std::min(len, C0 - in_c0);
seg0OutOff[oc1] = l;
seg0Len[oc1] = len0;
seg0InC1[oc1] = in_c1;
seg0InC0[oc1] = in_c0;
int len1 = len - len0;
seg1OutOff[oc1] = l + len0;
seg1Len[oc1] = len1;
seg1InC1[oc1] = in_c1 + 1;
} else {
seg0OutOff[oc1] = 0;
seg0Len[oc1] = 0;
seg0InC1[oc1] = 0;
seg0InC0[oc1] = 0;
seg1OutOff[oc1] = 0;
seg1Len[oc1] = 0;
seg1InC1[oc1] = 0;
}
}
const size_t inStep0 = inp.step.p[0] / inp.elemSize();
const size_t inStep1 = inp.step.p[1] / inp.elemSize();
const size_t inStep2 = inp.step.p[2] / inp.elemSize();
const size_t inStep3 = inp.step.p[3] / inp.elemSize();
const size_t outStep0 = out.step.p[0] / out.elemSize();
const size_t outStep1 = out.step.p[1] / out.elemSize();
const size_t outStep2 = out.step.p[2] / out.elemSize();
const size_t outStep3 = out.step.p[3] / out.elemSize();
int wL = 0;
while (wL < Wout && tabW[wL] < 0)
wL++;
int wR = Wout;
while (wR > wL && tabW[wR - 1] < 0)
wR--;
const int nrows = Nout * C1out * Hout;
#undef IMPL_PAD_BLOCK5D
#define IMPL_PAD_BLOCK5D(T) \
parallel_for_(Range(0, nrows), [&](const Range& r) { \
const T* inpdata0 = reinterpret_cast<const T*>(inp.data); \
T* outdata0 = reinterpret_cast<T*>(out.data); \
T val0 = *reinterpret_cast<T*>(vbuf.data); \
bool val_is_zero = (val0 == (T)0); \
for (int row = r.start; row < r.end; row++) { \
int t = row; \
int oh = t % Hout; \
t /= Hout; \
int oc1 = t % C1out; \
int on = t / C1out; \
T* outrow = outdata0 + on*outStep0 + oc1*outStep1 + oh*outStep2; \
int in_n = tabN[on]; \
int in_h = tabH[oh]; \
if ((in_n | in_h) < 0) { \
for (int ow = 0; ow < Wout; ow++) { \
T* outptr = outrow + ow*outStep3; \
if (val_is_zero) \
memset(outptr, 0, (size_t)C0 * sizeof(T)); \
else { \
for (int oc0 = 0; oc0 < C0; oc0++) outptr[oc0] = val0; \
} \
} \
continue; \
} \
int l = blockL[oc1]; \
int rr = blockR[oc1]; \
int len0 = seg0Len[oc1]; \
int len1 = seg1Len[oc1]; \
for (int ow = 0; ow < wL; ow++) { \
T* outptr = outrow + ow*outStep3; \
if (val_is_zero) \
memset(outptr, 0, (size_t)C0 * sizeof(T)); \
else { \
for (int oc0 = 0; oc0 < C0; oc0++) outptr[oc0] = val0; \
} \
} \
for (int ow = wL; ow < wR; ow++) { \
T* outptr = outrow + ow*outStep3; \
if (l > 0) { \
if (val_is_zero) \
memset(outptr, 0, (size_t)l * sizeof(T)); \
else { \
for (int oc0 = 0; oc0 < l; oc0++) \
outptr[oc0] = val0; \
} \
} \
if (l < rr) { \
int in_w = tabW[ow]; \
const T* inbase = inpdata0 + in_n*inStep0 + seg0InC1[oc1]*inStep1 + in_h*inStep2 + in_w*inStep3; \
if (len0 > 0) { \
const T* src0 = inbase + seg0InC0[oc1]; \
memcpy(outptr + seg0OutOff[oc1], src0, (size_t)len0 * sizeof(T)); \
} \
if (len1 > 0) { \
const T* src1 = inbase + inStep1; \
memcpy(outptr + seg1OutOff[oc1], src1, (size_t)len1 * sizeof(T)); \
} \
} \
if (rr < C0) { \
if (val_is_zero) \
memset(outptr + rr, 0, (size_t)(C0 - rr) * sizeof(T)); \
else { \
for (int oc0 = rr; oc0 < C0; oc0++) \
outptr[oc0] = val0; \
} \
} \
} \
for (int ow = wR; ow < Wout; ow++) { \
T* outptr = outrow + ow*outStep3; \
if (val_is_zero) \
memset(outptr, 0, (size_t)C0 * sizeof(T)); \
else { \
for (int oc0 = 0; oc0 < C0; oc0++) outptr[oc0] = val0; \
} \
} \
} \
})
if (inp.elemSize() == 1) {
IMPL_PAD_BLOCK5D(uint8_t);
} else if (inp.elemSize() == 2) {
IMPL_PAD_BLOCK5D(uint16_t);
} else if (inp.elemSize() == 4) {
IMPL_PAD_BLOCK5D(uint32_t);
} else {
CV_Assert(inp.elemSize() == 8);
IMPL_PAD_BLOCK5D(uint64_t);
}
}
static DataLayout getOriginalLayout(const Layer* layer)
{
Net::Impl* netimpl_ = getNetImpl(layer);
DataLayout layout = netimpl_ ? netimpl_->originalLayout : DATA_LAYOUT_NCHW;
CV_Assert(layout == DATA_LAYOUT_NCHW || layout == DATA_LAYOUT_NHWC);
return layout;
}
static int semanticToPhysicalAxis(int semanticAxis, int semanticNdims, DataLayout origLayout)
{
if (origLayout == DATA_LAYOUT_NCHW)
return semanticAxis;
CV_Assert(origLayout == DATA_LAYOUT_NHWC);
if (semanticAxis == 0)
return 0;
if (semanticAxis == semanticNdims - 1)
return 1;
return semanticAxis + 1;
}
static bool hasNonZeroChannelPad(const std::vector<int>& semanticPads,
int semanticNdims,
DataLayout origLayout)
{
if ((int)semanticPads.size() != semanticNdims * 2)
return false;
int cAxis = origLayout == DATA_LAYOUT_NCHW ? 1 : semanticNdims - 1;
if (!(0 <= cAxis && cAxis < semanticNdims))
return false;
return semanticPads[cAxis] != 0 || semanticPads[cAxis + semanticNdims] != 0;
}
static bool canUseBlockWithChannelPad(const MatShape& inpshape,
const std::vector<int>& semanticPads,
int mode,
DataLayout origLayout)
{
if (mode != BORDER_CONSTANT)
return false;
if ((int)semanticPads.size() != 8)
return false;
int cAxis = origLayout == DATA_LAYOUT_NCHW ? 1 : 3;
CV_Assert(cAxis >= 0);
int cBefore = semanticPads[cAxis];
int cAfter = semanticPads[cAxis + 4];
int semantic_Cin = 0;
if (inpshape.layout == DATA_LAYOUT_BLOCK && inpshape.dims == 5) {
semantic_Cin = inpshape.C;
} else if (inpshape.dims == 4) {
semantic_Cin = (origLayout == DATA_LAYOUT_NCHW) ? inpshape[1] : inpshape[3];
} else {
return false;
}
int semantic_Cout = semantic_Cin + cBefore + cAfter;
return semantic_Cout >= 0;
}
static MatShape getOutShapeBlockSemantic(const MatShape& inpshape,
const std::vector<int>& semanticPads,
DataLayout origLayout)
{
CV_Assert(inpshape.layout == DATA_LAYOUT_BLOCK);
CV_Assert(inpshape.dims == 5);
CV_Assert(semanticPads.size() == 8);
const int semanticNdims = 4;
const int cAxis = origLayout == DATA_LAYOUT_NCHW ? 1 : 3;
const int C0 = inpshape[4];
MatShape outshape = inpshape;
for (int i = 0; i < semanticNdims; i++) {
int inSz = (i == cAxis) ? inpshape.C : inpshape[semanticToPhysicalAxis(i, semanticNdims, origLayout)];
int outSz = inSz + semanticPads[i] + semanticPads[i + semanticNdims];
CV_Assert(outSz >= 0);
if (i == cAxis) {
outshape[1] = (outSz + C0 - 1) / C0;
} else {
int physAxis = semanticToPhysicalAxis(i, semanticNdims, origLayout);
outshape[physAxis] = outSz;
}
}
outshape[4] = C0;
outshape.C = inpshape.C + semanticPads[cAxis] + semanticPads[cAxis + semanticNdims];
return outshape;
}
static bool mapPadsToInputLayout(const MatShape& inpshape,
const std::vector<int>& semanticPads,
std::vector<int>& pads,
DataLayout origLayout)
{
int ndims = inpshape.dims;
DataLayout layout = inpshape.layout;
if (layout != DATA_LAYOUT_BLOCK) {
if ((int)semanticPads.size() != ndims*2)
return false;
pads = semanticPads;
return true;
}
if (ndims < 2 || ndims > PAD_MAX_DIMS)
return false;
int semanticNdims = ndims - 1;
if ((int)semanticPads.size() != semanticNdims*2)
return false;
int cAxis = origLayout == DATA_LAYOUT_NCHW ? 1 : semanticNdims - 1;
if (semanticPads[cAxis] != 0 || semanticPads[cAxis + semanticNdims] != 0)
return false;
pads.assign(ndims*2, 0);
for (int i = 0; i < semanticNdims; i++) {
int j = i;
if (origLayout == DATA_LAYOUT_NHWC) {
if (i == 0)
j = 0;
else if (i == semanticNdims - 1)
j = 1;
else
j = i + 1;
}
pads[j] = semanticPads[i];
pads[j + ndims] = semanticPads[i + semanticNdims];
}
pads[ndims - 1] = 0;
pads[ndims*2 - 1] = 0;
return true;
}
class Pad2LayerImpl CV_FINAL : public Pad2Layer
{
public:
@@ -202,6 +550,90 @@ public:
(ninputs >= 4 && !netimpl_->isConstArg(this->inputs[3]));
}
bool getConstSemanticPads(std::vector<int>& semanticPads) const
{
size_t ninputs = this->inputs.size();
CV_Assert(1 <= ninputs && ninputs <= 4);
if (ninputs == 1) {
semanticPads = pads0;
return !semanticPads.empty();
}
Net::Impl* netimpl_ = getNetImpl(this);
if (!netimpl_ || !netimpl_->isConstArg(this->inputs[1]))
return false;
Mat padsTensor = netimpl_->argTensor(this->inputs[1]);
Mat axesTensor;
if (ninputs >= 4) {
if (!netimpl_->isConstArg(this->inputs[3]))
return false;
axesTensor = netimpl_->argTensor(this->inputs[3]);
}
int ndims = netimpl_->argData(this->inputs[0]).shape.dims;
if (ndims <= 0) {
if (axesTensor.empty())
ndims = int(padsTensor.total()/2);
else
return false;
}
if (ndims < 1 || ndims > PAD_MAX_DIMS)
return false;
getPads(ndims, padsTensor, axesTensor, semanticPads);
return true;
}
int getLayouts(const std::vector<DataLayout>& actualInputs,
std::vector<DataLayout>& desiredInputs,
const int requiredOutputs,
std::vector<DataLayout>& outputs) const CV_OVERRIDE
{
size_t ninputs = actualInputs.size();
CV_Assert(1 <= ninputs && ninputs <= 4);
desiredInputs.assign(ninputs, DATA_LAYOUT_UNKNOWN);
desiredInputs[0] = actualInputs[0];
outputs.assign(requiredOutputs, actualInputs[0]);
if (actualInputs[0] != DATA_LAYOUT_BLOCK)
return 0;
DataLayout origLayout = getOriginalLayout(this);
std::vector<int> semanticPads;
bool canUseBlock = getConstSemanticPads(semanticPads);
if (canUseBlock) {
int semanticNdims = int(semanticPads.size() / 2);
canUseBlock = (semanticNdims >= 1 && semanticNdims + 1 <= PAD_MAX_DIMS);
if (canUseBlock) {
bool hasChannelPad = hasNonZeroChannelPad(semanticPads, semanticNdims, origLayout);
if (hasChannelPad) {
// Channel padding in semantic space requires special block path.
// 4D semantic tensors, constant mode.
canUseBlock = (mode == BORDER_CONSTANT && semanticNdims == 4);
} else {
// Non-channel semantic padding can be represented by per-axis
// physical pads in block layout.
canUseBlock = true;
}
}
} else {
canUseBlock = false;
}
if (!canUseBlock) {
desiredInputs[0] = origLayout;
outputs.assign(requiredOutputs, DATA_LAYOUT_UNKNOWN);
} else {
desiredInputs[0] = DATA_LAYOUT_BLOCK;
outputs.assign(requiredOutputs, DATA_LAYOUT_BLOCK);
}
return 0;
}
void getPads(int ndims, const Mat& pads_, const Mat& axes_, std::vector<int>& pads) const
{
int atype = axes_.type(), ptype = pads_.type();
@@ -282,20 +714,31 @@ public:
CV_Assert(1 <= ninputs && ninputs <= 4);
Net::Impl* netimpl_ = getNetImpl(this);
std::vector<int> semanticPads;
std::vector<int> padsbuf;
const std::vector<int>* pads = &pads0;
DataLayout origLayout = getOriginalLayout(this);
if (ninputs >= 2) {
int ndims = inputs[0].dims;
int ndims = inputs[0].layout == DATA_LAYOUT_BLOCK ? inputs[0].dims - 1 : inputs[0].dims;
Mat padsTensor = netimpl_->argTensor(this->inputs[1]);
Mat axesTensor;
if (ninputs >= 4)
axesTensor = netimpl_->argTensor(this->inputs[3]);
getPads(ndims, padsTensor, axesTensor, padsbuf);
pads = &padsbuf;
getPads(ndims, padsTensor, axesTensor, semanticPads);
} else {
semanticPads = pads0;
}
bool mapped = mapPadsToInputLayout(inputs[0], semanticPads, padsbuf, origLayout);
bool useBlockSemantic = !mapped && canUseBlockWithChannelPad(inputs[0], semanticPads, mode, origLayout);
if (mapped) {
outputs.assign(1, getOutShape(inputs[0], padsbuf));
} else if (useBlockSemantic) {
outputs.assign(1, getOutShapeBlockSemantic(inputs[0], semanticPads, origLayout));
} else {
CV_Error(Error::StsInternal, "Pad2: unexpected block layout with unsupported padding");
}
outputs.assign(1, getOutShape(inputs[0], *pads));
internals.clear();
return true;
}
@@ -331,36 +774,52 @@ public:
Mat inp = inputs_arr.getMat(0);
Mat value(1, 1, CV_32F, &value0);
int inptype = inp.type();
MatShape inpshape = inp.shape();
std::vector<int> semanticPads;
std::vector<int> padsbuf;
const std::vector<int>* pads = &pads0;
DataLayout origLayout = getOriginalLayout(this);
if (ninputs >= 2) {
int ndims = inp.dims;
int ndims = inpshape.layout == DATA_LAYOUT_BLOCK ? inpshape.dims - 1 : inpshape.dims;
Mat padsTensor = inputs_arr.getMat(1);
Mat axesTensor;
if (ninputs >= 4)
axesTensor = inputs_arr.getMat(3);
getPads(ndims, padsTensor, axesTensor, padsbuf);
pads = &padsbuf;
getPads(ndims, padsTensor, axesTensor, semanticPads);
if (ninputs >= 3)
value = inputs_arr.getMat(2);
} else {
semanticPads = pads0;
}
MatShape inpshape = inp.shape();
MatShape outshape = getOutShape(inpshape, *pads);
bool mapped = mapPadsToInputLayout(inpshape, semanticPads, padsbuf, origLayout);
bool useBlockSemantic = !mapped && canUseBlockWithChannelPad(inpshape, semanticPads, mode, origLayout);
MatShape outshape;
if (mapped)
outshape = getOutShape(inpshape, padsbuf);
else if (useBlockSemantic)
outshape = getOutShapeBlockSemantic(inpshape, semanticPads, origLayout);
else
CV_Error(Error::StsInternal, "Pad2: unexpected block layout with unsupported padding");
auto kind = outputs_arr.kind();
if (kind == _InputArray::STD_VECTOR_MAT) {
std::vector<Mat>& outs = outputs_arr.getMatVecRef();
outs.resize(1);
outs[0].fit(outshape, inptype);
pad(inp, *pads, mode, value, outs[0]);
if (mapped)
pad(inp, padsbuf, mode, value, outs[0]);
else
pad_block5d_semantic(inp, semanticPads, mode, value, origLayout, outs[0]);
} else if (kind == _InputArray::STD_VECTOR_UMAT) {
std::vector<UMat>& outs = outputs_arr.getUMatVecRef();
outs.resize(1);
outs[0].fit(outshape, inptype);
Mat temp(outshape, inptype);
pad(inp, *pads, mode, value, temp);
if (mapped)
pad(inp, padsbuf, mode, value, temp);
else
pad_block5d_semantic(inp, semanticPads, mode, value, origLayout, temp);
temp.copyTo(outs[0]);
} else {
CV_Error(Error::StsNotImplemented, "");
+130 -3
View File
@@ -40,6 +40,62 @@ class Reshape2LayerImpl CV_FINAL : public Reshape2Layer
public:
bool dynamicShapeSpec;
static bool sameDims(const MatShape& a, const MatShape& b)
{
if (a.dims != b.dims)
return false;
for (int i = 0; i < a.dims; i++) {
if (a[i] != b[i])
return false;
}
return true;
}
DataLayout getOriginalLayout() const
{
Net::Impl* netimpl_ = getNetImpl(this);
DataLayout layout = netimpl_ ? netimpl_->originalLayout : DATA_LAYOUT_NCHW;
CV_Assert(layout == DATA_LAYOUT_NCHW || layout == DATA_LAYOUT_NHWC);
return layout;
}
bool getConstShapeSpec(MatShape& shapeSpec) const
{
if (!dynamicShapeSpec) {
shapeSpec = newShapeDesc;
return true;
}
if (this->inputs.size() != 2)
return false;
Net::Impl* netimpl_ = getNetImpl(this);
if (!netimpl_ || !netimpl_->isConstArg(this->inputs[1]))
return false;
Mat shapeTensor = netimpl_->argTensor(this->inputs[1]);
shapeSpec = tensorToShape(shapeTensor);
return true;
}
bool canKeepBlockLayout(const MatShape& inpShape, const MatShape& shapeSpec) const
{
if (inpShape.layout != DATA_LAYOUT_BLOCK || inpShape.dims != 5)
return false;
DataLayout origLayout = getOriginalLayout();
MatShape semanticInp = inpShape.toLayout(origLayout);
MatShape semanticOut = getOutShape(semanticInp, shapeSpec);
if (sameDims(semanticOut, semanticInp))
return true;
if (semanticOut.dims != 4 || semanticInp.dims != 4)
return false;
if (origLayout == DATA_LAYOUT_NCHW)
return semanticOut[0] == semanticInp[0] && semanticOut[1] == semanticInp[1];
else
return semanticOut[0] == semanticInp[0] && semanticOut[3] == semanticInp[3];
}
Reshape2LayerImpl(const LayerParams& params)
{
dynamicShapeSpec = true;
@@ -81,7 +137,7 @@ public:
return newShapeDesc.dims >= 0;
}
MatShape getOutShape(const MatShape& inpShape, MatShape& shapeSpec) const
MatShape getOutShape(const MatShape& inpShape, const MatShape& shapeSpec) const
{
MatShape outShape = shapeSpec;
int m1idx = -1;
@@ -137,11 +193,62 @@ public:
} else {
CV_Assert(shapeSpec.dims >= 0);
}
outputs.assign(1, getOutShape(inputs[0], shapeSpec));
if (inputs[0].layout == DATA_LAYOUT_BLOCK) {
if (canKeepBlockLayout(inputs[0], shapeSpec)) {
DataLayout origLayout = getOriginalLayout();
MatShape semanticInp = inputs[0].toLayout(origLayout);
MatShape semanticOut = getOutShape(semanticInp, shapeSpec);
if (sameDims(semanticOut, semanticInp)) {
outputs.assign(1, inputs[0]);
} else {
outputs.assign(1, semanticOut.toLayout(DATA_LAYOUT_BLOCK, inputs[0][4]));
}
} else {
DataLayout origLayout = getOriginalLayout();
MatShape semanticInp = inputs[0].toLayout(origLayout);
outputs.assign(1, getOutShape(semanticInp, shapeSpec));
}
} else {
outputs.assign(1, getOutShape(inputs[0], shapeSpec));
}
internals.clear();
return true;
}
int getLayouts(const std::vector<DataLayout>& actualInputs,
std::vector<DataLayout>& desiredInputs,
const int requiredOutputs,
std::vector<DataLayout>& outputs) const CV_OVERRIDE
{
size_t ninputs = actualInputs.size();
CV_Assert(ninputs == 1u || ninputs == 2u);
desiredInputs.assign(ninputs, DATA_LAYOUT_UNKNOWN);
desiredInputs[0] = actualInputs[0];
outputs.assign(requiredOutputs, actualInputs[0]);
if (actualInputs[0] == DATA_LAYOUT_BLOCK) {
bool canUseBlock = false;
MatShape shapeSpec;
if (getConstShapeSpec(shapeSpec)) {
DataLayout origLayout = getOriginalLayout();
int cAxis = (origLayout == DATA_LAYOUT_NCHW) ? 1 : 3;
if (shapeSpec.dims == 4 && cAxis < shapeSpec.dims && shapeSpec[cAxis] == 0)
canUseBlock = true;
}
if (!canUseBlock) {
desiredInputs[0] = getOriginalLayout();
outputs.assign(requiredOutputs, DATA_LAYOUT_UNKNOWN);
}
}
if (ninputs > 1)
desiredInputs[1] = DATA_LAYOUT_UNKNOWN;
return outputs[0] == DATA_LAYOUT_BLOCK ? getNetImpl(this)->defaultC0 : 0;
}
void getTypes(const std::vector<MatType>& inputs,
const int requiredOutputs,
const int requiredInternals,
@@ -173,12 +280,32 @@ public:
(ninputs == 2 && !haveShapeSpec_));
MatShape inpShape = inputs_arr.shape(0);
CV_Assert(inpShape.layout != DATA_LAYOUT_BLOCK || inpShape.C > 0);
MatShape shapeSpec = newShapeDesc;
if (!haveShapeSpec_) {
Mat shapeTensor = inputs_arr.getMat(1);
shapeSpec = tensorToShape(shapeTensor);
}
MatShape outShape = getOutShape(inpShape, shapeSpec);
MatShape outShape;
if (inpShape.layout == DATA_LAYOUT_BLOCK) {
if (canKeepBlockLayout(inpShape, shapeSpec)) {
DataLayout origLayout = getOriginalLayout();
MatShape semanticInp = inpShape.toLayout(origLayout);
MatShape semanticOut = getOutShape(semanticInp, shapeSpec);
if (sameDims(semanticOut, semanticInp)) {
outShape = inpShape;
} else {
outShape = semanticOut.toLayout(DATA_LAYOUT_BLOCK, inpShape[4]);
}
} else {
DataLayout origLayout = getOriginalLayout();
MatShape semanticInp = inpShape.toLayout(origLayout);
outShape = getOutShape(semanticInp, shapeSpec);
}
} else {
outShape = getOutShape(inpShape, shapeSpec);
}
reshapeAndCopyFirst(inputs_arr, outputs_arr, outShape);
}
};
+239 -15
View File
@@ -278,15 +278,12 @@ void resizeNearest(const Mat &inp, Mat &out,
float start_x = 0.0f, float end_x = 1.0f,
float extrapolation_value = 0.0f)
{
int numPlanes = inp.size[0] * inp.size[1];
int inH = inp.size[2], inW = inp.size[3];
int outH = out.size[2], outW = out.size[3];
CV_Assert(inp.isContinuous() && out.isContinuous());
Mat inpP = inp.reshape(1, numPlanes * inH);
Mat outP = out.reshape(1, numPlanes * outH);
CoordTransMode coordMode = parseCoordTransMode(coordTransMode);
const bool tf_crop_and_resize_mode = (coordMode == CoordTransMode::TF_CROP_AND_RESIZE);
std::vector<int> mapY(outH);
buildNearestIndexMap(mapY, outH, inH, scaleH, lenY, start_y, end_y,
@@ -296,9 +293,64 @@ void resizeNearest(const Mat &inp, Mat &out,
buildNearestIndexMap(mapX, outW, inW, scaleW, lenX, start_x, end_x,
coordMode, nearestMode, halfPixelCenters);
if (inp.shape().layout == DATA_LAYOUT_BLOCK) {
CV_Assert(inp.dims == 5 && out.dims == 5);
const int N = inp.size[0], C1 = inp.size[1], C0 = inp.size[4];
const size_t inStep0 = inp.step.p[0] / inp.elemSize();
const size_t inStep1 = inp.step.p[1] / inp.elemSize();
const size_t inStep2 = inp.step.p[2] / inp.elemSize();
const size_t inStep3 = inp.step.p[3] / inp.elemSize();
const size_t outStep0 = out.step.p[0] / out.elemSize();
const size_t outStep1 = out.step.p[1] / out.elemSize();
const size_t outStep2 = out.step.p[2] / out.elemSize();
const size_t outStep3 = out.step.p[3] / out.elemSize();
const size_t C0bytes = (size_t)C0 * sizeof(T);
const int nplanes = N * C1 * outH;
parallel_for_(Range(0, nplanes), [&](const Range& range) {
const T* inptr0 = reinterpret_cast<const T*>(inp.data);
T* outptr0 = reinterpret_cast<T*>(out.data);
T ext = saturate_cast<T>(extrapolation_value);
for (int plane = range.start; plane < range.end; ++plane) {
int t = plane;
int oy = t % outH; t /= outH;
int c1 = t % C1;
int n = t / C1;
int iy = mapY[oy];
T* outRow = outptr0 + n * outStep0 + c1 * outStep1 + oy * outStep2;
if (tf_crop_and_resize_mode && iy == -1) {
for (int ox = 0; ox < outW; ++ox) {
T* outPix = outRow + ox * outStep3;
for (int c0 = 0; c0 < C0; ++c0) outPix[c0] = ext;
}
continue;
}
const T* inRow = inptr0 + n * inStep0 + c1 * inStep1 + iy * inStep2;
for (int ox = 0; ox < outW; ++ox) {
int ix = mapX[ox];
if (tf_crop_and_resize_mode && ix == -1) {
T* outPix = outRow + ox * outStep3;
for (int c0 = 0; c0 < C0; ++c0) outPix[c0] = ext;
} else {
memcpy(outRow + ox * outStep3, inRow + ix * inStep3, C0bytes);
}
}
}
}, kResizeNumStripes);
return;
}
int numPlanes = inp.size[0] * inp.size[1];
Mat inpP = inp.reshape(1, numPlanes * inH);
Mat outP = out.reshape(1, numPlanes * outH);
const int nstripes = kResizeNumStripes;
parallel_for_(Range(0, nstripes), [&](const Range& range) {
const bool tf_crop_and_resize_mode = (coordMode == CoordTransMode::TF_CROP_AND_RESIZE);
int row0 = range.start * (outH * numPlanes) / nstripes;
float extrapolation_value_ = extrapolation_value;
int row1 = range.end * (outH * numPlanes) / nstripes - 1;
@@ -346,14 +398,10 @@ void resizeBilinear(const Mat &inp, Mat &out,
float start_x = 0.0f, float end_x = 1.0f,
float extrapolation_value = 0.0f)
{
int numPlanes = inp.size[0]*inp.size[1];
int inH = inp.size[2], inW = inp.size[3];
int outH = out.size[2], outW = out.size[3];
CV_Assert(inp.isContinuous() && out.isContinuous());
Mat inpP = inp.reshape(1, numPlanes*inH);
Mat outP = out.reshape(1, numPlanes*outH);
CoordTransMode coordMode = parseCoordTransMode(coordTransMode);
const bool tf_crop_and_resize_mode = (coordMode == CoordTransMode::TF_CROP_AND_RESIZE);
@@ -371,6 +419,70 @@ void resizeBilinear(const Mat &inp, Mat &out,
outH, inH, scaleH, lenY, start_y, end_y,
coordMode, halfPixelCenters, tf_crop_and_resize_mode);
if (inp.shape().layout == DATA_LAYOUT_BLOCK) {
CV_Assert(inp.dims == 5 && out.dims == 5);
const int N = inp.size[0], C1 = inp.size[1], C0 = inp.size[4];
const size_t inStep0 = inp.step.p[0] / inp.elemSize();
const size_t inStep1 = inp.step.p[1] / inp.elemSize();
const size_t inStep2 = inp.step.p[2] / inp.elemSize();
const size_t inStep3 = inp.step.p[3] / inp.elemSize();
const size_t outStep0 = out.step.p[0] / out.elemSize();
const size_t outStep1 = out.step.p[1] / out.elemSize();
const size_t outStep2 = out.step.p[2] / out.elemSize();
const size_t outStep3 = out.step.p[3] / out.elemSize();
const int nplanes = N * C1 * outH;
parallel_for_(Range(0, nplanes), [&](const Range& range) {
const T* inptr0 = reinterpret_cast<const T*>(inp.data);
T* outptr0 = reinterpret_cast<T*>(out.data);
T ext = saturate_cast<T>(extrapolation_value);
for (int plane = range.start; plane < range.end; ++plane) {
int t = plane;
int oy = t % outH; t /= outH;
int c1 = t % C1;
int n = t / C1;
T* outRow = outptr0 + n * outStep0 + c1 * outStep1 + oy * outStep2;
if (tf_crop_and_resize_mode && outOfBoundsY[oy]) {
for (int ox = 0; ox < outW; ++ox) {
T* outPix = outRow + ox * outStep3;
for (int c0 = 0; c0 < C0; ++c0) outPix[c0] = ext;
}
continue;
}
const T* row00 = inptr0 + n * inStep0 + c1 * inStep1 + y0[oy] * inStep2;
const T* row01 = inptr0 + n * inStep0 + c1 * inStep1 + y1[oy] * inStep2;
float fy = ly[oy];
for (int ox = 0; ox < outW; ++ox) {
T* outPix = outRow + ox * outStep3;
if (tf_crop_and_resize_mode && outOfBoundsX[ox]) {
for (int c0 = 0; c0 < C0; ++c0) outPix[c0] = ext;
continue;
}
const T* p00 = row00 + x0[ox] * inStep3;
const T* p01 = row00 + x1[ox] * inStep3;
const T* p10 = row01 + x0[ox] * inStep3;
const T* p11 = row01 + x1[ox] * inStep3;
float fx = lx[ox];
for (int c0 = 0; c0 < C0; ++c0) {
float top = static_cast<float>(p00[c0]) + fx * (static_cast<float>(p01[c0]) - static_cast<float>(p00[c0]));
float bot = static_cast<float>(p10[c0]) + fx * (static_cast<float>(p11[c0]) - static_cast<float>(p10[c0]));
outPix[c0] = saturate_cast<T>(top + fy * (bot - top));
}
}
}
}, kResizeNumStripes);
return;
}
int numPlanes = inp.size[0] * inp.size[1];
Mat inpP = inp.reshape(1, numPlanes * inH);
Mat outP = out.reshape(1, numPlanes * outH);
const int nstripes = kResizeNumStripes;
parallel_for_(Range(0, nstripes), [&](const Range& range) {
int row0 = range.start * (outH * numPlanes) / nstripes;
@@ -448,13 +560,9 @@ void resizeCubic(const Mat &inp, Mat &out,
float start_x = 0.0f, float end_x = 1.0f,
float extrapolation_value = 0.0f)
{
int numPlanes = inp.size[0] * inp.size[1];
int inH = inp.size[2], inW = inp.size[3];
int outH = out.size[2], outW = out.size[3];
Mat inpPlanes = inp.reshape(1, numPlanes * inH);
Mat outPlanes = out.reshape(1, numPlanes * outH);
CoordTransMode coordMode = parseCoordTransMode(coordTransMode);
const bool tf_crop_and_resize_mode = (coordMode == CoordTransMode::TF_CROP_AND_RESIZE);
@@ -474,6 +582,96 @@ void resizeCubic(const Mat &inp, Mat &out,
coordMode, halfPixelCenters, tf_crop_and_resize_mode,
excludeOutside, cubicA);
if (inp.shape().layout == DATA_LAYOUT_BLOCK) {
CV_Assert(inp.dims == 5 && out.dims == 5);
CV_Assert(inp.isContinuous() && out.isContinuous());
const int N = inp.size[0], C1 = inp.size[1], C0 = inp.size[4];
const size_t inStep0 = inp.step.p[0] / inp.elemSize();
const size_t inStep1 = inp.step.p[1] / inp.elemSize();
const size_t inStep2 = inp.step.p[2] / inp.elemSize();
const size_t inStep3 = inp.step.p[3] / inp.elemSize();
const size_t outStep0 = out.step.p[0] / out.elemSize();
const size_t outStep1 = out.step.p[1] / out.elemSize();
const size_t outStep2 = out.step.p[2] / out.elemSize();
const size_t outStep3 = out.step.p[3] / out.elemSize();
const int nplanes = N * C1 * outH;
parallel_for_(Range(0, nplanes), [&](const Range& range) {
const T* inptr0 = reinterpret_cast<const T*>(inp.data);
T* outptr0 = reinterpret_cast<T*>(out.data);
T ext = saturate_cast<T>(extrapolation_value);
for (int plane = range.start; plane < range.end; ++plane) {
int t = plane;
int oy = t % outH; t /= outH;
int c1 = t % C1;
int n = t / C1;
T* outRow = outptr0 + n * outStep0 + c1 * outStep1 + oy * outStep2;
if (tf_crop_and_resize_mode && outOfBoundsY[oy]) {
for (int ox = 0; ox < outW; ++ox) {
T* outPix = outRow + ox * outStep3;
for (int c0 = 0; c0 < C0; ++c0) outPix[c0] = ext;
}
continue;
}
const int yy0 = y_id[oy][0], yy1 = y_id[oy][1], yy2 = y_id[oy][2], yy3 = y_id[oy][3];
const float wy0 = y_w[oy][0], wy1 = y_w[oy][1], wy2 = y_w[oy][2], wy3 = y_w[oy][3];
const T* row0 = yy0 >= 0 ? inptr0 + n * inStep0 + c1 * inStep1 + yy0 * inStep2 : nullptr;
const T* row1 = yy1 >= 0 ? inptr0 + n * inStep0 + c1 * inStep1 + yy1 * inStep2 : nullptr;
const T* row2 = yy2 >= 0 ? inptr0 + n * inStep0 + c1 * inStep1 + yy2 * inStep2 : nullptr;
const T* row3 = yy3 >= 0 ? inptr0 + n * inStep0 + c1 * inStep1 + yy3 * inStep2 : nullptr;
for (int ox = 0; ox < outW; ++ox) {
T* outPix = outRow + ox * outStep3;
if (tf_crop_and_resize_mode && outOfBoundsX[ox]) {
for (int c0 = 0; c0 < C0; ++c0) outPix[c0] = ext;
continue;
}
const int xx0 = x_id[ox][0], xx1 = x_id[ox][1], xx2 = x_id[ox][2], xx3 = x_id[ox][3];
const float wx0 = x_w[ox][0], wx1 = x_w[ox][1], wx2 = x_w[ox][2], wx3 = x_w[ox][3];
for (int c0 = 0; c0 < C0; ++c0) {
float val = 0.f;
if (row0) {
if (xx0 >= 0) val += wy0 * wx0 * static_cast<float>(row0[xx0 * inStep3 + c0]);
if (xx1 >= 0) val += wy0 * wx1 * static_cast<float>(row0[xx1 * inStep3 + c0]);
if (xx2 >= 0) val += wy0 * wx2 * static_cast<float>(row0[xx2 * inStep3 + c0]);
if (xx3 >= 0) val += wy0 * wx3 * static_cast<float>(row0[xx3 * inStep3 + c0]);
}
if (row1) {
if (xx0 >= 0) val += wy1 * wx0 * static_cast<float>(row1[xx0 * inStep3 + c0]);
if (xx1 >= 0) val += wy1 * wx1 * static_cast<float>(row1[xx1 * inStep3 + c0]);
if (xx2 >= 0) val += wy1 * wx2 * static_cast<float>(row1[xx2 * inStep3 + c0]);
if (xx3 >= 0) val += wy1 * wx3 * static_cast<float>(row1[xx3 * inStep3 + c0]);
}
if (row2) {
if (xx0 >= 0) val += wy2 * wx0 * static_cast<float>(row2[xx0 * inStep3 + c0]);
if (xx1 >= 0) val += wy2 * wx1 * static_cast<float>(row2[xx1 * inStep3 + c0]);
if (xx2 >= 0) val += wy2 * wx2 * static_cast<float>(row2[xx2 * inStep3 + c0]);
if (xx3 >= 0) val += wy2 * wx3 * static_cast<float>(row2[xx3 * inStep3 + c0]);
}
if (row3) {
if (xx0 >= 0) val += wy3 * wx0 * static_cast<float>(row3[xx0 * inStep3 + c0]);
if (xx1 >= 0) val += wy3 * wx1 * static_cast<float>(row3[xx1 * inStep3 + c0]);
if (xx2 >= 0) val += wy3 * wx2 * static_cast<float>(row3[xx2 * inStep3 + c0]);
if (xx3 >= 0) val += wy3 * wx3 * static_cast<float>(row3[xx3 * inStep3 + c0]);
}
outPix[c0] = saturate_cast<T>(val);
}
}
}
}, kResizeNumStripes);
return;
}
int numPlanes = inp.size[0] * inp.size[1];
Mat inpPlanes = inp.reshape(1, numPlanes * inH);
Mat outPlanes = out.reshape(1, numPlanes * outH);
const int nstripes = kResizeNumStripes;
parallel_for_(Range(0, nstripes), [&](const Range& range) {
int row0 = range.start * (outH * numPlanes) / nstripes;
@@ -633,6 +831,30 @@ public:
return false;
}
int getLayouts(const std::vector<DataLayout>& actualInputs,
std::vector<DataLayout>& desiredInputs,
const int requiredOutputs,
std::vector<DataLayout>& outputs) const CV_OVERRIDE
{
CV_Assert(!actualInputs.empty());
desiredInputs = actualInputs;
outputs.assign(requiredOutputs, actualInputs[0]);
if (actualInputs[0] != DATA_LAYOUT_BLOCK)
return 0;
if (interpolation == "nearest" || interpolation == "bilinear" || interpolation == "opencv_linear" || interpolation == "cubic") {
desiredInputs[0] = DATA_LAYOUT_BLOCK;
outputs.assign(requiredOutputs, DATA_LAYOUT_BLOCK);
} else {
Net::Impl* netimpl_ = getNetImpl(this);
DataLayout defaultLayout = netimpl_ ? netimpl_->originalLayout : DATA_LAYOUT_NCHW;
desiredInputs[0] = defaultLayout;
outputs.assign(requiredOutputs, DATA_LAYOUT_UNKNOWN);
}
return outputs[0] == DATA_LAYOUT_BLOCK ? getNetImpl(this)->defaultC0 : 0;
}
MatShape getOutShape(const MatShape& inpShape, const std::vector<int>& sizes,
const std::vector<float>& scales) const
{
@@ -734,7 +956,7 @@ public:
void updateOutSizeAndScale(const MatShape& inpShape, const MatShape& outShape)
{
CV_Assert(outShape.dims == 4);
CV_Assert(inpShape.dims >= 4 && outShape.dims >= 4);
outHeight = outShape[2];
outWidth = outShape[3];
if (alignCorners && outHeight > 1)
@@ -865,9 +1087,11 @@ public:
if(interpolation=="nearest"){
switch(depth){
case CV_8S:
case CV_8U:
resizeNearest<int8_t>(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,nearestModeE,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value);
break;
case CV_8U:
resizeNearest<uint8_t>(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,nearestModeE,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value);
break;
case CV_16F:
resizeNearest<hfloat>(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,nearestModeE,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value);
break;
+47 -3
View File
@@ -41,7 +41,7 @@ public:
Range getShapeRange(const MatShape& inpShape) const
{
int outDims = inpShape.dims;
int outDims = inpShape.layout == DATA_LAYOUT_BLOCK ? inpShape.dims - 1 : inpShape.dims;
int start_ = start < 0 ? start + outDims : start;
int end_ = end >= outDims ? outDims : end < 0 ? end + outDims : end;
@@ -66,6 +66,17 @@ public:
return outShape;
}
int getLayouts(const std::vector<DataLayout>& actualInputs,
std::vector<DataLayout>& desiredInputs,
const int requiredOutputs,
std::vector<DataLayout>& outputs) const CV_OVERRIDE
{
CV_Assert(actualInputs.size() == 1);
desiredInputs.assign(1, actualInputs[0]);
outputs.assign(requiredOutputs, DATA_LAYOUT_UNKNOWN);
return 0;
}
bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
@@ -110,8 +121,41 @@ public:
Range r = getShapeRange(inpShape);
shape_type_t shapeData[CV_MAX_DIM];
for (int i = r.start; i < r.end; i++)
shapeData[i - r.start] = (shape_type_t)inpShape[i];
if (inpShape.layout != DATA_LAYOUT_BLOCK)
{
for (int i = r.start; i < r.end; i++)
shapeData[i - r.start] = (shape_type_t)inpShape[i];
}
else
{
CV_Assert(inpShape.dims >= 3);
CV_Assert(inpShape.C > 0);
Net::Impl* netimpl_ = getNetImpl(this);
DataLayout origLayout = netimpl_ ? netimpl_->originalLayout : DATA_LAYOUT_NCHW;
CV_Assert(origLayout == DATA_LAYOUT_NCHW || origLayout == DATA_LAYOUT_NHWC);
const int semanticNdims = inpShape.dims - 1;
std::vector<shape_type_t> semanticShape(semanticNdims);
semanticShape[0] = (shape_type_t)inpShape[0];
if (origLayout == DATA_LAYOUT_NCHW)
{
CV_Assert(semanticNdims >= 2);
semanticShape[1] = (shape_type_t)inpShape.C;
for (int i = 2; i < semanticNdims; i++)
semanticShape[i] = (shape_type_t)inpShape[i];
}
else
{
CV_Assert(semanticNdims >= 2);
semanticShape[semanticNdims - 1] = (shape_type_t)inpShape.C;
for (int i = 1; i < semanticNdims - 1; i++)
semanticShape[i] = (shape_type_t)inpShape[i + 1];
}
for (int i = r.start; i < r.end; i++)
shapeData[i - r.start] = semanticShape[i];
}
Mat shape({r.end - r.start}, shapeType, shapeData);
+113
View File
@@ -1417,4 +1417,117 @@ INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_YOLOXS_ONNX,
testing::ValuesIn(getAvailableTargets(DNN_BACKEND_OPENCV)));
typedef testing::TestWithParam<Target> Reproducibility_BlazeFace_ONNX;
TEST_P(Reproducibility_BlazeFace_ONNX, Accuracy)
{
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
Target targetId = GetParam();
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/blazeface.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 = findDataFile("cv/cascadeandhog/images/karen-and-rob.png");
Mat image = imread(imgname);
ASSERT_FALSE(image.empty());
Mat input = blobFromImage(image, 1.0 / 255.0, Size(128, 128), Scalar(), true, false, CV_32F);
ASSERT_FALSE(input.empty());
Mat refSelected = blobFromNPY(_tf("onnx/data/output_blazeface_selectedBoxes.npy"));
ASSERT_FALSE(refSelected.empty());
const int oneDim[] = {1};
Mat conf(1, oneDim, CV_32F); conf.ptr<float>()[0] = 0.20f;
Mat iou(1, oneDim, CV_32F); iou.ptr<float>()[0] = 0.30f;
Mat maxDet(1, oneDim, CV_64S); maxDet.ptr<int64_t>()[0] = 25;
std::vector<String> outNames = net.getUnconnectedOutLayersNames();
std::vector<Mat> outs;
Mat selected;
int idxSel = -1;
net.setInput(input, "image");
net.setInput(conf, "conf_threshold");
net.setInput(iou, "iou_threshold");
net.setInput(maxDet, "max_detections");
net.forward(outs, outNames);
for (size_t j = 0; j < outNames.size(); ++j)
{
if (outNames[j].find("selectedBoxes") != std::string::npos)
{
idxSel = static_cast<int>(j);
break;
}
}
if (idxSel < 0 && !outs.empty())
idxSel = 0;
ASSERT_GE(idxSel, 0);
outs[idxSel].convertTo(selected, CV_32F);
Mat outFlat = selected.reshape(1, 1);
Mat refFlat = refSelected.reshape(1, 1);
ASSERT_EQ(outFlat.total(), refFlat.total())
<< "OpenCV output size differs from ORT reference";
EXPECT_LE(cv::norm(outFlat, refFlat, NORM_INF), 1e-2);
}
INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_BlazeFace_ONNX,
testing::ValuesIn(getAvailableTargets(DNN_BACKEND_OPENCV)));
typedef testing::TestWithParam<Target> Reproducibility_FacePaint_ONNX;
TEST_P(Reproducibility_FacePaint_ONNX, Accuracy)
{
Target targetId = GetParam();
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/face_paint_512_v2_0.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 = findDataFile("cv/shared/baboon.png");
Mat image = imread(imgname);
ASSERT_FALSE(image.empty());
Mat input = blobFromImage(image, 1.0/127.5, Size(512, 512),
Scalar(127.5, 127.5, 127.5), true, false, CV_32F);
ASSERT_TRUE(!input.empty());
net.setInput(input);
Mat out = net.forward();
Mat ref_img = imread(_tf("onnx/data/face_paint_512_v2_0_ort_output.png"));
ASSERT_FALSE(ref_img.empty()) << "Failed to load reference PNG";
Mat ref = blobFromImage(ref_img, 1.0 / 127.5, Size(),
Scalar(127.5, 127.5, 127.5), true, false, CV_32F);
Mat outFlat = out.reshape(1, 1);
Mat refFlat = ref.reshape(1, 1);
ASSERT_EQ(outFlat.total(), refFlat.total()) << "OpenCV output size differs from ORT reference";
EXPECT_LE(cv::norm(outFlat, refFlat, NORM_INF), 0.01);
}
INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_FacePaint_ONNX,
testing::ValuesIn(getAvailableTargets(DNN_BACKEND_OPENCV)));
}} // namespace