Skip to content

Commit bc0618b

Browse files
authored
Merge pull request opencv#25582 from fengyuentau:dnn/dump_pbtxt
Current net exporter `dump` and `dumpToFile` exports the network structure (and its params) to a .dot file which works with `graphviz`. This is hard to use and not friendly to new user. What's worse, the produced picture is not looking pretty. dnn: better net exporter that works with netron opencv#25582 This PR introduces new exporter `dumpToPbtxt` and uses this new exporter by default with environment variable `OPENCV_DNN_NETWORK_DUMP`. It mimics the string output of a onnx model but modified with dnn-specific changes, see below for an example. ![image](https://github.com/opencv/opencv/assets/17219438/0644bed1-da71-4019-8466-88390698e4df) ## Usage Call `cv::dnn::Net::dumpToPbtxt`: ```cpp TEST(DumpNet, dumpToPbtxt) { std::string path = "/path/to/model.onnx"; auto net = readNet(path); Mat input(std::vector<int>{1, 3, 640, 480}, CV_32F); net.setInput(input); net.dumpToPbtxt("yunet.pbtxt"); } ``` Set `export OPENCV_DNN_NETWORK_DUMP=1` ```cpp TEST(DumpNet, env) { std::string path = "/path/to/model.onnx"; auto net = readNet(path); Mat input(std::vector<int>{1, 3, 640, 480}, CV_32F); net.setInput(input); net.forward(); } ``` --- Note: - `pbtxt` is registered as one of the ONNX model suffix in netron. So you can see `module: ai.onnx` and such in the model. - We can get the string output of an ONNX model with the following script ```python import onnx net = onnx.load("/path/to/model.onnx") net_str = str(net) file = open("/path/to/model.pbtxt", "w") file.write(net_str) file.close() ``` ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
1 parent 0044047 commit bc0618b

File tree

5 files changed

+367
-5
lines changed

5 files changed

+367
-5
lines changed

apps/model-diagnostics/model_diagnostics.cpp

+83-3
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,36 @@ static std::string checkFileExists(const std::string& fileName)
3232
"Please, specify a full path to the file.");
3333
}
3434

35+
static std::vector<int> parseShape(const std::string &shape_str) {
36+
std::stringstream ss(shape_str);
37+
std::string item;
38+
std::vector<std::string> items;
39+
40+
while (std::getline(ss, item, ',')) {
41+
items.push_back(item);
42+
}
43+
44+
std::vector<int> shape;
45+
for (size_t i = 0; i < items.size(); i++) {
46+
shape.push_back(std::stoi(items[i]));
47+
}
48+
return shape;
49+
}
50+
3551
std::string diagnosticKeys =
3652
"{ model m | | Path to the model file. }"
3753
"{ config c | | Path to the model configuration file. }"
38-
"{ framework f | | [Optional] Name of the model framework. }";
39-
40-
54+
"{ framework f | | [Optional] Name of the model framework. }"
55+
"{ input0_name | | [Optional] Name of input0. Use with input0_shape}"
56+
"{ input0_shape | | [Optional] Shape of input0. Use with input0_name}"
57+
"{ input1_name | | [Optional] Name of input1. Use with input1_shape}"
58+
"{ input1_shape | | [Optional] Shape of input1. Use with input1_name}"
59+
"{ input2_name | | [Optional] Name of input2. Use with input2_shape}"
60+
"{ input2_shape | | [Optional] Shape of input2. Use with input2_name}"
61+
"{ input3_name | | [Optional] Name of input3. Use with input3_shape}"
62+
"{ input3_shape | | [Optional] Shape of input3. Use with input3_name}"
63+
"{ input4_name | | [Optional] Name of input4. Use with input4_shape}"
64+
"{ input4_shape | | [Optional] Shape of input4. Use with input4_name}";
4165

4266
int main( int argc, const char** argv )
4367
{
@@ -55,6 +79,17 @@ int main( int argc, const char** argv )
5579
std::string config = checkFileExists(argParser.get<std::string>("config"));
5680
std::string frameworkId = argParser.get<std::string>("framework");
5781

82+
std::string input0_name = argParser.get<std::string>("input0_name");
83+
std::string input0_shape = argParser.get<std::string>("input0_shape");
84+
std::string input1_name = argParser.get<std::string>("input1_name");
85+
std::string input1_shape = argParser.get<std::string>("input1_shape");
86+
std::string input2_name = argParser.get<std::string>("input2_name");
87+
std::string input2_shape = argParser.get<std::string>("input2_shape");
88+
std::string input3_name = argParser.get<std::string>("input3_name");
89+
std::string input3_shape = argParser.get<std::string>("input3_shape");
90+
std::string input4_name = argParser.get<std::string>("input4_name");
91+
std::string input4_shape = argParser.get<std::string>("input4_shape");
92+
5893
CV_Assert(!model.empty());
5994

6095
enableModelDiagnostics(true);
@@ -63,5 +98,50 @@ int main( int argc, const char** argv )
6398

6499
Net ocvNet = readNet(model, config, frameworkId);
65100

101+
std::vector<std::string> input_names;
102+
std::vector<std::vector<int>> input_shapes;
103+
if (!input0_name.empty() || !input0_shape.empty()) {
104+
CV_CheckFalse(input0_name.empty(), "input0_name cannot be empty");
105+
CV_CheckFalse(input0_shape.empty(), "input0_shape cannot be empty");
106+
input_names.push_back(input0_name);
107+
input_shapes.push_back(parseShape(input0_shape));
108+
}
109+
if (!input1_name.empty() || !input1_shape.empty()) {
110+
CV_CheckFalse(input1_name.empty(), "input1_name cannot be empty");
111+
CV_CheckFalse(input1_shape.empty(), "input1_shape cannot be empty");
112+
input_names.push_back(input1_name);
113+
input_shapes.push_back(parseShape(input1_shape));
114+
}
115+
if (!input2_name.empty() || !input2_shape.empty()) {
116+
CV_CheckFalse(input2_name.empty(), "input2_name cannot be empty");
117+
CV_CheckFalse(input2_shape.empty(), "input2_shape cannot be empty");
118+
input_names.push_back(input2_name);
119+
input_shapes.push_back(parseShape(input2_shape));
120+
}
121+
if (!input3_name.empty() || !input3_shape.empty()) {
122+
CV_CheckFalse(input3_name.empty(), "input3_name cannot be empty");
123+
CV_CheckFalse(input3_shape.empty(), "input3_shape cannot be empty");
124+
input_names.push_back(input3_name);
125+
input_shapes.push_back(parseShape(input3_shape));
126+
}
127+
if (!input4_name.empty() || !input4_shape.empty()) {
128+
CV_CheckFalse(input4_name.empty(), "input4_name cannot be empty");
129+
CV_CheckFalse(input4_shape.empty(), "input4_shape cannot be empty");
130+
input_names.push_back(input4_name);
131+
input_shapes.push_back(parseShape(input4_shape));
132+
}
133+
134+
if (!input_names.empty() && !input_shapes.empty() && input_names.size() == input_shapes.size()) {
135+
ocvNet.setInputsNames(input_names);
136+
for (size_t i = 0; i < input_names.size(); i++) {
137+
Mat input(input_shapes[i], CV_32F);
138+
ocvNet.setInput(input, input_names[i]);
139+
}
140+
141+
size_t dot_index = model.rfind('.');
142+
std::string graph_filename = model.substr(0, dot_index) + ".pbtxt";
143+
ocvNet.dumpToPbtxt(graph_filename);
144+
}
145+
66146
return 0;
67147
}

modules/dnn/include/opencv2/dnn/dnn.hpp

+8
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,14 @@ CV__DNN_INLINE_NS_BEGIN
518518
* @see dump()
519519
*/
520520
CV_WRAP void dumpToFile(CV_WRAP_FILE_PATH const String& path);
521+
/** @brief Dump net structure, hyperparameters, backend, target and fusion to pbtxt file
522+
* @param path path to output file with .pbtxt extension
523+
*
524+
* Use Netron (https://netron.app) to open the target file to visualize the model.
525+
* Call method after setInput(). To see correct backend, target and fusion run after forward().
526+
*/
527+
CV_WRAP void dumpToPbtxt(CV_WRAP_FILE_PATH const String& path);
528+
521529
/** @brief Adds new layer to the net.
522530
* @param name unique name of the adding layer.
523531
* @param type typename of the adding layer (type must be registered in LayerRegister).

modules/dnn/src/net.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ void Net::dumpToFile(const String& path)
216216
file.close();
217217
}
218218

219+
void Net::dumpToPbtxt(const String& path)
220+
{
221+
CV_TRACE_FUNCTION();
222+
CV_Assert(impl);
223+
CV_Assert(!empty());
224+
std::ofstream file(path.c_str());
225+
file << impl->dumpToPbtxt(true);
226+
file.close();
227+
}
228+
219229
Ptr<Layer> Net::getLayer(int layerId) const
220230
{
221231
CV_Assert(impl);

0 commit comments

Comments
 (0)