// 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 "precomp.hpp" #include "net_impl.hpp" #ifdef HAVE_ONNXRUNTIME #include #include #include #endif namespace cv { namespace dnn { CV__DNN_INLINE_NS_BEGIN static int g_networkId = 0; detail::NetImplBase::NetImplBase() : networkId(CV_XADD(&g_networkId, 1)) , networkDumpCounter(0) , dumpLevel(getParam_DNN_NETWORK_DUMP()) { // nothing } std::string detail::NetImplBase::getDumpFileNameBase() const { std::string dumpFileNameBase = cv::format("ocv_dnn_net_%05d_%02d", networkId, networkDumpCounter++); return dumpFileNameBase; } Net::Impl::~Impl() { #ifdef HAVE_VULKAN if (context) context->reset(); #endif } Net::Impl::Impl() { // allocate fake net input layer netInputLayer = Ptr(new DataLayer()); LayerData& inpl = layers.insert(make_pair(0, LayerData())).first->second; inpl.id = 0; netInputLayer->name = inpl.name = "_input"; inpl.type = "__NetInputLayer__"; inpl.layerInstance = netInputLayer; layerNameToId.insert(std::make_pair(inpl.name, inpl.id)); lastLayerId = 0; netWasAllocated = false; netWasQuantized = false; fusion = true; isAsync = false; preferableBackend = (Backend)getParam_DNN_BACKEND_DEFAULT(); preferableTarget = DNN_TARGET_CPU; hasDynamicShapes = false; useWinograd = true; ////////////// extra initialization for the new engine ///////////////// modelFormat = DNN_MODEL_GENERIC; originalLayout = DATA_LAYOUT_NCHW; // onnx_opset = 0; accuracy = CV_32F; defaultC0 = DEFAULT_C0; enableFP16 = haveFP16 = false; // FP16 is not ready yet in the new DNN engine // Ticket: https://github.com/opencv/opencv/issues/26196 /*if (checkHardwareSupport(CV_CPU_FP16)) { enableFP16 = haveFP16 = true; }*/ tracingMode = DNN_TRACE_NONE; profilingMode = DNN_PROFILE_NONE; dump_strm = &std::cout; dump_indent = 3; clear(); } bool Net::Impl::empty() const { if (mainGraph) return false; return layers.size() <= 1; // first layer is default Data layer } void Net::Impl::clear() { CV_TRACE_FUNCTION(); MapIdToLayerData::iterator it; for (it = layers.begin(); it != layers.end(); it++) { if (it->second.id != 0) { it->second.inputBlobs.clear(); it->second.outputBlobs.clear(); it->second.internals.clear(); } it->second.skip = false; // it->second.consumers.clear(); Ptr currLayer = it->second.layerInstance; if (currLayer.empty()) continue; currLayer->unsetAttached(); } netWasAllocated = false; layersTimings.clear(); /////////////// for the new inference engine ////////////////// modelFormat = DNN_MODEL_GENERIC; dimnames = NamesHash(); dimnames_vec = std::vector(); args = std::vector(); argnames = NamesHash(); __tensors__ = std::vector(); bufidxs = std::vector(); buffers = std::vector(); mainGraph = Ptr(); ArgData adata; adata.name = ""; adata.kind = DNN_ARG_CONST; args.push_back(adata); argnames.insert(std::make_pair(std::string(""), 0)); __tensors__.push_back(Mat()); bufidxs.push_back(-1); prepared = false; finalizeLayers = true; } void Net::Impl::validateBackendAndTarget() { CV_TRACE_FUNCTION(); CV_Assert(preferableBackend != DNN_BACKEND_OPENCV || preferableTarget == DNN_TARGET_CPU || preferableTarget == DNN_TARGET_CPU_FP16 || preferableTarget == DNN_TARGET_OPENCL || preferableTarget == DNN_TARGET_OPENCL_FP16); #ifdef HAVE_WEBNN if (preferableBackend == DNN_BACKEND_WEBNN) { CV_Assert(preferableTarget == DNN_TARGET_CPU || preferableTarget == DNN_TARGET_OPENCL); } #endif CV_Assert(preferableBackend != DNN_BACKEND_VKCOM || preferableTarget == DNN_TARGET_VULKAN); CV_Assert(preferableBackend != DNN_BACKEND_CUDA || IS_DNN_CUDA_TARGET(preferableTarget)); CV_Assert(preferableBackend != DNN_BACKEND_TIMVX || preferableTarget == DNN_TARGET_NPU); CV_Assert(preferableBackend != DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && "Inheritance internal error"); } void Net::Impl::setUpNet(const std::vector& blobsToKeep_) { CV_TRACE_FUNCTION(); if (dumpLevel && networkDumpCounter == 0) { dumpNetworkToFile(); } validateBackendAndTarget(); if (!netWasAllocated || this->blobsToKeep != blobsToKeep_) { if (preferableBackend == DNN_BACKEND_OPENCV && IS_DNN_OPENCL_TARGET(preferableTarget)) #ifndef HAVE_OPENCL { CV_LOG_WARNING(NULL, "DNN: OpenCL target is not available in this OpenCV build, switching to CPU."); preferableTarget = DNN_TARGET_CPU; } #else { if (!getParam_DNN_OPENCL_ALLOW_ALL_DEVICES()) { // Current implementation is only valid for GPU (#11494) if (ocl::Device::getDefault().type() != ocl::Device::TYPE_GPU) { CV_LOG_WARNING(NULL, "DNN: OpenCL target is not supported with current OpenCL device (tested with GPUs only), switching to CPU."); preferableTarget = DNN_TARGET_CPU; } else if (preferableTarget == DNN_TARGET_OPENCL_FP16 && !ocl::Device::getDefault().isIntel()) { CV_LOG_WARNING(NULL, "DNN: OpenCL target with fp16 precision is not supported " "with current OpenCL device (tested with Intel GPUs only), " "switching to OpenCL with fp32 precision."); preferableTarget = DNN_TARGET_OPENCL; } } } #endif if (preferableBackend == DNN_BACKEND_VKCOM && !haveVulkan()) { preferableBackend = DNN_BACKEND_OPENCV; preferableTarget = DNN_TARGET_CPU; } if (preferableBackend == DNN_BACKEND_CUDA && !haveCUDA()) { #ifdef HAVE_CUDA CV_LOG_WARNING(NULL, "unable to use CUDA backend; switching to CPU"); #else CV_LOG_WARNING(NULL, "DNN module was not built with CUDA backend; switching to CPU"); #endif preferableBackend = DNN_BACKEND_OPENCV; preferableTarget = DNN_TARGET_CPU; } if (preferableBackend == DNN_BACKEND_TIMVX && !haveTimVX()) { preferableBackend = DNN_BACKEND_OPENCV; preferableTarget = DNN_TARGET_CPU; } clear(); this->blobsToKeep = blobsToKeep_; allocateLayers(blobsToKeep_); MapIdToLayerData::iterator it = layers.find(0); CV_Assert(it != layers.end()); it->second.skip = netInputLayer->skip; initBackend(blobsToKeep_); netWasAllocated = true; if (dumpLevel) { dumpNetworkToFile(); } } } Ptr Net::Impl::getLayer(int layerId) const { if (mainGraph) { CV_Assert(0 <= layerId && layerId < totalLayers); int graph_ofs = 0; for (const Ptr& graph : allgraphs) { const std::vector >& prog = graph->prog(); int nops = (int)prog.size(); CV_Assert(layerId >= graph_ofs); if (layerId < graph_ofs + nops) return prog[layerId - graph_ofs]; graph_ofs += nops; } CV_Error_(Error::StsObjectNotFound, ("layer #%d is not found", layerId)); } else { LayerData& ld = getLayerData(layerId); return getLayerInstance(ld); } } Ptr Net::Impl::getLayer(const LayerId& layerId) const { LayerData& ld = getLayerData(layerId); return getLayerInstance(ld); } int Net::Impl::getLayerId(const String& layerName) const { std::map::const_iterator it = layerNameToId.find(layerName); return (it != layerNameToId.end()) ? it->second : -1; } int Net::Impl::getLayerId(int id) const { MapIdToLayerData::const_iterator it = layers.find(id); return (it != layers.end()) ? id : -1; } int Net::Impl::getLayerId(DictValue& layerDesc) const { if (layerDesc.isInt()) return getLayerId(layerDesc.get()); else if (layerDesc.isString()) return getLayerId(layerDesc.get()); CV_Assert(layerDesc.isInt() || layerDesc.isString()); return -1; } String Net::Impl::getLayerName(int id) const { MapIdToLayerData::const_iterator it = layers.find(id); return (it != layers.end()) ? it->second.name : "(unknown layer)"; } LayerData& Net::Impl::getLayerData(int id) const { MapIdToLayerData::const_iterator it = layers.find(id); if (it == layers.end()) CV_Error(Error::StsObjectNotFound, format("Layer with requested id=%d not found", id)); return const_cast(it->second); } LayerData& Net::Impl::getLayerData(const String& layerName) const { int id = getLayerId(layerName); if (id < 0) CV_Error(Error::StsError, "Requested layer \"" + layerName + "\" not found"); return getLayerData(id); } LayerData& Net::Impl::getLayerData(const DictValue& layerDesc) const { CV_Assert(layerDesc.isInt() || layerDesc.isString()); if (layerDesc.isInt()) return getLayerData(layerDesc.get()); else /*if (layerDesc.isString())*/ return getLayerData(layerDesc.get()); } /*static*/ void Net::Impl::addLayerInput(LayerData& ld, int inNum, LayerPin from) { if ((int)ld.inputBlobsId.size() <= inNum) { ld.inputBlobsId.resize(inNum + 1); } else { LayerPin storedFrom = ld.inputBlobsId[inNum]; if (storedFrom.valid() && !storedFrom.equal(from)) CV_Error(Error::StsError, format("Input #%d of layer \"%s\" already was connected", inNum, ld.name.c_str())); } ld.inputBlobsId[inNum] = from; } int Net::Impl::resolvePinOutputName(LayerData& ld, const String& outName) const { if (outName.empty()) return 0; return getLayerInstance(ld)->outputNameToIndex(outName); } LayerPin Net::Impl::getPinByAlias(const String& layerName) const { LayerPin pin; pin.lid = (layerName.empty()) ? 0 : getLayerId(layerName); if (pin.lid >= 0) pin.oid = resolvePinOutputName(getLayerData(pin.lid), layerName); return pin; } std::vector Net::Impl::getLayerOutPins(const String& layerName) const { int lid = (layerName.empty()) ? 0 : getLayerId(layerName); MapIdToLayerData::const_iterator it = layers.find(lid); if (it == layers.end()) CV_Error_(Error::StsOutOfRange, ("Layer #%d is not valid", lid)); const size_t nOutputs = it->second.outputBlobs.size(); std::vector pins; for (int i = 0; i < nOutputs; i++) { pins.push_back(LayerPin(lid, i)); } return pins; } // FIXIT remove dtype int Net::Impl::addLayer(const String& name, const String& type, const int& dtype, LayerParams& params) { int id = getLayerId(name); if (id >= 0) { if (!DNN_DIAGNOSTICS_RUN || type != "NotImplemented") { CV_Error(Error::StsBadArg, "Layer \"" + name + "\" has been already added into net"); return -1; } else { LayerData& ld = layers.find(id)->second; ld.type = type; ld.params = params; return -1; } } id = ++lastLayerId; layerNameToId.insert(std::make_pair(name, id)); layers.insert(std::make_pair(id, LayerData(id, name, type, dtype, params))); if (params.get("has_dynamic_shapes", false)) hasDynamicShapes = true; if (dtype == CV_8S) netWasQuantized = true; return id; } int Net::Impl::addLayerToPrev(const String& name, const String& type, const int& dtype, LayerParams& params) { int prvLid = lastLayerId; int newLid = addLayer(name, type, dtype, params); connect(prvLid, 0, newLid, 0); return newLid; } void Net::Impl::connect(int outLayerId, int outNum, int inLayerId, int inNum) { CV_Assert(outLayerId < inLayerId); LayerData& ldOut = getLayerData(outLayerId); LayerData& ldInp = getLayerData(inLayerId); addLayerInput(ldInp, inNum, LayerPin(outLayerId, outNum)); ldOut.requiredOutputs.insert(outNum); ldOut.consumers.push_back(LayerPin(inLayerId, outNum)); CV_LOG_VERBOSE(NULL, 0, "DNN: connect(" << outLayerId << ":" << outNum << " ==> " << inLayerId << ":" << inNum << ")"); } int Net::Impl::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 Net::Impl::allocateLayer(int lid, const LayersShapesMap& layersShapes) { CV_TRACE_FUNCTION(); LayerData& ld = layers[lid]; // already allocated if (ld.flag) return; size_t ninputs = ld.inputBlobsId.size(); #if 0 printf("layer %s:", ld.name.c_str()); for (size_t i = 0; i < ninputs; i++) { int inp_lid = ld.inputBlobsId[i].lid; LayerData &inp_ld = layers[inp_lid]; int inp_outputs = (int)inp_ld.outputBlobs.size(); std::cout << " " << inp_ld.name << "(" << inp_outputs; for( int j = 0; j < inp_outputs; j++ ) { std::cout << (j == 0 ? ": " : ", ") << inp_ld.outputBlobs[j].size; } std::cout << ")"; } printf("\n"); #endif // determine parent layers for (size_t i = 0; i < ninputs; i++) ld.inputLayersId.insert(ld.inputBlobsId[i].lid); // allocate parents for (std::set::const_iterator i = ld.inputLayersId.begin(); i != ld.inputLayersId.end(); i++) allocateLayer(*i, layersShapes); // bind inputs for DataLayer if (ld.id == 0 && netInputLayer->supportBackend(preferableBackend)) { ninputs = netInputLayer->inputsData.size(); ld.inputBlobsWrappers.resize(ninputs); for (size_t i = 0; i < ninputs; i++) ld.inputBlobsWrappers[i] = wrap(netInputLayer->inputsData[i]); } else { ld.inputBlobs.resize(ninputs); ld.inputBlobsWrappers.resize(ninputs); for (size_t i = 0; i < ninputs; i++) { LayerPin from = ld.inputBlobsId[i]; CV_Assert(from.valid()); CV_DbgAssert(layers.count(from.lid) && (int)layers[from.lid].outputBlobs.size() > from.oid); ld.inputBlobs[i] = &layers[from.lid].outputBlobs[from.oid]; ld.inputBlobsWrappers[i] = layers[from.lid].outputBlobsWrappers[from.oid]; } } LayersShapesMap::const_iterator layerShapesIt = layersShapes.find(lid); CV_Assert(layerShapesIt != layersShapes.end()); if (preferableBackend == DNN_BACKEND_OPENCV && ld.dtype == CV_32F && preferableTarget == DNN_TARGET_OPENCL_FP16) ld.dtype = CV_16F; std::vector pinsForInternalBlobs; blobManager.allocateBlobsForLayer(ld, layerShapesIt->second, pinsForInternalBlobs); ld.outputBlobsWrappers.resize(ld.outputBlobs.size()); for (int i = 0; i < ld.outputBlobs.size(); ++i) ld.outputBlobsWrappers[i] = wrap(ld.outputBlobs[i]); /* CUDA & CANN backend has its own system for internal blobs; we don't need these */ ld.internalBlobsWrappers.resize((preferableBackend == DNN_BACKEND_CUDA || preferableBackend == DNN_BACKEND_TIMVX || preferableBackend == DNN_BACKEND_CANN) ? 0 : ld.internals.size()); for (int i = 0; i < ld.internalBlobsWrappers.size(); ++i) ld.internalBlobsWrappers[i] = wrap(ld.internals[i]); Ptr layerPtr = getLayerInstance(ld); { std::vector inps(ld.inputBlobs.size()); for (int i = 0; i < ld.inputBlobs.size(); ++i) { inps[i] = *ld.inputBlobs[i]; } layerPtr->finalize(inps, ld.outputBlobs); #if 0 std::cout << "\toutputs:"; size_t noutputs = ld.outputBlobs.size(); for (size_t j = 0; j < noutputs; j++) { std::cout << (j == 0 ? " " : ", ") << ld.outputBlobs[j].size; } std::cout << "\n"; #endif } // After allocation of layer, we decrease counters to it's input blobs. blobManager.releaseReferences(ld.inputBlobsId); blobManager.releaseReferences(pinsForInternalBlobs); ld.flag = 1; } void Net::Impl::allocateLayers(const std::vector& blobsToKeep_) { CV_TRACE_FUNCTION(); for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); it++) it->second.flag = 0; CV_Assert(!layers[0].outputBlobs.empty()); ShapesVec inputShapes; TypesVec inputTypes; for (int i = 0; i < layers[0].outputBlobs.size(); i++) { Mat& inp = layers[0].outputBlobs[i]; CV_Assert(inp.total()); int type = inp.type(); if (type == CV_32F) { type = CV_32F; if (preferableBackend == DNN_BACKEND_OPENCV && preferableTarget == DNN_TARGET_OPENCL_FP16) { type = CV_16F; if (layers[0].dtype == CV_32F) layers[0].outputBlobs[i].create(inp.size, CV_16F); } } inputShapes.push_back(shape(inp)); inputTypes.push_back(type); } for (auto& layer : layers) { auto& ld = layer.second; Ptr layerPtr = getLayerInstance(ld); layerPtr->preferableTarget = preferableTarget; } LayersShapesMap layersShapes; getLayersShapes(inputShapes, inputTypes, layersShapes); blobManager.reset(); backendWrappers.clear(); for (auto& layer : layers) { auto& ld = layer.second; ld.inputBlobsWrappers.clear(); ld.outputBlobsWrappers.clear(); ld.internalBlobsWrappers.clear(); } // Fake references to input blobs. for (int i = 0; i < layers[0].outputBlobs.size(); ++i) blobManager.addReference(LayerPin(0, i)); for (MapIdToLayerData::const_iterator it = layers.begin(); it != layers.end(); ++it) { const LayerData& ld = it->second; blobManager.addReferences(ld.inputBlobsId); } for (int i = 0; i < blobsToKeep_.size(); i++) { blobManager.addReference(blobsToKeep_[i]); } for (MapIdToLayerData::const_iterator it = layers.begin(); it != layers.end(); it++) { int lid = it->first; allocateLayer(lid, layersShapes); } layersTimings.resize(lastLayerId + 1, 0); fuseLayers(blobsToKeep_); } #define TRACE_INFERENCE 0 void Net::Impl::forwardLayer(LayerData& ld) { CV_TRACE_FUNCTION(); Ptr layer = ld.layerInstance; #if TRACE_INFERENCE if (layer) { printf("------------------------------------------------\n"); printf("Running layer '%s' (%s)\n", layer->name.c_str(), layer->type.c_str()); } #endif if (!ld.skip) { TickMeter tm; tm.start(); #ifndef HAVE_VULKAN std::map>::const_iterator it = ld.backendNodes.find(preferableBackend); #else std::map>::iterator it = ld.backendNodes.find(preferableBackend); #endif if (preferableBackend == DNN_BACKEND_OPENCV || it == ld.backendNodes.end() || it->second.empty()) { if (isAsync) CV_Error(Error::StsNotImplemented, "Default implementation fallbacks in asynchronous mode"); if (!layer->supportBackend(DNN_BACKEND_OPENCV)) CV_Error(Error::StsNotImplemented, format("Layer \"%s\" of type \"%s\" unsupported on OpenCV backend", ld.name.c_str(), ld.type.c_str())); #ifdef HAVE_OPENCL if (preferableBackend == DNN_BACKEND_OPENCV && IS_DNN_OPENCL_TARGET(preferableTarget)) { std::vector umat_inputBlobs = OpenCLBackendWrapper::getUMatVector(ld.inputBlobsWrappers); std::vector umat_outputBlobs = OpenCLBackendWrapper::getUMatVector(ld.outputBlobsWrappers); std::vector umat_internalBlobs = OpenCLBackendWrapper::getUMatVector(ld.internalBlobsWrappers); layer->forward(umat_inputBlobs, umat_outputBlobs, umat_internalBlobs); if (getParam_DNN_CHECK_NAN_INF()) { bool fail = false; for (size_t i = 0; i < umat_outputBlobs.size(); ++i) { UMat& u = umat_outputBlobs[i]; Mat m; if (u.depth() == CV_16F) // FP16 u.convertTo(m, CV_32F); else m = u.getMat(ACCESS_READ); if (!checkRange(m)) { CV_LOG_WARNING(NULL, "NaN detected in layer output: id=" << ld.id << " name=" << layer->name << " output id=" << i << " output shape=" << shape(m)); fail = true; } else if (!checkRange(m, true, NULL, -1e6, 1e6)) { CV_LOG_WARNING(NULL, "Inf detected in layer output: id=" << ld.id << " name=" << layer->name << " output id=" << i << " output shape=" << shape(m)); fail = true; } } if (fail) { for (size_t i = 0; i < umat_inputBlobs.size(); ++i) { UMat& u = umat_inputBlobs[i]; Mat m; if (u.depth() == CV_16F) // FP16 u.convertTo(m, CV_32F); else m = u.getMat(ACCESS_READ); std::cout << "INPUT " << i << " " << cv::typeToString(u.type()) << " " << shape(m) << std::endl; if (getParam_DNN_CHECK_NAN_INF_DUMP()) std::cout << m.reshape(1, 1) << std::endl; } for (size_t i = 0; i < umat_outputBlobs.size(); ++i) { UMat& u = umat_outputBlobs[i]; Mat m; if (u.depth() == CV_16F) // FP16 u.convertTo(m, CV_32F); else m = u.getMat(ACCESS_READ); std::cout << "OUTPUT " << i << " " << cv::typeToString(u.type()) << " " << shape(m) << std::endl; if (getParam_DNN_CHECK_NAN_INF_DUMP()) std::cout << m.reshape(1, 1) << std::endl; } for (size_t i = 0; i < umat_internalBlobs.size(); ++i) { UMat& u = umat_internalBlobs[i]; Mat m; if (u.depth() == CV_16F) // FP16 u.convertTo(m, CV_32F); else m = u.getMat(ACCESS_READ); std::cout << "INTERNAL " << i << " " << shape(m) << std::endl; if (getParam_DNN_CHECK_NAN_INF_DUMP()) std::cout << cv::typeToString(u.type()) << " " << m.reshape(1, 1) << std::endl; } if (getParam_DNN_CHECK_NAN_INF_RAISE_ERROR()) CV_Assert(!fail); } } OpenCLBackendWrapper::update(ld.outputBlobsWrappers, umat_outputBlobs); } else #endif { for (int i = 0, n = ld.inputBlobsWrappers.size(); i < n; ++i) { if (!ld.inputBlobsWrappers[i].empty()) ld.inputBlobsWrappers[i]->copyToHost(); } std::vector inps(ld.inputBlobs.size()); for (int i = 0; i < ld.inputBlobs.size(); ++i) { inps[i] = *ld.inputBlobs[i]; } layer->forward(inps, ld.outputBlobs, ld.internals); if (getParam_DNN_CHECK_NAN_INF()) { bool fail = false; for (size_t i = 0; i < ld.outputBlobs.size(); ++i) { const Mat& m = ld.outputBlobs[i]; if (!checkRange(m)) { CV_LOG_WARNING(NULL, "NaN detected in layer output: " << cv::format("id=%d name=%s output id=%zu output shape=", ld.id, layer->name.c_str(), i) << shape(m)); fail = true; } else if (!checkRange(m, true, NULL, -1e6, 1e6)) { CV_LOG_WARNING(NULL, "Inf detected in layer output: " << cv::format("id=%d name=%s output id=%zu output shape=", ld.id, layer->name.c_str(), i) << shape(m)); fail = true; } } if (fail) { for (size_t i = 0; i < ld.inputBlobs.size(); ++i) { const Mat* pM = ld.inputBlobs[i]; if (!pM) { std::cout << "INPUT " << i << " is NULL" << std::endl; continue; } const Mat& m = *pM; std::cout << "INPUT " << i << " " << cv::typeToString(m.type()) << " " << shape(m) << std::endl; if (getParam_DNN_CHECK_NAN_INF_DUMP()) std::cout << m.reshape(1, 1) << std::endl; } for (size_t i = 0; i < ld.outputBlobs.size(); ++i) { const Mat& m = ld.outputBlobs[i]; std::cout << "OUTPUT " << i << " " << cv::typeToString(m.type()) << " " << shape(m) << std::endl; if (getParam_DNN_CHECK_NAN_INF_DUMP()) std::cout << m.reshape(1, 1) << std::endl; } for (size_t i = 0; i < ld.internals.size(); ++i) { const Mat& m = ld.internals[i]; std::cout << "INTERNAL " << i << " " << cv::typeToString(m.type()) << " " << shape(m) << std::endl; if (getParam_DNN_CHECK_NAN_INF_DUMP()) std::cout << m.reshape(1, 1) << std::endl; } if (getParam_DNN_CHECK_NAN_INF_RAISE_ERROR()) CV_Assert(!fail); } } for (int i = 0, n = ld.outputBlobsWrappers.size(); i < n; ++i) { if (!ld.outputBlobsWrappers[i].empty()) ld.outputBlobsWrappers[i]->setHostDirty(); } } } else { Ptr node = it->second; CV_Assert(!node.empty()); if (preferableBackend == DNN_BACKEND_CUDA) { CV_Assert(haveCUDA()); #ifdef HAVE_CUDA Ptr cudaNode = node.dynamicCast(); CV_Assert(!cudaNode.empty()); cudaNode->forward(ld.inputBlobsWrappers, ld.outputBlobsWrappers, cudaInfo->workspace); for (auto id : ld.cudaD2HBackgroundTransfers) { auto wrapper = ld.outputBlobsWrappers[id].dynamicCast(); wrapper->copyToHostInBackground(); } #endif } else if (preferableBackend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) { CV_Assert(preferableBackend != DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && "Inheritance internal error"); } else if (preferableBackend == DNN_BACKEND_WEBNN) { forwardWebnn(ld.outputBlobsWrappers, node, isAsync); } else if (preferableBackend == DNN_BACKEND_TIMVX) { forwardTimVX(ld.outputBlobsWrappers, node); } #ifdef HAVE_VULKAN else if (preferableBackend == DNN_BACKEND_VKCOM) { try { forwardVkCom(ld.outputBlobsWrappers, node); } catch (const cv::Exception& e) { CV_LOG_ERROR(NULL, "forwardVkCom failed, fallback to CPU implementation. " << e.what()); it->second = Ptr(); forwardLayer(ld); } } #endif else { CV_Error(Error::StsNotImplemented, cv::format("Unknown backend identifier: %d", preferableBackend)); } } tm.stop(); int64 t = tm.getTimeTicks(); layersTimings[ld.id] = (t > 0) ? t : t + 1; // zero for skipped layers only #if TRACE_INFERENCE size_t noutputs = ld.outputBlobs.size(); for (size_t i = 0; i < noutputs; i++) { const Mat& out = ld.outputBlobs[i]; printf("Output %zu.\n", i); printf(" Type: %s\n", typeToString(out.type()).c_str()); printf(" Shape: "); if (out.empty()) { printf("\n"); } else if (out.dims == 0) { printf("\n"); } else { for (int j = 0; j < out.dims; j++) { printf("%s%d", (j == 0 ? "[" : " x "), out.size[j]); } printf("]\n"); } //fflush(stdout); //pprint(std::cout, out, 0, 3, 100, '['); //std::cout.flush(); //printf("\n"); } #endif } else { layersTimings[ld.id] = 0; } ld.flag = 1; } void Net::Impl::forwardToLayer(LayerData& ld, bool clearFlags) { CV_TRACE_FUNCTION(); if (clearFlags) { for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); it++) it->second.flag = 0; } // already was forwarded if (ld.flag) return; // forward parents for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end() && (it->second.id < ld.id); ++it) { LayerData& ld = it->second; if (ld.flag) continue; forwardLayer(ld); } // forward itself forwardLayer(ld); #ifdef HAVE_CUDA if (preferableBackend == DNN_BACKEND_CUDA) cudaInfo->context.stream.synchronize(); #endif } Mat Net::Impl::forward(const String& outputName) { CV_Assert(!empty()); FPDenormalsIgnoreHintScope fp_denormals_ignore_scope; if (mainGraph) { Mat result; forwardWithSingleOutput(outputName, result); return result; } String layerName = outputName; if (layerName.empty()) { std::vector layerNames = getLayerNames(); CV_Assert(!layerNames.empty()); layerName = layerNames.back(); } std::vector pins(1, getPinByAlias(layerName)); setUpNet(pins); forwardToLayer(getLayerData(layerName)); return getBlob(layerName); } AsyncArray Net::Impl::forwardAsync(const String& outputName) { CV_Assert(!empty()); FPDenormalsIgnoreHintScope fp_denormals_ignore_scope; if (mainGraph) CV_Error(Error::StsNotImplemented, "The new dnn engine doesn't support the async inference. If you want to run the sync inference, please call forward() instead of forwardAsync(). If you want to run the async inference, please use the old dnn engine"); String layerName = outputName; if (layerName.empty()) { std::vector layerNames = getLayerNames(); CV_Assert(!layerNames.empty()); layerName = layerNames.back(); } std::vector pins(1, getPinByAlias(layerName)); setUpNet(pins); if (preferableBackend != DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) CV_Error(Error::StsNotImplemented, "DNN: Asynchronous forward is supported for Inference Engine backend only"); isAsync = true; forwardToLayer(getLayerData(layerName)); isAsync = false; return getBlobAsync(layerName); } void Net::Impl::forward(OutputArrayOfArrays outputBlobs, const String& outputName) { CV_Assert(!empty()); FPDenormalsIgnoreHintScope fp_denormals_ignore_scope; if (mainGraph) { if (!outputName.empty()) { forwardWithSingleOutput(outputName, outputBlobs); return; } forwardWithMultipleOutputs(outputBlobs, {}); return; } String layerName = outputName; if (layerName.empty()) { std::vector layerNames = getLayerNames(); CV_Assert(!layerNames.empty()); layerName = layerNames.back(); } std::vector pins(1, getPinByAlias(layerName)); setUpNet(pins); forwardToLayer(getLayerData(layerName)); LayerPin pin = getPinByAlias(layerName); LayerData& ld = layers[pin.lid]; if (outputBlobs.isUMat()) { getBlob(layerName).copyTo(outputBlobs); } else if (outputBlobs.isMat()) { outputBlobs.assign(getBlob(layerName)); } else if (outputBlobs.isMatVector()) { // The DNN_TARGET_CPU and DNN_TARGET_CPU_FP16 both use the CPU memory, do not need the copyToHost. if (preferableTarget != DNN_TARGET_CPU && preferableTarget != DNN_TARGET_CPU_FP16) { for (int i = 0; i < ld.outputBlobsWrappers.size(); ++i) { CV_Assert(!ld.outputBlobsWrappers[i].empty()); ld.outputBlobsWrappers[i]->copyToHost(); } } if (ld.outputBlobs[0].depth() == CV_16F) { std::vector& outputvec = *(std::vector*)outputBlobs.getObj(); outputvec.resize(ld.outputBlobs.size()); for (int i = 0; i < outputvec.size(); i++) { if (ld.outputBlobs[i].depth() == CV_32S || ld.outputBlobs[i].depth() == CV_64S) outputvec[i] = ld.outputBlobs[i]; else ld.outputBlobs[i].convertTo(outputvec[i], CV_32F); } } else { // Output depth can be CV_32F or CV_8S std::vector& outputvec = *(std::vector*)outputBlobs.getObj(); outputvec = ld.outputBlobs; } } else if (outputBlobs.isUMatVector()) { std::vector& outputvec = *(std::vector*)outputBlobs.getObj(); #ifdef HAVE_OPENCL if (preferableBackend == DNN_BACKEND_OPENCV && IS_DNN_OPENCL_TARGET(preferableTarget)) { if (preferableTarget == DNN_TARGET_OPENCL) outputvec = OpenCLBackendWrapper::getUMatVector(ld.outputBlobsWrappers); else if (preferableTarget == DNN_TARGET_OPENCL_FP16) { std::vector out_vec = OpenCLBackendWrapper::getUMatVector(ld.outputBlobsWrappers); outputvec.resize(out_vec.size()); for (int i = 0; i < out_vec.size(); i++) out_vec[i].convertTo(outputvec[i], CV_32F); } } else #endif { outputvec.resize(ld.outputBlobs.size()); for (int i = 0; i < outputvec.size(); ++i) ld.outputBlobs[i].copyTo(outputvec[i]); } } } void Net::Impl::forward(OutputArrayOfArrays outputBlobs, const std::vector& outBlobNames) { CV_Assert(!empty()); FPDenormalsIgnoreHintScope fp_denormals_ignore_scope; if (mainGraph) { forwardWithMultipleOutputs(outputBlobs, outBlobNames); return; } if (outBlobNames.empty()) CV_Error(Error::StsBadArg, "in Net::forward(), outBlobNames cannot be empty"); std::vector pins; for (int i = 0; i < outBlobNames.size(); i++) { pins.push_back(getPinByAlias(outBlobNames[i])); } setUpNet(pins); LayerPin out = getLatestLayerPin(pins); forwardToLayer(getLayerData(out.lid)); std::vector matvec; for (int i = 0; i < pins.size(); i++) { matvec.push_back(getBlob(pins[i])); } outputBlobs.create((int)matvec.size(), 1, CV_32F/*FIXIT*/, -1); // allocate vector outputBlobs.assign(matvec); } void Net::Impl::forward(std::vector>& outputBlobs, const std::vector& outBlobNames) { CV_Assert(!empty()); if (outBlobNames.empty()) CV_Error(Error::StsBadArg, "in Net::forward(), outBlobNames cannot be empty"); FPDenormalsIgnoreHintScope fp_denormals_ignore_scope; if (mainGraph) CV_Error(Error::StsNotImplemented, "The new dnn engine doesn't support inference until a specified layer. If you want to run the whole model, please don't set the outputName argument in the forward() call. If you want to run the model until a specified layer, please use the old dnn engine"); std::vector pins; for (int i = 0; i < outBlobNames.size(); i++) { pins.push_back(getPinByAlias(outBlobNames[i])); } setUpNet(pins); LayerPin out = getLatestLayerPin(pins); forwardToLayer(getLayerData(out.lid)); outputBlobs.resize(outBlobNames.size()); for (int i = 0; i < outBlobNames.size(); i++) { std::vector lp = getLayerOutPins(outBlobNames[i]); outputBlobs[i].resize(lp.size()); for (int j = 0; j < lp.size(); j++) { outputBlobs[i][j] = getBlob(lp[j]); } } } void Net::Impl::getLayerShapesRecursively(int id, LayersShapesMap& inOutShapes) { CV_CheckGE(id, 0, ""); CV_CheckLT(id, (int)layers.size(), ""); LayerData& layerData = layers[id]; std::vector& inputLayerIds = layerData.inputBlobsId; LayerShapes& layerShapes = inOutShapes[id]; if (id == 0 && layerShapes.in[0].empty()) { if (!layerData.outputBlobs.empty()) { ShapesVec shapes; TypesVec types; for (int i = 0; i < layerData.outputBlobs.size(); i++) { Mat& inp = layerData.outputBlobs[i]; CV_Assert(!inp.empty()); shapes.push_back(shape(inp)); types.push_back(inp.type()); } layerShapes.in = shapes; layerShapes.inTypes = types; } else { const std::vector& inputShapes = netInputLayer->shapes; bool none = true; for (size_t i = 0; i < inputShapes.size(); i++) { if (!inputShapes[i].empty()) { none = false; break; } } if (none) { layerShapes.out.clear(); layerShapes.outTypes.clear(); return; } else { layerShapes.in = inputShapes; layerShapes.inTypes.assign(inputShapes.size(), layerData.dtype); } } } if (layerShapes.in.empty()) { for (int i = 0; i < inputLayerIds.size(); i++) { int layerId = inputLayerIds[i].lid; LayersShapesMap::const_iterator it = inOutShapes.find(layerId); if (it == inOutShapes.end() || it->second.out.empty()) { getLayerShapesRecursively(layerId, inOutShapes); it = inOutShapes.find(layerId); CV_Assert(it != inOutShapes.end()); } const int out_port = inputLayerIds[i].oid; CV_CheckLT(out_port, (int)it->second.out.size(), ""); const MatShape& shape = it->second.out[out_port]; const MatType& type = it->second.outTypes[out_port]; layerShapes.in.push_back(shape); layerShapes.inTypes.push_back(type); } } const ShapesVec& is = layerShapes.in; ShapesVec& os = layerShapes.out; ShapesVec& ints = layerShapes.internal; int requiredOutputs = layerData.requiredOutputs.size(); const Ptr& l = getLayerInstance(layerData); CV_Assert(l); bool layerSupportInPlace = false; try { l->updateMemoryShapes(layerShapes.in); layerSupportInPlace = l->getMemoryShapes(is, requiredOutputs, os, ints); l->getTypes(layerShapes.inTypes, os.size(), ints.size(), layerShapes.outTypes, layerShapes.internalTypes); CV_CheckEQ(layerShapes.out.size(), layerShapes.outTypes.size(), "Number of shapes and types should be equal"); CV_CheckEQ(layerShapes.internal.size(), layerShapes.internalTypes.size(), "Number of shapes and types should be equal"); } catch (const cv::Exception& e) { CV_LOG_ERROR(NULL, "OPENCV/DNN: [" << l->type << "]:(" << l->name << "): getMemoryShapes() throws exception." << " inputs=" << is.size() << " outputs=" << os.size() << "/" << requiredOutputs << " blobs=" << l->blobs.size()); for (size_t i = 0; i < is.size(); ++i) { CV_LOG_ERROR(NULL, " input[" << i << "] = " << toString(is[i])); } for (size_t i = 0; i < os.size(); ++i) { CV_LOG_ERROR(NULL, " output[" << i << "] = " << toString(os[i])); } for (size_t i = 0; i < l->blobs.size(); ++i) { CV_LOG_ERROR(NULL, " blobs[" << i << "] = " << typeToString(l->blobs[i].type()) << " " << toString(shape(l->blobs[i]))); } CV_LOG_ERROR(NULL, "Exception message: " << e.what()); throw; } layerShapes.supportInPlace = layerSupportInPlace; try { for (int i = 0; i < (int)ints.size(); i++) CV_CheckGT(total(ints[i]), (size_t)0, ""); for (int i = 0; i < (int)os.size(); i++) CV_CheckGT(total(os[i]), (size_t)0, ""); } catch (const cv::Exception& e) { CV_LOG_ERROR(NULL, "OPENCV/DNN: [" << l->type << "]:(" << l->name << "): getMemoryShapes() post validation failed." << " inputs=" << is.size() << " outputs=" << os.size() << "/" << requiredOutputs << " blobs=" << l->blobs.size() << " inplace=" << layerSupportInPlace); for (size_t i = 0; i < is.size(); ++i) { CV_LOG_ERROR(NULL, " input[" << i << "] = " << toString(is[i])); } for (size_t i = 0; i < os.size(); ++i) { CV_LOG_ERROR(NULL, " output[" << i << "] = " << toString(os[i])); } for (size_t i = 0; i < l->blobs.size(); ++i) { CV_LOG_ERROR(NULL, " blobs[" << i << "] = " << typeToString(l->blobs[i].type()) << " " << toString(shape(l->blobs[i]))); } CV_LOG_ERROR(NULL, "Exception message: " << e.what()); throw; } } void Net::Impl::getLayersShapes( const ShapesVec& netInputShapes, const TypesVec& netInputTypes, std::vector& layersIds, std::vector& inLayersShapes, std::vector& outLayersShapes) /*const*/ { layersIds.clear(); inLayersShapes.clear(); outLayersShapes.clear(); Impl::LayersShapesMap inOutShapes; getLayersShapes(netInputShapes, netInputTypes, inOutShapes); for (Impl::LayersShapesMap::const_iterator it = inOutShapes.begin(); it != inOutShapes.end(); it++) { layersIds.push_back(it->first); inLayersShapes.push_back(it->second.in); outLayersShapes.push_back(it->second.out); } } void Net::Impl::getLayersShapes(const ShapesVec& netInputShapes, const TypesVec& netInputTypes, LayersShapesMap& inOutShapes) { inOutShapes.clear(); inOutShapes[0].in = netInputShapes; // insert shape for first input layer inOutShapes[0].inTypes = netInputTypes; for (MapIdToLayerData::const_iterator it = layers.begin(); it != layers.end(); it++) { getLayerShapesRecursively(it->first, inOutShapes); } } void Net::Impl::getLayerShapes(const ShapesVec& netInputShapes, const TypesVec& netInputTypes, const int layerId, LayerShapes& shapes) { if (mainGraph) { std::vector shapeCache; std::vector typeCache; CV_Assert(layerId == 0); tryInferShapes(netInputShapes, netInputTypes, shapes, shapeCache, typeCache); } else { LayersShapesMap inOutShapes; inOutShapes[0].in = netInputShapes; // insert shape for first input layer inOutShapes[0].inTypes = netInputTypes; getLayerShapesRecursively(layerId, inOutShapes); shapes = inOutShapes[layerId]; } } void Net::Impl::updateLayersShapes() { CV_LOG_DEBUG(NULL, "updateLayersShapes() with layers.size=" << layers.size()); CV_Assert(netInputLayer); DataLayer& inputLayer = *netInputLayer; LayerData& inputLayerData = layers[0]; CV_Assert(inputLayerData.layerInstance.get() == &inputLayer); CV_Assert(!inputLayerData.outputBlobs.empty()); ShapesVec inputShapes; TypesVec inputTypes; for (int i = 0; i < inputLayerData.outputBlobs.size(); i++) { Mat& inp = inputLayerData.outputBlobs[i]; CV_Assert(!inp.empty()); if (preferableBackend == DNN_BACKEND_OPENCV && // FIXIT: wrong place for output allocation preferableTarget == DNN_TARGET_OPENCL_FP16 && inputLayerData.dtype == CV_32F) { inp.create(inp.size, CV_16F); } inputShapes.push_back(shape(inp)); inputTypes.push_back(inp.type()); } CV_LOG_DEBUG(NULL, toString(inputShapes, "Network input shapes")); LayersShapesMap layersShapes; layersShapes[0].in = inputShapes; layersShapes[0].inTypes = inputTypes; for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); it++) { int layerId = it->first; LayerData& layerData = it->second; const std::vector& inputLayerIds = layerData.inputBlobsId; LayerShapes& layerShapes = layersShapes[layerId]; CV_LOG_DEBUG(NULL, "layer " << layerId << ": [" << layerData.type << "]:(" << layerData.name << ") with inputs.size=" << inputLayerIds.size()); if (layerShapes.in.empty()) { for (int i = 0; i < inputLayerIds.size(); i++) { const LayerPin& inputPin = inputLayerIds[i]; int inputLayerId = inputPin.lid; CV_LOG_DEBUG(NULL, " input[" << i << "] " << inputLayerId << ":" << inputPin.oid << " as [" << layers[inputLayerId].type << "]:(" << layers[inputLayerId].name << ")"); LayersShapesMap::const_iterator inputIt = layersShapes.find(inputLayerId); if (inputIt == layersShapes.end() || inputIt->second.out.empty()) { getLayerShapesRecursively(inputLayerId, layersShapes); } const MatShape& shape = layersShapes[inputLayerId].out[inputPin.oid]; const MatType& type = layersShapes[inputLayerId].outTypes[inputPin.oid]; layerShapes.in.push_back(shape); layerShapes.inTypes.push_back(type); } getLayerInstance(layerData)->updateMemoryShapes(layerShapes.in); } CV_LOG_DEBUG(NULL, "Layer " << layerId << ": " << toString(layerShapes.in, "input shapes")); CV_LOG_IF_DEBUG(NULL, !layerShapes.out.empty(), "Layer " << layerId << ": " << toString(layerShapes.out, "output shapes")); CV_LOG_IF_DEBUG(NULL, !layerShapes.internal.empty(), "Layer " << layerId << ": " << toString(layerShapes.internal, "internal shapes")); } CV_LOG_DEBUG(NULL, "updateLayersShapes() - DONE"); } LayerPin Net::Impl::getLatestLayerPin(const std::vector& pins) const { if (pins.empty()) CV_Error(Error::StsBadArg, "Cannot Net::Impl::getLatestLayerPin() from empty vector of pins"); return *std::max_element(pins.begin(), pins.end()); } Mat Net::Impl::getBlob(const LayerPin& pin) const { CV_TRACE_FUNCTION(); if (!pin.valid()) CV_Error(Error::StsObjectNotFound, "Requested blob not found"); MapIdToLayerData::const_iterator it = layers.find(pin.lid); if (it == layers.end()) CV_Error_(Error::StsOutOfRange, ("Layer #%d is not valid (output #%d requested)", pin.lid, pin.oid)); const LayerData& ld = it->second; if ((size_t)pin.oid >= ld.outputBlobs.size()) { CV_Error(Error::StsOutOfRange, format("Layer \"%s\" produce only %zu outputs, " "the #%d was requested", ld.name.c_str(), ld.outputBlobs.size(), pin.oid)); } if (preferableTarget != DNN_TARGET_CPU && preferableTarget != DNN_TARGET_CPU_FP16) { CV_Assert(!ld.outputBlobsWrappers.empty() && !ld.outputBlobsWrappers[pin.oid].empty()); // Transfer data to CPU if it's require. ld.outputBlobsWrappers[pin.oid]->copyToHost(); } if (ld.outputBlobs[pin.oid].depth() == CV_16F) { Mat output_blob; ld.outputBlobs[pin.oid].convertTo(output_blob, CV_32F); return output_blob; } else return ld.outputBlobs[pin.oid]; } Mat Net::Impl::getBlob(String outputName) const { return getBlob(getPinByAlias(outputName)); } AsyncArray Net::Impl::getBlobAsync(const LayerPin& pin) { CV_TRACE_FUNCTION(); CV_Error(Error::StsNotImplemented, "DNN: OpenVINO/nGraph backend is required"); } AsyncArray Net::Impl::getBlobAsync(String outputName) { return getBlobAsync(getPinByAlias(outputName)); } void Net::Impl::setInputsNames(const std::vector& inputBlobNames) { CV_Assert(netInputLayer); netInputLayer->setNames(inputBlobNames); } void Net::Impl::setInputShape(const String& inputName, const MatShape& shape) { CV_Assert(netInputLayer); netInputLayer->setInputShape(inputName, shape); } void Net::Impl::setInput(InputArray blob, const String& name, double scalefactor, const Scalar& mean) { FPDenormalsIgnoreHintScope fp_denormals_ignore_scope; if (mainGraph) { CV_Assert(scalefactor == 1); CV_Assert(mean.val[0] == 0 && mean.val[1] == 0 && mean.val[2] == 0 && mean.val[3] == 0); setMainGraphInput(blob, name); return; } LayerPin pin; pin.lid = 0; pin.oid = resolvePinOutputName(getLayerData(pin.lid), name); if (!pin.valid()) CV_Error(Error::StsObjectNotFound, "Requested blob \"" + name + "\" not found"); Mat blob_ = blob.getMat(); // can't use InputArray directly due MatExpr stuff MatShape blobShape = shape(blob_); #if 0 // TODO: DNNTestNetwork.MobileNet_SSD_Caffe_Different_Width_Height/0 if (pin.lid == 0) { CV_Assert(!netInputLayer.empty()); const DataLayer& netInputLayer = *(this->netInputLayer); if (!netInputLayer.shapes.empty()) { CV_CheckLT(pin.oid, (int)netInputLayer.shapes.size(), ""); const MatShape& inputShapeLimitation = netInputLayer.shapes[pin.oid]; if (!inputShapeLimitation.empty()) { CV_CheckEQ(inputShapeLimitation.size(), blobShape.size(), ""); const size_t dims = inputShapeLimitation.size(); for (size_t dim = 0; dim < dims; dim++) { if (dims >= 3 && dim == 0 && inputShapeLimitation[0] == 1) continue; // don't limit batch CV_CheckEQ(inputShapeLimitation[dim], blobShape[dim], ""); } } } } #endif LayerData& ld = layers[pin.lid]; const int numInputs = std::max(pin.oid + 1, (int)ld.requiredOutputs.size()); ld.outputBlobs.resize(numInputs); ld.outputBlobsWrappers.resize(numInputs); netInputLayer->inputsData.resize(numInputs); netInputLayer->scaleFactors.resize(numInputs); netInputLayer->means.resize(numInputs); MatShape prevShape = shape(netInputLayer->inputsData[pin.oid]); bool oldShape = prevShape == blobShape; blob_.copyTo(netInputLayer->inputsData[pin.oid]); if (!oldShape) ld.outputBlobs[pin.oid] = netInputLayer->inputsData[pin.oid]; if (!ld.outputBlobsWrappers[pin.oid].empty()) { ld.outputBlobsWrappers[pin.oid]->setHostDirty(); } netInputLayer->scaleFactors[pin.oid] = scalefactor; netInputLayer->means[pin.oid] = mean; netWasAllocated = netWasAllocated && oldShape; } Mat Net::Impl::getParam(int layer, int numParam) const { LayerData& ld = getLayerData(layer); std::vector& layerBlobs = getLayerInstance(ld)->blobs; CV_Assert(numParam < (int)layerBlobs.size()); return layerBlobs[numParam]; } void Net::Impl::setParam(int layer, int numParam, const Mat& blob) { LayerData& ld = getLayerData(layer); // FIXIT we should not modify "execution" instance std::vector& layerBlobs = getLayerInstance(ld)->blobs; CV_Assert(numParam < (int)layerBlobs.size()); // we don't make strong checks, use this function carefully layerBlobs[numParam] = blob; } void Net::Impl::setParam(const std::string& outputTensorName, int numParam, const Mat& blob) { if (mainGraph) { auto it = argnames.find(outputTensorName); if (it == argnames.end()) { size_t excl = outputTensorName.rfind('!'); if (excl != std::string::npos) it = argnames.find(outputTensorName.substr(excl + 1)); } if (it == argnames.end()) CV_Error_(Error::StsObjectNotFound, ("DNN: tensor '%s' not found in the graph", outputTensorName.c_str())); int targetIdx = (int)it->second; const std::vector>& prog = mainGraph->prog(); for (const auto& layer : prog) { bool produces = false; for (const Arg& out : layer->outputs) if (out.idx == targetIdx) { produces = true; break; } if (!produces) continue; if (numParam < (int)layer->blobs.size()) { layer->blobs[numParam] = blob; finalizeLayers = true; return; } Conv2Layer* conv = dynamic_cast(layer.get()); if (conv && numParam == 0) { conv->setWeights(blob, Mat(), defaultC0, accuracy); finalizeLayers = true; return; } ConvTranspose2Layer* deconv = dynamic_cast(layer.get()); if (deconv && numParam == 0) { deconv->setWeights(blob, Mat(), defaultC0, accuracy); finalizeLayers = true; return; } CV_Error_(Error::StsOutOfRange, ("DNN: op producing '%s' has fewer than %d params", outputTensorName.c_str(), numParam + 1)); } CV_Error_(Error::StsObjectNotFound, ("DNN: no op found in graph producing tensor '%s'", outputTensorName.c_str())); } setParam(getLayerId(outputTensorName), numParam, blob); } static string dumpLayerParameterSize(const string& name, const LayerParams& lp) { std::ostringstream out(name, std::ios::ate); DictValue param = lp.get(name); switch (param.size()) { case 1: out << " : "; break; case 2: out << " (HxW): "; break; case 3: out << " (DxHxW): "; break; default: CV_LOG_INFO(NULL, format("DNN/dumpLayerParameterSize(): Unsupported '%s' size = %d", name.c_str(), param.size())); out << ": "; } for (size_t i = 0; i < param.size(); i++) { if (i > 0) out << " x "; out << param.get(i); } return out.str(); } string Net::Impl::dump(bool forceAllocation) const { bool hasInput = !netInputLayer->inputsData.empty(); if (forceAllocation) { if (!netWasAllocated) const_cast(this)->setUpNet(); } std::ostringstream out; const std::map& map = layers; Backend prefBackend = (Backend)preferableBackend; std::vector> skippedLayers; std::vector skipId; std::vector allLayers(map.size(), -1); int idPrev = -1; Ptr prevNode; for (std::map::const_reverse_iterator rit = map.rbegin(); rit != map.rend(); ++rit) { std::map>::const_iterator itBackend = rit->second.backendNodes.find(prefBackend); if (prefBackend == DNN_BACKEND_OPENCV || itBackend == rit->second.backendNodes.end() || itBackend->second.empty()) { if (rit->second.skip) skipId.push_back(rit->first); else if (!skipId.empty()) { if (prefBackend == DNN_BACKEND_OPENCV || prevNode.empty()) skipId.push_back(rit->first); else if (idPrev != -1) skipId.push_back(idPrev); std::sort(skipId.begin(), skipId.end()); for (int i = 0; i < skipId.size(); i++) { allLayers[skipId[i]] = skippedLayers.size(); } skippedLayers.push_back(skipId); skipId.clear(); } } else { if (itBackend->second == prevNode) { if (idPrev != -1) skipId.push_back(idPrev); } else if (!skipId.empty()) { if (idPrev != -1) skipId.push_back(idPrev); std::sort(skipId.begin(), skipId.end()); for (int i = 0; i < skipId.size(); i++) { allLayers[skipId[i]] = skippedLayers.size(); } skippedLayers.push_back(skipId); skipId.clear(); } idPrev = rit->first; prevNode = itBackend->second; } } std::vector colors = { "#ffffb3", "#fccde5", "#8dd3c7", "#bebada", "#80b1d3", "#fdb462", "#ff4848", "#b35151", "#b266ff", "#b266ff", "#3cb371", "#ffcab3"}; string backend; switch (prefBackend) { case DNN_BACKEND_DEFAULT: backend = "DEFAULT/"; break; case DNN_BACKEND_INFERENCE_ENGINE: // fallthru case DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019: // fallthru case DNN_BACKEND_INFERENCE_ENGINE_NGRAPH: backend = "OpenVINO/"; break; case DNN_BACKEND_OPENCV: backend = "OCV/"; break; case DNN_BACKEND_VKCOM: backend = "VULKAN/"; break; case DNN_BACKEND_CUDA: backend = "CUDA/"; break; case DNN_BACKEND_WEBNN: backend = "WEBNN/"; break; case DNN_BACKEND_TIMVX: backend = "TIMVX/"; break; case DNN_BACKEND_CANN: backend = "CANN/"; break; // don't use default: } out << "digraph G {\n"; // Add nodes for (std::map::const_iterator it = map.begin(); it != map.end(); ++it) { const LayerData& ld = it->second; string name = ld.params.name; std::vector clusterIds(1, it->first); if (allLayers[it->first] == -1 && !name.empty()) { out << "\t\"" << name << "\" [label=\""; } else if (name.empty() || it->first != skippedLayers[allLayers[it->first]][0]) { continue; } else // first node in cluster : it->first == skippedLayers[allLayers[it->first]][0] { int cluster = allLayers[it->first]; out << "\t\"" << "cluster_" << cluster << "\" [label=\"{"; clusterIds = skippedLayers[allLayers[it->first]]; // vertices in current cluster } for (int i = 0; i < clusterIds.size(); i++) { CV_DbgAssert(map.find(clusterIds[i]) != map.end()); const LayerParams& lp = map.find(clusterIds[i])->second.params; if (!lp.name.empty()) { if (i > 0) { out << " | "; } out << lp.name << "\\n" << lp.type << "\\n"; // align center if (lp.has("kernel_size")) { string kernel = dumpLayerParameterSize("kernel_size", lp); out << kernel; out << "\\l"; // align left } else if (lp.has("kernel_h") && lp.has("kernel_w")) { DictValue h = lp.get("kernel_h"); DictValue w = lp.get("kernel_w"); out << "kernel (HxW): " << h << " x " << w; out << "\\l"; // align left } if (lp.has("stride")) { string stride = dumpLayerParameterSize("stride", lp); out << stride; out << "\\l"; // align left } else if (lp.has("stride_h") && lp.has("stride_w")) { DictValue h = lp.get("stride_h"); DictValue w = lp.get("stride_w"); out << "stride (HxW): " << h << " x " << w; out << "\\l"; // align left } if (lp.has("dilation")) { string dilation = dumpLayerParameterSize("dilation", lp); out << dilation; out << "\\l"; // align left } else if (lp.has("dilation_h") && lp.has("dilation_w")) { DictValue h = lp.get("dilation_h"); DictValue w = lp.get("dilation_w"); out << "dilation (HxW): " << h << " x " << w; out << "\\l"; // align left } if (lp.has("pad")) { DictValue pad = lp.get("pad"); out << "pad "; switch (pad.size()) { case 1: out << ": " << pad; break; case 2: out << "(HxW): (" << pad.get(0) << " x " << pad.get(1) << ")"; break; case 4: out << "(HxW): (" << pad.get(0) << ", " << pad.get(2) << ") x (" << pad.get(1) << ", " << pad.get(3) << ")"; break; case 6: out << "(DxHxW): (" << pad.get(0) << ", " << pad.get(3) << ") x (" << pad.get(1) << ", " << pad.get(4) << ") x (" << pad.get(2) << ", " << pad.get(5) << ")"; break; default: CV_Error(Error::StsNotImplemented, format("Unsupported pad size = %d", pad.size())); } out << "\\l"; // align left } else if (lp.has("pad_l") && lp.has("pad_t") && lp.has("pad_r") && lp.has("pad_b")) { DictValue l = lp.get("pad_l"); DictValue t = lp.get("pad_t"); DictValue r = lp.get("pad_r"); DictValue b = lp.get("pad_b"); out << "pad (HxW): (" << t << ", " << b << ") x (" << l << ", " << r << ")"; out << "\\l"; // align left } else if (lp.has("pooled_w") || lp.has("pooled_h")) { DictValue h = lp.get("pooled_h"); DictValue w = lp.get("pooled_w"); out << "pad pooled (HxW): " << h << " x " << w; out << "\\l"; // align left } if (lp.has("pool")) { out << "pool: " << lp.get("pool"); out << "\\l"; // align left } if (lp.has("global_pooling")) { out << "global_pooling: " << lp.get("global_pooling"); out << "\\l"; // align left } if (lp.has("group")) { out << "group: " << lp.get("group"); out << "\\l"; // align left } } } if (!ld.outputBlobs.empty()) { out << "output: " << ld.outputBlobs[0].size; out << "\\l"; // align left } Ptr layerBackend; std::map>::const_iterator ibn = ld.backendNodes.find(prefBackend); if (ibn != ld.backendNodes.end()) layerBackend = ibn->second; out << (!layerBackend.empty() ? backend : "OCV/"); int colorId = 0; const Target target = ld.layerInstance.empty() ? DNN_TARGET_CPU : (Target)(ld.layerInstance->preferableTarget); // TODO fix preferableTarget type switch (target) { case DNN_TARGET_CPU: out << "CPU"; colorId = layerBackend.empty() ? 0 : 5; break; case DNN_TARGET_OPENCL: out << "OCL"; colorId = 1; break; case DNN_TARGET_OPENCL_FP16: out << "OCL_FP16"; colorId = 2; break; case DNN_TARGET_MYRIAD: out << "MYRIAD"; colorId = 3; break; case DNN_TARGET_HDDL: out << "HDDL"; colorId = 8; break; case DNN_TARGET_VULKAN: out << "VULKAN"; colorId = 7; break; case DNN_TARGET_FPGA: out << "FPGA"; colorId = 4; break; case DNN_TARGET_CUDA: out << "CUDA"; colorId = 5; break; case DNN_TARGET_CUDA_FP16: out << "CUDA_FP16"; colorId = 6; break; case DNN_TARGET_NPU: out << "NPU"; colorId = 9; break; case DNN_TARGET_CPU_FP16: out << "CPU_FP16"; colorId = 10; break; // don't use default: } CV_Assert(colorId < colors.size()); out << "\\n"; // align center out << ((clusterIds.size() == 1) ? "\" " : " }\" "); out << "fillcolor=\"" << colors[colorId] << "\" "; out << "style=filled "; out << "shape=" << ((clusterIds.size() == 1) ? "box" : "record") << "]\n"; } out << '\n'; // Add edges int inputsSize = hasInput ? netInputLayer->outNames.size() : 0; for (std::map::const_iterator it = map.begin(); it != map.end(); ++it) { const LayerData& ld = it->second; if (allLayers[it->first] == -1) // node { for (int i = 0; i < ld.consumers.size(); i++) { int outId = ld.consumers[i].lid; if (it == map.begin() && inputsSize > 1) out << "\t\"" << ld.name << "_" << i << "\"" << " -> "; else out << "\t\"" << ld.name << "\"" << " -> "; if (allLayers[outId] == -1) // node { CV_DbgAssert(map.find(outId) != map.end()); out << "\"" << map.find(outId)->second.name << "\"\n"; } else // cluster { out << "\"" << "cluster_" << allLayers[outId] << "\"\n"; } } } else if (it->first == skippedLayers[allLayers[it->first]].back()) // edges from last layer in cluster { for (int i = 0; i < ld.consumers.size(); i++) { int outId = ld.consumers[i].lid; if (allLayers[outId] == -1) // node { CV_DbgAssert(map.find(outId) != map.end()); out << "\t\"" << "cluster_" << allLayers[it->first] << "\"" << " -> "; out << "\"" << map.find(outId)->second.name << "\"\n"; } else if (allLayers[outId] != allLayers[it->first]) { // another cluster out << "\t\"" << "cluster_" << allLayers[it->first] << "\"" << " -> "; out << "\"" << "cluster_" << allLayers[outId] << "\"\n"; } } } } out << "}\n"; return out.str(); } static void dumpTensorToString(std::ostringstream &out, const Mat &m, const int num_indent_spaces = 4) { string indent_spaces(num_indent_spaces, ' '); int type = 1; /* Check TensorProto::DataType from https://github.com/onnx/onnx/blob/main/onnx/onnx.proto */ switch (m.type()) { case CV_32F: break; case CV_8U: type = 2; break; case CV_8S: type = 3; break; case CV_16U: type = 4; break; case CV_16S: type = 5; break; case CV_32S: type = 6; break; #if CV_VERSION_MAJOR > 4 case CV_64S: type = 7; break; // STRING: 8 #endif case CV_16F: type = 10; break; case CV_64F: type = 11; break; #if CV_VERSION_MAJOR > 4 case CV_32U: type = 12; break; case CV_64U: type = 13; break; // COMPLEX64: 14 // COMPLEX128: 15 case CV_16BF: type = 16; break; #endif default: CV_Error(Error::StsUnsupportedFormat, "Type of mat is not supported"); } const auto &mshape = shape(m); out << indent_spaces << "type {\n" << indent_spaces << " tensor_type {\n" << indent_spaces << " elem_type: " << type << "\n"; out << indent_spaces << " shape {\n"; for (size_t i = 0; i < mshape.size(); i++) { out << indent_spaces << format(" dim { dim_value: %d }\n", mshape[i]); } out << indent_spaces << " }\n" // shape{} << indent_spaces << " }\n" // tensor_type{} << indent_spaces << "}\n"; // type{} } static void dumpParamToString(std::ostringstream &out, const std::string &key, const DictValue &value, const int num_indent_spaces = 2) { std::string indent_spaces(num_indent_spaces, ' '); out << indent_spaces << "attribute {\n" << indent_spaces << format(" name: \"%s\"\n", key.c_str()); if (value.size() == 1) { if (value.isString()) { out << indent_spaces << format(" type: STRING\n") << indent_spaces << format(" s: \"%s\"\n", value.getStringValue(0).c_str()); } else if (value.isInt()) { out << indent_spaces << format(" type: INT\n") << indent_spaces << format(" i: %d\n", value.getIntValue(0)); } else if (value.isReal()) { out << indent_spaces << format(" type: FLOAT\n") << indent_spaces << format(" f: %f\n", value.getRealValue(0)); } else { out << indent_spaces << format(" type: UNKNOWN-SCALAR\n"); } } else { if (value.isString()) { out << indent_spaces << format(" type: STRINGS\n"); } else if (value.isInt()) { out << indent_spaces << format(" type: INTS\n"); } else if (value.isReal()) { out << indent_spaces << format(" type: FLOATS\n"); } else { out << indent_spaces << format(" type: UNKNOWN-ARRAY\n"); } for (int i = 0; i < value.size(); i++) { if (value.isString()) { out << indent_spaces << format(" strings: \"%s\"\n", value.getStringValue(i).c_str()); } else if (value.isInt()) { out << indent_spaces << format(" ints: %d\n", value.getIntValue(i)); } else if (value.isReal()) { out << indent_spaces << format(" floats: %f\n", value.getRealValue()); } } } out << indent_spaces << "}\n"; // attribute{} } static void dumpLayerToString(std::ostringstream &out, const std::vector &inputs, const std::vector &outputs, const std::string &name, const std::string &op_type, const LayerParams ¶ms, const std::string &backend_name, const std::string &target_name, const int num_indent_spaces = 2) { std::string indent_spaces(num_indent_spaces, ' '); for (size_t i = 0; i < inputs.size(); i++) { out << indent_spaces << format("input: \"%s\"\n", inputs[i].c_str()); } for (size_t i = 0; i < outputs.size(); i++) { out << indent_spaces << format("output: \"%s\"\n", outputs[i].c_str()); } if (!name.empty()) { out << indent_spaces << format("name: \"%s\"\n", name.c_str()); } if (!op_type.empty()) { out << indent_spaces << format("op_type: \"%s\"\n", op_type.c_str()); } if (!params.name.empty()) { for (auto param_iter = params.begin(); param_iter != params.end(); param_iter++) { auto key = param_iter->first; auto value = param_iter->second; dumpParamToString(out, key, value, num_indent_spaces); } } if (!backend_name.empty()) { DictValue dvb(backend_name); dumpParamToString(out, "Backend", dvb, num_indent_spaces); } if (!target_name.empty()) { DictValue dvt(target_name); dumpParamToString(out, "Target", dvt, num_indent_spaces); } } string Net::Impl::dumpToPbtxt(bool forceAllocation) const { if (forceAllocation && !netWasAllocated) { const_cast(this)->setUpNet(); } std::ostringstream out; const std::map &map = layers; std::map value_info; Backend prefBackend = (Backend)preferableBackend; Target prefTarget = (Target)preferableTarget; auto GetBackendName = [] (int backendId) { std::string backend = "Unknown"; switch (backendId) { case DNN_BACKEND_DEFAULT: backend = "DEFAULT"; break; #if CV_VERSION_MAJOR <= 4 case DNN_BACKEND_HALIDE: backend = "HALIDE"; break; #endif case DNN_BACKEND_INFERENCE_ENGINE: // fallthru case DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019: // fallthru case DNN_BACKEND_INFERENCE_ENGINE_NGRAPH: backend = "OpenVINO"; break; case DNN_BACKEND_OPENCV: backend = "OCV"; break; case DNN_BACKEND_VKCOM: backend = "VULKAN"; break; case DNN_BACKEND_CUDA: backend = "CUDA"; break; case DNN_BACKEND_WEBNN: backend = "WEBNN"; break; case DNN_BACKEND_TIMVX: backend = "TIMVX"; break; case DNN_BACKEND_CANN: backend = "CANN"; break; } return backend; }; auto GetTargetName = [] (int targetId) { std::string target = "Unknown"; switch (targetId) { case DNN_TARGET_CPU: target = "CPU"; break; case DNN_TARGET_OPENCL: target = "OCL"; break; case DNN_TARGET_OPENCL_FP16: target = "OCL_FP16"; break; case DNN_TARGET_MYRIAD: target = "MYRIAD"; break; case DNN_TARGET_VULKAN: target = "VULKAN"; break; case DNN_TARGET_FPGA: target = "FPGA"; break; case DNN_TARGET_CUDA: target = "CUDA"; break; case DNN_TARGET_CUDA_FP16: target = "CUDA_FP16"; break; case DNN_TARGET_HDDL: target = "HDDL"; break; case DNN_TARGET_NPU: target = "NPU"; break; case DNN_TARGET_CPU_FP16: target = "CPU_FP16"; break; } return target; }; const int num_indent_spaces = 2; std::string indent_spaces(num_indent_spaces, ' '); out << "producer_name: \"opencv dnn\"\n" << "producer_version: \"" << getVersionString() << "\"\n" << "graph {\n"; // Add nodes, inputs and outputs for (std::map::const_iterator iter = map.begin(); iter != map.end(); iter++) { auto &ld = iter->second; if (ld.id == 0) { for (int i = 0; i < ld.outputBlobs.size(); i++) { const auto &name = netInputLayer->outNames.empty() ? cv::format("%s_%d", ld.name.c_str(), i) : netInputLayer->outNames[i]; out << indent_spaces << "input {\n" << indent_spaces << format(" name: \"%s\"\n", name.c_str()); // Add shape if (!ld.outputBlobs.empty()) { dumpTensorToString(out, ld.outputBlobs[i], num_indent_spaces + 2); } out << indent_spaces << "}\n"; // input{} } } else if (ld.consumers.size() == 0) { out << indent_spaces << "output {\n" << indent_spaces << format(" name: \"%s\"\n", ld.name.c_str()); // Add shape if (!ld.outputBlobs.empty()) { dumpTensorToString(out, ld.outputBlobs.front(), num_indent_spaces + 2); } out << indent_spaces << "}\n"; // output{} } else { out << indent_spaces << "node {\n"; const auto &name = ld.name; const auto &op_type = "cv::dnn::" + ld.type; std::vector inputs, outputs; // Collect names of inputs for (size_t i = 0; i < ld.inputBlobsId.size(); i++) { int lid = ld.inputBlobsId[i].lid; int oid = ld.inputBlobsId[i].oid; std::string name; if (lid == 0) { name = netInputLayer->outNames.empty() ? cv::format("%s_%d", ld.name.c_str(), oid) : netInputLayer->outNames[oid]; } else { name = format("%s_output%d", map.find(lid)->second.name.c_str(), oid); if (!ld.inputBlobs.empty()) { value_info.insert({name, ld.inputBlobs[i]}); } } inputs.push_back(name); } // Collect names of outputs for (size_t i = 0; i < ld.consumers.size(); i++) { int lid = ld.consumers[i].lid; const auto &layer_output_layer = map.find(lid)->second; std::string name; if (layer_output_layer.consumers.size() == 0) { name = layer_output_layer.name; } else { name = format("%s_output%zu", ld.name.c_str(), i); } outputs.push_back(name); } const auto ¶ms = ld.params; // Collect backend and target const Backend backend = ld.backendNodes.find(prefBackend) == ld.backendNodes.end() ? DNN_BACKEND_OPENCV : prefBackend; const std::string backend_name = GetBackendName(backend); const Target target = ld.layerInstance.empty() ? DNN_TARGET_CPU : (Target)(ld.layerInstance->preferableTarget); const std::string target_name = GetTargetName(target); dumpLayerToString(out, inputs, outputs, name, op_type, params, backend_name, target_name, num_indent_spaces + 2); out << indent_spaces << "}\n"; // node{} } } // Add value_info for (std::map::const_iterator iter = value_info.begin(); iter != value_info.end(); iter++) { out << indent_spaces << "value_info {\n" << indent_spaces << format(" name: \"%s\"\n", iter->first.c_str()); dumpTensorToString(out, *(iter->second), num_indent_spaces + 2); out << indent_spaces << "}\n"; // value_info{} } out << "}\n"; // graph{} // Add preferable backend and target as metadata out << "metadata_props {\n"; out << indent_spaces << format(" key: \"%s\"", "Preferable Backend") << indent_spaces << format(" value: \"%s\"", GetBackendName(prefBackend).c_str()); out << "}\n"; // metadata_props{} out << "metadata_props {\n"; out << indent_spaces << format(" key: \"%s\"", "Preferable Target") << indent_spaces << format(" value: \"%s\"", GetTargetName(prefTarget).c_str()); out << "}\n"; // metadata_props{} return out.str(); } void Net::Impl::dumpNetworkToFile() const { #ifndef OPENCV_DNN_DISABLE_NETWORK_AUTO_DUMP string dumpFileNameBase = getDumpFileNameBase(); string dumpFileName = dumpFileNameBase + ".pbtxt"; try { string dumpStr = dumpToPbtxt(); std::ofstream out(dumpFileName.c_str(), std::ios::out | std::ios::binary); out << dumpStr; } catch (const std::exception& e) { std::ofstream out((dumpFileName + ".error").c_str(), std::ios::out); out << "Exception: " << e.what() << std::endl; } catch (...) { std::ofstream out((dumpFileName + ".error").c_str(), std::ios::out); out << "Can't dump: unknown exception" << std::endl; } #endif } std::vector> Net::Impl::getLayerInputs(int layerId) const { LayerData& ld = getLayerData(layerId); std::vector> inputLayers; inputLayers.reserve(ld.inputBlobsId.size()); for (int i = 0; i < ld.inputBlobsId.size(); ++i) { inputLayers.push_back(getLayer(ld.inputBlobsId[i].lid)); } return inputLayers; } std::vector Net::Impl::getLayerNames() const { std::vector res; if (mainGraph) { res.reserve(totalLayers); for (const Ptr& graph: allgraphs) { const std::vector >& prog = graph->prog(); for (const Ptr& layer: prog) res.push_back(layer->name); } } else { res.reserve(layers.size()); Impl::MapIdToLayerData::const_iterator it; for (it = layers.begin(); it != layers.end(); it++) { if (it->second.id) // skip Data layer res.push_back(it->second.name); } } return res; } std::vector Net::Impl::getUnconnectedOutLayers() const { std::vector layersIds; if (mainGraph) { const std::vector& outargs = mainGraph->outputs(); std::set outArgIdxs; for (const auto& out : outargs) outArgIdxs.insert(out.idx); int graph_ofs = 0; for (const auto& graph : allgraphs) { const std::vector>& prog = graph->prog(); for (int i = 0; i < (int)prog.size(); i++) { for (const auto& layerOut : prog[i]->outputs) { if (outArgIdxs.count(layerOut.idx)) { layersIds.push_back(graph_ofs + i); break; } } } graph_ofs += (int)prog.size(); } if (!layersIds.empty()) return layersIds; } // registerOutput() flow if (!outputNameToId.empty()) { for (std::map::const_iterator it = outputNameToId.begin(); it != outputNameToId.end(); ++it) { layersIds.push_back(it->second); } return layersIds; } Impl::MapIdToLayerData::const_iterator it; for (it = layers.begin(); it != layers.end(); it++) { int lid = it->first; const LayerData& ld = it->second; if (ld.requiredOutputs.size() == 0) layersIds.push_back(lid); } return layersIds; } // FIXIT drop "unconnected" API std::vector Net::Impl::getUnconnectedOutLayersNames() /*const*/ { if (mainGraph) { std::vector outnames; const std::vector& outargs = mainGraph->outputs(); for (auto out: outargs) { const ArgData& adata = args.at(out.idx); outnames.push_back(adata.name); } return outnames; } std::vector ids = getUnconnectedOutLayers(); const size_t n = ids.size(); std::vector names(n); for (size_t i = 0; i < n; ++i) { names[i] = layers[ids[i]].name; } return names; } int64 Net::Impl::getFLOPSGraph(const Ptr& graph, const std::vector& shapeCache, const std::vector& typeCache) const { if (!graph) return 0; int64 flops = 0; const std::vector>& prog = graph->prog(); for (const Ptr& layer : prog) { if (!layer) continue; const std::vector& inputs = layer->inputs; const std::vector& outputs = layer->outputs; int ninputs = (int)inputs.size(); int noutputs = (int)outputs.size(); std::vector inpShapes(ninputs), outShapes(noutputs); for (int i = 0; i < ninputs; i++) { Arg inp = inputs[i]; const ArgData& adata = args.at(inp.idx); if (adata.kind == DNN_ARG_CONST || adata.kind == DNN_ARG_EMPTY) inpShapes[i] = adata.shape; else inpShapes[i] = shapeCache[inp.idx]; } for (int i = 0; i < noutputs; i++) { Arg out = outputs[i]; if (out.idx > 0 && out.idx < (int)shapeCache.size()) outShapes[i] = shapeCache[out.idx]; } // Skip FLOPS calculation if any shape is empty (unknown due to dynamic shapes) bool hasEmptyShape = false; for (int i = 0; i < ninputs && !hasEmptyShape; i++) hasEmptyShape = inpShapes[i].empty(); for (int i = 0; i < noutputs && !hasEmptyShape; i++) hasEmptyShape = outShapes[i].empty(); if (!hasEmptyShape) flops += layer->getFLOPS(inpShapes, outShapes); const std::vector>* subgraphs = layer->subgraphs(); if (subgraphs) { for (const Ptr& sg : *subgraphs) flops += getFLOPSGraph(sg, shapeCache, typeCache); } } return flops; } int64 Net::Impl::getFLOPS(const std::vector& netInputShapes, const std::vector& netInputTypes) /*const*/ { if (mainGraph) { LayerShapes shapes; std::vector shapeCache; std::vector typeCache; tryInferShapes(netInputShapes, netInputTypes, shapes, shapeCache, typeCache); return getFLOPSGraph(mainGraph, shapeCache, typeCache); } int64 flops = 0; std::vector ids; std::vector> inShapes, outShapes; getLayersShapes(netInputShapes, netInputTypes, ids, inShapes, outShapes); CV_Assert(inShapes.size() == outShapes.size()); CV_Assert(inShapes.size() == ids.size()); for (int i = 0; i < ids.size(); i++) { flops += getLayerInstance(layers[ids[i]])->getFLOPS(inShapes[i], outShapes[i]); } return flops; } int64 Net::Impl::getFLOPS( const int layerId, const std::vector& netInputShapes, const std::vector& netInputTypes) /*const*/ { if (mainGraph) { LayerShapes shapes; std::vector shapeCache; std::vector typeCache; tryInferShapes(netInputShapes, netInputTypes, shapes, shapeCache, typeCache); CV_Assert(0 <= layerId && layerId < (int)totalLayers); int localIdx = layerId; for (const Ptr& graph : allgraphs) { int progSize = (int)graph->prog().size(); if (localIdx < progSize) { const Ptr& layer = graph->prog()[localIdx]; if (!layer) return 0; const std::vector& inputs = layer->inputs; const std::vector& outputs = layer->outputs; int ninputs = (int)inputs.size(); int noutputs = (int)outputs.size(); std::vector inpShapes(ninputs), outShapes(noutputs); for (int i = 0; i < ninputs; i++) { Arg inp = inputs[i]; const ArgData& adata = args.at(inp.idx); if (adata.kind == DNN_ARG_CONST || adata.kind == DNN_ARG_EMPTY) inpShapes[i] = adata.shape; else inpShapes[i] = shapeCache[inp.idx]; } for (int i = 0; i < noutputs; i++) { Arg out = outputs[i]; if (out.idx > 0 && out.idx < (int)shapeCache.size()) outShapes[i] = shapeCache[out.idx]; } return layer->getFLOPS(inpShapes, outShapes); } localIdx -= progSize; } CV_Error(Error::StsOutOfRange, format("Layer id %d is out of range", layerId)); } Impl::MapIdToLayerData::const_iterator layer = layers.find(layerId); CV_Assert(layer != layers.end()); LayerShapes shapes; getLayerShapes(netInputShapes, netInputTypes, layerId, shapes); return getLayerInstance(const_cast(layer->second))->getFLOPS(shapes.in, shapes.out); } void Net::Impl::getMemoryConsumption( const int layerId, const std::vector& netInputShapes, const std::vector& netInputTypes, size_t& weights, size_t& blobs) /*const*/ { Impl::MapIdToLayerData::const_iterator layer = layers.find(layerId); CV_Assert(layer != layers.end()); weights = blobs = 0; for (int i = 0; i < layer->second.params.blobs.size(); i++) { const Mat& weightsBlob = layer->second.params.blobs[i]; weights += weightsBlob.total() * weightsBlob.elemSize(); } LayerShapes shapes; getLayerShapes(netInputShapes, netInputTypes, layerId, shapes); const ShapesVec& outLayerShapes = shapes.out; for (int i = 0; i < outLayerShapes.size(); i++) blobs += total(outLayerShapes[i]) * CV_ELEM_SIZE(shapes.outTypes[i]); } void Net::Impl::getMemoryConsumption( const std::vector& netInputShapes, const std::vector& netInputTypes, size_t& weights, size_t& blobs) /*const*/ { std::vector layerIds; std::vector w, b; getMemoryConsumption(netInputShapes, netInputTypes, layerIds, w, b); weights = blobs = 0; for (int i = 0; i < layerIds.size(); i++) { weights += w[i]; blobs += b[i]; } } int64 Net::Impl::getPerfProfile(std::vector& timings) const { timings = std::vector(layersTimings.begin() + 1, layersTimings.end()); int64 total = (int64)std::accumulate(timings.begin(), timings.end(), 0.0); return total; } void Net::Impl::collectLayerInfo(std::vector& names, std::vector& types) const { if (mainGraph) { names.reserve(totalLayers); types.reserve(totalLayers); for (const Ptr& graph : allgraphs) { const std::vector>& prog = graph->prog(); for (const Ptr& layer : prog) { names.push_back(layer ? layer->name : "null"); types.push_back(layer ? layer->type : "null"); } } } else { for (MapIdToLayerData::const_iterator it = layers.begin(); it != layers.end(); ++it) { if (it->second.id) { // skip Data layer (id==0) names.push_back(it->second.name); types.push_back(it->second.type); } } } } #ifdef HAVE_ONNXRUNTIME static void parseOrtProfileJson(const std::string& text, std::map>& out_ms) { const std::string wrapped = "{\"events\":" + text + "}"; FileStorage fs(wrapped, FileStorage::READ | FileStorage::MEMORY | FileStorage::FORMAT_JSON); if (!fs.isOpened()) { CV_LOG_WARNING(NULL, "DNN/ORT: failed to parse profile JSON"); return; } static const std::string KT = "_kernel_time"; FileNode events = fs["events"]; for (FileNodeIterator it = events.begin(); it != events.end(); ++it) { FileNode entry = *it; if (!entry.isMap()) continue; if ((std::string)entry["cat"] != "Node") continue; const std::string name = (std::string)entry["name"]; if (name.size() < KT.size() || name.compare(name.size() - KT.size(), KT.size(), KT) != 0) continue; FileNode args = entry["args"]; if (args.empty()) continue; const std::string op = (std::string)args["op_name"]; const double dur_us = (double)entry["dur"]; if (op.empty() || dur_us <= 0) continue; const std::string canonical = name.substr(0, name.size() - KT.size()); auto& slot = out_ms[canonical]; slot.first = op; slot.second += dur_us / 1000.0; } } void Net::Impl::collectOrtProfileData() const { if (ort_profile_collected) return; ort_profile_collected = true; if (!ort_session || ort_profile_path_prefix.empty()) return; // EndProfiling closes the file ORT has been writing to and returns its path. Ort::AllocatorWithDefaultOptions allocator; Ort::AllocatedStringPtr profile_path = ort_session->EndProfilingAllocated(allocator); if (!profile_path) { CV_LOG_WARNING(NULL, "DNN/ORT: EndProfiling did not return a path (prefix=" << ort_profile_path_prefix << ")"); return; } // Read the JSON entirely into memory, then parse it from the string. std::ifstream in(profile_path.get()); if (!in.is_open()) { CV_LOG_WARNING(NULL, "DNN/ORT: failed to open profile JSON " << profile_path.get()); return; } std::stringstream ss; ss << in.rdbuf(); const std::string text = ss.str(); std::map> by_name; parseOrtProfileJson(text, by_name); const int runs = ort_profile_runs > 0 ? ort_profile_runs : 1; ort_profile_data.clear(); ort_profile_data.reserve(by_name.size()); for (auto& kv : by_name) { const String name = kv.first; const String type = kv.second.first; const double ms_per_run = kv.second.second / std::max(1, runs); ort_profile_data.emplace_back(name, type, ms_per_run); } } #endif PerfProfile Net::Impl::getPerfProfile() const { PerfProfile result; result.mode = profilingMode; if (profilingMode == DNN_PROFILE_NONE) return result; #ifdef HAVE_ONNXRUNTIME if (useOrtEngine && ort_session) { collectOrtProfileData(); if (profilingMode == DNN_PROFILE_DETAILED) { for (const auto& t : ort_profile_data) { if (std::get<2>(t) <= 0) continue; PerfProfileEntry e; e.label = std::get<0>(t) + " (" + std::get<1>(t) + ")"; e.timeMs = std::get<2>(t); e.count = 1; result.entries.push_back(e); } } else if (profilingMode == DNN_PROFILE_SUMMARY) { std::map typeTimings; std::map typeCounts; for (const auto& t : ort_profile_data) { if (std::get<2>(t) <= 0) continue; typeTimings[std::get<1>(t)] += std::get<2>(t); typeCounts[std::get<1>(t)]++; } result.entries.reserve(typeTimings.size()); for (auto it = typeTimings.begin(); it != typeTimings.end(); ++it) { PerfProfileEntry e; e.label = it->first; e.timeMs = it->second; e.count = typeCounts[it->first]; result.entries.push_back(e); } } std::sort(result.entries.begin(), result.entries.end(), [](const PerfProfileEntry& a, const PerfProfileEntry& b) { return a.timeMs > b.timeMs; }); return result; } #endif std::vector timings(layersTimings.begin() + 1, layersTimings.end()); double tickFreq = getTickFrequency(); std::vector names; std::vector types; collectLayerInfo(names, types); size_t n = std::min(timings.size(), names.size()); if (profilingMode == DNN_PROFILE_DETAILED) { for (size_t i = 0; i < n; i++) { if (timings[i] > 0) { PerfProfileEntry e; e.label = names[i] + " (" + types[i] + ")"; e.timeMs = timings[i] * 1000.0 / tickFreq; e.count = 1; result.entries.push_back(e); } } } else if (profilingMode == DNN_PROFILE_SUMMARY) { std::map typeTimings; std::map typeCounts; for (size_t i = 0; i < n; i++) { if (timings[i] > 0) { typeTimings[types[i]] += timings[i] * 1000.0 / tickFreq; typeCounts[types[i]]++; } } result.entries.reserve(typeTimings.size()); for (auto it = typeTimings.begin(); it != typeTimings.end(); ++it) { PerfProfileEntry e; e.label = it->first; e.timeMs = it->second; e.count = typeCounts[it->first]; result.entries.push_back(e); } } std::sort(result.entries.begin(), result.entries.end(), [](const PerfProfileEntry& a, const PerfProfileEntry& b) { return a.timeMs > b.timeMs; }); return result; } void Net::Impl::getPerfProfile(std::vector& names, std::vector& timems, std::vector& counts) const { PerfProfile profile = getPerfProfile(); names.clear(); timems.clear(); counts.clear(); names.reserve(profile.entries.size()); timems.reserve(profile.entries.size()); counts.reserve(profile.entries.size()); for (const PerfProfileEntry& e : profile.entries) { names.push_back(e.label); timems.push_back(cv::format("%.3f", e.timeMs)); counts.push_back(cv::format("%d", e.count)); } } void Net::Impl::printPerfProfile() const { PerfProfile profile = getPerfProfile(); if (profile.mode == DNN_PROFILE_NONE) return; double totalMs = 0.0; for (const PerfProfileEntry& e : profile.entries) totalMs += e.timeMs; if (totalMs <= 0.0) return; if (profile.mode == DNN_PROFILE_DETAILED) { CV_LOG_INFO(NULL, "\n=== DNN Layer Profiling (Detailed) ==="); CV_LOG_INFO(NULL, cv::format("%-5s %-60s %10s %8s", "ID", "Layer (Type)", "Time (ms)", " (%)")); CV_LOG_INFO(NULL, "-----------------------------------------------------------------------------------------------"); for (size_t i = 0; i < profile.entries.size(); i++) { const PerfProfileEntry& e = profile.entries[i]; double pct = e.timeMs * 100.0 / totalMs; CV_LOG_INFO(NULL, cv::format("%-5zu %-60s %10.3f %7.1f%%", i, e.label.c_str(), e.timeMs, pct)); } CV_LOG_INFO(NULL, "-----------------------------------------------------------------------------------------------"); CV_LOG_INFO(NULL, cv::format("%-5s %-60s %10.3f %7s", "", "TOTAL", totalMs, "100.0%")); CV_LOG_INFO(NULL, ""); } else if (profile.mode == DNN_PROFILE_SUMMARY) { CV_LOG_INFO(NULL, "\n=== DNN Layer Profiling (Summary by Type) ==="); CV_LOG_INFO(NULL, cv::format("%-25s %6s %10s %8s", "Layer Type", "Count", "Time (ms)", " (%)")); CV_LOG_INFO(NULL, "-----------------------------------------------------------"); for (const PerfProfileEntry& e : profile.entries) { double pct = e.timeMs * 100.0 / totalMs; CV_LOG_INFO(NULL, cv::format("%-25s %6d %10.3f %7.1f%%", e.label.c_str(), e.count, e.timeMs, pct)); } CV_LOG_INFO(NULL, "-----------------------------------------------------------"); CV_LOG_INFO(NULL, cv::format("%-25s %6s %10.3f %7s", "TOTAL", "", totalMs, "100.0%")); CV_LOG_INFO(NULL, ""); } } void Net::Impl::getMemoryConsumption( const std::vector& netInputShapes, const std::vector& netInputTypes, std::vector& layerIds, std::vector& weights, std::vector& blobs) /*const*/ { layerIds.clear(); weights.clear(); blobs.clear(); std::vector> inLayerShapes, outLayerShapes; getLayersShapes(netInputShapes, netInputTypes, layerIds, inLayerShapes, outLayerShapes); // FIXIT netWasQuantized check is not enough - per layer check should be done size_t elemSize = netWasQuantized ? sizeof(char) : sizeof(float); for (int i = 0; i < layerIds.size(); i++) { int w = 0, b = 0; Impl::MapIdToLayerData::const_iterator layer = layers.find(layerIds[i]); CV_Assert(layer != layers.end()); for (int j = 0; j < layer->second.params.blobs.size(); j++) { const Mat& weightsBlob = layer->second.params.blobs[j]; w += weightsBlob.total() * weightsBlob.elemSize(); } for (int j = 0; j < outLayerShapes[i].size(); j++) { b += total(outLayerShapes[i][j]) * elemSize; } weights.push_back(w); blobs.push_back(b); } } void Net::Impl::enableWinograd(bool useWinograd_) { if (useWinograd != useWinograd_) { useWinograd = useWinograd_; for (MapIdToLayerData::const_iterator it = layers.begin(); it != layers.end(); it++) { int lid = it->first; LayerData &ld = layers[lid]; Ptr& currLayer = ld.layerInstance; if (ld.type == "Convolution") { ld.params.set("use_winograd", useWinograd_); Ptr convLayer = ld.layerInstance.dynamicCast(); if (!convLayer.empty()) convLayer->useWinograd = useWinograd_; } if (ld.type == "ConvolutionInt8") { Ptr convLayer = currLayer.dynamicCast(); ld.params.set("use_winograd", useWinograd_); if (!convLayer.empty()) convLayer->useWinograd = useWinograd_; } } } } // TODO drop? void Net::Impl::getLayerTypes(std::vector& layersTypes) const { layersTypes.clear(); if (mainGraph) { std::set layersTypesSet; for (const Ptr& g: allgraphs) { const std::vector >& prog = g->prog(); for (const Ptr& layer: prog) { if (!layer) continue; layersTypesSet.insert(layer->type); } } for (auto it = layersTypesSet.begin(); it != layersTypesSet.end(); ++it) layersTypes.push_back(*it); return; } std::map layers_type_map; for (MapIdToLayerData::const_iterator it = layers.begin(); it != layers.end(); it++) { if (layers_type_map.find(it->second.type) == layers_type_map.end()) layers_type_map[it->second.type] = 0; layers_type_map[it->second.type]++; } for (std::map::const_iterator it = layers_type_map.begin(); it != layers_type_map.end(); it++) { layersTypes.push_back(it->first); } } // TODO drop? int Net::Impl::getLayersCount(const String& layerType) const { if (mainGraph) { int count = 0; for (const Ptr& g: allgraphs) { const std::vector >& prog = g->prog(); for (const Ptr& layer: prog) { if (!layer) continue; if (layer->type == layerType) count++; } } return count; } int count = 0; for (Impl::MapIdToLayerData::const_iterator it = layers.begin(); it != layers.end(); it++) { if (it->second.type == layerType) count++; } return count; } CV__DNN_INLINE_NS_END }} // namespace cv::dnn