diff --git a/modules/dnn/include/opencv2/dnn/dnn.hpp b/modules/dnn/include/opencv2/dnn/dnn.hpp index 8af42ea4f40f47252841036d348eae6ec8c0f683..8c9b32cdd718f39c8862e3388df64495b5d94640 100644 --- a/modules/dnn/include/opencv2/dnn/dnn.hpp +++ b/modules/dnn/include/opencv2/dnn/dnn.hpp @@ -489,6 +489,18 @@ CV__DNN_EXPERIMENTAL_NS_BEGIN */ void connect(int outLayerId, int outNum, int inpLayerId, int inpNum); + /** @brief Registers network output with name + * + * Function may create additional 'Identity' layer. + * + * @param outputName identifier of the output + * @param layerId identifier of the second layer + * @param outputPort number of the second layer input + * + * @returns index of bound layer (the same as layerId or newly created) + */ + int registerOutput(const std::string& outputName, int layerId, int outputPort); + /** @brief Sets outputs names of the network input pseudo layer. * * Each net always has special own the network input pseudo layer with id=0. @@ -610,10 +622,14 @@ CV__DNN_EXPERIMENTAL_NS_BEGIN CV_WRAP inline Mat getParam(const String& layerName, int numParam = 0) const { return getParam(getLayerId(layerName), numParam); } /** @brief Returns indexes of layers with unconnected outputs. + * + * FIXIT: Rework API to registerOutput() approach, deprecate this call */ CV_WRAP std::vector getUnconnectedOutLayers() const; /** @brief Returns names of layers with unconnected outputs. + * + * FIXIT: Rework API to registerOutput() approach, deprecate this call */ CV_WRAP std::vector getUnconnectedOutLayersNames() const; diff --git a/modules/dnn/src/dnn.cpp b/modules/dnn/src/dnn.cpp index 43b2a7219914a559d1c1f6f746aa5d07f6757889..245a2c5c1dc1e798e2e50f8b9eb10a20224ed332 100644 --- a/modules/dnn/src/dnn.cpp +++ b/modules/dnn/src/dnn.cpp @@ -1135,6 +1135,7 @@ struct Net::Impl : public detail::NetImplBase std::vector blobsToKeep; MapIdToLayerData layers; std::map layerNameToId; + std::map outputNameToId; // use registerOutput() to populate outputs BlobManager blobManager; int preferableBackend; int preferableTarget; @@ -1483,6 +1484,23 @@ struct Net::Impl : public detail::NetImplBase return pins; } + int addLayer(const String &name, const String &type, LayerParams ¶ms) + { + if (getLayerId(name) >= 0) + { + CV_Error(Error::StsBadArg, "Layer \"" + name + "\" already into net"); + return -1; + } + + int id = ++lastLayerId; + layerNameToId.insert(std::make_pair(name, id)); + layers.insert(std::make_pair(id, LayerData(id, name, type, params))); + if (params.get("has_dynamic_shapes", false)) + hasDynamicShapes = true; + + return id; + } + void connect(int outLayerId, int outNum, int inLayerId, int inNum) { CV_Assert(outLayerId < inLayerId); @@ -1492,6 +1510,39 @@ struct Net::Impl : public detail::NetImplBase 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 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 outputLayerId = addLayer(outputLayerParams.name, outputLayerParams.type, 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& blobsToKeep_) @@ -3599,20 +3650,8 @@ Net::~Net() int Net::addLayer(const String &name, const String &type, LayerParams ¶ms) { CV_TRACE_FUNCTION(); - - if (impl->getLayerId(name) >= 0) - { - CV_Error(Error::StsBadArg, "Layer \"" + name + "\" already into net"); - return -1; - } - - int id = ++impl->lastLayerId; - impl->layerNameToId.insert(std::make_pair(name, id)); - impl->layers.insert(std::make_pair(id, LayerData(id, name, type, params))); - if (params.get("has_dynamic_shapes", false)) - impl->hasDynamicShapes = true; - - return id; + CV_Assert(impl); + return impl->addLayer(name, type, params); } int Net::addLayerToPrev(const String &name, const String &type, LayerParams ¶ms) @@ -3644,6 +3683,13 @@ void Net::connect(String _outPin, String _inPin) impl->connect(outPin.lid, outPin.oid, inpPin.lid, inpPin.oid); } +int Net::registerOutput(const std::string& outputName, int layerId, int outputPort) +{ + CV_TRACE_FUNCTION(); + CV_Assert(impl); + return impl->registerOutput(outputName, layerId, outputPort); +} + Mat Net::forward(const String& outputName) { CV_TRACE_FUNCTION(); @@ -4328,8 +4374,22 @@ bool Net::empty() const std::vector Net::getUnconnectedOutLayers() const { + CV_TRACE_FUNCTION(); + CV_Assert(impl); + std::vector layersIds; + // registerOutput() flow + const std::map& outputNameToId = impl->outputNameToId; + 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 = impl->layers.begin(); it != impl->layers.end(); it++) { diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index 9f7d8461ca1b2fae394ba52c782150a2f2f1f8f0..1ab2b35cd0f106eaf915277e39673fc59f74211d 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -130,6 +130,7 @@ protected: std::map layer_id; typedef std::map::iterator IterLayerId_t; + typedef std::map::const_iterator ConstIterLayerId_t; void handleNode(const opencv_onnx::NodeProto& node_proto); @@ -687,9 +688,31 @@ void ONNXImporter::populateNet() handleNode(node_proto); } + // 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/ONNX: import completed!"); } +static const std::string& extractNodeName(const opencv_onnx::NodeProto& node_proto) { if (node_proto.has_name() && !node_proto.name().empty())