From 8bcd7e122a1be544e2abee9058a7ff11ca6d52f5 Mon Sep 17 00:00:00 2001 From: Lubov Batanina Date: Thu, 11 Jul 2019 20:13:52 +0300 Subject: [PATCH] Merge pull request #14842 from l-bat:ocv_conv3d * Support Conv3D on OCV backend * Add header * Add perf tests * Support pool3d * Enable Resnet34_kinetics on OCV backend * Add test * Fix conv * Optimize Conv2D --- modules/dnn/perf/perf_convolution3d.cpp | 182 +++++++++++++ modules/dnn/src/layers/convolution_layer.cpp | 270 +++++++++++++------ modules/dnn/src/layers/pooling_layer.cpp | 174 ++++++++---- modules/dnn/test/test_onnx_importer.cpp | 28 +- modules/dnn/test/test_tf_importer.cpp | 12 +- 5 files changed, 515 insertions(+), 151 deletions(-) create mode 100644 modules/dnn/perf/perf_convolution3d.cpp diff --git a/modules/dnn/perf/perf_convolution3d.cpp b/modules/dnn/perf/perf_convolution3d.cpp new file mode 100644 index 0000000000..1f512b2a15 --- /dev/null +++ b/modules/dnn/perf/perf_convolution3d.cpp @@ -0,0 +1,182 @@ +// 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. + +#include "perf_precomp.hpp" +#include + +namespace opencv_test { + +struct Conv3DParam_t { + int kernel[3]; + struct BlobShape { int dims[5]; } shapeIn; + int outCN; + int groups; + int stride[3]; + int dilation[3]; + int pad[6]; + const char* padMode; + bool hasBias; + double declared_flops; +}; +// Details: #12142 +static const Conv3DParam_t testConvolution3DConfigs[] = { + {{3, 3, 3}, {{1, 6, 10, 38, 50}}, 6, 1, {1, 1, 1}, {1, 1, 1}, {0, 0, 0, 0, 0, 0}, "VALID", true, 26956800.}, + {{3, 3, 3}, {{1, 2, 19, 19, 19}}, 2, 2, {2, 2, 2}, {1, 1, 1}, {1, 1, 1, 1, 1, 1}, "", true, 218000.}, + {{3, 3, 3}, {{1, 2, 25, 19, 19}}, 2, 2, {1, 2, 2}, {1, 1, 1}, {2, 2, 2, 2, 2, 2}, "SAME", false, 545000.}, + {{3, 3, 3}, {{1, 11, 9, 150, 200}}, 11, 1, {1, 1, 1}, {1, 1, 1}, {0, 0, 0, 0, 0, 0}, "VALID", true, 1342562760.}, + {{3, 3, 3}, {{1, 10, 98, 10, 10}}, 10, 1, {1, 1, 1}, {1, 1, 1}, {1, 0, 1, 1, 0,1}, "SAME", false, 53018000.}, + {{5, 5, 5}, {{1, 6, 19, 19, 19}}, 6, 2, {1, 1, 1}, {1, 1, 1}, {0, 0, 0, 0, 0, 0}, "", false, 30395250.}, + {{5, 5, 5}, {{1, 4, 50, 19, 19}}, 4, 1, {2, 2, 2}, {1, 1, 1}, {1, 1, 1, 1, 1, 1}, "VALID", false, 5893888.}, + {{5, 5, 5}, {{1, 3, 75, 75, 100}}, 3, 1, {1, 1, 1}, {1, 1, 1}, {0, 0, 0, 0, 0, 0}, "SAME", true, 1267312500.}, + {{5, 5, 5}, {{1, 2, 21, 75, 100}}, 2, 1, {1, 1, 1}, {1, 1, 1}, {0, 0, 0, 0, 0, 0}, "", true, 116103744.}, + {{5, 5, 5}, {{1, 4, 40, 75, 75}}, 4, 1, {2, 2, 2}, {1, 1, 1}, {0, 0, 0, 0, 0, 0}, "", false, 93405312.}, + {{7, 7, 7}, {{1, 6, 15, 19, 19}}, 6, 1, {2, 1, 1}, {1, 1, 1}, {3, 3, 3, 3, 3, 3}, "SAME", true, 71339376.}, + {{7, 7, 7}, {{1, 2, 38, 38, 38}}, 2, 1, {1, 2, 1}, {1, 1, 1}, {0, 0, 0, 0, 0, 0}, "", false, 44990464.}, + {{1, 1, 1}, {{1, 4, 9, 10, 10}}, 4, 1, {1, 1, 2}, {1, 1, 1}, {1, 1, 1, 1, 1, 1}, "VALID", false, 16200.}, + {{3, 1, 4}, {{1, 14, 5, 10, 10}}, 14, 1, {1, 1, 1}, {1, 1, 1}, {0, 0, 0, 0, 0, 0}, "SAME", false, 2359000.}, + {{1, 1, 1}, {{1, 8, 1, 10, 10}}, 8, 8, {1, 1, 1}, {1, 1, 1}, {1, 1, 1, 1, 1, 1}, "", true, 58752.}, + {{3, 4, 2}, {{1, 4, 8, 10, 10}}, 4, 4, {1, 2, 1}, {1, 1, 1}, {0, 0, 0, 0, 0, 0}, "", true, 166752.} +}; + +struct Conv3DParamID +{ + enum { + CONV_0 = 0, + CONV_100 = 16, + CONV_LAST = sizeof(testConvolution3DConfigs) / sizeof(testConvolution3DConfigs[0]) + }; + int val_; \ + Conv3DParamID(int val = 0) : val_(val) {} + operator int() const { return val_; } + static ::testing::internal::ParamGenerator all() + { +#if 0 + enum { NUM = (int)CONV_LAST }; +#else + enum { NUM = (int)CONV_100 }; +#endif + Conv3DParamID v_[NUM]; for (int i = 0; i < NUM; ++i) { v_[i] = Conv3DParamID(i); } // reduce generated code size + return ::testing::ValuesIn(v_, v_ + NUM); + } +}; \ +static inline void PrintTo(const Conv3DParamID& v, std::ostream* os) +{ + CV_Assert((int)v >= 0); CV_Assert((int)v < Conv3DParamID::CONV_LAST); + const Conv3DParam_t& p = testConvolution3DConfigs[(int)v]; + + *os << "GFLOPS=" << cv::format("%.3f", p.declared_flops * 1e-9) + << ", K=[" << p.kernel[0] << " x " << p.kernel[1] << " x " << p.kernel[2] << "]" + << ", IN={" << p.shapeIn.dims[0] << ", " << p.shapeIn.dims[1] << ", " << p.shapeIn.dims[2] << ", " << p.shapeIn.dims[3] << ", " << p.shapeIn.dims[4] << "}" + << ", OCN=" << p.outCN; + if (p.groups > 1) + *os << ", G=" << p.groups; + if (p.stride[0] * p.stride[1] * p.stride[2] != 1) + *os << ", S=[" << p.stride[0] << " x " << p.stride[1] << " x " << p.stride[2] << "]"; + if (p.dilation[0] * p.dilation[1] * p.dilation[2] != 1) + *os << ", D=[" << p.dilation[0] << " x " << p.dilation[1] << " x " << p.dilation[2] << "]"; + if (p.pad[0] != 0 && p.pad[1] != 0 && p.pad[2] != 0 && + p.pad[3] != 0 && p.pad[4] != 0 && p.pad[5] != 0) + *os << ", P=(" << p.pad[0] << ", " << p.pad[3] << ") x (" + << p.pad[1] << ", " << p.pad[4] << ") x (" + << p.pad[2] << ", " << p.pad[5] << ")"; + if (!((std::string)p.padMode).empty()) + *os << ", PM=" << ((std::string)p.padMode); + if (p.hasBias) + *os << ", BIAS"; +} + + +typedef tuple > Conv3DTestParam_t; +typedef TestBaseWithParam Conv3D; + +PERF_TEST_P_(Conv3D, conv3d) +{ + int test_id = (int)get<0>(GetParam()); + ASSERT_GE(test_id, 0); ASSERT_LT(test_id, Conv3DParamID::CONV_LAST); + const Conv3DParam_t& params = testConvolution3DConfigs[test_id]; + double declared_flops = params.declared_flops; + + DictValue kernel = DictValue::arrayInt(¶ms.kernel[0], 3); + DictValue stride = DictValue::arrayInt(¶ms.stride[0], 3); + DictValue pad = DictValue::arrayInt(¶ms.pad[0], 6); + DictValue dilation = DictValue::arrayInt(¶ms.dilation[0], 3); + + MatShape inputShape = MatShape(params.shapeIn.dims, params.shapeIn.dims + 5); + int outChannels = params.outCN; + int groups = params.groups; + std::string padMode(params.padMode); + + bool hasBias = params.hasBias; + Backend backendId = get<0>(get<1>(GetParam())); + Target targetId = get<1>(get<1>(GetParam())); + + if (targetId != DNN_TARGET_CPU) + throw SkipTestException("Only CPU is supported"); + + int inChannels = inputShape[1]; + + int sz[] = {outChannels, inChannels / groups, params.kernel[0], params.kernel[1], params.kernel[2]}; + Mat weights(5, &sz[0], CV_32F); + randu(weights, -1.0f, 1.0f); + + LayerParams lp; + lp.set("kernel_size", kernel); + lp.set("pad", pad); + if (!padMode.empty()) + lp.set("pad_mode", padMode); + + lp.set("stride", stride); + lp.set("dilation", dilation); + lp.set("num_output", outChannels); + lp.set("group", groups); + lp.set("bias_term", hasBias); + lp.type = "Convolution"; + lp.name = "testLayer"; + lp.blobs.push_back(weights); + + if (hasBias) + { + Mat bias(1, outChannels, CV_32F); + randu(bias, -1.0f, 1.0f); + lp.blobs.push_back(bias); + } + int inpSz[] = {1, inChannels, inputShape[2], inputShape[3], inputShape[4]}; + Mat input(5, &inpSz[0], CV_32F); + randu(input, -1.0f, 1.0f); + + Net net; + net.addLayerToPrev(lp.name, lp.type, lp); + + net.setInput(input); + net.setPreferableBackend(backendId); + net.setPreferableTarget(targetId); + + Mat output = net.forward(); + + MatShape netInputShape = shape(input); + size_t weightsMemory = 0, blobsMemory = 0; + net.getMemoryConsumption(netInputShape, weightsMemory, blobsMemory); + int64 flops = net.getFLOPS(netInputShape); + CV_Assert(flops > 0); + + std::cout + << "IN=" << divUp(input.total() * input.elemSize(), 1u<<10) << " Kb " << netInputShape + << " OUT=" << divUp(output.total() * output.elemSize(), 1u<<10) << " Kb " << shape(output) + << " Weights(parameters): " << divUp(weightsMemory, 1u<<10) << " Kb" + << " MFLOPS=" << flops * 1e-6 << std::endl; + + TEST_CYCLE() + { + Mat res = net.forward(); + } + EXPECT_NEAR(flops, declared_flops, declared_flops * 1e-6); + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Conv3D, Combine( + Conv3DParamID::all(), + dnnBackendsAndTargets(false, false) // defined in ../test/test_common.hpp +)); + +} // namespace diff --git a/modules/dnn/src/layers/convolution_layer.cpp b/modules/dnn/src/layers/convolution_layer.cpp index ec17d14879..0b12f949bc 100644 --- a/modules/dnn/src/layers/convolution_layer.cpp +++ b/modules/dnn/src/layers/convolution_layer.cpp @@ -47,6 +47,7 @@ #include "opencv2/core/hal/hal.hpp" #include "opencv2/core/hal/intrin.hpp" #include +#include #ifdef HAVE_OPENCL #include "opencl_kernels_dnn.hpp" @@ -256,7 +257,8 @@ public: } else #endif - return (kernel_size.size() == 2) && (backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_HALIDE); + return (kernel_size.size() == 3 && preferableTarget == DNN_TARGET_CPU && backendId == DNN_BACKEND_OPENCV) || + (kernel_size.size() == 2 && (backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_HALIDE)); } bool getMemoryShapes(const std::vector &inputs, @@ -530,8 +532,8 @@ public: const Mat* input_; const Mat* weights_; Mat* output_; - int outShape[4]; - Size kernel_, pad_, stride_, dilation_; + int outShape[4]; // used only for conv2d + std::vector kernel_size, pads_begin, pads_end, strides, dilations; int ngroups_, nstripes_; std::vector ofstab_; const std::vector* biasvec_; @@ -550,14 +552,18 @@ public: static void run( const Mat& input, Mat& output, const Mat& weights, const std::vector& biasvec, const std::vector& reluslope, - Size kernel, Size pad, Size stride, Size dilation, + const std::vector& kernel_size, const std::vector& strides, + const std::vector& pads_begin, const std::vector& pads_end, + const std::vector& dilations, const ActivationLayer* activ, int ngroups, int nstripes ) { + size_t karea = std::accumulate(kernel_size.begin(), kernel_size.end(), + 1, std::multiplies()); CV_Assert_N( - input.dims == 4 && output.dims == 4, + (input.dims == 4 || input.dims == 5) && (input.dims == output.dims), input.size[0] == output.size[0], weights.rows == output.size[1], - weights.cols == (input.size[1]/ngroups)*kernel.width*kernel.height, + weights.cols == (input.size[1]/ngroups)*karea, input.type() == output.type(), input.type() == weights.type(), input.type() == CV_32FC1, @@ -571,26 +577,58 @@ public: p.output_ = &output; for( int i = 0; i < 4; i++ ) p.outShape[i] = output.size[i]; p.outShape[1] /= ngroups; - p.kernel_ = kernel; p.pad_ = pad; p.stride_ = stride; p.dilation_ = dilation; + + p.kernel_size = kernel_size; p.strides = strides; p.dilations = dilations; + p.pads_begin = pads_begin; p.pads_end = pads_end; + p.ngroups_ = ngroups; p.nstripes_ = nstripes; - int inpCnAll = input.size[1], width = input.size[3], height = input.size[2]; + int inpCnAll = input.size[1]; + int depth = (input.dims == 5) ? input.size[2] : 1; + int width = input.size[input.dims - 1]; + int height = input.size[input.dims - 2]; int inpCn = inpCnAll / ngroups; - p.is1x1_ = kernel == Size(1,1) && pad == Size(0, 0); - p.useAVX = checkHardwareSupport(CPU_AVX); - p.useAVX2 = checkHardwareSupport(CPU_AVX2); - p.useAVX512 = CV_CPU_HAS_SUPPORT_AVX512_SKX; + + bool isConv2D = kernel_size.size() == 2; + + p.is1x1_ = isConv2D && kernel_size[0] == 1 && kernel_size[1] == 1 && + pads_begin[0] == 0 && pads_begin[1] == 0; + + p.useAVX = checkHardwareSupport(CPU_AVX) && isConv2D; + p.useAVX2 = checkHardwareSupport(CPU_AVX2) && isConv2D; + p.useAVX512 = CV_CPU_HAS_SUPPORT_AVX512_SKX && isConv2D; int ncn = std::min(inpCn, (int)BLK_SIZE_CN); - p.ofstab_.resize(kernel.width*kernel.height*ncn); + + int kernel_d = !isConv2D? kernel_size[0] : 1; + int kernel_h = kernel_size[kernel_size.size() - 2]; + int kernel_w = kernel_size.back(); + + int dil_d = !isConv2D? dilations[0] : 1; + int dil_h = dilations[dilations.size() - 2]; + int dil_w = dilations.back(); + + p.ofstab_.resize(karea * ncn); int* ofstab = &p.ofstab_[0]; - for( int k = 0; k < ncn; k++ ) - for( int k_r = 0; k_r < kernel.height; k_r++ ) - for( int k_c = 0; k_c < kernel.width; k_c++ ) - ofstab[(k*kernel.height + k_r)*kernel.width + k_c] = - (k*height + k_r*dilation.height)*width + k_c*dilation.width; + if (isConv2D) + { + for( int k = 0; k < ncn; k++ ) + for( int k_r = 0; k_r < kernel_h; k_r++ ) + for( int k_c = 0; k_c < kernel_w; k_c++ ) + ofstab[(k*kernel_h + k_r)*kernel_w + k_c] = + (k*height + k_r*dil_h)*width + k_c*dil_w; + } + else + { + for( int k = 0; k < ncn; k++ ) + for (int k_d = 0; k_d < kernel_d; k_d++) + for( int k_r = 0; k_r < kernel_h; k_r++ ) + for( int k_c = 0; k_c < kernel_w; k_c++ ) + ofstab[(k*kernel_d*kernel_h + k_d*kernel_h + k_r)*kernel_w + k_c] = + (k*depth*height + k_d*dil_d*height + k_r*dil_h)*width + k_c*dil_w; + } p.biasvec_ = &biasvec; p.reluslope_ = &reluslope; @@ -603,17 +641,39 @@ public: { const int valign = ConvolutionLayerImpl::VEC_ALIGN; int ngroups = ngroups_, batchSize = input_->size[0]*ngroups; - int outW = output_->size[3], outH = output_->size[2], outCn = output_->size[1]/ngroups; - int width = input_->size[3], height = input_->size[2], inpCn = input_->size[1]/ngroups; + bool isConv2D = input_->dims == 4; + + int outW = output_->size[output_->dims - 1]; + int outH = output_->size[output_->dims - 2]; + int outCn = output_->size[1]/ngroups; + + int depth = !isConv2D? input_->size[2] : 1; + int height = input_->size[input_->dims - 2]; + int width = input_->size[input_->dims - 1]; + int inpCn = input_->size[1]/ngroups; + const int nstripes = nstripes_; - int kernel_w = kernel_.width, kernel_h = kernel_.height; - int pad_w = pad_.width, pad_h = pad_.height; - int stride_w = stride_.width, stride_h = stride_.height; - int dilation_w = dilation_.width, dilation_h = dilation_.height; - int karea = kernel_w*kernel_h; - int i, j, k; - size_t inpPlaneSize = width*height; - size_t outPlaneSize = outW*outH; + + int kernel_d = !isConv2D? kernel_size[0] : 1; + int kernel_h = kernel_size[kernel_size.size() - 2]; + int kernel_w = kernel_size.back(); + int karea = kernel_w*kernel_h*kernel_d; + + int pad_d = !isConv2D? pads_begin[0] : 0; + int pad_t = pads_begin[pads_begin.size() - 2]; + int pad_l = pads_begin.back(); + + int stride_d = !isConv2D? strides[0] : 0; + int stride_h = strides[strides.size() - 2]; + int stride_w = strides.back(); + + int dilation_d = !isConv2D? dilations[0] : 1; + int dilation_h = dilations[dilations.size() - 2]; + int dilation_w = dilations.back(); + + int i, j, k, d; + size_t inpPlaneSize = input_->total(2); + size_t outPlaneSize = output_->total(2); bool is1x1 = is1x1_; int stripesPerSample; @@ -682,72 +742,125 @@ public: for( int ofs0 = stripeStart; ofs0 < stripeEnd; ofs0 += BLK_SIZE ) { int ofs, ofs1 = std::min(ofs0 + BLK_SIZE, stripeEnd); - int out_i = ofs0 / outW; - int out_j = ofs0 - out_i * outW; + + int out_d = ofs0 / (outH * outW); + int out_i = (ofs0 - out_d * outH * outW) / outW; + int out_j = ofs0 % outW; // do im2row for a part of input tensor float* rowbuf = rowbuf0; - for( ofs = ofs0; ofs < ofs1; out_j = 0, ++out_i ) + + if (isConv2D) { - int delta = std::min(ofs1 - ofs, outW - out_j); - int out_j1 = out_j + delta; - int in_i = out_i * stride_h - pad_h; - int in_j = out_j * stride_w - pad_w; - const float* imgptr = data_inp0 + (cn0*height + in_i)*width + in_j; - ofs += delta; - - // do im2row for a part of input tensor - if( is1x1 ) + for( ofs = ofs0; ofs < ofs1; out_j = 0, ++out_i ) { - for( ; out_j < out_j1; out_j++, rowbuf += vsz_a, imgptr += stride_w ) + int delta = std::min(ofs1 - ofs, outW - out_j); + int out_j1 = out_j + delta; + + int in_i = out_i * stride_h - pad_t; + int in_j = out_j * stride_w - pad_l; + const float* imgptr = data_inp0 + (cn0*height + in_i)*width + in_j; + ofs += delta; + + // do im2row for a part of input tensor + if( is1x1 ) { - for( k = 0; k < vsz; k++ ) - rowbuf[k] = imgptr[k*inpPlaneSize]; + for( ; out_j < out_j1; out_j++, rowbuf += vsz_a, imgptr += stride_w ) + { + for( k = 0; k < vsz; k++ ) + rowbuf[k] = imgptr[k*inpPlaneSize]; + } + } + else + { + bool ok_i = 0 <= in_i && in_i < height - (kernel_h-1)*dilation_h; + int i0 = std::max(0, (-in_i + dilation_h-1)/dilation_h); + int i1 = std::min(kernel_h, (height - in_i + dilation_h-1)/dilation_h); + + for( ; out_j < out_j1; out_j++, rowbuf += vsz_a, imgptr += stride_w, in_j += stride_w ) + { + // this condition should be true for most of the tensor elements, i.e. + // most of the time the kernel aperture is inside the tensor X-Y plane. + if( ok_i && out_j + 2 <= out_j1 && 0 <= in_j && in_j + stride_w*2 <= width - (kernel_w-1)*dilation_w ) + { + for( k = 0; k < vsz; k++ ) + { + int k1 = ofstab[k]; + float v0 = imgptr[k1]; + float v1 = imgptr[k1 + stride_w]; + rowbuf[k] = v0; + rowbuf[k+vsz_a] = v1; + } + out_j++; + rowbuf += vsz_a; + imgptr += stride_w; + in_j += stride_w; + } + else + { + int j0 = std::max(0, (-in_j + dilation_w-1)/dilation_w); + int j1 = std::min(kernel_w, (width - in_j + dilation_w-1)/dilation_w); + + // here some non-continuous sub-row of the row will not be + // filled from the tensor; we need to make sure that the uncovered + // elements are explicitly set to 0's. the easiest way is to + // set all the elements to 0's before the loop. + memset(rowbuf, 0, vsz*sizeof(rowbuf[0])); + for( k = 0; k < ncn; k++ ) + { + for( i = i0; i < i1; i++ ) + { + for( j = j0; j < j1; j++ ) + { + int imgofs = k*(width*height) + i*(dilation_h*width) + j*dilation_w; + rowbuf[(k*kernel_h + i)*kernel_w + j] = imgptr[imgofs]; + } + } + } + } + } } } - else + } + else + { + for( ofs = ofs0; ofs < ofs1; out_d += (out_i + 1) / outH, out_i = (out_i + 1) % outH, out_j = 0 ) { - bool ok_i = 0 <= in_i && in_i < height - (kernel_h-1)*dilation_h; + int delta = std::min(ofs1 - ofs, outW - out_j); + int out_j1 = out_j + delta; + + int in_d = out_d * stride_d - pad_d; + int in_i = out_i * stride_h - pad_t; + int in_j = out_j * stride_w - pad_l; + const float* imgptr = data_inp0 + (cn0*depth*height + in_d*height + in_i)*width + in_j; + ofs += delta; + + int d0 = std::max(0, (-in_d + dilation_d - 1) / dilation_d); + int d1 = std::min(kernel_d, (depth - in_d + dilation_d - 1) / dilation_d); + int i0 = std::max(0, (-in_i + dilation_h-1)/dilation_h); int i1 = std::min(kernel_h, (height - in_i + dilation_h-1)/dilation_h); for( ; out_j < out_j1; out_j++, rowbuf += vsz_a, imgptr += stride_w, in_j += stride_w ) { - // this condition should be true for most of the tensor elements, i.e. - // most of the time the kernel aperture is inside the tensor X-Y plane. - if( ok_i && out_j + 2 <= out_j1 && 0 <= in_j && in_j + stride_w*2 <= width - (kernel_w-1)*dilation_w ) + int j0 = std::max(0, (-in_j + dilation_w-1)/dilation_w); + int j1 = std::min(kernel_w, (width - in_j + dilation_w-1)/dilation_w); + + // here some non-continuous sub-row of the row will not be + // filled from the tensor; we need to make sure that the uncovered + // elements are explicitly set to 0's. the easiest way is to + // set all the elements to 0's before the loop. + memset(rowbuf, 0, vsz*sizeof(rowbuf[0])); + for( k = 0; k < ncn; k++ ) { - for( k = 0; k < vsz; k++ ) - { - int k1 = ofstab[k]; - float v0 = imgptr[k1]; - float v1 = imgptr[k1 + stride_w]; - rowbuf[k] = v0; - rowbuf[k+vsz_a] = v1; - } - out_j++; - rowbuf += vsz_a; - imgptr += stride_w; - in_j += stride_w; - } - else - { - int j0 = std::max(0, (-in_j + dilation_w-1)/dilation_w); - int j1 = std::min(kernel_w, (width - in_j + dilation_w-1)/dilation_w); - - // here some non-continuous sub-row of the row will not be - // filled from the tensor; we need to make sure that the uncovered - // elements are explicitly set to 0's. the easiest way is to - // set all the elements to 0's before the loop. - memset(rowbuf, 0, vsz*sizeof(rowbuf[0])); - for( k = 0; k < ncn; k++ ) + for ( d = d0; d < d1; d++) { for( i = i0; i < i1; i++ ) { for( j = j0; j < j1; j++ ) { - int imgofs = k*(width*height) + i*(dilation_h*width) + j*dilation_w; - rowbuf[(k*kernel_h + i)*kernel_w + j] = imgptr[imgofs]; + int imgofs = k*(depth*width*height) + d*dilation_d*width*height + i*(dilation_h*width) + j*dilation_w; + rowbuf[(k*kernel_d*kernel_h + d*kernel_h + i)*kernel_w + j] = imgptr[imgofs]; } } } @@ -1057,10 +1170,6 @@ public: CV_Assert_N(inputs.size() == (size_t)1, inputs[0].size[1] % blobs[0].size[1] == 0, outputs.size() == 1, inputs[0].data != outputs[0].data); - if (inputs[0].dims == 5) { - CV_Error(Error::StsNotImplemented, "Convolution3D layer is not supported on OCV backend"); - } - int ngroups = inputs[0].size[1]/blobs[0].size[1]; CV_Assert(outputs[0].size[1] % ngroups == 0); int outCn = blobs[0].size[0]; @@ -1089,7 +1198,7 @@ public: int nstripes = std::max(getNumThreads(), 1); ParallelConv::run(inputs[0], outputs[0], weightsMat, biasvec, reluslope, - kernel, pad, stride, dilation, activ.get(), ngroups, nstripes); + kernel_size, strides, pads_begin, pads_end, dilations, activ.get(), ngroups, nstripes); } virtual int64 getFLOPS(const std::vector &inputs, @@ -1098,9 +1207,10 @@ public: CV_Assert(inputs.size() == outputs.size()); int64 flops = 0; + int karea = std::accumulate(kernel_size.begin(), kernel_size.end(), 1, std::multiplies()); for (int i = 0; i < inputs.size(); i++) { - flops += total(outputs[i])*(CV_BIG_INT(2)*kernel.area()*inputs[i][1] + 1); + flops += total(outputs[i])*(CV_BIG_INT(2)*karea*inputs[i][1] + 1); } return flops; diff --git a/modules/dnn/src/layers/pooling_layer.cpp b/modules/dnn/src/layers/pooling_layer.cpp index e50d292f27..08436ee971 100644 --- a/modules/dnn/src/layers/pooling_layer.cpp +++ b/modules/dnn/src/layers/pooling_layer.cpp @@ -47,6 +47,7 @@ #include "../op_inf_engine.hpp" #include #include +#include using std::max; using std::min; @@ -177,9 +178,10 @@ public: #endif } else - return (kernel_size.empty() || kernel_size.size() == 2) && (backendId == DNN_BACKEND_OPENCV || + return (kernel_size.size() == 3 && backendId == DNN_BACKEND_OPENCV && preferableTarget == DNN_TARGET_CPU) || + ((kernel_size.empty() || kernel_size.size() == 2) && (backendId == DNN_BACKEND_OPENCV || (backendId == DNN_BACKEND_HALIDE && haveHalide() && - (type == MAX || (type == AVE && !pad_t && !pad_l && !pad_b && !pad_r)))); + (type == MAX || (type == AVE && !pad_t && !pad_l && !pad_b && !pad_r))))); } #ifdef HAVE_OPENCL @@ -341,18 +343,25 @@ public: int poolingType; float spatialScale; + std::vector pads_begin, pads_end; + std::vector kernel_size; + std::vector strides; + PoolingInvoker() : src(0), rois(0), dst(0), mask(0), avePoolPaddedArea(false), nstripes(0), computeMaxIdx(0), poolingType(MAX), spatialScale(0) {} - static void run(const Mat& src, const Mat& rois, Mat& dst, Mat& mask, Size kernel, - Size stride, int pad_l, int pad_t, int pad_r, int pad_b, bool avePoolPaddedArea, int poolingType, float spatialScale, + static void run(const Mat& src, const Mat& rois, Mat& dst, Mat& mask, + std::vector kernel_size, std::vector strides, + std::vector pads_begin, std::vector pads_end, + bool avePoolPaddedArea, int poolingType, float spatialScale, bool computeMaxIdx, int nstripes) { CV_Assert_N( src.isContinuous(), dst.isContinuous(), src.type() == CV_32F, src.type() == dst.type(), - src.dims == 4, dst.dims == 4, - (((poolingType == ROI || poolingType == PSROI) && dst.size[0] == rois.size[0]) || src.size[0] == dst.size[0]), + src.dims == 4 || src.dims == 5, dst.dims == 4 || dst.dims == 5, + (((poolingType == ROI || poolingType == PSROI) && + dst.size[0] == rois.size[0]) || src.size[0] == dst.size[0]), poolingType == PSROI || src.size[1] == dst.size[1], (mask.empty() || (mask.type() == src.type() && mask.size == dst.size))); @@ -361,13 +370,20 @@ public: p.src = &src; p.rois = &rois; p.dst = &dst; + + p.kernel_size = kernel_size; + p.strides = strides; + p.pads_begin = pads_begin; + p.pads_end = pads_end; + p.mask = &mask; - p.kernel = kernel; - p.stride = stride; - p.pad_l = pad_l; - p.pad_t = pad_t; - p.pad_r = pad_r; - p.pad_b = pad_b; + p.kernel = Size(kernel_size[1], kernel_size[0]); + p.stride = Size(strides[1], strides[0]); + p.pad_l = pads_begin.back(); + p.pad_t = pads_begin[pads_begin.size() - 2]; + p.pad_r = pads_end.back(); + p.pad_b = pads_end[pads_end.size() - 2]; + p.avePoolPaddedArea = avePoolPaddedArea; p.nstripes = nstripes; p.computeMaxIdx = computeMaxIdx; @@ -376,10 +392,21 @@ public: if( !computeMaxIdx ) { - p.ofsbuf.resize(kernel.width*kernel.height); - for( int i = 0; i < kernel.height; i++ ) - for( int j = 0; j < kernel.width; j++ ) - p.ofsbuf[i*kernel.width + j] = src.size[3]*i + j; + int height = src.size[src.dims - 2]; + int width = src.size[src.dims - 1]; + + int kernel_d = (kernel_size.size() == 3) ? kernel_size[0] : 1; + int kernel_h = kernel_size[kernel_size.size() - 2]; + int kernel_w = kernel_size.back(); + + p.ofsbuf.resize(kernel_d * kernel_h * kernel_w); + for (int i = 0; i < kernel_d; ++i) { + for (int j = 0; j < kernel_h; ++j) { + for (int k = 0; k < kernel_w; ++k) { + p.ofsbuf[i * kernel_h * kernel_w + j * kernel_w + k] = width * height * i + width * j + k; + } + } + } } parallel_for_(Range(0, nstripes), p, nstripes); @@ -387,14 +414,29 @@ public: void operator()(const Range& r) const CV_OVERRIDE { - int channels = dst->size[1], width = dst->size[3], height = dst->size[2]; - int inp_width = src->size[3], inp_height = src->size[2]; + int channels = dst->size[1]; + + bool isPool2D = src->dims == 4; + int depth = !isPool2D? dst->size[2] : 1; + int height = dst->size[dst->dims - 2]; + int width = dst->size[dst->dims - 1]; + + int inp_depth = !isPool2D? src->size[2] : 1; + int inp_height = src->size[src->dims - 2]; + int inp_width = src->size[src->dims - 1]; + size_t total = dst->total(); size_t stripeSize = (total + nstripes - 1)/nstripes; size_t stripeStart = r.start*stripeSize; size_t stripeEnd = std::min(r.end*stripeSize, total); - int kernel_w = kernel.width, kernel_h = kernel.height; - int stride_w = stride.width, stride_h = stride.height; + + int kernel_d = !isPool2D? kernel_size[0] : 1; + int kernel_h = kernel_size[kernel_size.size() - 2]; + int kernel_w = kernel_size.back(); + + int stride_d = !isPool2D? strides[0] : 0; + int stride_h = strides[strides.size() - 2]; + int stride_w = strides.back(); bool compMaxIdx = computeMaxIdx; #if CV_SIMD128 @@ -413,9 +455,14 @@ public: ofs /= width; int y0 = (int)(ofs % height); ofs /= height; + + int d0 = (int)(ofs % depth); + ofs /= depth; + int c = (int)(ofs % channels); int n = (int)(ofs / channels); int ystart, yend; + int dstart = 0, dend = 1; const float *srcData = 0; if (poolingType == ROI) @@ -445,15 +492,22 @@ public: } else { + int pad_d_begin = (pads_begin.size() == 3) ? pads_begin[0] : 0; + dstart = d0 * stride_d - pad_d_begin; + dend = min(dstart + kernel_d, (int)(inp_depth + pads_end[0])); + ystart = y0 * stride_h - pad_t; yend = min(ystart + kernel_h, inp_height + pad_b); srcData = src->ptr(n, c); } + int ddelta = dend - dstart; + dstart = max(dstart, 0); + dend = min(dend, inp_depth); int ydelta = yend - ystart; ystart = max(ystart, 0); yend = min(yend, inp_height); - float *dstData = dst->ptr(n, c, y0); - float *dstMaskData = mask->data ? mask->ptr(n, c, y0) : 0; + float *dstData = &dst->ptr(n, c, d0)[y0 * width]; + float *dstMaskData = mask->data ? &mask->ptr(n, c, d0)[y0 * width] : 0; int delta = std::min((int)(stripeEnd - ofs0), width - x0); ofs0 += delta; @@ -473,7 +527,7 @@ public: continue; } #if CV_SIMD128 - if( xstart > 0 && x0 + 7 < x1 && (x0 + 7) * stride_w - pad_l + kernel_w < inp_width ) + if( isPool2D && xstart > 0 && x0 + 7 < x1 && (x0 + 7) * stride_w - pad_l + kernel_w < inp_width ) { if( compMaxIdx ) { @@ -578,49 +632,51 @@ public: if( compMaxIdx ) { int max_index = -1; - for (int y = ystart; y < yend; ++y) - for (int x = xstart; x < xend; ++x) - { - const int index = y * inp_width + x; - float val = srcData[index]; - if (val > max_val) + for (int d = dstart; d < dend; ++d) + for (int y = ystart; y < yend; ++y) + for (int x = xstart; x < xend; ++x) { - max_val = val; - max_index = index; + const int index = d * inp_width * inp_height + y * inp_width + x; + float val = srcData[index]; + if (val > max_val) + { + max_val = val; + max_index = index; + } } - } - dstData[x0] = max_val; if (dstMaskData) dstMaskData[x0] = max_index; } else { - for (int y = ystart; y < yend; ++y) - for (int x = xstart; x < xend; ++x) - { - const int index = y * inp_width + x; - float val = srcData[index]; - max_val = std::max(max_val, val); + for (int d = dstart; d < dend; ++d) { + for (int y = ystart; y < yend; ++y) { + for (int x = xstart; x < xend; ++x) { + const int index = d * inp_width * inp_height + y * inp_width + x; + float val = srcData[index]; + max_val = std::max(max_val, val); + } } - + } dstData[x0] = max_val; } } } else if (poolingType == AVE) { - for( ; x0 < x1; x0++ ) + for( ; x0 < x1; ++x0) { int xstart = x0 * stride_w - pad_l; int xend = min(xstart + kernel_w, inp_width + pad_r); int xdelta = xend - xstart; xstart = max(xstart, 0); xend = min(xend, inp_width); - float inv_kernel_area = avePoolPaddedArea ? xdelta * ydelta : ((yend - ystart) * (xend - xstart)); + float inv_kernel_area = avePoolPaddedArea ? xdelta * ydelta * ddelta : + ((dend - dstart) * (yend - ystart) * (xend - xstart)); inv_kernel_area = 1.0 / inv_kernel_area; #if CV_SIMD128 - if( xstart > 0 && x0 + 7 < x1 && (x0 + 7) * stride_w - pad_l + kernel_w < inp_width ) + if( isPool2D && xstart > 0 && x0 + 7 < x1 && (x0 + 7) * stride_w - pad_l + kernel_w < inp_width ) { v_float32x4 sum_val0 = v_setzero_f32(), sum_val1 = v_setzero_f32(); v_float32x4 ikarea = v_setall_f32(inv_kernel_area); @@ -646,14 +702,15 @@ public: #endif { float sum_val = 0.f; - for (int y = ystart; y < yend; ++y) - for (int x = xstart; x < xend; ++x) - { - const int index = y * inp_width + x; - float val = srcData[index]; - sum_val += val; + for (int d = dstart; d < dend; ++d) { + for (int y = ystart; y < yend; ++y) { + for (int x = xstart; x < xend; ++x) { + const int index = d * inp_width * inp_height + y * inp_width + x; + float val = srcData[index]; + sum_val += val; + } } - + } dstData[x0] = sum_val*inv_kernel_area; } } @@ -729,21 +786,25 @@ public: { const int nstripes = getNumThreads(); Mat rois; - PoolingInvoker::run(src, rois, dst, mask, kernel, stride, pad_l, pad_t, pad_r, pad_b, avePoolPaddedArea, type, spatialScale, computeMaxIdx, nstripes); + PoolingInvoker::run(src, rois, dst, mask, kernel_size, strides, pads_begin, pads_end, avePoolPaddedArea, type, spatialScale, computeMaxIdx, nstripes); } void avePooling(Mat &src, Mat &dst) { const int nstripes = getNumThreads(); Mat rois, mask; - PoolingInvoker::run(src, rois, dst, mask, kernel, stride, pad_l, pad_t, pad_r, pad_b, avePoolPaddedArea, type, spatialScale, computeMaxIdx, nstripes); + PoolingInvoker::run(src, rois, dst, mask, kernel_size, strides, pads_begin, pads_end, avePoolPaddedArea, type, spatialScale, computeMaxIdx, nstripes); } void roiPooling(const Mat &src, const Mat &rois, Mat &dst) { const int nstripes = getNumThreads(); Mat mask; - PoolingInvoker::run(src, rois, dst, mask, kernel, stride, pad_l, pad_t, pad_r, pad_b, avePoolPaddedArea, type, spatialScale, computeMaxIdx, nstripes); + kernel_size.resize(2); + strides.resize(2); + pads_begin.resize(2); + pads_end.resize(2); + PoolingInvoker::run(src, rois, dst, mask, kernel_size, strides, pads_begin, pads_end, avePoolPaddedArea, type, spatialScale, computeMaxIdx, nstripes); } virtual Ptr initMaxPoolingHalide(const std::vector > &inputs) @@ -931,17 +992,18 @@ public: { CV_UNUSED(inputs); // suppress unused variable warning long flops = 0; - + size_t karea = std::accumulate(kernel_size.begin(), kernel_size.end(), + 1, std::multiplies()); for(int i = 0; i < outputs.size(); i++) { if (type == MAX) { if (i%2 == 0) - flops += total(outputs[i])*kernel.area(); + flops += total(outputs[i])*karea; } else { - flops += total(outputs[i])*(kernel.area() + 1); + flops += total(outputs[i])*(karea + 1); } } return flops; diff --git a/modules/dnn/test/test_onnx_importer.cpp b/modules/dnn/test/test_onnx_importer.cpp index fc38a2378c..5dd919fd7e 100644 --- a/modules/dnn/test/test_onnx_importer.cpp +++ b/modules/dnn/test/test_onnx_importer.cpp @@ -100,8 +100,8 @@ TEST_P(Test_ONNX_layers, Convolution3D) #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2019010000) throw SkipTestException("Test is enabled starts from 2019R1"); #endif - if (backend != DNN_BACKEND_INFERENCE_ENGINE || target != DNN_TARGET_CPU) - throw SkipTestException("Only DLIE backend on CPU is supported"); + if (target != DNN_TARGET_CPU) + throw SkipTestException("Only CPU is supported"); testONNXModels("conv3d"); testONNXModels("conv3d_bias"); } @@ -185,8 +185,8 @@ TEST_P(Test_ONNX_layers, MaxPooling3D) #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2019010000) throw SkipTestException("Test is enabled starts from 2019R1"); #endif - if (backend != DNN_BACKEND_INFERENCE_ENGINE || target != DNN_TARGET_CPU) - throw SkipTestException("Only DLIE backend on CPU is supported"); + if (target != DNN_TARGET_CPU) + throw SkipTestException("Only CPU is supported"); testONNXModels("max_pool3d"); } @@ -195,11 +195,21 @@ TEST_P(Test_ONNX_layers, AvePooling3D) #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2019010000) throw SkipTestException("Test is enabled starts from 2019R1"); #endif - if (backend != DNN_BACKEND_INFERENCE_ENGINE || target != DNN_TARGET_CPU) - throw SkipTestException("Only DLIE backend on CPU is supported"); + if (target != DNN_TARGET_CPU) + throw SkipTestException("Only CPU is supported"); testONNXModels("ave_pool3d"); } +TEST_P(Test_ONNX_layers, PoolConv3D) +{ +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2019010000) + throw SkipTestException("Test is enabled starts from 2019R1"); +#endif + if (target != DNN_TARGET_CPU) + throw SkipTestException("Only CPU is supported"); + testONNXModels("pool_conv_3d"); +} + TEST_P(Test_ONNX_layers, BatchNormalization) { testONNXModels("batch_norm"); @@ -579,10 +589,10 @@ TEST_P(Test_ONNX_nets, Resnet34_kinetics) #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2019010000) throw SkipTestException("Test is enabled starts from 2019R1"); #endif - if (backend != DNN_BACKEND_INFERENCE_ENGINE || target != DNN_TARGET_CPU) - throw SkipTestException("Only DLIE backend on CPU is supported"); + if (target != DNN_TARGET_CPU) + throw SkipTestException("Only CPU is supported"); - String onnxmodel = findDataFile("dnn/resnet-34_kinetics.onnx", false); + String onnxmodel = findDataFile("dnn/resnet-34_kinetics.onnx"); Mat image0 = imread(findDataFile("dnn/dog416.png")); Mat image1 = imread(findDataFile("dnn/street.png")); diff --git a/modules/dnn/test/test_tf_importer.cpp b/modules/dnn/test/test_tf_importer.cpp index 0687c0ba68..2dae678403 100644 --- a/modules/dnn/test/test_tf_importer.cpp +++ b/modules/dnn/test/test_tf_importer.cpp @@ -136,8 +136,8 @@ TEST_P(Test_TensorFlow_layers, Convolution3D) #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2019010000) throw SkipTestException("Test is enabled starts from 2019R1"); #endif - if (backend != DNN_BACKEND_INFERENCE_ENGINE || target != DNN_TARGET_CPU) - throw SkipTestException("Only DLIE backend on CPU is supported"); + if (target != DNN_TARGET_CPU) + throw SkipTestException("Only CPU is supported"); runTensorFlowNet("conv3d"); } @@ -243,8 +243,8 @@ TEST_P(Test_TensorFlow_layers, MaxPooling3D) #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2019010000) throw SkipTestException("Test is enabled starts from 2019R1"); #endif - if (backend != DNN_BACKEND_INFERENCE_ENGINE || target != DNN_TARGET_CPU) - throw SkipTestException("Only DLIE backend on CPU is supported"); + if (target != DNN_TARGET_CPU) + throw SkipTestException("Only CPU is supported"); runTensorFlowNet("max_pool3d"); } @@ -253,8 +253,8 @@ TEST_P(Test_TensorFlow_layers, AvePooling3D) #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2019010000) throw SkipTestException("Test is enabled starts from 2019R1"); #endif - if (backend != DNN_BACKEND_INFERENCE_ENGINE || target != DNN_TARGET_CPU) - throw SkipTestException("Only DLIE backend on CPU is supported"); + if (target != DNN_TARGET_CPU) + throw SkipTestException("Only CPU is supported"); runTensorFlowNet("ave_pool3d"); }