diff --git a/modules/dnn/perf/perf_net.cpp b/modules/dnn/perf/perf_net.cpp index 7a38ed4f49..c2e500556b 100644 --- a/modules/dnn/perf/perf_net.cpp +++ b/modules/dnn/perf/perf_net.cpp @@ -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()[0] = 0.20f; + Mat iou(1, oneDim, CV_32F); iou.ptr()[0] = 0.30f; + Mat maxDet(1, oneDim, CV_64S); maxDet.ptr()[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 diff --git a/modules/dnn/src/graph_fusion_basic.cpp b/modules/dnn/src/graph_fusion_basic.cpp index 5525224853..6a8cc52daf 100644 --- a/modules/dnn/src/graph_fusion_basic.cpp +++ b/modules/dnn/src/graph_fusion_basic.cpp @@ -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(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(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(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(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(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(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(); + const float* bp = in_bias.ptr(); + 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 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(); + newprog[reshape2_idx] = Ptr(); + newprog[mul_idx] = Ptr(); + break; + } + } + } + } + } + } + } + } + } break; } diff --git a/modules/dnn/src/layers/cpu_kernels/fast_norm.cpp b/modules/dnn/src/layers/cpu_kernels/fast_norm.cpp index 85e2794d51..36feb3bb1b 100644 --- a/modules/dnn/src/layers/cpu_kernels/fast_norm.cpp +++ b/modules/dnn/src/layers/cpu_kernels/fast_norm.cpp @@ -4,6 +4,7 @@ #include "../../precomp.hpp" #include "fast_norm.hpp" +#include 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(); + const float* bias_data = bias.ptr(); + + 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::vlanes(); +#endif + + parallel_for_(Range(0, N * C1), [&](const Range& r) { + const float* inptr0 = (const float*)input.data; + float* outptr0 = (float*)output.data; + + AutoBuffer 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(total(input_shape, 2)); - float inv_norm_size = 1.0 / norm_size; auto fn = [&](const Range &r) { const auto *input_data = input.ptr(); @@ -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(); + const float* bias_data = bias.ptr(); + + 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::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 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(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(); @@ -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++) { diff --git a/modules/dnn/src/layers/group_norm_layer.cpp b/modules/dnn/src/layers/group_norm_layer.cpp index f8df14b98c..c614fd24de 100644 --- a/modules/dnn/src/layers/group_norm_layer.cpp +++ b/modules/dnn/src/layers/group_norm_layer.cpp @@ -5,6 +5,7 @@ #include "../precomp.hpp" #include #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(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()); 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()); @@ -56,6 +57,16 @@ public: return false; } + int getLayouts(const std::vector& actualInputs, + std::vector& desiredInputs, + const int requiredOutputs, + std::vector& 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()); diff --git a/modules/dnn/src/layers/instance_norm_layer.cpp b/modules/dnn/src/layers/instance_norm_layer.cpp index d695ca12f4..066bbd6af4 100644 --- a/modules/dnn/src/layers/instance_norm_layer.cpp +++ b/modules/dnn/src/layers/instance_norm_layer.cpp @@ -5,6 +5,7 @@ #include "../precomp.hpp" #include #include "./cpu_kernels/fast_norm.hpp" +#include // 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(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()); 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()); @@ -66,6 +69,16 @@ public: return false; } + int getLayouts(const std::vector& actualInputs, + std::vector& desiredInputs, + const int requiredOutputs, + std::vector& 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()); diff --git a/modules/dnn/src/layers/pad2_layer.cpp b/modules/dnn/src/layers/pad2_layer.cpp index 1c0e04c16e..0de0380553 100644 --- a/modules/dnn/src/layers/pad2_layer.cpp +++ b/modules/dnn/src/layers/pad2_layer.cpp @@ -152,6 +152,354 @@ static void pad(const Mat& inp, const std::vector& 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& 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 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 blockL(C1out), blockR(C1out), blockInStart(C1out); + std::vector seg0OutOff(C1out), seg0Len(C1out), seg0InC1(C1out), seg0InC0(C1out); + std::vector 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(inp.data); \ + T* outdata0 = reinterpret_cast(out.data); \ + T val0 = *reinterpret_cast(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& 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& 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& 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& semanticPads, + std::vector& 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& 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& actualInputs, + std::vector& desiredInputs, + const int requiredOutputs, + std::vector& 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 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& 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 semanticPads; std::vector padsbuf; - const std::vector* 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 semanticPads; std::vector padsbuf; - const std::vector* 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& 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& 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, ""); diff --git a/modules/dnn/src/layers/reshape2_layer.cpp b/modules/dnn/src/layers/reshape2_layer.cpp index 2f95325056..9a25b39968 100644 --- a/modules/dnn/src/layers/reshape2_layer.cpp +++ b/modules/dnn/src/layers/reshape2_layer.cpp @@ -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& actualInputs, + std::vector& desiredInputs, + const int requiredOutputs, + std::vector& 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& 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); } }; diff --git a/modules/dnn/src/layers/resize2_layer.cpp b/modules/dnn/src/layers/resize2_layer.cpp index beb4369fde..2f85b391f4 100644 --- a/modules/dnn/src/layers/resize2_layer.cpp +++ b/modules/dnn/src/layers/resize2_layer.cpp @@ -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 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(inp.data); + T* outptr0 = reinterpret_cast(out.data); + T ext = saturate_cast(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(inp.data); + T* outptr0 = reinterpret_cast(out.data); + T ext = saturate_cast(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(p00[c0]) + fx * (static_cast(p01[c0]) - static_cast(p00[c0])); + float bot = static_cast(p10[c0]) + fx * (static_cast(p11[c0]) - static_cast(p10[c0])); + outPix[c0] = saturate_cast(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(inp.data); + T* outptr0 = reinterpret_cast(out.data); + T ext = saturate_cast(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(row0[xx0 * inStep3 + c0]); + if (xx1 >= 0) val += wy0 * wx1 * static_cast(row0[xx1 * inStep3 + c0]); + if (xx2 >= 0) val += wy0 * wx2 * static_cast(row0[xx2 * inStep3 + c0]); + if (xx3 >= 0) val += wy0 * wx3 * static_cast(row0[xx3 * inStep3 + c0]); + } + if (row1) { + if (xx0 >= 0) val += wy1 * wx0 * static_cast(row1[xx0 * inStep3 + c0]); + if (xx1 >= 0) val += wy1 * wx1 * static_cast(row1[xx1 * inStep3 + c0]); + if (xx2 >= 0) val += wy1 * wx2 * static_cast(row1[xx2 * inStep3 + c0]); + if (xx3 >= 0) val += wy1 * wx3 * static_cast(row1[xx3 * inStep3 + c0]); + } + if (row2) { + if (xx0 >= 0) val += wy2 * wx0 * static_cast(row2[xx0 * inStep3 + c0]); + if (xx1 >= 0) val += wy2 * wx1 * static_cast(row2[xx1 * inStep3 + c0]); + if (xx2 >= 0) val += wy2 * wx2 * static_cast(row2[xx2 * inStep3 + c0]); + if (xx3 >= 0) val += wy2 * wx3 * static_cast(row2[xx3 * inStep3 + c0]); + } + if (row3) { + if (xx0 >= 0) val += wy3 * wx0 * static_cast(row3[xx0 * inStep3 + c0]); + if (xx1 >= 0) val += wy3 * wx1 * static_cast(row3[xx1 * inStep3 + c0]); + if (xx2 >= 0) val += wy3 * wx2 * static_cast(row3[xx2 * inStep3 + c0]); + if (xx3 >= 0) val += wy3 * wx3 * static_cast(row3[xx3 * inStep3 + c0]); + } + outPix[c0] = saturate_cast(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& actualInputs, + std::vector& desiredInputs, + const int requiredOutputs, + std::vector& 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& sizes, const std::vector& 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(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(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(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; diff --git a/modules/dnn/src/layers/shape_layer.cpp b/modules/dnn/src/layers/shape_layer.cpp index 56df592ee4..618ba3d68a 100644 --- a/modules/dnn/src/layers/shape_layer.cpp +++ b/modules/dnn/src/layers/shape_layer.cpp @@ -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& actualInputs, + std::vector& desiredInputs, + const int requiredOutputs, + std::vector& 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 &inputs, const int requiredOutputs, std::vector &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 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); diff --git a/modules/dnn/test/test_model.cpp b/modules/dnn/test/test_model.cpp index f5b429032c..10d5565797 100644 --- a/modules/dnn/test/test_model.cpp +++ b/modules/dnn/test/test_model.cpp @@ -1417,4 +1417,117 @@ INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_YOLOXS_ONNX, testing::ValuesIn(getAvailableTargets(DNN_BACKEND_OPENCV))); +typedef testing::TestWithParam Reproducibility_BlazeFace_ONNX; +TEST_P(Reproducibility_BlazeFace_ONNX, Accuracy) +{ + auto engine_forced = static_cast( + 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()[0] = 0.20f; + Mat iou(1, oneDim, CV_32F); iou.ptr()[0] = 0.30f; + Mat maxDet(1, oneDim, CV_64S); maxDet.ptr()[0] = 25; + + std::vector outNames = net.getUnconnectedOutLayersNames(); + std::vector 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(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 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