blob: 75bc9a32449e739a5cbca6b4fc17b2373ee7ef7a [file] [log] [blame]
Derek Lambertid6cb30e2020-04-28 13:31:29 +01001//
2// Copyright © 2020 Arm Ltd. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
Jan Eilerse7cc7c32020-06-25 13:18:47 +01005
Derek Lambertid6cb30e2020-04-28 13:31:29 +01006#include "armnnTfLiteParser/ITfLiteParser.hpp"
7
8#include "NMS.hpp"
9
10#include <stb/stb_image.h>
11
12#include <armnn/INetwork.hpp>
13#include <armnn/IRuntime.hpp>
14#include <armnn/Logging.hpp>
15#include <armnn/utility/IgnoreUnused.hpp>
16
Jan Eilerse7cc7c32020-06-25 13:18:47 +010017#include <cxxopts/cxxopts.hpp>
18#include <ghc/filesystem.hpp>
19
Derek Lambertid6cb30e2020-04-28 13:31:29 +010020#include <chrono>
Derek Lambertid6cb30e2020-04-28 13:31:29 +010021#include <fstream>
Jan Eilerse7cc7c32020-06-25 13:18:47 +010022#include <iostream>
Pablo Marquez Tellod71ae442022-12-05 14:14:31 +000023#include <iterator>
Keith Mok16fb1a22021-03-19 08:58:44 -070024#include <cmath>
Derek Lambertid6cb30e2020-04-28 13:31:29 +010025
26using namespace armnnTfLiteParser;
27using namespace armnn;
28
29static const int OPEN_FILE_ERROR = -2;
30static const int OPTIMIZE_NETWORK_ERROR = -3;
31static const int LOAD_NETWORK_ERROR = -4;
32static const int LOAD_IMAGE_ERROR = -5;
33static const int GENERAL_ERROR = -100;
34
Pavel Macenauer855a47b2020-05-26 10:54:22 +000035#define CHECK_OK(v) \
36 do { \
37 try { \
38 auto r_local = v; \
39 if (r_local != 0) { return r_local;} \
40 } \
41 catch (const armnn::Exception& e) \
42 { \
43 ARMNN_LOG(error) << "Oops: " << e.what(); \
44 return GENERAL_ERROR; \
45 } \
Derek Lambertid6cb30e2020-04-28 13:31:29 +010046 } while(0)
47
48
49
50template<typename TContainer>
51inline armnn::InputTensors MakeInputTensors(const std::vector<armnn::BindingPointInfo>& inputBindings,
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +010052 const std::vector<std::reference_wrapper<TContainer>>& inputDataContainers)
Derek Lambertid6cb30e2020-04-28 13:31:29 +010053{
54 armnn::InputTensors inputTensors;
55
56 const size_t numInputs = inputBindings.size();
57 if (numInputs != inputDataContainers.size())
58 {
59 throw armnn::Exception("Mismatching vectors");
60 }
61
62 for (size_t i = 0; i < numInputs; i++)
63 {
64 const armnn::BindingPointInfo& inputBinding = inputBindings[i];
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +010065 const TContainer& inputData = inputDataContainers[i].get();
Derek Lambertid6cb30e2020-04-28 13:31:29 +010066
67 armnn::ConstTensor inputTensor(inputBinding.second, inputData.data());
68 inputTensors.push_back(std::make_pair(inputBinding.first, inputTensor));
69 }
70
71 return inputTensors;
72}
73
74template<typename TContainer>
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +010075inline armnn::OutputTensors MakeOutputTensors(
76 const std::vector<armnn::BindingPointInfo>& outputBindings,
77 const std::vector<std::reference_wrapper<TContainer>>& outputDataContainers)
Derek Lambertid6cb30e2020-04-28 13:31:29 +010078{
79 armnn::OutputTensors outputTensors;
80
81 const size_t numOutputs = outputBindings.size();
82 if (numOutputs != outputDataContainers.size())
83 {
84 throw armnn::Exception("Mismatching vectors");
85 }
86
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +010087 outputTensors.reserve(numOutputs);
88
Derek Lambertid6cb30e2020-04-28 13:31:29 +010089 for (size_t i = 0; i < numOutputs; i++)
90 {
91 const armnn::BindingPointInfo& outputBinding = outputBindings[i];
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +010092 const TContainer& outputData = outputDataContainers[i].get();
Derek Lambertid6cb30e2020-04-28 13:31:29 +010093
94 armnn::Tensor outputTensor(outputBinding.second, const_cast<float*>(outputData.data()));
95 outputTensors.push_back(std::make_pair(outputBinding.first, outputTensor));
96 }
97
98 return outputTensors;
99}
100
Derek Lambertifbab30b2020-09-17 13:58:18 +0100101#define S_BOOL(name) enum class name {False=0, True=1};
102
103S_BOOL(ImportMemory)
104S_BOOL(DumpToDot)
105S_BOOL(ExpectFile)
106S_BOOL(OptionalArg)
107
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100108int LoadModel(const char* filename,
109 ITfLiteParser& parser,
110 IRuntime& runtime,
111 NetworkId& networkId,
Narumol Prangnawaratef6f3002020-08-17 17:02:12 +0100112 const std::vector<BackendId>& backendPreferences,
Derek Lambertifbab30b2020-09-17 13:58:18 +0100113 ImportMemory enableImport,
114 DumpToDot dumpToDot)
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100115{
116 std::ifstream stream(filename, std::ios::in | std::ios::binary);
117 if (!stream.is_open())
118 {
119 ARMNN_LOG(error) << "Could not open model: " << filename;
120 return OPEN_FILE_ERROR;
121 }
122
123 std::vector<uint8_t> contents((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
124 stream.close();
125
126 auto model = parser.CreateNetworkFromBinary(contents);
127 contents.clear();
128 ARMNN_LOG(debug) << "Model loaded ok: " << filename;
129
130 // Optimize backbone model
Narumol Prangnawarata2493a02020-08-19 14:39:07 +0100131 OptimizerOptions options;
Derek Lambertifbab30b2020-09-17 13:58:18 +0100132 options.m_ImportEnabled = enableImport != ImportMemory::False;
Narumol Prangnawarata2493a02020-08-19 14:39:07 +0100133 auto optimizedModel = Optimize(*model, backendPreferences, runtime.GetDeviceSpec(), options);
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100134 if (!optimizedModel)
135 {
136 ARMNN_LOG(fatal) << "Could not optimize the model:" << filename;
137 return OPTIMIZE_NETWORK_ERROR;
138 }
139
Derek Lambertifbab30b2020-09-17 13:58:18 +0100140 if (dumpToDot != DumpToDot::False)
141 {
142 std::stringstream ss;
143 ss << filename << ".dot";
144 std::ofstream dotStream(ss.str().c_str(), std::ofstream::out);
145 optimizedModel->SerializeToDot(dotStream);
146 dotStream.close();
147 }
Narumol Prangnawaratef6f3002020-08-17 17:02:12 +0100148 // Load model into runtime
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100149 {
150 std::string errorMessage;
Francis Murtagh73d3e2e2021-04-29 14:23:04 +0100151
152 armnn::MemorySource memSource = options.m_ImportEnabled ? armnn::MemorySource::Malloc
153 : armnn::MemorySource::Undefined;
154 INetworkProperties modelProps(false, memSource, memSource);
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100155 Status status = runtime.LoadNetwork(networkId, std::move(optimizedModel), errorMessage, modelProps);
156 if (status != Status::Success)
157 {
158 ARMNN_LOG(fatal) << "Could not load " << filename << " model into runtime: " << errorMessage;
159 return LOAD_NETWORK_ERROR;
160 }
161 }
162
163 return 0;
164}
165
166std::vector<float> LoadImage(const char* filename)
167{
Derek Lambertifbab30b2020-09-17 13:58:18 +0100168 if (strlen(filename) == 0)
169 {
170 return std::vector<float>(1920*10180*3, 0.0f);
171 }
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100172 struct Memory
173 {
174 ~Memory() {stbi_image_free(m_Data);}
175 bool IsLoaded() const { return m_Data != nullptr;}
176
177 unsigned char* m_Data;
178 };
179
180 std::vector<float> image;
181
182 int width;
183 int height;
184 int channels;
185
186 Memory mem = {stbi_load(filename, &width, &height, &channels, 3)};
187 if (!mem.IsLoaded())
188 {
189 ARMNN_LOG(error) << "Could not load input image file: " << filename;
190 return image;
191 }
192
193 if (width != 1920 || height != 1080 || channels != 3)
194 {
195 ARMNN_LOG(error) << "Input image has wong dimension: " << width << "x" << height << "x" << channels << ". "
196 " Expected 1920x1080x3.";
197 return image;
198 }
199
200 image.resize(1920*1080*3);
201
202 // Expand to float. Does this need de-gamma?
203 for (unsigned int idx=0; idx <= 1920*1080*3; idx++)
204 {
205 image[idx] = static_cast<float>(mem.m_Data[idx]) /255.0f;
206 }
207
208 return image;
209}
210
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100211
Derek Lambertifbab30b2020-09-17 13:58:18 +0100212bool ValidateFilePath(std::string& file, ExpectFile expectFile)
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100213{
214 if (!ghc::filesystem::exists(file))
215 {
216 std::cerr << "Given file path " << file << " does not exist" << std::endl;
217 return false;
218 }
Derek Lambertifbab30b2020-09-17 13:58:18 +0100219 if (!ghc::filesystem::is_regular_file(file) && expectFile == ExpectFile::True)
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100220 {
221 std::cerr << "Given file path " << file << " is not a regular file" << std::endl;
222 return false;
223 }
224 return true;
225}
226
Ryan OShea74af0932020-08-07 16:27:34 +0100227void CheckAccuracy(std::vector<float>* toDetector0, std::vector<float>* toDetector1,
228 std::vector<float>* toDetector2, std::vector<float>* detectorOutput,
229 const std::vector<yolov3::Detection>& nmsOut, const std::vector<std::string>& filePaths)
230{
231 std::ifstream pathStream;
232 std::vector<float> expected;
233 std::vector<std::vector<float>*> outputs;
234 float compare = 0;
235 unsigned int count = 0;
236
237 //Push back output vectors from inference for use in loop
238 outputs.push_back(toDetector0);
239 outputs.push_back(toDetector1);
240 outputs.push_back(toDetector2);
241 outputs.push_back(detectorOutput);
242
243 for (unsigned int i = 0; i < outputs.size(); ++i)
244 {
245 // Reading expected output files and assigning them to @expected. Close and Clear to reuse stream and clean RAM
246 pathStream.open(filePaths[i]);
247 if (!pathStream.is_open())
248 {
249 ARMNN_LOG(error) << "Expected output file can not be opened: " << filePaths[i];
250 continue;
251 }
252
253 expected.assign(std::istream_iterator<float>(pathStream), {});
254 pathStream.close();
255 pathStream.clear();
256
257 // Ensure each vector is the same length
258 if (expected.size() != outputs[i]->size())
259 {
260 ARMNN_LOG(error) << "Expected output size does not match actual output size: " << filePaths[i];
261 }
262 else
263 {
264 count = 0;
265
266 // Compare abs(difference) with tolerance to check for value by value equality
267 for (unsigned int j = 0; j < outputs[i]->size(); ++j)
268 {
Keith Mok16fb1a22021-03-19 08:58:44 -0700269 compare = std::abs(expected[j] - outputs[i]->at(j));
Ryan OShea74af0932020-08-07 16:27:34 +0100270 if (compare > 0.001f)
271 {
272 count++;
273 }
274 }
275 if (count > 0)
276 {
277 ARMNN_LOG(error) << count << " output(s) do not match expected values in: " << filePaths[i];
278 }
279 }
280 }
281
282 pathStream.open(filePaths[4]);
283 if (!pathStream.is_open())
284 {
285 ARMNN_LOG(error) << "Expected output file can not be opened: " << filePaths[4];
286 }
287 else
288 {
289 expected.assign(std::istream_iterator<float>(pathStream), {});
290 pathStream.close();
291 pathStream.clear();
292 unsigned int y = 0;
293 unsigned int numOfMember = 6;
294 std::vector<float> intermediate;
295
296 for (auto& detection: nmsOut)
297 {
298 for (unsigned int x = y * numOfMember; x < ((y * numOfMember) + numOfMember); ++x)
299 {
300 intermediate.push_back(expected[x]);
301 }
302 if (!yolov3::compare_detection(detection, intermediate))
303 {
304 ARMNN_LOG(error) << "Expected NMS output does not match: Detection " << y + 1;
305 }
306 intermediate.clear();
307 y++;
308 }
309 }
310}
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100311
312struct ParseArgs
313{
314 ParseArgs(int ac, char *av[]) : options{"TfLiteYoloV3Big-Armnn",
315 "Executes YoloV3Big using ArmNN. YoloV3Big consists "
316 "of 3 parts: A backbone TfLite model, a detector TfLite "
317 "model, and None Maximum Suppression. All parts are "
318 "executed successively."}
319 {
320 options.add_options()
321 ("b,backbone-path",
322 "File path where the TfLite model for the yoloV3big backbone "
323 "can be found e.g. mydir/yoloV3big_backbone.tflite",
324 cxxopts::value<std::string>())
325
Ryan OShea74af0932020-08-07 16:27:34 +0100326 ("c,comparison-files",
327 "Defines the expected outputs for the model "
328 "of yoloV3big e.g. 'mydir/file1.txt,mydir/file2.txt,mydir/file3.txt,mydir/file4.txt'->InputToDetector1"
329 " will be tried first then InputToDetector2 then InputToDetector3 then the Detector Output and finally"
330 " the NMS output. NOTE: Files are passed as comma separated list without whitespaces.",
Derek Lambertifa4c1d12020-12-07 13:56:40 +0000331 cxxopts::value<std::vector<std::string>>()->default_value({}))
Ryan OShea74af0932020-08-07 16:27:34 +0100332
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100333 ("d,detector-path",
334 "File path where the TfLite model for the yoloV3big "
335 "detector can be found e.g.'mydir/yoloV3big_detector.tflite'",
336 cxxopts::value<std::string>())
337
338 ("h,help", "Produce help message")
339
340 ("i,image-path",
341 "File path to a 1080x1920 jpg image that should be "
342 "processed e.g. 'mydir/example_img_180_1920.jpg'",
343 cxxopts::value<std::string>())
344
345 ("B,preferred-backends-backbone",
346 "Defines the preferred backends to run the backbone model "
347 "of yoloV3big e.g. 'GpuAcc,CpuRef' -> GpuAcc will be tried "
348 "first before falling back to CpuRef. NOTE: Backends are passed "
349 "as comma separated list without whitespaces.",
350 cxxopts::value<std::vector<std::string>>()->default_value("GpuAcc,CpuRef"))
351
352 ("D,preferred-backends-detector",
353 "Defines the preferred backends to run the detector model "
354 "of yoloV3big e.g. 'CpuAcc,CpuRef' -> CpuAcc will be tried "
355 "first before falling back to CpuRef. NOTE: Backends are passed "
356 "as comma separated list without whitespaces.",
Derek Lambertifbab30b2020-09-17 13:58:18 +0100357 cxxopts::value<std::vector<std::string>>()->default_value("CpuAcc,CpuRef"))
358
359 ("M, model-to-dot",
360 "Dump the optimized model to a dot file for debugging/analysis",
361 cxxopts::value<bool>()->default_value("false"))
362
363 ("Y, dynamic-backends-path",
364 "Define a path from which to load any dynamic backends.",
365 cxxopts::value<std::string>());
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100366
367 auto result = options.parse(ac, av);
368
369 if (result.count("help"))
370 {
371 std::cout << options.help() << "\n";
372 exit(EXIT_SUCCESS);
373 }
374
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100375
Derek Lambertifbab30b2020-09-17 13:58:18 +0100376 backboneDir = GetPathArgument(result, "backbone-path", ExpectFile::True, OptionalArg::False);
Ryan OShea74af0932020-08-07 16:27:34 +0100377
Derek Lambertifbab30b2020-09-17 13:58:18 +0100378 comparisonFiles = GetPathArgument(result["comparison-files"].as<std::vector<std::string>>(), OptionalArg::True);
379
380 detectorDir = GetPathArgument(result, "detector-path", ExpectFile::True, OptionalArg::False);
381
382 imageDir = GetPathArgument(result, "image-path", ExpectFile::True, OptionalArg::True);
383
384 dynamicBackendPath = GetPathArgument(result, "dynamic-backends-path", ExpectFile::False, OptionalArg::True);
Ryan OShea74af0932020-08-07 16:27:34 +0100385
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100386 prefBackendsBackbone = GetBackendIDs(result["preferred-backends-backbone"].as<std::vector<std::string>>());
387 LogBackendsInfo(prefBackendsBackbone, "Backbone");
388 prefBackendsDetector = GetBackendIDs(result["preferred-backends-detector"].as<std::vector<std::string>>());
389 LogBackendsInfo(prefBackendsDetector, "detector");
Derek Lambertifbab30b2020-09-17 13:58:18 +0100390
391 dumpToDot = result["model-to-dot"].as<bool>() ? DumpToDot::True : DumpToDot::False;
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100392 }
393
394 /// Takes a vector of backend strings and returns a vector of backendIDs
395 std::vector<BackendId> GetBackendIDs(const std::vector<std::string>& backendStrings)
396 {
397 std::vector<BackendId> backendIDs;
398 for (const auto& b : backendStrings)
399 {
400 backendIDs.push_back(BackendId(b));
401 }
402 return backendIDs;
403 }
404
405 /// Verifies if the program argument with the name argName contains a valid file path.
406 /// Returns the valid file path string if given argument is associated a valid file path.
407 /// Otherwise throws an exception.
Derek Lambertifbab30b2020-09-17 13:58:18 +0100408 std::string GetPathArgument(cxxopts::ParseResult& result,
409 std::string&& argName,
410 ExpectFile expectFile,
411 OptionalArg isOptionalArg)
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100412 {
413 if (result.count(argName))
414 {
Derek Lambertifbab30b2020-09-17 13:58:18 +0100415 std::string path = result[argName].as<std::string>();
416 if (!ValidateFilePath(path, expectFile))
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100417 {
Derek Lambertifbab30b2020-09-17 13:58:18 +0100418 std::stringstream ss;
419 ss << "Argument given to" << argName << "is not a valid file path";
420 throw cxxopts::option_syntax_exception(ss.str().c_str());
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100421 }
Derek Lambertifbab30b2020-09-17 13:58:18 +0100422 return path;
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100423 }
424 else
425 {
Derek Lambertifbab30b2020-09-17 13:58:18 +0100426 if (isOptionalArg == OptionalArg::True)
427 {
428 return "";
429 }
430
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100431 throw cxxopts::missing_argument_exception(argName);
432 }
433 }
434
Ryan OShea74af0932020-08-07 16:27:34 +0100435 /// Assigns vector of strings to struct member variable
Derek Lambertifbab30b2020-09-17 13:58:18 +0100436 std::vector<std::string> GetPathArgument(const std::vector<std::string>& pathStrings, OptionalArg isOptional)
Ryan OShea74af0932020-08-07 16:27:34 +0100437 {
438 if (pathStrings.size() < 5){
Derek Lambertifbab30b2020-09-17 13:58:18 +0100439 if (isOptional == OptionalArg::True)
440 {
441 return std::vector<std::string>();
442 }
Ryan OShea74af0932020-08-07 16:27:34 +0100443 throw cxxopts::option_syntax_exception("Comparison files requires 5 file paths.");
444 }
445
446 std::vector<std::string> filePaths;
447 for (auto& path : pathStrings)
448 {
449 filePaths.push_back(path);
Derek Lambertifbab30b2020-09-17 13:58:18 +0100450 if (!ValidateFilePath(filePaths.back(), ExpectFile::True))
Ryan OShea74af0932020-08-07 16:27:34 +0100451 {
452 throw cxxopts::option_syntax_exception("Argument given to Comparison Files is not a valid file path");
453 }
454 }
455 return filePaths;
456 }
457
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100458 /// Log info about assigned backends
459 void LogBackendsInfo(std::vector<BackendId>& backends, std::string&& modelName)
460 {
461 std::string info;
462 info = "Preferred backends for " + modelName + " set to [ ";
463 for (auto const &backend : backends)
464 {
465 info = info + std::string(backend) + " ";
466 }
467 ARMNN_LOG(info) << info << "]";
468 }
469
470 // Member variables
471 std::string backboneDir;
Ryan OShea74af0932020-08-07 16:27:34 +0100472 std::vector<std::string> comparisonFiles;
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100473 std::string detectorDir;
474 std::string imageDir;
Derek Lambertifbab30b2020-09-17 13:58:18 +0100475 std::string dynamicBackendPath;
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100476
477 std::vector<BackendId> prefBackendsBackbone;
478 std::vector<BackendId> prefBackendsDetector;
479
480 cxxopts::Options options;
Derek Lambertifbab30b2020-09-17 13:58:18 +0100481
482 DumpToDot dumpToDot;
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100483};
484
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100485int main(int argc, char* argv[])
486{
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100487 // Configure logging
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100488 SetAllLoggingSinks(true, true, true);
489 SetLogFilter(LogSeverity::Trace);
490
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100491 // Check and get given program arguments
492 ParseArgs progArgs = ParseArgs(argc, argv);
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100493
494 // Create runtime
495 IRuntime::CreationOptions runtimeOptions; // default
Derek Lambertifbab30b2020-09-17 13:58:18 +0100496
497 if (!progArgs.dynamicBackendPath.empty())
498 {
499 std::cout << "Loading backends from" << progArgs.dynamicBackendPath << "\n";
500 runtimeOptions.m_DynamicBackendsPath = progArgs.dynamicBackendPath;
501 }
502
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100503 auto runtime = IRuntime::Create(runtimeOptions);
504 if (!runtime)
505 {
506 ARMNN_LOG(fatal) << "Could not create runtime.";
507 return -1;
508 }
509
510 // Create TfLite Parsers
511 ITfLiteParser::TfLiteParserOptions parserOptions;
512 auto parser = ITfLiteParser::Create(parserOptions);
513
514 // Load backbone model
515 ARMNN_LOG(info) << "Loading backbone...";
516 NetworkId backboneId;
Derek Lambertifbab30b2020-09-17 13:58:18 +0100517 const DumpToDot dumpToDot = progArgs.dumpToDot;
518 CHECK_OK(LoadModel(progArgs.backboneDir.c_str(),
519 *parser,
520 *runtime,
521 backboneId,
522 progArgs.prefBackendsBackbone,
523 ImportMemory::False,
524 dumpToDot));
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100525 auto inputId = parser->GetNetworkInputBindingInfo(0, "inputs");
526 auto bbOut0Id = parser->GetNetworkOutputBindingInfo(0, "input_to_detector_1");
527 auto bbOut1Id = parser->GetNetworkOutputBindingInfo(0, "input_to_detector_2");
528 auto bbOut2Id = parser->GetNetworkOutputBindingInfo(0, "input_to_detector_3");
529 auto backboneProfile = runtime->GetProfiler(backboneId);
530 backboneProfile->EnableProfiling(true);
531
Derek Lambertifbab30b2020-09-17 13:58:18 +0100532
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100533 // Load detector model
534 ARMNN_LOG(info) << "Loading detector...";
535 NetworkId detectorId;
Derek Lambertifbab30b2020-09-17 13:58:18 +0100536 CHECK_OK(LoadModel(progArgs.detectorDir.c_str(),
537 *parser,
538 *runtime,
539 detectorId,
540 progArgs.prefBackendsDetector,
541 ImportMemory::True,
542 dumpToDot));
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100543 auto detectIn0Id = parser->GetNetworkInputBindingInfo(0, "input_to_detector_1");
544 auto detectIn1Id = parser->GetNetworkInputBindingInfo(0, "input_to_detector_2");
545 auto detectIn2Id = parser->GetNetworkInputBindingInfo(0, "input_to_detector_3");
546 auto outputBoxesId = parser->GetNetworkOutputBindingInfo(0, "output_boxes");
547 auto detectorProfile = runtime->GetProfiler(detectorId);
548
549 // Load input from file
550 ARMNN_LOG(info) << "Loading test image...";
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100551 auto image = LoadImage(progArgs.imageDir.c_str());
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100552 if (image.empty())
553 {
554 return LOAD_IMAGE_ERROR;
555 }
556
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100557 // Allocate the intermediate tensors
558 std::vector<float> intermediateMem0(bbOut0Id.second.GetNumElements());
559 std::vector<float> intermediateMem1(bbOut1Id.second.GetNumElements());
560 std::vector<float> intermediateMem2(bbOut2Id.second.GetNumElements());
561 std::vector<float> intermediateMem3(outputBoxesId.second.GetNumElements());
562
563 // Setup inputs and outputs
564 using BindingInfos = std::vector<armnn::BindingPointInfo>;
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +0100565 using FloatTensors = std::vector<std::reference_wrapper<std::vector<float>>>;
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100566
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +0100567 InputTensors bbInputTensors = MakeInputTensors(BindingInfos{ inputId },
568 FloatTensors{ image });
569 OutputTensors bbOutputTensors = MakeOutputTensors(BindingInfos{ bbOut0Id, bbOut1Id, bbOut2Id },
570 FloatTensors{ intermediateMem0,
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100571 intermediateMem1,
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +0100572 intermediateMem2 });
573 InputTensors detectInputTensors = MakeInputTensors(BindingInfos{ detectIn0Id,
574 detectIn1Id,
575 detectIn2Id } ,
576 FloatTensors{ intermediateMem0,
577 intermediateMem1,
578 intermediateMem2 });
579 OutputTensors detectOutputTensors = MakeOutputTensors(BindingInfos{ outputBoxesId },
580 FloatTensors{ intermediateMem3 });
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100581
582 static const int numIterations=2;
583 using DurationUS = std::chrono::duration<double, std::micro>;
584 std::vector<DurationUS> nmsDurations(0);
Ryan OShea74af0932020-08-07 16:27:34 +0100585 std::vector<yolov3::Detection> filtered_boxes;
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100586 nmsDurations.reserve(numIterations);
587 for (int i=0; i < numIterations; i++)
588 {
589 // Execute backbone
590 ARMNN_LOG(info) << "Running backbone...";
591 runtime->EnqueueWorkload(backboneId, bbInputTensors, bbOutputTensors);
592
593 // Execute detector
594 ARMNN_LOG(info) << "Running detector...";
595 runtime->EnqueueWorkload(detectorId, detectInputTensors, detectOutputTensors);
596
597 // Execute NMS
598 ARMNN_LOG(info) << "Running nms...";
599 using clock = std::chrono::steady_clock;
600 auto nmsStartTime = clock::now();
601 yolov3::NMSConfig config;
602 config.num_boxes = 127800;
603 config.num_classes = 80;
604 config.confidence_threshold = 0.9f;
605 config.iou_threshold = 0.5f;
Ryan OShea74af0932020-08-07 16:27:34 +0100606 filtered_boxes = yolov3::nms(config, intermediateMem3);
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100607 auto nmsEndTime = clock::now();
608
609 // Enable the profiling after the warm-up run
610 if (i>0)
611 {
612 print_detection(std::cout, filtered_boxes);
613
614 const auto nmsDuration = DurationUS(nmsStartTime - nmsEndTime);
615 nmsDurations.push_back(nmsDuration);
616 }
617 backboneProfile->EnableProfiling(true);
618 detectorProfile->EnableProfiling(true);
619 }
620 // Log timings to file
621 std::ofstream backboneProfileStream("backbone.json");
622 backboneProfile->Print(backboneProfileStream);
623 backboneProfileStream.close();
624
625 std::ofstream detectorProfileStream("detector.json");
626 detectorProfile->Print(detectorProfileStream);
627 detectorProfileStream.close();
628
629 // Manually construct the json output
630 std::ofstream nmsProfileStream("nms.json");
631 nmsProfileStream << "{" << "\n";
632 nmsProfileStream << R"( "NmsTimings": {)" << "\n";
633 nmsProfileStream << R"( "raw": [)" << "\n";
634 bool isFirst = true;
635 for (auto duration : nmsDurations)
636 {
637 if (!isFirst)
638 {
639 nmsProfileStream << ",\n";
640 }
641
642 nmsProfileStream << " " << duration.count();
643 isFirst = false;
644 }
645 nmsProfileStream << "\n";
646 nmsProfileStream << R"( "units": "us")" << "\n";
647 nmsProfileStream << " ]" << "\n";
648 nmsProfileStream << " }" << "\n";
649 nmsProfileStream << "}" << "\n";
650 nmsProfileStream.close();
651
Derek Lambertifbab30b2020-09-17 13:58:18 +0100652 if (progArgs.comparisonFiles.size() > 0)
653 {
654 CheckAccuracy(&intermediateMem0,
655 &intermediateMem1,
656 &intermediateMem2,
657 &intermediateMem3,
658 filtered_boxes,
659 progArgs.comparisonFiles);
660 }
Ryan OShea74af0932020-08-07 16:27:34 +0100661
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100662 ARMNN_LOG(info) << "Run completed";
663 return 0;
Keith Mok16fb1a22021-03-19 08:58:44 -0700664}