// This file is part of OpenCV project. // It is subject to the license terms in the LICENSE file found in the top-level directory // of this distribution and at http://opencv.org/license.html. // Copyright (C) 2025, BigVision LLC, all rights reserved. // Third party copyrights are property of their respective owners. #include "../precomp.hpp" #include "layers_common.hpp" #include "../op_cuda.hpp" #include "../op_inf_engine.hpp" #include "../op_cann.hpp" #include "../net_impl.hpp" #include // Implements ONNX Resize operator semantics (ai.onnx) as per onnx.ai documentation. // See: https://onnx.ai/onnx/operators/onnx__Resize.html (opsets 10, 11, 13, 18 supported) #ifdef HAVE_DNN_NGRAPH #include "../ie_ngraph.hpp" #include #endif #ifdef HAVE_CUDA #include "../cuda4dnn/primitives/resize.hpp" using namespace cv::dnn::cuda4dnn; #endif namespace cv { namespace dnn { namespace { enum class CoordTransMode { HALF_PIXEL, PYTORCH_HALF_PIXEL, TF_HALF_PIXEL_FOR_NN, TF_CROP_AND_RESIZE, HALF_PIXEL_SYMMETRIC, ASYMMETRIC }; static inline CoordTransMode parseCoordTransMode(const String& s) { if (s == "half_pixel") return CoordTransMode::HALF_PIXEL; if (s == "pytorch_half_pixel") return CoordTransMode::PYTORCH_HALF_PIXEL; if (s == "tf_half_pixel_for_nn") return CoordTransMode::TF_HALF_PIXEL_FOR_NN; if (s == "tf_crop_and_resize") return CoordTransMode::TF_CROP_AND_RESIZE; if (s == "half_pixel_symmetric") return CoordTransMode::HALF_PIXEL_SYMMETRIC; return CoordTransMode::ASYMMETRIC; } enum class NearestMode { FLOOR, CEIL, ROUND_PREFER_CEIL, ROUND_PREFER_FLOOR }; using std::clamp; static inline NearestMode parseNearestMode(const String& s) { if (s == "floor") return NearestMode::FLOOR; if (s == "ceil") return NearestMode::CEIL; if (s == "round_prefer_ceil") return NearestMode::ROUND_PREFER_CEIL; return NearestMode::ROUND_PREFER_FLOOR; } static constexpr int kResizeNumStripes = 16; inline float computeSrcGeneric(int dst, float scale, int limit, int len, CoordTransMode coordTransMode, bool /*halfPixelCenters*/, float start_coord = 0.0f, float end_coord = 1.0f) { if (coordTransMode == CoordTransMode::TF_CROP_AND_RESIZE) { if (len > 1) return start_coord * (limit - 1) + dst * (end_coord - start_coord) * (limit - 1) / float(len - 1); else return 0.5f * (start_coord + end_coord) * (limit - 1); } if (coordTransMode == CoordTransMode::PYTORCH_HALF_PIXEL) return (len > 1) ? (dst + 0.5f)*scale - 0.5f : 0.f; if (coordTransMode == CoordTransMode::HALF_PIXEL) return (dst + 0.5f)*scale - 0.5f; if (coordTransMode == CoordTransMode::TF_HALF_PIXEL_FOR_NN) return (dst + 0.5f)*scale; if (coordTransMode == CoordTransMode::HALF_PIXEL_SYMMETRIC) { // ONNX half_pixel_symmetric: offset = center*(1 - len_resized/(len_orig*x_scale)), // with scale == 1/x_scale and limit == input length. const float offset = limit*0.5f - len*scale*0.5f; return offset + (dst + 0.5f)*scale - 0.5f; } return dst*scale; } static inline void buildNearestIndexMap(std::vector& map, int outLen, int inLen, float scale, int len, float start_coord, float end_coord, CoordTransMode coordTransMode, NearestMode nearestMode, bool halfPixelCenters) { auto nearestIndex = [&](float src) { const int f = cvFloor(src); const float frac = src - f; const float eps = 1e-6f; int idx; if (nearestMode == NearestMode::FLOOR) idx = cvFloor(src); else if (nearestMode == NearestMode::CEIL) idx = cvCeil(src); else if (nearestMode == NearestMode::ROUND_PREFER_CEIL) { idx = (abs(frac - 0.5f) <= eps) ? (f + 1) : cvRound(src); } else { idx = (abs(frac - 0.5f) <= eps) ? f : cvRound(src); } return clamp(idx, 0, inLen - 1); }; map.resize(outLen); for (int i = 0; i < outLen; ++i) { float src = computeSrcGeneric(i, scale, inLen, len, coordTransMode, halfPixelCenters, start_coord, end_coord); if (coordTransMode == CoordTransMode::TF_CROP_AND_RESIZE) { if (src < 0.f || src >= float(inLen)) { map[i] = -1; // out of bounds continue; } } else { src = std::min(std::max(src, 0.f), float(inLen - 1)); } map[i] = nearestIndex(src); } } static inline void buildBilinearIndexAndLerp(std::vector& i0, std::vector& i1, std::vector& frac, std::vector& outOfBounds, int outLen, int inLen, float scale, int len, float start_coord, float end_coord, CoordTransMode coordTransMode, bool halfPixelCenters, bool tf_crop_and_resize_mode) { i0.resize(outLen); i1.resize(outLen); frac.resize(outLen); outOfBounds.assign(outLen, 0); for (int o = 0; o < outLen; ++o) { float src = computeSrcGeneric(o, scale, inLen, len, coordTransMode, halfPixelCenters, start_coord, end_coord); if (tf_crop_and_resize_mode) { int base = int(std::floor(src)); if (base < 0 || base >= inLen - 1) { outOfBounds[o] = 1; i0[o] = 0; i1[o] = 0; frac[o] = 0.0f; } else { i0[o] = base; i1[o] = base + 1; frac[o] = src - float(base); } } else { src = std::min(std::max(src, 0.f), std::max(0.f, float(inLen - 1) - 1e-6f)); int base = int(std::floor(src)); i0[o] = clamp(base, 0, inLen - 1); i1[o] = clamp(base + 1, 0, inLen - 1); frac[o] = src - float(base); } } } static inline void interpolateCubicResize(float x, float A, float* coeffs ) { coeffs[0] = ((A*(x + 1) - 5*A)*(x + 1) + 8*A)*(x + 1) - 4*A; coeffs[1] = ((A + 2)*x - (A + 3))*x*x + 1; coeffs[2] = ((A + 2)*(1 - x) - (A + 3))*(1 - x)*(1 - x) + 1; coeffs[3] = 1.f - coeffs[0] - coeffs[1] - coeffs[2]; } static inline void buildCubicIndexAndWeights(std::vector>& ids, std::vector>& weights, std::vector& outOfBounds, int outLen, int inLen, float scale, int len, float start_coord, float end_coord, CoordTransMode coordTransMode, bool halfPixelCenters, bool tf_crop_and_resize_mode, bool excludeOutside, float cubicA) { ids.resize(outLen); weights.resize(outLen); outOfBounds.assign(outLen, 0); for (int o = 0; o < outLen; ++o) { float src = computeSrcGeneric(o, scale, inLen, len, coordTransMode, halfPixelCenters, start_coord, end_coord); int i = int(std::floor(src)); float d = src - i; float sw = 0.f; bool hasOutOfBounds = false; interpolateCubicResize(d, cubicA, weights[o].data()); if (!tf_crop_and_resize_mode && !excludeOutside) { for (int k = -1; k <= 2; ++k) { int idx = clamp(i + k, 0, inLen - 1); ids[o][k+1] = idx; sw += weights[o][k+1]; } } else if (tf_crop_and_resize_mode) { for (int k = -1; k <= 2; ++k) { int idx = i + k; unsigned valid = (unsigned)idx < (unsigned)inLen; ids[o][k+1] = valid ? idx : -1; float w = weights[o][k+1]; float wv = valid ? w : 0.f; weights[o][k+1] = wv; sw += wv; hasOutOfBounds |= !valid; } } else { for (int k = -1; k <= 2; ++k) { int idx = i + k; unsigned valid = (unsigned)idx < (unsigned)inLen; ids[o][k+1] = valid ? idx : -1; float w = weights[o][k+1]; float wv = valid ? w : 0.f; weights[o][k+1] = wv; sw += wv; } } if (sw != 0.f) for (int k = 0; k < 4; ++k) weights[o][k] /= sw; if (tf_crop_and_resize_mode && hasOutOfBounds) outOfBounds[o] = 1; } } template void resizeNearest(const Mat &inp, Mat &out, float scaleH, float scaleW, int lenY, int lenX, NearestMode nearestMode, const String &coordTransMode, bool halfPixelCenters, float start_y = 0.0f, float end_y = 1.0f, float start_x = 0.0f, float end_x = 1.0f, float extrapolation_value = 0.0f) { int inH = inp.size[2], inW = inp.size[3]; int outH = out.size[2], outW = out.size[3]; CV_Assert(inp.isContinuous() && out.isContinuous()); 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, coordMode, nearestMode, halfPixelCenters); std::vector mapX(outW); 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) { int row0 = range.start * (outH * numPlanes) / nstripes; float extrapolation_value_ = extrapolation_value; int row1 = range.end * (outH * numPlanes) / nstripes - 1; int plane0 = row0 / outH, plane1 = row1 / outH; row0 %= outH; row1 %= outH; const int* mapYptr = mapY.data(); const int* mapXptr = mapX.data(); for (int p = plane0; p <= plane1; p++) { int y0 = p == plane0 ? row0 : 0; int y1 = p == plane1 ? row1 : outH - 1; for (int y = y0; y <= y1; y++) { int my = mapYptr[y]; if (tf_crop_and_resize_mode && my == -1) { T* outRowFill = outP.ptr(p * outH + y); for (int x = 0; x < outW; ++x) outRowFill[x] = T(extrapolation_value_); continue; } const T* inpRow = inpP.ptr(p * inH + my); T* outRow = outP.ptr(p * outH + y); for (int x = 0; x < outW; ++x) { int mx = mapXptr[x]; if (tf_crop_and_resize_mode && mx == -1) { outRow[x] = T(extrapolation_value_); } else { outRow[x] = inpRow[mx]; } } } } }, nstripes); } template void resizeBilinear(const Mat &inp, Mat &out, float scaleH, float scaleW, int lenY, int lenX, const String &coordTransMode, bool halfPixelCenters, float start_y = 0.0f, float end_y = 1.0f, float start_x = 0.0f, float end_x = 1.0f, float extrapolation_value = 0.0f) { int inH = inp.size[2], inW = inp.size[3]; int outH = out.size[2], outW = out.size[3]; CV_Assert(inp.isContinuous() && out.isContinuous()); CoordTransMode coordMode = parseCoordTransMode(coordTransMode); const bool tf_crop_and_resize_mode = (coordMode == CoordTransMode::TF_CROP_AND_RESIZE); std::vector x0(outW), x1(outW); std::vector lx(outW); std::vector outOfBoundsX(outW); buildBilinearIndexAndLerp(x0, x1, lx, outOfBoundsX, outW, inW, scaleW, lenX, start_x, end_x, coordMode, halfPixelCenters, tf_crop_and_resize_mode); std::vector y0(outH), y1(outH); std::vector ly(outH); std::vector outOfBoundsY(outH); buildBilinearIndexAndLerp(y0, y1, ly, outOfBoundsY, 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; int row1 = range.end * (outH * numPlanes) / nstripes - 1; int plane0 = row0 / outH, plane1 = row1 / outH; row0 %= outH; row1 %= outH; const int* y0ptr = y0.data(); const int* y1ptr = y1.data(); const float* lyptr = ly.data(); const int* x0ptr = x0.data(); const float* lxptr = lx.data(); const uint8_t* outOfBoundsYptr = outOfBoundsY.data(); const uint8_t* outOfBoundsXptr = outOfBoundsX.data(); float extrapolation_value_ = extrapolation_value; const bool tf_crop_and_resize_mode_ = tf_crop_and_resize_mode; std::vector hbufbuf(inW + 3); float* hbuf = hbufbuf.data() + 1; for (int p = plane0; p <= plane1; ++p) { int oy0 = (p == plane0) ? row0 : 0; int oy1 = (p == plane1) ? row1 : outH - 1; for (int oy = oy0; oy <= oy1; ++oy) { if (tf_crop_and_resize_mode_ && outOfBoundsYptr[oy]) { T* outRowFill = outP.ptr(p * outH + oy); for (int ox = 0; ox < outW; ++ox) outRowFill[ox] = T(extrapolation_value_); continue; } const T* row0ptr = inpP.ptr( p * inH + y0ptr[oy] ); const T* row1ptr = inpP.ptr( p * inH + y1ptr[oy] ); float fy = lyptr[oy]; T* outRowBase = outP.ptr( p * outH + oy ); for (int ix = 0; ix < inW; ++ix) { float v0 = static_cast(row0ptr[ix]); float v1 = static_cast(row1ptr[ix]); hbuf[ix] = v0 + fy * (v1 - v0); } hbuf[-1] = hbuf[0]; hbuf[inW] = hbuf[inW - 1]; hbuf[inW + 1] = hbuf[inW - 1]; for (int ox = 0; ox < outW; ++ox) { if (tf_crop_and_resize_mode_ && outOfBoundsXptr[ox]) { outRowBase[ox] = T(extrapolation_value_); } else { int xi = x0ptr[ox]; float fx = lxptr[ox]; float left = hbuf[xi]; float res = left + fx * (hbuf[xi + 1] - left); outRowBase[ox] = T(res); } } } } }, nstripes); } template void resizeCubic(const Mat &inp, Mat &out, float scaleH, float scaleW, int lenY, int lenX, float cubicA, bool excludeOutside, const String &coordTransMode, bool halfPixelCenters, float start_y = 0.0f, float end_y = 1.0f, float start_x = 0.0f, float end_x = 1.0f, float extrapolation_value = 0.0f) { int inH = inp.size[2], inW = inp.size[3]; int outH = out.size[2], outW = out.size[3]; CoordTransMode coordMode = parseCoordTransMode(coordTransMode); const bool tf_crop_and_resize_mode = (coordMode == CoordTransMode::TF_CROP_AND_RESIZE); std::vector> x_id(outW); std::vector> x_w (outW); std::vector outOfBoundsX(outW); buildCubicIndexAndWeights(x_id, x_w, outOfBoundsX, outW, inW, scaleW, lenX, start_x, end_x, coordMode, halfPixelCenters, tf_crop_and_resize_mode, excludeOutside, cubicA); std::vector> y_id(outH); std::vector> y_w (outH); std::vector outOfBoundsY(outH); buildCubicIndexAndWeights(y_id, y_w, outOfBoundsY, outH, inH, scaleH, lenY, start_y, end_y, 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; int row1 = range.end * (outH * numPlanes) / nstripes - 1; int plane0 = row0 / outH, plane1 = row1 / outH; row0 %= outH; row1 %= outH; const bool tf_crop_and_resize_mode_ = tf_crop_and_resize_mode; const uint8_t* outOfBoundsYptr = outOfBoundsY.data(); const uint8_t* outOfBoundsXptr = outOfBoundsX.data(); float extrapolation_value_ = extrapolation_value; std::vector hbuf(inW, 0.f); for (int p = plane0; p <= plane1; ++p) { int oy0 = (p == plane0) ? row0 : 0; int oy1 = (p == plane1) ? row1 : outH - 1; const T* inpBase = inpPlanes.ptr(p * inH); for (int oy = oy0; oy <= oy1; ++oy) { T* outRow = outPlanes.ptr(p * outH) + oy * outW; if (tf_crop_and_resize_mode_ && outOfBoundsYptr[oy]) { for (int ox = 0; ox < outW; ++ox) outRow[ox] = cv::saturate_cast(extrapolation_value_); continue; } const float w0y = y_w[oy][0]; const float w1y = y_w[oy][1]; const float w2y = y_w[oy][2]; const float w3y = y_w[oy][3]; int yy0, yy1, yy2, yy3; yy0 = y_id[oy][0]; yy1 = y_id[oy][1]; yy2 = y_id[oy][2]; yy3 = y_id[oy][3]; const T* ptr0 = (yy0 >= 0) ? (inpBase + (size_t)yy0 * inW) : nullptr; const T* ptr1 = (yy1 >= 0) ? (inpBase + (size_t)yy1 * inW) : nullptr; const T* ptr2 = (yy2 >= 0) ? (inpBase + (size_t)yy2 * inW) : nullptr; const T* ptr3 = (yy3 >= 0) ? (inpBase + (size_t)yy3 * inW) : nullptr; if (!ptr0 && !ptr1 && !ptr2 && !ptr3) { for (int ix = 0; ix < inW; ++ix) { hbuf[ix] = 0.f; } } else { const T* ptrNZ = ptr0 ? ptr0 : (ptr1 ? ptr1 : (ptr2 ? ptr2 : ptr3)); float w0 = ptr0 ? w0y : 0.f; float w1 = ptr1 ? w1y : 0.f; float w2 = ptr2 ? w2y : 0.f; float w3 = ptr3 ? w3y : 0.f; if (!ptr0) ptr0 = ptrNZ; if (!ptr1) ptr1 = ptrNZ; if (!ptr2) ptr2 = ptrNZ; if (!ptr3) ptr3 = ptrNZ; for (int ix = 0; ix < inW; ++ix) { hbuf[ix] = static_cast(ptr0[ix]) * w0 + static_cast(ptr1[ix]) * w1 + static_cast(ptr2[ix]) * w2 + static_cast(ptr3[ix]) * w3; } } for (int ox = 0; ox < outW; ++ox) { if (tf_crop_and_resize_mode_ && outOfBoundsXptr[ox]) { outRow[ox] = cv::saturate_cast(extrapolation_value_); continue; } const int xx = x_id[ox][1]; const float w0x = x_w[ox][0]; const float w1x = x_w[ox][1]; const float w2x = x_w[ox][2]; const float w3x = x_w[ox][3]; float val; if (1 <= xx && xx + 3 < inW) { val = hbuf[xx - 1] * w0x + hbuf[xx] * w1x + hbuf[xx + 1] * w2x + hbuf[xx + 2] * w3x; } else { const int xx0 = x_id[ox][0]; const int xx1 = x_id[ox][1]; const int xx2 = x_id[ox][2]; const int xx3 = x_id[ox][3]; val = 0.f; if (xx0 >= 0) val += hbuf[xx0] * w0x; if (xx1 >= 0) val += hbuf[xx1] * w1x; if (xx2 >= 0) val += hbuf[xx2] * w2x; if (xx3 >= 0) val += hbuf[xx3] * w3x; } outRow[ox] = cv::saturate_cast(val); } } } }, nstripes); } // ---- ONNX antialias (PIL-style) resampling ---------------------------------- static inline float aaTriangle(float x) { x = std::abs(x); return x < 1.f ? 1.f - x : 0.f; } static inline float aaCubic(float x, float a) { x = std::abs(x); if (x < 1.f) return ((a + 2.f)*x - (a + 3.f))*x*x + 1.f; if (x < 2.f) return a*(((x - 5.f)*x + 8.f)*x - 4.f); return 0.f; } // Per-output filter taps for one axis. After clamping out-of-bound samples to // the edge (exclude_outside == false), the contributing indices are contiguous, // so each output stores a start index 'lo', a 'cnt' and an offset into 'w'. struct AAWeights { std::vector lo, cnt, ofs; std::vector w; }; static void buildAAWeights(AAWeights& p, int inS, int outS, float xscale, bool cubic, float cubicA, CoordTransMode coordMode) { const float scaleC = 1.f / xscale; // input/output direction const float radius = cubic ? 2.f : 1.f; const float support = scaleC >= 1.f ? radius*scaleC : radius; const float inv = scaleC >= 1.f ? 1.f/scaleC : 1.f; p.lo.resize(outS); p.cnt.resize(outS); p.ofs.resize(outS); p.w.clear(); std::vector tmp; for (int y = 0; y < outS; y++) { const float center = computeSrcGeneric(y, scaleC, inS, outS, coordMode, true); const int xmin = (int)std::floor(center - support + 0.5f); const int xmax = (int)std::floor(center + support + 0.5f); // inclusive const int lo = std::min(std::max(xmin, 0), inS - 1); const int hi = std::min(std::max(xmax, 0), inS - 1); const int cnt = hi - lo + 1; tmp.assign(cnt, 0.f); float tot = 0.f; for (int x = xmin; x <= xmax; x++) { const float wt = cubic ? aaCubic((x - center)*inv, cubicA) : aaTriangle((x - center)*inv); const int idx = std::min(std::max(x, 0), inS - 1); tmp[idx - lo] += wt; tot += wt; } p.lo[y] = lo; p.cnt[y] = cnt; p.ofs[y] = (int)p.w.size(); for (int k = 0; k < cnt; k++) p.w.push_back(tot != 0.f ? tmp[k] / tot : 0.f); } } template void resizeAntialias(const Mat& inp, Mat& out, float xscaleH, float xscaleW, bool cubic, float cubicA, CoordTransMode coordMode) { CV_Assert(inp.dims == 4 && out.dims == 4 && inp.isContinuous() && out.isContinuous()); const int N = inp.size[0], C = inp.size[1]; const int inH = inp.size[2], inW = inp.size[3]; const int outH = out.size[2], outW = out.size[3]; AAWeights px, py; buildAAWeights(px, inW, outW, xscaleW, cubic, cubicA, coordMode); buildAAWeights(py, inH, outH, xscaleH, cubic, cubicA, coordMode); const int planes = N * C; parallel_for_(Range(0, planes), [&](const Range& r) { std::vector buf((size_t)inH * outW); for (int pl = r.start; pl < r.end; pl++) { const T* inPlane = inp.ptr(0) + (size_t)pl * inH * inW; T* outPlane = out.ptr(0) + (size_t)pl * outH * outW; // Horizontal pass: inp[inH x inW] -> buf[inH x outW]. for (int y = 0; y < inH; y++) { const T* inRow = inPlane + (size_t)y * inW; float* bufRow = buf.data() + (size_t)y * outW; for (int ox = 0; ox < outW; ox++) { const float* w = px.w.data() + px.ofs[ox]; const int lo = px.lo[ox], cnt = px.cnt[ox]; float acc = 0.f; for (int k = 0; k < cnt; k++) acc += w[k] * (float)inRow[lo + k]; bufRow[ox] = acc; } } // Vertical pass: buf[inH x outW] -> out[outH x outW]. for (int oy = 0; oy < outH; oy++) { const float* w = py.w.data() + py.ofs[oy]; const int lo = py.lo[oy], cnt = py.cnt[oy]; T* outRow = outPlane + (size_t)oy * outW; for (int ox = 0; ox < outW; ox++) { float acc = 0.f; for (int k = 0; k < cnt; k++) acc += w[k] * buf[(size_t)(lo + k) * outW + ox]; outRow[ox] = saturate_cast(acc); } } } }); } } class Resize2LayerImpl : public Resize2Layer { public: int outWidth0, outHeight0; Resize2LayerImpl(const LayerParams& params) : zoomFactorWidth(params.get("zoom_factor_x", params.get("zoom_factor", 0))), zoomFactorHeight(params.get("zoom_factor_y", params.get("zoom_factor", 0))), scaleWidth(0), scaleHeight(0), cubicCoeffA(params.get("cubic_coeff_a", -0.75f)), roi_start_y(0.0f), roi_end_y(1.0f), roi_start_x(0.0f), roi_end_x(1.0f), extrapolation_value(params.get("extrapolation_value", 0.0f)) { setParamsFrom(params); outWidth = outWidth0 = params.get("width", 0); outHeight = outHeight0 = params.get("height", 0); if (params.has("zoom_factor")) { CV_Assert(!params.has("zoom_factor_x") && !params.has("zoom_factor_y")); } else if (params.has("zoom_factor_x") || params.has("zoom_factor_y")) { CV_Assert(params.has("zoom_factor_x") && params.has("zoom_factor_y")); } interpolation = params.get("interpolation"); // Keep nearest_mode if provided (ONNX attribute). Default is "round_prefer_floor" as per ONNX spec. nearestModeE = parseNearestMode(params.get("nearest_mode", "round_prefer_floor")); CV_Check(interpolation, interpolation == "nearest" || interpolation == "opencv_linear" || interpolation == "bilinear" || interpolation == "cubic", ""); excludeOutside = params.get("exclude_outside", false); dynamicROI = params.get("dynamic_roi", false); alignCorners = params.get("align_corners", false); halfPixelCenters = params.get("half_pixel_centers", false); coordTransMode = params.get("coordinate_transformation_mode", "half_pixel"); coordTransModeE = parseCoordTransMode(coordTransMode); if (interpolation == "opencv_linear") halfPixelCenters = true; keepAspectPolicy = params.get("keep_aspect_ratio_policy", "stretch"); antialias = params.get("antialias", 0) != 0; if (params.has("axes")) { const DictValue& a = params.get("axes"); axesAttr.resize(a.size()); for (int i = 0; i < a.size(); i++) axesAttr[i] = a.get(i); } } // Map the H (axis 2) and W (axis 3) entries within a 2- or 4-element // sizes/scales vector, honoring the ONNX "axes" attribute order. void spatialIndices(size_t nelems, int& hIdx, int& wIdx) const { if (nelems == 4) { hIdx = 2; wIdx = 3; } else { hIdx = 0; wIdx = 1; } if (axesAttr.size() == nelems) { int foundH = -1, foundW = -1; for (size_t k = 0; k < nelems; k++) { int ax = axesAttr[k] < 0 ? axesAttr[k] + 4 : axesAttr[k]; if (ax == 2) foundH = (int)k; else if (ax == 3) foundW = (int)k; } if (foundH >= 0 && foundW >= 0) { hIdx = foundH; wIdx = foundW; } } } bool dynamicOutputShapes() const CV_OVERRIDE { if (dynamicROI) return true; size_t ninputs = inputs.size(); if (ninputs <= 1 && ((outWidth0 > 0 && outHeight0 > 0) || (zoomFactorWidth > 0 && zoomFactorHeight > 0))) return false; Net::Impl* netimpl_ = getNetImpl(this); if (!netimpl_) return true; for (size_t i = 1; i < ninputs; i++) { if (!netimpl_->isConstArg(inputs[i])) return true; } 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 { // ONNX Resize allows either "sizes" or "scales" input. These tensors may // describe all 4 dims (N,C,H,W) or only spatial dims (H,W) when accompanied // by an "axes" input equal to {2,3}. To stay backwards-compatible, we keep // the legacy 4-element handling but also accept 2-element vectors. CV_Assert((sizes.empty() ^ scales.empty()) && (sizes.empty() ? (scales.size() == 4 || scales.size() == 2) : (sizes.size() == 4 || sizes.size() == 2))); MatShape outShape = inpShape; const int inH = inpShape[2], inW = inpShape[3]; if (!sizes.empty()) { int hIdx, wIdx; spatialIndices(sizes.size(), hIdx, wIdx); int szH = sizes[hIdx], szW = sizes[wIdx]; if (keepAspectPolicy == "not_larger" || keepAspectPolicy == "not_smaller") { float scH = float(szH) / inH, scW = float(szW) / inW; float sc = keepAspectPolicy == "not_larger" ? std::min(scH, scW) : std::max(scH, scW); outShape[2] = int(std::round(sc * inH)); outShape[3] = int(std::round(sc * inW)); } else { outShape[2] = szH; outShape[3] = szW; } } else { int hIdx, wIdx; spatialIndices(scales.size(), hIdx, wIdx); outShape[2] = cvFloor(inH * scales[hIdx]); outShape[3] = cvFloor(inW * scales[wIdx]); } return outShape; } bool getMemoryShapes(const std::vector &inputs, const int requiredOutputs, std::vector &outputs, std::vector &internals) const CV_OVERRIDE { size_t ninputs = inputs.size(); CV_Assert(ninputs == 1 || ninputs == 2 || ninputs >= 4); outputs.resize(1, inputs[0]); // Rank-3 (N,C,W): 1-D resize of the W axis; width baked into params, stays rank-3. if (inputs[0].dims == 3) { CV_CheckEQ(ninputs, (size_t)1, "1-D Resize2 expects sizes baked as width param"); outputs[0][2] = zoomFactorWidth > 0 ? cvFloor(inputs[0][2] * zoomFactorWidth) : outWidth0; return outputs[0][2] == inputs[0][2]; } // New ONNX importer may provide "sizes" or "scales" via constant blobs // (blobs[0] = roi, blobs[1] = scales, blobs[2] = sizes, blobs[3] = axes). if (ninputs == 1 && !this->blobs.empty()) { std::vector sizes; std::vector scales; if (this->blobs.size() >= 3 && this->blobs[2].total() > 0) tensorToIntVec(this->blobs[2], sizes); if (this->blobs.size() >= 2 && this->blobs[1].total() > 0) tensorToFloatVec(this->blobs[1], scales); if (!sizes.empty() || !scales.empty()) { outputs[0] = getOutShape(inputs[0], sizes, scales); // in-place if spatial dims unchanged return (outputs[0][2] == inputs[0][2]) && (outputs[0][3] == inputs[0][3]); } } if (ninputs == 1) { outputs[0][2] = zoomFactorHeight > 0 ? cvFloor(inputs[0][2] * zoomFactorHeight) : outHeight0; outputs[0][3] = zoomFactorWidth > 0 ? cvFloor(inputs[0][3] * zoomFactorWidth) : outWidth0; } else if (ninputs == 2 && inputs[1].dims == 4) { outputs[0][2] = inputs[1][2]; outputs[0][3] = inputs[1][3]; } else { Net::Impl* netimpl_ = getNetImpl(this); std::vector sizes; std::vector scales; if (ninputs >= 4) { Mat sizesTensor = netimpl_->argTensor(this->inputs[3]); tensorToIntVec(sizesTensor, sizes); } Mat scalesTensor = netimpl_->argTensor(this->inputs[(ninputs == 2) ? 1 : 2]); tensorToFloatVec(scalesTensor, scales); outputs[0] = getOutShape(inputs[0], sizes, scales); } // We can work in-place (do nothing) if input shape == output shape. return (outputs[0][2] == inputs[0][2]) && (outputs[0][3] == inputs[0][3]); } // Only resizeNearest has a genuine CV_32S path; other modes reject it below. void getTypes(const std::vector& inputs, const int requiredOutputs, const int requiredInternals, std::vector& outputs, std::vector& internals) const CV_OVERRIDE { CV_Assert(inputs.size()); for (auto input : inputs) CV_CheckType(input, input == CV_32F || input == CV_64F || input == CV_8S || input == CV_8U || input == CV_64S || input == CV_32S, ""); outputs.assign(requiredOutputs, inputs[0]); internals.assign(requiredInternals, inputs[0]); } virtual bool supportBackend(int backendId) CV_OVERRIDE { if (backendId == DNN_BACKEND_CUDA) return interpolation == "nearest" || interpolation == "bilinear" || interpolation == "opencv_linear"; if (backendId == DNN_BACKEND_CANN) return interpolation == "nearest" || interpolation == "bilinear" || interpolation == "opencv_linear"; #ifdef HAVE_INF_ENGINE if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) { return (interpolation == "nearest" && scaleWidth == scaleHeight) || (interpolation == "bilinear"); } #endif return backendId == DNN_BACKEND_OPENCV; } void updateOutSizeAndScale(const MatShape& inpShape, const MatShape& outShape) { CV_Assert(inpShape.dims >= 4 && outShape.dims >= 4); outHeight = outShape[2]; outWidth = outShape[3]; if (alignCorners && outHeight > 1) scaleHeight = float(inpShape[2] - 1) / (outHeight - 1); else scaleHeight = float(inpShape[2]) / outHeight; if (alignCorners && outWidth > 1) scaleWidth = float(inpShape[3] - 1) / (outWidth - 1); else scaleWidth = float(inpShape[3]) / outWidth; } 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()); std::vector sizes; std::vector scales; std::vector inputs; inputs_arr.getMatVector(inputs); size_t ninputs = inputs.size(); CV_Assert(ninputs > 0); Mat& inp_ = inputs[0]; MatShape inpShape = inp_.shape(); MatShape outShape; // Rank-3 (N,C,W): fold a unit H axis so the rank-4 kernels run, unfold on output. const bool fold1d = (inp_.dims == 3); MatShape outShape1d; if (fold1d) { int outW = zoomFactorWidth > 0 ? cvFloor(inpShape[2] * zoomFactorWidth) : outWidth0; outShape1d = inpShape; outShape1d[2] = outW; inp_ = inp_.reshape(1, MatShape({inpShape[0], inpShape[1], 1, inpShape[2]})); inpShape = inp_.shape(); } if (ninputs == 1) { outShape = inpShape; outShape[2] = zoomFactorHeight > 0 ? cvFloor(inpShape[2] * zoomFactorHeight) : outHeight0; outShape[3] = zoomFactorWidth > 0 ? cvFloor(inpShape[3] * zoomFactorWidth) : outWidth0; } else if (ninputs == 2 && inputs[0].dims == 4 && inputs[1].dims == 4) { outShape = inpShape; outShape[2] = inputs[1].size[2]; outShape[3] = inputs[1].size[3]; } else { if (ninputs >= 4) { Mat sizesTensor = inputs[3]; tensorToIntVec(sizesTensor, sizes); } Mat scalesTensor = inputs[(ninputs == 2) ? 1 : 2]; tensorToFloatVec(scalesTensor, scales); outShape = getOutShape(inpShape, sizes, scales); } int length_resized_y = outShape[2]; int length_resized_x = outShape[3]; updateOutSizeAndScale(inpShape, outShape); // Read ROI if dynamicROI is enabled if (dynamicROI && coordTransModeE == CoordTransMode::TF_CROP_AND_RESIZE && ninputs >= 2) { Mat roiTensor = inputs[1]; std::vector roi; tensorToFloatVec(roiTensor, roi); if (axesAttr.size() == 2 && roi.size() == 4) { // ROI given per "axes": [start_axes[0], start_axes[1], end_axes[0], end_axes[1]] float start[4] = {0.f, 0.f, 0.f, 0.f}; float end[4] = {1.f, 1.f, 1.f, 1.f}; for (int k = 0; k < 2; k++) { int ax = axesAttr[k] < 0 ? axesAttr[k] + 4 : axesAttr[k]; start[ax] = roi[k]; end[ax] = roi[2 + k]; } roi_start_y = start[2]; roi_start_x = start[3]; roi_end_y = end[2]; roi_end_x = end[3]; } else if (roi.size() >= 4) { if (roi.size() == 4) { roi_start_y = roi[0]; roi_start_x = roi[1]; roi_end_y = roi[2]; roi_end_x = roi[3]; } else if (roi.size() == 6) { roi_start_y = roi[1]; roi_start_x = roi[2]; roi_end_y = roi[4]; roi_end_x = roi[5]; } else if (roi.size() == 8) { roi_start_y = roi[2]; roi_start_x = roi[3]; roi_end_y = roi[6]; roi_end_x = roi[7]; } } } if (sizes.empty() && !scales.empty() && halfPixelCenters) { int hIdx, wIdx; spatialIndices(scales.size(), hIdx, wIdx); scaleHeight = 1.f / scales[hIdx]; scaleWidth = 1.f / scales[wIdx]; } else if (sizes.empty() && !scales.empty() && alignCorners) { int hIdx, wIdx; spatialIndices(scales.size(), hIdx, wIdx); float lenH = inpShape[2] * scales[hIdx]; float lenW = inpShape[3] * scales[wIdx]; if (lenH > 1.f) scaleHeight = float(inpShape[2] - 1) / (lenH - 1.f); if (lenW > 1.f) scaleWidth = float(inpShape[3] - 1) / (lenW - 1.f); } auto kind = outputs_arr.kind(); Mat out_; UMat uout_; if (kind == _InputArray::STD_VECTOR_MAT) { std::vector& outputs = outputs_arr.getMatVecRef(); outputs[0].fit(fold1d ? outShape1d : outShape, inp_.type()); out_ = fold1d ? outputs[0].reshape(1, outShape) : outputs[0]; if (outShape == inpShape) { inp_.copyTo(out_); return; } } else { CV_Assert(kind == _InputArray::STD_VECTOR_UMAT); std::vector& u_outputs = outputs_arr.getUMatVecRef(); u_outputs[0].fit(fold1d ? outShape1d : outShape, inp_.type()); uout_ = fold1d ? u_outputs[0].reshape(1, outShape) : u_outputs[0]; if (outShape == inpShape) { inp_.copyTo(uout_); return; } out_.create(outShape, inp_.type()); } int depth = inp_.type(), orig_depth = depth; // Bilinear/cubic/antialias have no fixed-point CV_32S path yet. if (depth == CV_32S && interpolation != "nearest") { CV_Error(Error::StsNotImplemented, "Resize2: CV_32S is currently only supported with nearest-neighbor " "interpolation; bilinear/cubic/antialias would need a fixed-point " "implementation to preserve int32 precision"); } Mat inp, out; if (depth != CV_32F && depth != CV_8S && depth != CV_8U && depth != CV_16F && depth != CV_16BF && depth != CV_32S) { inp_.convertTo(inp, CV_32F); out.fit(outShape, CV_32F); depth = CV_32F; } else { inp = inp_; out = out_; } if (antialias && inp.dims == 4 && (interpolation == "bilinear" || interpolation == "opencv_linear" || interpolation == "cubic")) { const bool cubic = (interpolation == "cubic"); float xsH, xsW; if (!scales.empty()) { int hIdx, wIdx; spatialIndices(scales.size(), hIdx, wIdx); xsH = scales[hIdx]; xsW = scales[wIdx]; } else { xsH = float(outShape[2]) / inpShape[2]; xsW = float(outShape[3]) / inpShape[3]; } switch (depth) { case CV_8S: resizeAntialias(inp, out, xsH, xsW, cubic, cubicCoeffA, coordTransModeE); break; case CV_8U: resizeAntialias(inp, out, xsH, xsW, cubic, cubicCoeffA, coordTransModeE); break; case CV_16F: resizeAntialias(inp, out, xsH, xsW, cubic, cubicCoeffA, coordTransModeE); break; case CV_16BF: resizeAntialias(inp, out, xsH, xsW, cubic, cubicCoeffA, coordTransModeE); break; case CV_32F: resizeAntialias(inp, out, xsH, xsW, cubic, cubicCoeffA, coordTransModeE); break; default: CV_Error(Error::StsUnsupportedFormat, "Unsupported depth"); } } else if(interpolation=="nearest"){ switch(depth){ case CV_8S: 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; case CV_16BF: 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_32F: 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_32S: 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; default: CV_Error(Error::StsUnsupportedFormat,"Unsupported depth"); } } else if(interpolation=="bilinear"||interpolation=="opencv_linear"){ switch(depth){ case CV_8S: resizeBilinear(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value); break; case CV_8U: resizeBilinear(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value); break; case CV_16F: resizeBilinear(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value); break; case CV_16BF: resizeBilinear(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value); break; case CV_32F: resizeBilinear(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value); break; default: CV_Error(Error::StsUnsupportedFormat,"Unsupported depth"); } } else if(interpolation=="cubic"){ switch (depth) { case CV_8S: resizeCubic(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,cubicCoeffA,excludeOutside,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value); break; case CV_8U: resizeCubic(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,cubicCoeffA,excludeOutside,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value); break; case CV_16F: resizeCubic(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,cubicCoeffA,excludeOutside,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value); break; case CV_16BF: resizeCubic(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,cubicCoeffA,excludeOutside,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value); break; case CV_32F: resizeCubic(inp,out,scaleHeight,scaleWidth,length_resized_y,length_resized_x,cubicCoeffA,excludeOutside,coordTransMode,halfPixelCenters,roi_start_y,roi_end_y,roi_start_x,roi_end_x,extrapolation_value); break; default: CV_Error(Error::StsUnsupportedFormat, "Unsupported depth"); } } else CV_Error(Error::StsNotImplemented,"Unknown interpolation: "+interpolation); if (orig_depth != depth) { if (!uout_.empty()) out.convertTo(uout_, orig_depth); else out.convertTo(out_, orig_depth); } else if (!uout_.empty()) { out.copyTo(uout_); } } #ifdef HAVE_CANN virtual Ptr initCann(const std::vector > &inputs, const std::vector > &outputs, const std::vector >& nodes) CV_OVERRIDE { auto x = inputs[0].dynamicCast(); auto x_desc = x->getTensorDesc(); auto op_x = nodes[0].dynamicCast()->getOp(); auto output_y_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); // create operator if (interpolation == "nearest") { auto op = std::make_shared(name); // set attributes op->set_attr_align_corners(alignCorners); op->set_attr_half_pixel_centers(halfPixelCenters); // set inputs : x op->set_input_x_by_name(*op_x, x->name.c_str()); op->update_input_desc_x(*x_desc); // set inputs : size std::vector shape_of_size_mat{2}; std::vector size_vec{outHeight, outWidth}; Mat size_mat(shape_of_size_mat, CV_32S, size_vec.data()); auto op_const_size = std::make_shared(size_mat.data, size_mat.type(), shape_of_size_mat, cv::format("%s_size", name.c_str())); op->set_input_size(*(op_const_size->getOp())); op->update_input_desc_size(*(op_const_size->getTensorDesc())); // set outputs op->update_output_desc_y(*output_y_desc); return Ptr(new CannBackendNode(op)); } else if (interpolation == "opencv_linear" || interpolation == "bilinear") { auto op = std::make_shared(name); // set attributes op->set_attr_align_corners(alignCorners); op->set_attr_half_pixel_centers(halfPixelCenters); std::vector taget_size{(int64_t)outHeight, (int64_t)outWidth}; op->set_attr_size(taget_size); // set inputs : x op->set_input_x_by_name(*op_x, x->name.c_str()); op->update_input_desc_x(*x_desc); // set outputs op->update_output_desc_y(*output_y_desc); return Ptr(new CannBackendNode(op)); } else CV_Error(Error::StsNotImplemented, "Unsupported interpolation by CANN backend: " + interpolation); } #endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, const std::vector >& nodes) CV_OVERRIDE { auto& ieInpNode = nodes[0].dynamicCast()->node; ov::op::v4::Interpolate::InterpolateAttrs attrs; if (interpolation == "nearest") { attrs.mode = ov::op::v4::Interpolate::InterpolateMode::NEAREST; attrs.coordinate_transformation_mode = ov::op::v4::Interpolate::CoordinateTransformMode::HALF_PIXEL; } else if (interpolation == "bilinear") { attrs.mode = ov::op::v4::Interpolate::InterpolateMode::LINEAR_ONNX; attrs.coordinate_transformation_mode = ov::op::v4::Interpolate::CoordinateTransformMode::ASYMMETRIC; } else { CV_Error(Error::StsNotImplemented, format("Unsupported interpolation: %s", interpolation.c_str())); } attrs.shape_calculation_mode = ov::op::v4::Interpolate::ShapeCalcMode::SIZES; CV_Assert(!halfPixelCenters || !alignCorners); if (halfPixelCenters) { attrs.coordinate_transformation_mode = ov::op::v4::Interpolate::CoordinateTransformMode::HALF_PIXEL; } else if (alignCorners) { attrs.coordinate_transformation_mode = ov::op::v4::Interpolate::CoordinateTransformMode::ALIGN_CORNERS; } attrs.nearest_mode = ov::op::v4::Interpolate::NearestMode::ROUND_PREFER_FLOOR; std::vector shape = {outHeight, outWidth}; auto out_shape = std::make_shared(ov::element::i64, ov::Shape{2}, shape.data()); auto& input_shape = ieInpNode.get_shape(); CV_Assert_N(input_shape[2] != 0, input_shape[3] != 0); std::vector scales = {static_cast(outHeight) / input_shape[2], static_cast(outWidth) / input_shape[3]}; auto scales_shape = std::make_shared(ov::element::f32, ov::Shape{2}, scales.data()); auto axes = std::make_shared(ov::element::i64, ov::Shape{2}, std::vector{2, 3}); auto interp = std::make_shared(ieInpNode, out_shape, scales_shape, axes, attrs); return Ptr(new InfEngineNgraphNode(interp)); } #endif // HAVE_DNN_NGRAPH #ifdef HAVE_CUDA Ptr initCUDA( void *context_, const std::vector>& inputs, const std::vector>& outputs ) override { auto context = reinterpret_cast(context_); cuda4dnn::ResizeConfiguration config; if (interpolation == "nearest") { config.type = InterpolationType::NEAREST_NEIGHBOUR; config.align_corners = alignCorners; config.half_pixel_centers = halfPixelCenters; } else if (interpolation == "bilinear") { config.type = InterpolationType::BILINEAR; config.align_corners = alignCorners; config.half_pixel_centers = halfPixelCenters; } else if (interpolation == "opencv_linear") { config.type = InterpolationType::BILINEAR; config.align_corners = false; config.half_pixel_centers = true; } else CV_Error(Error::StsNotImplemented, "Requested interpolation mode is not available in resize layer."); return make_cuda_node(preferableTarget, std::move(context->stream), config); } #endif protected: int outWidth, outHeight; const float zoomFactorWidth, zoomFactorHeight; String interpolation; float scaleWidth, scaleHeight; bool alignCorners; bool dynamicROI; bool halfPixelCenters; String coordTransMode; CoordTransMode coordTransModeE; NearestMode nearestModeE; // ONNX "nearest_mode" attribute bool excludeOutside; // ONNX attribute for cubic float cubicCoeffA; float roi_start_y, roi_end_y, roi_start_x, roi_end_x; float extrapolation_value; // Extrapolation value for tf_crop_and_resize mode std::vector axesAttr; // ONNX "axes" attribute (subset of dims that sizes/scales refer to) String keepAspectPolicy; // ONNX "keep_aspect_ratio_policy": stretch|not_larger|not_smaller bool antialias; // ONNX "antialias" attribute (filter stretching when downsampling) }; Ptr Resize2Layer::create(const LayerParams& params) { return Ptr(new Resize2LayerImpl(params)); } } // namespace dnn } // namespace cv