Merge remote-tracking branch 'upstream/3.4' into merge-3.4
authorAlexander Alekhin <alexander.a.alekhin@gmail.com>
Mon, 31 Jan 2022 12:53:45 +0000 (12:53 +0000)
committerAlexander Alekhin <alexander.a.alekhin@gmail.com>
Mon, 31 Jan 2022 12:53:45 +0000 (12:53 +0000)
18 files changed:
1  2 
cmake/OpenCVCompilerOptions.cmake
modules/core/include/opencv2/core/core_c.h
modules/core/src/parallel.cpp
modules/core/src/system.cpp
modules/core/test/test_misc.cpp
modules/dnn/include/opencv2/dnn/dnn.hpp
modules/dnn/src/caffe/caffe_importer.cpp
modules/dnn/src/darknet/darknet_importer.cpp
modules/dnn/src/dnn.cpp
modules/dnn/src/layers/convolution_layer.cpp
modules/dnn/src/onnx/onnx_importer.cpp
modules/dnn/src/tensorflow/tf_importer.cpp
modules/dnn/src/torch/torch_importer.cpp
modules/dnn/test/test_onnx_conformance.cpp
modules/highgui/CMakeLists.txt
modules/imgcodecs/include/opencv2/imgcodecs.hpp
modules/imgproc/src/drawing.cpp
samples/python/digits_video.py

Simple merge
Simple merge
  #include <opencv2/core/utils/tls.hpp>
  #include <opencv2/core/utils/instrumentation.hpp>
  
 +#include <opencv2/core/utils/filesystem.private.hpp>
 +
+ #include <opencv2/core/utils/fp_control_utils.hpp>
+ #include <opencv2/core/utils/fp_control.private.hpp>
  #ifndef OPENCV_WITH_THREAD_SANITIZER
    #if defined(__clang__) && defined(__has_feature)
    #if __has_feature(thread_sanitizer)
@@@ -2770,8 -2722,126 +2773,84 @@@ void setUseIPP_NotExact(bool flag
  #endif
  }
  
 -#if OPENCV_ABI_COMPATIBILITY < 400
 -bool useIPP_NE()
 -{
 -    return useIPP_NotExact();
 -}
 -
 -void setUseIPP_NE(bool flag)
 -{
 -    setUseIPP_NotExact(flag);
 -}
 -#endif
 -
  } // namespace ipp
  
+ namespace details {
+ #if OPENCV_IMPL_FP_HINTS_X86
+ #ifndef _MM_DENORMALS_ZERO_ON  // requires pmmintrin.h (SSE3)
+ #define _MM_DENORMALS_ZERO_ON 0x0040
+ #endif
+ #ifndef _MM_DENORMALS_ZERO_MASK  // requires pmmintrin.h (SSE3)
+ #define _MM_DENORMALS_ZERO_MASK 0x0040
+ #endif
+ #endif
+ void setFPDenormalsIgnoreHint(bool ignore, CV_OUT FPDenormalsModeState& state)
+ {
+ #if OPENCV_IMPL_FP_HINTS_X86
+     unsigned mask = _MM_FLUSH_ZERO_MASK;
+     unsigned value = ignore ? _MM_FLUSH_ZERO_ON : 0;
+     if (featuresEnabled.have[CPU_SSE3])
+     {
+         mask |= _MM_DENORMALS_ZERO_MASK;
+         value |= ignore ? _MM_DENORMALS_ZERO_ON : 0;
+     }
+     const unsigned old_flags = _mm_getcsr();
+     const unsigned old_value = old_flags & mask;
+     unsigned flags = (old_flags & ~mask) | value;
+     CV_LOG_DEBUG(NULL, "core: update FP mxcsr flags = " << cv::format("0x%08x", flags));
+     // save state
+     state.reserved[0] = (uint32_t)mask;
+     state.reserved[1] = (uint32_t)old_value;
+     _mm_setcsr(flags);
+ #else
+     CV_UNUSED(ignore); CV_UNUSED(state);
+ #endif
+ }
+ int saveFPDenormalsState(CV_OUT FPDenormalsModeState& state)
+ {
+ #if OPENCV_IMPL_FP_HINTS_X86
+     unsigned mask = _MM_FLUSH_ZERO_MASK;
+     if (featuresEnabled.have[CPU_SSE3])
+     {
+         mask |= _MM_DENORMALS_ZERO_MASK;
+     }
+     const unsigned old_flags = _mm_getcsr();
+     const unsigned old_value = old_flags & mask;
+     // save state
+     state.reserved[0] = (uint32_t)mask;
+     state.reserved[1] = (uint32_t)old_value;
+     return 2;
+ #else
+     CV_UNUSED(state);
+     return 0;
+ #endif
+ }
+ bool restoreFPDenormalsState(const FPDenormalsModeState& state)
+ {
+ #if OPENCV_IMPL_FP_HINTS_X86
+     const unsigned mask = (unsigned)state.reserved[0];
+     CV_DbgAssert(mask != 0); // invalid state (ensure that state is properly saved earlier)
+     const unsigned value = (unsigned)state.reserved[1];
+     CV_DbgCheck((int)value, value == (value & mask), "invalid SSE FP state");
+     const unsigned old_flags = _mm_getcsr();
+     unsigned flags = (old_flags & ~mask) | value;
+     CV_LOG_DEBUG(NULL, "core: restore FP mxcsr flags = " << cv::format("0x%08x", flags));
+     _mm_setcsr(flags);
+     return true;
+ #else
+     CV_UNUSED(state);
+     return false;
+ #endif
+ }
+ }  // namespace details
  } // namespace cv
  
 -#ifdef HAVE_TEGRA_OPTIMIZATION
 -
 -namespace tegra {
 -
 -bool useTegra()
 -{
 -    cv::CoreTLSData* data = cv::getCoreTlsData();
 -
 -    if (data->useTegra < 0)
 -    {
 -        const char* pTegraEnv = getenv("OPENCV_TEGRA");
 -        if (pTegraEnv && (cv::String(pTegraEnv) == "disabled"))
 -            data->useTegra = false;
 -        else
 -            data->useTegra = true;
 -    }
 -
 -    return (data->useTegra > 0);
 -}
 -
 -void setUseTegra(bool flag)
 -{
 -    cv::CoreTLSData* data = cv::getCoreTlsData();
 -    data->useTegra = flag;
 -}
 -
 -} // namespace tegra
 -
 -#endif
 -
  /* End of file. */
@@@ -2,8 -2,16 +2,17 @@@
  // 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 "test_precomp.hpp"
 +#include <cmath>
  
+ #include "opencv2/core/utils/logger.hpp"
+ #include <opencv2/core/utils/fp_control_utils.hpp>
+ #ifdef CV_CXX11
+ #include <chrono>
+ #include <thread>
+ #endif
  namespace opencv_test { namespace {
  
  TEST(Core_OutputArrayCreate, _1997)
  #include "caffe_io.hpp"
  #endif
  
+ #include <opencv2/core/utils/fp_control_utils.hpp>
  namespace cv {
  namespace dnn {
 -CV__DNN_EXPERIMENTAL_NS_BEGIN
 +CV__DNN_INLINE_NS_BEGIN
  
  #ifdef HAVE_PROTOBUF
  using ::google::protobuf::RepeatedFieldRef;
  #include <fstream>
  #include <iterator>
  #include <numeric>
 +#include <memory>
  #include <opencv2/dnn/shape_utils.hpp>
  #include <opencv2/imgproc.hpp>
 +#include <opencv2/dnn/layer_reg.private.hpp>
  
+ #include <opencv2/core/utils/fp_control_utils.hpp>
  #include <opencv2/core/utils/configuration.private.hpp>
  #include <opencv2/core/utils/logger.hpp>
  
@@@ -1632,6 -1484,23 +1635,38 @@@ struct Net::Impl : public detail::NetIm
          return pins;
      }
  
 -    int addLayer(const String &name, const String &type, LayerParams &params)
++    // FIXIT remove dtype
++    int addLayer(const String &name, const String &type, const int &dtype, LayerParams &params)
+     {
 -        if (getLayerId(name) >= 0)
++        int id = getLayerId(name);
++        if (id >= 0)
+         {
 -            CV_Error(Error::StsBadArg, "Layer \"" + name + "\" already into net");
 -            return -1;
++            if (!DNN_DIAGNOSTICS_RUN || type != "NotImplemented")
++            {
++                CV_Error(Error::StsBadArg, "Layer \"" + name + "\" already into net");
++                return -1;
++            }
++            else
++            {
++                LayerData& ld = layers.find(id)->second;
++                ld.type = type;
++                ld.params = params;
++                return -1;
++            }
+         }
 -        int id = ++lastLayerId;
++        id = ++lastLayerId;
+         layerNameToId.insert(std::make_pair(name, id));
 -        layers.insert(std::make_pair(id, LayerData(id, name, type, params)));
++        layers.insert(std::make_pair(id, LayerData(id, name, type, dtype, params)));
+         if (params.get<bool>("has_dynamic_shapes", false))
+             hasDynamicShapes = true;
++        if (dtype == CV_8S)
++            netWasQuantized = true;
++
+         return id;
+     }
      void connect(int outLayerId, int outNum, int inLayerId, int inNum)
      {
          CV_Assert(outLayerId < inLayerId);
          addLayerInput(ldInp, inNum, LayerPin(outLayerId, outNum));
          ldOut.requiredOutputs.insert(outNum);
          ldOut.consumers.push_back(LayerPin(inLayerId, outNum));
 -        int outputLayerId = addLayer(outputLayerParams.name, outputLayerParams.type, outputLayerParams);
+         CV_LOG_VERBOSE(NULL, 0, "DNN: connect(" << outLayerId << ":" << outNum << " ==> " << inLayerId << ":" << inNum << ")");
+     }
+     int registerOutput(const std::string& outputName, int layerId, int outputPort)
+     {
+         int checkLayerId = getLayerId(outputName);
+         if (checkLayerId >= 0)
+         {
+             if (checkLayerId == layerId)
+             {
+                 if (outputPort == 0)
+                 {
+                     // layer name correlates with its output name
+                     CV_LOG_DEBUG(NULL, "DNN: register output='" << outputName << "': reuse layer with the same name and id=" << layerId << " to be linked");
+                     outputNameToId.insert(std::make_pair(outputName, layerId));
+                     return checkLayerId;
+                 }
+             }
+             CV_Error_(Error::StsBadArg, ("Layer with name='%s' already exists id=%d (to be linked with %d:%d)", outputName.c_str(), checkLayerId, layerId, outputPort));
+         }
+ #if 0  // TODO
+         if (outputPort == 0)
+             // make alias only, need to adopt getUnconnectedOutLayers() call
+ #endif
+         LayerParams outputLayerParams;
+         outputLayerParams.name = outputName;
+         outputLayerParams.type = "Identity";
++        int dtype = CV_32F;  // FIXIT remove
++        int outputLayerId = addLayer(outputLayerParams.name, outputLayerParams.type, dtype, outputLayerParams);
+         connect(layerId, outputPort, outputLayerId, 0);
+         CV_LOG_DEBUG(NULL, "DNN: register output='" << outputName << "' id=" << outputLayerId << " defined as " << layerId << ":" << outputPort);
+         outputNameToId.insert(std::make_pair(outputName, outputLayerId));
+         return outputLayerId;
      }
  
      void initBackend(const std::vector<LayerPin>& blobsToKeep_)
@@@ -4407,46 -3647,14 +4481,20 @@@ Net::~Net(
  {
  }
  
 -int Net::addLayer(const String &name, const String &type, LayerParams &params)
 +int Net::addLayer(const String &name, const String &type, const int &dtype, LayerParams &params)
  {
      CV_TRACE_FUNCTION();
-     int id = impl->getLayerId(name);
-     if (id >= 0)
-     {
-         if (!DNN_DIAGNOSTICS_RUN || type != "NotImplemented")
-         {
-             CV_Error(Error::StsBadArg, "Layer \"" + name + "\" already into net");
-             return -1;
-         }
-         else
-         {
-             LayerData& ld = impl->layers.find(id)->second;
-             ld.type = type;
-             ld.params = params;
-             return -1;
-         }
-     }
-     id = ++impl->lastLayerId;
-     impl->layerNameToId.insert(std::make_pair(name, id));
-     impl->layers.insert(std::make_pair(id, LayerData(id, name, type, dtype, params)));
-     if (params.get<bool>("has_dynamic_shapes", false))
-         impl->hasDynamicShapes = true;
-     if (dtype == CV_8S)
-         impl->netWasQuantized = true;
-     return id;
+     CV_Assert(impl);
 -    return impl->addLayer(name, type, params);
++    return impl->addLayer(name, type, dtype, params);
  }
  
 -int Net::addLayerToPrev(const String &name, const String &type, LayerParams &params)
 +int Net::addLayer(const String &name, const String &type, LayerParams &params)
 +{
 +    CV_TRACE_FUNCTION();
 +    return addLayer(name, type, CV_32F, params);
 +}
 +
 +int Net::addLayerToPrev(const String &name, const String &type, const int &dtype, LayerParams &params)
  {
      CV_TRACE_FUNCTION();
  
@@@ -2139,139 -1741,8 +2132,135 @@@ public
              ParallelConv::run(inputs[0], outputs[0], weightsMat, biasvec, reluslope,
                              kernel_size, strides, pads_begin, pads_end, dilations, activ.get(), ngroups, nstripes);
          }
- #if CV_SSE3
-         _MM_SET_FLUSH_ZERO_MODE(ftzMode);
-         _MM_SET_DENORMALS_ZERO_MODE(dazMode);
- #endif
      }
  
 +#ifdef HAVE_CUDA
 +    Ptr<BackendNode> initCUDA(
 +        void *context_,
 +        const std::vector<Ptr<BackendWrapper>>& inputs,
 +        const std::vector<Ptr<BackendWrapper>>& outputs
 +    ) override
 +    {
 +        auto context = reinterpret_cast<csl::CSLContext*>(context_);
 +
 +        CV_Assert(inputs.size() == 1 || inputs.size() == 2);
 +        auto input_wrapper = inputs[0].dynamicCast<CUDABackendWrapper>();
 +        auto input_shape = input_wrapper->getShape();
 +
 +        CV_Assert(outputs.size() == 1);
 +        auto output_wrapper = outputs[0].dynamicCast<CUDABackendWrapper>();
 +        auto output_shape = output_wrapper->getShape();
 +
 +        CV_Assert(!blobs.empty());
 +        const auto output_feature_maps = blobs[0].size[0];
 +        const auto input_feature_maps = input_shape[1];
 +        const auto input_feature_maps_per_group = blobs[0].size[1];
 +        const auto groups = input_feature_maps / input_feature_maps_per_group;
 +
 +        ConvolutionConfiguration config;
 +
 +        if (input_shape.size() == 3)
 +        {
 +            // Conv1D
 +            // We add an extra dim for input and output tensors, because CuDNN doesn't support convolution with 3D tensors
 +            input_shape.insert(std::end(input_shape) - 1, 1);
 +            output_shape.insert(std::end(output_shape) - 1, 1);
 +
 +            // Do the similar thing for the other parameters
 +            pads_begin.insert(std::begin(pads_begin), 0);
 +            pads_end.insert(std::begin(pads_end), 0);
 +            strides.insert(std::begin(strides), 1);
 +            dilations.insert(std::begin(dilations), 1);
 +            kernel_size.insert(std::begin(kernel_size), 1);
 +        }
 +        config.kernel_size.assign(std::begin(kernel_size), std::end(kernel_size));
 +        config.dilations.assign(std::begin(dilations), std::end(dilations));
 +        config.strides.assign(std::begin(strides), std::end(strides));
 +
 +        if (padMode.empty())
 +        {
 +            config.padMode = ConvolutionConfiguration::PaddingMode::MANUAL;
 +            config.pads_begin.assign(std::begin(pads_begin), std::end(pads_begin));
 +            config.pads_end.assign(std::begin(pads_end), std::end(pads_end));
 +        }
 +        else if (padMode == "VALID")
 +        {
 +            config.padMode = ConvolutionConfiguration::PaddingMode::VALID;
 +        }
 +        else if (padMode == "SAME")
 +        {
 +            config.padMode = ConvolutionConfiguration::PaddingMode::SAME;
 +        }
 +        else
 +        {
 +            CV_Error(Error::StsNotImplemented, padMode + " padding mode not supported by ConvolutionLayer");
 +        }
 +
 +        config.input_shape.assign(std::begin(input_shape), std::end(input_shape));
 +        config.output_shape.assign(std::begin(output_shape), std::end(output_shape));
 +        config.groups = groups;
 +
 +        config.fusion_mode = cudaFusionMode;
 +        config.activation_type = cudaActType;
 +        config.relu_negative_slope = cuda_relu_slope;
 +        config.crelu_floor = cuda_crelu_floor;
 +        config.crelu_ceil = cuda_crelu_ceil;
 +        config.power_exp = cuda_power_exp;
 +        config.power_scale = cuda_power_scale;
 +        config.power_shift = cuda_power_shift;
 +
 +        Mat filtersMat = fusedWeights ? weightsMat : blobs[0];
 +        Mat biasMat = (hasBias() || fusedBias) ? Mat(output_feature_maps, 1, CV_32F, biasvec.data()) : Mat();
 +        if (countNonZero(biasMat) == 0)
 +            biasMat = Mat();
 +
 +        return make_cuda_node<cuda4dnn::ConvolutionOp>(
 +            preferableTarget, std::move(context->stream), std::move(context->cudnn_handle), config, filtersMat, biasMat);
 +    }
 +#endif
 +
 +    virtual bool tryQuantize(const std::vector<std::vector<float> > &scales,
 +                             const std::vector<std::vector<int> > &zeropoints, LayerParams& params) CV_OVERRIDE
 +    {
 +        // References - https://arxiv.org/pdf/1712.05877.pdf
 +
 +        // Quantized convolution with variable weights is not supported.
 +        if (blobs.empty())
 +            return false;
 +
 +        float inputScale = scales[0][0], outputScale = scales[1][0];
 +        int inputZp = zeropoints[0][0];
 +        params.set("input_zeropoint", inputZp);
 +
 +        Mat weightsQuantized(weightsMat.rows, weightsMat.cols, CV_8S);
 +        Mat biasQuantized(1, numOutput, CV_32S);
 +        Mat outputMultiplier(1, numOutput, CV_32F);
 +        double realMin, realMax, weightsScale;
 +
 +        for( int i = 0; i < numOutput; i++ )
 +        {
 +            // Quantize weights
 +            cv::minMaxIdx(weightsMat.row(i), &realMin, &realMax);
 +            realMin = std::min(realMin, 0.0);
 +            realMax = std::max(realMax, 0.0);
 +            weightsScale = (realMax == realMin) ? 1.0 : std::max(-realMin, realMax)/127;
 +            weightsMat.row(i).convertTo(weightsQuantized.row(i), CV_8S, 1.f/weightsScale);
 +
 +            // Quantize biases
 +            float biasScale = inputScale * weightsScale;
 +            biasQuantized.at<int>(i) = (int)std::round(biasvec[i]/biasScale) - inputZp*(cv::sum(weightsQuantized.row(i))[0]);
 +
 +            // Store multiplier
 +            outputMultiplier.at<float>(i) = biasScale / outputScale;
 +        }
 +
 +        params.blobs.clear();
 +        params.blobs.push_back(weightsQuantized.reshape(1, shape(blobs[0])));
 +        params.blobs.push_back(biasQuantized);
 +        params.blobs.push_back(outputMultiplier);
 +        return true;
 +    }
 +
      virtual int64 getFLOPS(const std::vector<MatShape> &inputs,
                             const std::vector<MatShape> &outputs) const CV_OVERRIDE
      {
@@@ -8,8 -8,8 +8,10 @@@
  #include "../precomp.hpp"
  #include <opencv2/dnn/shape_utils.hpp>
  
 +#include <opencv2/dnn/layer_reg.private.hpp>
 +
+ #include <opencv2/core/utils/fp_control_utils.hpp>
  #include <opencv2/core/utils/logger.defines.hpp>
  #undef CV_LOG_STRIP_LEVEL
  #define CV_LOG_STRIP_LEVEL CV_LOG_LEVEL_VERBOSE + 1
@@@ -548,11 -457,14 +553,15 @@@ Mat ONNXImporter::getBlob(const std::st
  void ONNXImporter::addLayer(LayerParams& layerParams,
                              const opencv_onnx::NodeProto& node_proto)
  {
 -    int id = dstNet.addLayer(layerParams.name, layerParams.type, layerParams);
 +    int depth = layerParams.get<int>("depth", CV_32F);
 +    int id = dstNet.addLayer(layerParams.name, layerParams.type, depth, layerParams);
      for (int i = 0; i < node_proto.output_size(); ++i)
      {
-         layer_id.insert(std::make_pair(node_proto.output(i), LayerInfo(id, i)));
+         const std::string& output_name = node_proto.output(i);
+         if (!output_name.empty())
+         {
+             layer_id.insert(std::make_pair(output_name, LayerInfo(id, i)));
+         }
      }
  
      std::vector<MatShape> layerInpShapes, layerOutShapes, layerInternalShapes;
@@@ -839,53 -688,69 +852,94 @@@ void ONNXImporter::populateNet(
          handleNode(node_proto);
      }
  
 -    CV_LOG_DEBUG(NULL, "DNN/ONNX: import completed!");
+     // register outputs
+     for (int i = 0; i < graph_proto.output_size(); ++i)
+     {
+         const std::string& output_name = graph_proto.output(i).name();
+         if (output_name.empty())
+         {
+             CV_LOG_ERROR(NULL, "DNN/ONNX: can't register output without name: " << i);
+             continue;
+         }
+         ConstIterLayerId_t layerIt = layer_id.find(output_name);
+         if (layerIt == layer_id.end())
+         {
+             CV_LOG_ERROR(NULL, "DNN/ONNX: can't find layer for output name: '" << output_name << "'. Does model imported properly?");
+             continue;
+         }
+         const LayerInfo& li = layerIt->second;
+         int outputId = dstNet.registerOutput(output_name, li.layerId, li.outputId); CV_UNUSED(outputId);
+         // no need to duplicate message from engine: CV_LOG_DEBUG(NULL, "DNN/ONNX: registered output='" << output_name << "' with id=" << outputId);
+     }
 +    CV_LOG_DEBUG(NULL, (DNN_DIAGNOSTICS_RUN ? "DNN/ONNX: diagnostic run completed!" : "DNN/ONNX: import completed!"));
 +}
 +
 +std::string ONNXImporter::getLayerTypeDomain(const opencv_onnx::NodeProto& node_proto)
 +{
 +    if (!node_proto.has_domain())
 +        return str_domain_ai_onnx;
 +    const std::string& domain = node_proto.domain();
 +    if (domain.empty())
 +        return str_domain_ai_onnx;
 +    return domain;
 +}
 +
 +const ONNXImporter::DispatchMap& ONNXImporter::getDispatchMap(const opencv_onnx::NodeProto& node_proto)
 +{
 +    static DispatchMap empty_map;
 +    const std::string& layer_type_domain = getLayerTypeDomain(node_proto);
 +    auto it = domain_dispatch_map.find(layer_type_domain);
 +    if (it == domain_dispatch_map.end())
 +    {
 +        return empty_map;
 +    }
 +
 +    return it->second;
  }
  
 -static
+ const std::string& extractNodeName(const opencv_onnx::NodeProto& node_proto)
+ {
+     if (node_proto.has_name() && !node_proto.name().empty())
+     {
+         return node_proto.name();
+     }
+     for (int i = 0; i < node_proto.output_size(); ++i)
+     {
+         const std::string& name = node_proto.output(i);
+         // There are two ways to leave an optional input or output unspecified:
+         // the first, available only for trailing inputs and outputs, is to simply not provide that input;
+         // the second method is to use an empty string in place of an input or output name.
+         if (!name.empty())
+         {
+             return name;
+         }
+     }
+     CV_Error(Error::StsAssert, "Couldn't deduce Node name.");
+ }
  void ONNXImporter::handleNode(const opencv_onnx::NodeProto& node_proto)
  {
      CV_Assert(node_proto.output_size() >= 1);
-     const std::string& name = node_proto.output(0);
+     const std::string& name = extractNodeName(node_proto);
      const std::string& layer_type = node_proto.op_type();
 -    const std::string& layer_type_domain = node_proto.has_domain() ? node_proto.domain() : std::string();
 -    if (!layer_type_domain.empty() && layer_type_domain != "ai.onnx")
 +    const std::string& layer_type_domain = getLayerTypeDomain(node_proto);
 +    const auto& dispatch = getDispatchMap(node_proto);
 +
 +    CV_LOG_DEBUG(NULL, "DNN/ONNX: processing node with " << node_proto.input_size() << " inputs and "
 +                                                         << node_proto.output_size() << " outputs: "
 +                                                         << cv::format("[%s]:(%s)", layer_type.c_str(), name.c_str())
 +                                                         << cv::format(" from %sdomain='", onnx_opset_map.count(layer_type_domain) == 1 ? "" : "undeclared ")
 +                                                         << layer_type_domain << "'"
 +    );
 +
 +    if (dispatch.empty())
      {
 -        CV_LOG_WARNING(NULL, "DNN/ONNX: can't handle node with " << node_proto.input_size() << " inputs and " << node_proto.output_size() << " outputs: "
 -                << cv::format("[%s@%s]:(%s)", layer_type.c_str(), layer_type_domain.c_str(), name.c_str())
 -        );
 -        CV_Error(Error::StsNotImplemented, cv::format("ONNX: unsupported domain: %s", layer_type_domain.c_str()));
 +        CV_LOG_WARNING(NULL, "DNN/ONNX: missing dispatch map for domain='" << layer_type_domain << "'");
      }
  
 -    CV_LOG_DEBUG(NULL, "DNN/ONNX: processing node with " << node_proto.input_size() << " inputs and " << node_proto.output_size() << " outputs: "
 -            << cv::format("[%s]:(%s)", layer_type.c_str(), name.c_str())
 -    );
 -
 +    LayerParams layerParams;
      try
      {
          // FIXIT not all cases can be repacked into "LayerParams". Importer should handle such cases directly for each "layer_type"
@@@ -1583,46 -1392,6 +1639,47 @@@ void ONNXImporter::parseLSTM(LayerParam
      addLayer(layerParams, node_proto);
  }
  
-     node_proto.set_output(0, layerParams.name);  // keep origin GRU's name
 +void ONNXImporter::parseGRU(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_)
 +{
 +    opencv_onnx::NodeProto node_proto = node_proto_;
++    const std::string output_name = node_proto.output(0);
 +    LayerParams gruParams = layerParams;
 +    gruParams.name += "/gru";
 +
 +    // https://pytorch.org/docs/stable/generated/torch.nn.GRU.html?highlight=gru#
 +    CV_Assert(node_proto.input_size() == 6);
 +    Mat Wx = getBlob(node_proto, 1);
 +    Mat Wh = getBlob(node_proto, 2);
 +    Mat b = getBlob(node_proto, 3);
 +    Mat h0 = getBlob(node_proto, 5);
 +
 +    Wx = Wx.reshape(1, Wx.size[0] * Wx.size[1]);
 +    Wh = Wh.reshape(1, Wh.size[0] * Wh.size[1]);
 +    h0 = h0.reshape(1, h0.size[0] * h0.size[1]);
 +    b = b.reshape(1, b.size[0]);
 +
 +    gruParams.blobs.resize(4);
 +    gruParams.blobs[0] = Wh;
 +    gruParams.blobs[1] = Wx;
 +    gruParams.blobs[2] = b;
 +    gruParams.blobs[3] = h0;
 +    gruParams.set("bidirectional", gruParams.get<String>("direction", "") == "bidirectional");
 +
 +    node_proto.set_output(0, gruParams.name);  // set different name so output shapes will be registered on that name
 +    addLayer(gruParams, node_proto);
 +
 +    MatShape gruShape = outShapes[node_proto.output(0)];
 +
 +    // Add fake 1 as it is done in ONNX
 +    gruShape.insert(gruShape.begin() + 1, 1);
 +
 +    layerParams.type = "Reshape";
 +    layerParams.set("dim", DictValue::arrayInt(&gruShape[0], gruShape.size()));
 +    node_proto.set_input(0, gruParams.name);  // redirect input to GRU
++    node_proto.set_output(0, output_name);  // keep origin GRU's name
 +    addLayer(layerParams, node_proto);
 +}
 +
  void ONNXImporter::parseImageScaler(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
  {
      const float scale = layerParams.has("scale") ? layerParams.get<float>("scale") : 1.0f;
@@@ -2224,11 -1961,9 +2282,11 @@@ void ONNXImporter::parseSqueeze(LayerPa
          Mat inp = getBlob(node_proto, 0);
          Mat out = inp.reshape(1, outShape);
          out.dims = outShape.size();  // to workaround dims == 1
-         addConstant(layerParams.name, out);
+         addConstant(node_proto.output(0), out);
          return;
      }
 +    int depth = layerParams.get<int>("depth", CV_32F);
 +    layerParams.type += (depth == CV_8S) ? "Int8" : "";
      addLayer(layerParams, node_proto);
  }
  
@@@ -2555,11 -2286,9 +2614,11 @@@ void ONNXImporter::parseShape(LayerPara
      if (isDynamicShape)
      {
          CV_LOG_ERROR(NULL, "DNN/ONNX(Shape): dynamic 'zero' shapes are not supported, input " << toString(inpShape, node_proto.input(0)));
 -        CV_Assert(!isDynamicShape);  // not supported
 +        // FIXIT repair assertion
 +        // Disabled to pass face detector tests from #20422
 +        // CV_Assert(!isDynamicShape);  // not supported
      }
-     addConstant(layerParams.name, shapeMat);
+     addConstant(node_proto.output(0), shapeMat);
  }
  
  void ONNXImporter::parseCast(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
@@@ -509,10 -509,9 +511,11 @@@ void ExcludeLayer(tensorflow::GraphDef
          net.mutable_node()->DeleteSubrange(layer_index, 1);
  }
  
 +class TFLayerHandler;
 +
  class TFImporter
  {
+     FPDenormalsIgnoreHintScope fp_denormals_ignore_scope;
  public:
      TFImporter(Net& net, const char *model, const char *config = NULL);
      TFImporter(Net& net, const char *dataModel, size_t lenModel,
@@@ -1180,11 -1170,11 +1180,11 @@@ TEST_P(Test_ONNX_conformance, Layer_Tes
          throw;
      }
  
 -    std::vector<String> layerNames = net.getUnconnectedOutLayersNames();
 +    std::vector<std::string> layerNames = net.getUnconnectedOutLayersNames();
-     std::vector< std::vector<Mat> > outputs_;
+     std::vector<Mat> outputs;
      try
      {
-         net.forward(outputs_, layerNames);
+         net.forward(outputs, layerNames);
      }
      catch (...)
      {
@@@ -78,12 -66,10 +78,15 @@@ if(HAVE_QT
  
      set(qt_deps Core Gui Widgets Test Concurrent)
      if(HAVE_QT_OPENGL)
 +      add_definitions(-DHAVE_QT_OPENGL)
 +      # QOpenGLWidget requires Qt6 package component OpenGLWidgets
 +      if(QT_VERSION_MAJOR GREATER 5)
 +        list(APPEND qt_deps OpenGLWidgets)
 +      endif()
        list(APPEND qt_deps OpenGL)
+       if(OPENGL_LIBRARIES)
+         list(APPEND HIGHGUI_LIBRARIES "${OPENGL_LIBRARIES}")
+       endif()
      endif()
  
      foreach(dt_dep ${qt_deps})
@@@ -150,89 -149,6 +156,92 @@@ elseif(HAVE_COCOA
    list(APPEND HIGHGUI_LIBRARIES "-framework Cocoa")
  endif()
  
 +if(TARGET ocv.3rdparty.win32ui)
 +  if("win32ui" IN_LIST HIGHGUI_PLUGIN_LIST OR HIGHGUI_PLUGIN_LIST STREQUAL "all")
 +    ocv_create_builtin_highgui_plugin(opencv_highgui_win32 ocv.3rdparty.win32ui "window_w32.cpp")
 +  elseif(NOT OPENCV_HIGHGUI_BUILTIN_BACKEND)
 +    set(OPENCV_HIGHGUI_BUILTIN_BACKEND "WIN32UI")
 +    list(APPEND highgui_srcs ${CMAKE_CURRENT_LIST_DIR}/src/window_w32.cpp)
 +    list(APPEND tgts ocv.3rdparty.win32ui)
++    if(HAVE_OPENGL AND OPENGL_LIBRARIES)
++      list(APPEND tgts "${OPENGL_LIBRARIES}")
++    endif()
 +  endif()
 +endif()
 +
 +if(TARGET ocv.3rdparty.gtk3 OR TARGET ocv.3rdparty.gtk2)
 +  if(TARGET ocv.3rdparty.gtk3 AND NOT WITH_GTK_2_X)
 +    set(__gtk_dependency "ocv.3rdparty.gtk3")
 +  else()
 +    set(__gtk_dependency "ocv.3rdparty.gtk2")
 +  endif()
 +  if(
 +    NOT HIGHGUI_PLUGIN_LIST STREQUAL "all"
 +    AND NOT "gtk" IN_LIST HIGHGUI_PLUGIN_LIST
 +    AND NOT "gtk2" IN_LIST HIGHGUI_PLUGIN_LIST
 +    AND NOT "gtk3" IN_LIST HIGHGUI_PLUGIN_LIST
 +    AND NOT OPENCV_HIGHGUI_BUILTIN_BACKEND
 +  )
 +    if(__gtk_dependency STREQUAL "ocv.3rdparty.gtk3")
 +      set(OPENCV_HIGHGUI_BUILTIN_BACKEND "GTK3")
 +    elseif(__gtk_dependency STREQUAL "ocv.3rdparty.gtk2")
 +      set(OPENCV_HIGHGUI_BUILTIN_BACKEND "GTK2")
 +    else()
 +      set(OPENCV_HIGHGUI_BUILTIN_BACKEND "GTK")
 +    endif()
 +    list(APPEND highgui_srcs ${CMAKE_CURRENT_LIST_DIR}/src/window_gtk.cpp)
 +    list(APPEND tgts ${__gtk_dependency})
 +    if(TARGET ocv.3rdparty.gthread)
 +      list(APPEND tgts ocv.3rdparty.gthread)
 +    endif()
 +    if(TARGET ocv.3rdparty.gtkglext
 +        AND __gtk_dependency STREQUAL "ocv.3rdparty.gtk2"
 +        AND NOT OPENCV_GTK_DISABLE_GTKGLEXT
 +    )
 +      list(APPEND tgts ocv.3rdparty.gtkglext)
 +      if(TARGET ocv.3rdparty.gtk_opengl
 +          AND __gtk_dependency STREQUAL "ocv.3rdparty.gtk2"
 +          AND NOT OPENCV_GTK_DISABLE_OPENGL
 +      )
 +        list(APPEND tgts ocv.3rdparty.gtk_opengl)
 +      endif()
 +    endif()
 +  elseif("gtk" IN_LIST HIGHGUI_PLUGIN_LIST)
 +    ocv_create_builtin_highgui_plugin(opencv_highgui_gtk ${__gtk_dependency} "window_gtk.cpp")
 +    if(TARGET ocv.3rdparty.gthread)
 +      ocv_target_link_libraries(opencv_highgui_gtk ocv.3rdparty.gthread)
 +    endif()
 +    if(TARGET ocv.3rdparty.gtkglext)
 +      ocv_target_link_libraries(opencv_highgui_gtk ocv.3rdparty.gtkglext)
 +    endif()
 +  else()
 +    if(TARGET ocv.3rdparty.gtk3 AND ("gtk3" IN_LIST HIGHGUI_PLUGIN_LIST OR HIGHGUI_PLUGIN_LIST STREQUAL "all"))
 +      ocv_create_builtin_highgui_plugin(opencv_highgui_gtk3 ocv.3rdparty.gtk3 "window_gtk.cpp")
 +      if(TARGET ocv.3rdparty.gthread)
 +        ocv_target_link_libraries(opencv_highgui_gtk3 ocv.3rdparty.gthread)
 +      endif()
 +      if(TARGET ocv.3rdparty.gtkglext)
 +        ocv_target_link_libraries(opencv_highgui_gtk3 ocv.3rdparty.gtkglext)
 +      endif()
 +    endif()
 +    if(TARGET ocv.3rdparty.gtk2 AND ("gtk2" IN_LIST HIGHGUI_PLUGIN_LIST OR HIGHGUI_PLUGIN_LIST STREQUAL "all"))
 +      ocv_create_builtin_highgui_plugin(opencv_highgui_gtk2 ocv.3rdparty.gtk2 "window_gtk.cpp")
 +      if(TARGET ocv.3rdparty.gthread)
 +        ocv_target_link_libraries(opencv_highgui_gtk2 ocv.3rdparty.gthread)
 +      endif()
 +      if(TARGET ocv.3rdparty.gtkglext)
 +        ocv_target_link_libraries(opencv_highgui_gtk2 ocv.3rdparty.gtkglext)
 +      endif()
 +    endif()
 +  endif()
 +endif()
 +
 +if(NOT OPENCV_HIGHGUI_BUILTIN_BACKEND)
 +  set(OPENCV_HIGHGUI_BUILTIN_BACKEND "NONE")
 +endif()
 +message(STATUS "highgui: using builtin backend: ${OPENCV_HIGHGUI_BUILTIN_BACKEND}")
 +set(OPENCV_HIGHGUI_BUILTIN_BACKEND "${OPENCV_HIGHGUI_BUILTIN_BACKEND}" PARENT_SCOPE)  # informational
 +
  if(TRUE)
    # these variables are set by 'ocv_append_build_options(HIGHGUI ...)'
    foreach(P ${HIGHGUI_INCLUDE_DIRS})
@@@ -95,35 -94,20 +95,35 @@@ enum ImwriteFlags 
         IMWRITE_PNG_BILEVEL         = 18, //!< Binary level PNG, 0 or 1, default is 0.
         IMWRITE_PXM_BINARY          = 32, //!< For PPM, PGM, or PBM, it can be a binary format flag, 0 or 1. Default value is 1.
         IMWRITE_EXR_TYPE            = (3 << 4) + 0, /* 48 */ //!< override EXR storage type (FLOAT (FP32) is default)
 +       IMWRITE_EXR_COMPRESSION     = (3 << 4) + 1, /* 49 */ //!< override EXR compression type (ZIP_COMPRESSION = 3 is default)
         IMWRITE_WEBP_QUALITY        = 64, //!< For WEBP, it can be a quality from 1 to 100 (the higher is the better). By default (without any parameter) and for quality above 100 the lossless compression is used.
         IMWRITE_PAM_TUPLETYPE       = 128,//!< For PAM, sets the TUPLETYPE field to the corresponding string value that is defined for the format
-        IMWRITE_TIFF_RESUNIT = 256,//!< For TIFF, use to specify which DPI resolution unit to set; see libtiff documentation for valid values
-        IMWRITE_TIFF_XDPI = 257,//!< For TIFF, use to specify the X direction DPI
-        IMWRITE_TIFF_YDPI = 258, //!< For TIFF, use to specify the Y direction DPI
-        IMWRITE_TIFF_COMPRESSION = 259, //!< For TIFF, use to specify the image compression scheme. See libtiff for integer constants corresponding to compression formats. Note, for images whose depth is CV_32F, only libtiff's SGILOG compression scheme is used. For other supported depths, the compression scheme can be specified by this flag; LZW compression is the default.
 -       IMWRITE_TIFF_RESUNIT        = 256,//!< For TIFF, use to specify which DPI resolution unit to set; see libtiff documentation for valid values.
 -       IMWRITE_TIFF_XDPI           = 257,//!< For TIFF, use to specify the X direction DPI.
 -       IMWRITE_TIFF_YDPI           = 258,//!< For TIFF, use to specify the Y direction DPI.
 -       IMWRITE_TIFF_COMPRESSION    = 259 //!< For TIFF, use to specify the image compression scheme. See libtiff for integer constants corresponding to compression formats. Note, for images whose depth is CV_32F, only libtiff's SGILOG compression scheme is used. For other supported depths, the compression scheme can be specified by this flag; LZW compression is the default.
++       IMWRITE_TIFF_RESUNIT        = 256,//!< For TIFF, use to specify which DPI resolution unit to set; see libtiff documentation for valid values
++       IMWRITE_TIFF_XDPI           = 257,//!< For TIFF, use to specify the X direction DPI
++       IMWRITE_TIFF_YDPI           = 258,//!< For TIFF, use to specify the Y direction DPI
++       IMWRITE_TIFF_COMPRESSION    = 259,//!< For TIFF, use to specify the image compression scheme. See libtiff for integer constants corresponding to compression formats. Note, for images whose depth is CV_32F, only libtiff's SGILOG compression scheme is used. For other supported depths, the compression scheme can be specified by this flag; LZW compression is the default.
 +       IMWRITE_JPEG2000_COMPRESSION_X1000 = 272 //!< For JPEG2000, use to specify the target compression rate (multiplied by 1000). The value can be from 0 to 1000. Default is 1000.
       };
  
  enum ImwriteEXRTypeFlags {
         /*IMWRITE_EXR_TYPE_UNIT = 0, //!< not supported */
-        IMWRITE_EXR_TYPE_HALF = 1,   //!< store as HALF (FP16)
-        IMWRITE_EXR_TYPE_FLOAT = 2   //!< store as FP32 (default)
+        IMWRITE_EXR_TYPE_HALF   = 1, //!< store as HALF (FP16)
+        IMWRITE_EXR_TYPE_FLOAT  = 2  //!< store as FP32 (default)
       };
  
 +enum ImwriteEXRCompressionFlags {
 +       IMWRITE_EXR_COMPRESSION_NO    = 0, //!< no compression
 +       IMWRITE_EXR_COMPRESSION_RLE   = 1, //!< run length encoding
 +       IMWRITE_EXR_COMPRESSION_ZIPS  = 2, //!< zlib compression, one scan line at a time
 +       IMWRITE_EXR_COMPRESSION_ZIP   = 3, //!< zlib compression, in blocks of 16 scan lines
 +       IMWRITE_EXR_COMPRESSION_PIZ   = 4, //!< piz-based wavelet compression
 +       IMWRITE_EXR_COMPRESSION_PXR24 = 5, //!< lossy 24-bit float compression
 +       IMWRITE_EXR_COMPRESSION_B44   = 6, //!< lossy 4-by-4 pixel block compression, fixed compression rate
 +       IMWRITE_EXR_COMPRESSION_B44A  = 7, //!< lossy 4-by-4 pixel block compression, flat fields are compressed more
 +       IMWRITE_EXR_COMPRESSION_DWAA  = 8, //!< lossy DCT based compression, in blocks of 32 scanlines. More efficient for partial buffer access. Supported since OpenEXR 2.2.0.
 +       IMWRITE_EXR_COMPRESSION_DWAB  = 9, //!< lossy DCT based compression, in blocks of 256 scanlines. More efficient space wise and faster to decode full frames than DWAA_COMPRESSION. Supported since OpenEXR 2.2.0.
 +     };
 +
  //! Imwrite PNG specific flags used to tune the compression algorithm.
  /** These flags will be modify the way of PNG image compression and will be passed to the underlying zlib processing stage.
  
Simple merge
Simple merge