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:
committed by
GitHub
parent
2760c0c08a
commit
e519173241
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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, "");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user