1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-28 23:03:03 +04:00

Merge pull request #28752 from abhishek-gola:net_profiling

Added net profiling support #28752

### 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
- [x] There is a reference to the original bug report and related work
- [x] 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
This commit is contained in:
Abhishek Gola
2026-05-14 17:17:38 +05:30
committed by GitHub
parent 851e0196b4
commit 873a4635c6
12 changed files with 361 additions and 2 deletions
+42
View File
@@ -545,6 +545,33 @@ CV__DNN_INLINE_NS_BEGIN
virtual void setProg(const std::vector<Ptr<Layer> >& newprog) = 0;
};
/** @brief Single entry in a @ref PerfProfile.
*
* In DNN_PROFILE_DETAILED mode, @p label is "layer_name (type)" and @p count is 1.
* In DNN_PROFILE_SUMMARY mode, @p label is the layer type and @p count is the
* number of layers of that type that contributed to @p timeMs.
*/
struct CV_EXPORTS_W_SIMPLE PerfProfileEntry
{
CV_WRAP PerfProfileEntry() : timeMs(0.0), count(0) {}
CV_PROP_RW String label;
CV_PROP_RW double timeMs;
CV_PROP_RW int count;
};
/** @brief Self-describing snapshot of profiling data from one inference.
*
* Carries the @ref ProfilingMode it was captured in so it can be saved, kept across
* runs (e.g. best-of-N by total time), and printed later via @ref Net::printPerfProfile
* without needing access to the originating @ref Net.
*/
struct CV_EXPORTS_W_SIMPLE PerfProfile
{
CV_WRAP PerfProfile() : mode(DNN_PROFILE_NONE) {}
CV_PROP_RW ProfilingMode mode;
CV_PROP_RW std::vector<PerfProfileEntry> entries;
};
/** @brief This class allows to create and manipulate comprehensive artificial neural networks.
*
* Neural network is presented as directed acyclic graph (DAG), where vertices are Layer instances,
@@ -1027,6 +1054,21 @@ CV__DNN_INLINE_NS_BEGIN
*/
CV_WRAP int64 getPerfProfile(CV_OUT std::vector<double>& timings);
/** @brief Returns profiling data captured during the last forward pass.
*
* Entries are sorted by time in descending order. Empty vectors are returned
* if profiling is disabled (DNN_PROFILE_NONE).
*/
CV_WRAP void getPerfProfile(CV_OUT std::vector<std::string>& names, CV_OUT std::vector<std::string>& timems, CV_OUT std::vector<std::string>& counts) const;
/** @brief Prints the profile captured during the last forward pass in a formatted table using CV_LOG_INFO.
*
* In DNN_PROFILE_DETAILED mode, prints per-layer label, time, and percentage.
* In DNN_PROFILE_SUMMARY mode, prints per-type count, time, and percentage.
* Does nothing if profiling is disabled (DNN_PROFILE_NONE) or all timings are zero.
*/
CV_WRAP void printPerfProfile() const;
// Get the main model graph
Ptr<Graph> getMainGraph() const;
+14
View File
@@ -446,6 +446,20 @@ int64 Net::getPerfProfile(std::vector<double>& timings)
return impl->getPerfProfile(timings);
}
void Net::getPerfProfile(std::vector<std::string>& names,
std::vector<std::string>& timems,
std::vector<std::string>& counts) const
{
CV_Assert(impl);
impl->getPerfProfile(names, timems, counts);
}
void Net::printPerfProfile() const
{
CV_Assert(impl);
impl->printPerfProfile();
}
bool Net::isConstArg(Arg arg) const
{
return argKind(arg) == DNN_ARG_CONST;
+251
View File
@@ -6,6 +6,12 @@
#include "net_impl.hpp"
#ifdef HAVE_ONNXRUNTIME
#include <onnxruntime_cxx_api.h>
#include <fstream>
#include <sstream>
#endif
namespace cv {
namespace dnn {
CV__DNN_INLINE_NS_BEGIN
@@ -2642,6 +2648,251 @@ int64 Net::Impl::getPerfProfile(std::vector<double>& timings) const
return total;
}
void Net::Impl::collectLayerInfo(std::vector<String>& names, std::vector<String>& types) const
{
if (mainGraph) {
names.reserve(totalLayers);
types.reserve(totalLayers);
for (const Ptr<Graph>& graph : allgraphs) {
const std::vector<Ptr<Layer>>& prog = graph->prog();
for (const Ptr<Layer>& 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<std::string, std::pair<std::string, double>>& 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<std::string, std::pair<std::string, double>> 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 / 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<String, double> typeTimings;
std::map<String, int> 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<double> timings(layersTimings.begin() + 1, layersTimings.end());
double tickFreq = getTickFrequency();
std::vector<String> names;
std::vector<String> 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<String, double> typeTimings;
std::map<String, int> 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<std::string>& names,
std::vector<std::string>& timems,
std::vector<std::string>& 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<MatShape>& netInputShapes,
const std::vector<MatType>& netInputTypes,
+9
View File
@@ -254,12 +254,17 @@ struct Net::Impl : public detail::NetImplBase
void finalizeOrt();
void refreshOrtMainGraphOutputs();
void applyStagedOrtInputs();
void collectOrtProfileData() const;
std::vector<std::pair<std::string, Mat>> ort_staged_inputs;
std::shared_ptr<Ort::Env> ort_env;
std::shared_ptr<Ort::Session> ort_session;
std::shared_ptr<OrtNamesCache> ort_names_cache;
bool useOrtEngine = false; // true only when user explicitly selected ENGINE_ORT
bool ortNeedsReinit = false; // session needs (re)creation on next finalizeNet
std::string ort_profile_path_prefix; // prefix passed to EnableProfiling
mutable bool ort_profile_collected = false; // EndProfiling was already called once
mutable int ort_profile_runs = 0; // number of session.Run calls since profiling started
mutable std::vector<std::tuple<String, String, double>> ort_profile_data; // (name, type, ms_per_run)
#endif
void allocateLayer(int lid, const LayersShapesMap& layersShapes);
@@ -330,6 +335,10 @@ struct Net::Impl : public detail::NetImplBase
std::vector<int>& layerIds, std::vector<size_t>& weights,
std::vector<size_t>& blobs) /*const*/;
int64 getPerfProfile(std::vector<double>& timings) const;
void collectLayerInfo(std::vector<String>& names, std::vector<String>& types) const;
PerfProfile getPerfProfile() const;
void getPerfProfile(std::vector<std::string>& names, std::vector<std::string>& timems, std::vector<std::string>& counts) const;
void printPerfProfile() const;
// TODO drop
LayerPin getLatestLayerPin(const std::vector<LayerPin>& pins) const;
+1
View File
@@ -221,6 +221,7 @@ std::vector<Mat> Net::Impl::runOrtSession(std::vector<Mat> inputBlobs, const std
Ort::RunOptions{nullptr},
in_names.data(), input_tensors.data(), input_tensors.size(),
out_names.data(), out_names.size());
if (profilingMode != DNN_PROFILE_NONE) ort_profile_runs++;
CV_CheckEQ(output_tensors.size(), out_names.size(), "DNN/ORT: ORT returned unexpected number of outputs");
+11
View File
@@ -88,6 +88,17 @@ void Net::Impl::finalizeOrt()
}
}
// If the user set DNN_PROFILE_*, turn on ORT's session profiler. The JSON
// file is parsed lazily by collectOrtProfileData() inside getPerfProfile()/printPerfProfile().
ort_profile_path_prefix.clear();
ort_profile_collected = false;
ort_profile_runs = 0;
ort_profile_data.clear();
if (profilingMode != DNN_PROFILE_NONE) {
ort_profile_path_prefix = cv::tempfile("opencv_ort_profile_");
opts.EnableProfiling(ort_profile_path_prefix.c_str());
}
#ifdef _WIN32
std::wstring wpath(modelFileName.begin(), modelFileName.end());
ort_session = std::make_shared<Ort::Session>(*ort_env, wpath.c_str(), opts);
+5
View File
@@ -5,6 +5,7 @@
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/utils/logger.hpp>
#include "common.hpp"
@@ -91,6 +92,8 @@ static bool readStringList( const string& filename, vector<string>& l )
int main(int argc, char** argv)
{
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_INFO);
CommandLineParser parser(argc, argv, keys);
if (!parser.has("@alias") || parser.has("help"))
@@ -150,6 +153,7 @@ int main(int argc, char** argv)
Net net = readNetFromONNX(model, engine);
net.setPreferableBackend(getBackendID(backend));
net.setPreferableTarget(getTargetID(target));
net.setProfilingMode(DNN_PROFILE_SUMMARY);
//! [Read and initialize network]
// Create a window
@@ -229,6 +233,7 @@ int main(int argc, char** argv)
timeRecorder.start();
prob = net.forward();
timeRecorder.stop();
net.printPerfProfile();
//! [Make forward pass]
//! [Get a class with a highest score]
+4
View File
@@ -72,6 +72,7 @@ def main(func_args=None):
help()
exit(1)
cv.utils.logging.setLogLevel(cv.utils.logging.LOG_LEVEL_INFO)
args.model = findModel(args.model, args.sha1)
args.labels = findFile(args.labels)
@@ -88,6 +89,8 @@ def main(func_args=None):
net = cv.dnn.readNetFromONNX(args.model, engine)
net.setPreferableBackend(get_backend_id(args.backend))
net.setPreferableTarget(get_target_id(args.target))
if hasattr(cv.dnn, 'DNN_PROFILE_SUMMARY'):
net.setProfilingMode(cv.dnn.DNN_PROFILE_SUMMARY)
winName = 'Deep learning image classification in OpenCV'
cv.namedWindow(winName, cv.WINDOW_NORMAL)
@@ -138,6 +141,7 @@ def main(func_args=None):
t0 = cv.getTickCount()
out = net.forward()
t = (cv.getTickCount() - t0) / cv.getTickFrequency()
net.printPerfProfile()
(h, w, _) = frame.shape
roi_rows = min(300, h)
+6
View File
@@ -6,6 +6,7 @@
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/utils/logger.hpp>
#include <mutex>
#include <thread>
@@ -161,6 +162,8 @@ private:
int main(int argc, char** argv)
{
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_INFO);
CommandLineParser parser(argc, argv, keys);
string zooFile = parser.get<String>("zoo");
@@ -225,6 +228,7 @@ int main(int argc, char** argv)
int backend = getBackendID(parser.get<String>("backend"));
net.setPreferableBackend(backend);
net.setPreferableTarget(getTargetID(parser.get<String>("target")));
net.setProfilingMode(DNN_PROFILE_SUMMARY);
//![read_net]
// Create a window
@@ -302,6 +306,7 @@ int main(int argc, char** argv)
//![forward]
vector<Mat> outs;
net.forward(outs, net.getUnconnectedOutLayersNames());
net.printPerfProfile();
predictionsQueue.push(outs);
//![forward]
}
@@ -372,6 +377,7 @@ int main(int argc, char** argv)
tickMeter.start();
net.forward(outs, net.getUnconnectedOutLayersNames());
tickMeter.stop();
net.printPerfProfile();
classIds.clear();
confidences.clear();
+5
View File
@@ -71,6 +71,7 @@ if args.alias is None or hasattr(args, 'help'):
help()
exit(1)
cv.utils.logging.setLogLevel(cv.utils.logging.LOG_LEVEL_INFO)
args.model = findModel(args.model, args.sha1)
if args.config is not None:
args.config = findModel(args.config, args.config_sha1)
@@ -104,6 +105,8 @@ if args.backend != "default" or args.target != "cpu":
net = cv.dnn.readNet(args.model, args.config, "", engine)
net.setPreferableBackend(get_backend_id(args.backend))
net.setPreferableTarget(get_target_id(args.target))
if hasattr(cv.dnn, 'DNN_PROFILE_SUMMARY'):
net.setProfilingMode(cv.dnn.DNN_PROFILE_SUMMARY)
outNames = net.getUnconnectedOutLayersNames()
confThreshold = args.thr
@@ -340,6 +343,7 @@ def processingThreadBody():
futureOutputs.append(net.forwardAsync())
else:
outs = net.forward(outNames)
net.printPerfProfile()
predictionsQueue.put(copy.deepcopy(outs))
while futureOutputs and futureOutputs[0].wait_for(0):
@@ -408,6 +412,7 @@ else:
net.setInput(blob)
outs = net.forward(outNames)
net.printPerfProfile()
boxes, classIds, confidences, indices = postprocess(frame, outs)
drawPred(classIds, confidences, boxes, indices, (stdSize*max(frame.shape[:2]))/stdImgSize, (stdWeight*max(frame.shape[:2]))//stdImgSize)
+7 -1
View File
@@ -5,6 +5,7 @@
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/utils/logger.hpp>
#include "common.hpp"
@@ -139,6 +140,8 @@ static void showLegend(FontFace fontFace)
int main(int argc, char **argv)
{
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_INFO);
CommandLineParser parser(argc, argv, keys);
const string modelName = parser.get<String>("@alias");
@@ -218,7 +221,8 @@ int main(int argc, char **argv)
Net net = readNetFromONNX(model, engine);
net.setPreferableBackend(getBackendID(backend));
net.setPreferableTarget(getTargetID(target));
//! [Read and initialize network]
net.setProfilingMode(DNN_PROFILE_SUMMARY);
//! [Read and initialize network]
// Create a window
static const string kWinName = "Deep learning semantic segmentation in OpenCV";
namedWindow(kWinName, WINDOW_AUTOSIZE);
@@ -263,6 +267,7 @@ int main(int argc, char **argv)
{
vector<Mat> output;
net.forward(output, net.getUnconnectedOutLayersNames());
net.printPerfProfile();
Mat pred = output[0].reshape(1, output[0].size[2]);
pred.convertTo(pred, CV_8U, 255.0);
@@ -284,6 +289,7 @@ int main(int argc, char **argv)
{
//! [Make forward pass]
Mat score = net.forward();
net.printPerfProfile();
//! [Make forward pass]
Mat segm;
colorizeSegmentation(score, segm);
+6 -1
View File
@@ -73,6 +73,7 @@ def main(func_args=None):
help()
exit(1)
cv.utils.logging.setLogLevel(cv.utils.logging.LOG_LEVEL_INFO)
args.model = findModel(args.model, args.sha1)
if args.labels is not None:
args.labels = findFile(args.labels)
@@ -105,6 +106,8 @@ def main(func_args=None):
net = cv.dnn.readNetFromONNX(args.model, engine)
net.setPreferableBackend(get_backend_id(args.backend))
net.setPreferableTarget(get_target_id(args.target))
if hasattr(cv.dnn, 'DNN_PROFILE_SUMMARY'):
net.setProfilingMode(cv.dnn.DNN_PROFILE_SUMMARY)
winName = 'Deep learning semantic segmentation in OpenCV'
cv.namedWindow(winName, cv.WINDOW_AUTOSIZE)
@@ -138,6 +141,7 @@ def main(func_args=None):
t0 = cv.getTickCount()
if args.alias == 'u2netp':
output = net.forward(net.getUnconnectedOutLayersNames())
net.printPerfProfile()
pred = output[0][0, 0, :, :]
mask = (pred * 255).astype(np.uint8)
mask = cv.resize(mask, (frame.shape[1], frame.shape[0]), interpolation=cv.INTER_AREA)
@@ -149,6 +153,7 @@ def main(func_args=None):
frame = cv.addWeighted(frame, 0.25, foreground_overlay, 0.75, 0)
else:
score = net.forward()
net.printPerfProfile()
numClasses = score.shape[1]
height = score.shape[2]
@@ -176,4 +181,4 @@ def main(func_args=None):
cv.imshow(winName, frame)
if __name__ == "__main__":
main()
main()