blob: 3453fdd70bf7855608305f94a3162a77029c9344 [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>
Keith Mok16fb1a22021-03-19 08:58:44 -070023#include <cmath>
Derek Lambertid6cb30e2020-04-28 13:31:29 +010024
25using namespace armnnTfLiteParser;
26using namespace armnn;
27
28static const int OPEN_FILE_ERROR = -2;
29static const int OPTIMIZE_NETWORK_ERROR = -3;
30static const int LOAD_NETWORK_ERROR = -4;
31static const int LOAD_IMAGE_ERROR = -5;
32static const int GENERAL_ERROR = -100;
33
Pavel Macenauer855a47b2020-05-26 10:54:22 +000034#define CHECK_OK(v) \
35 do { \
36 try { \
37 auto r_local = v; \
38 if (r_local != 0) { return r_local;} \
39 } \
40 catch (const armnn::Exception& e) \
41 { \
42 ARMNN_LOG(error) << "Oops: " << e.what(); \
43 return GENERAL_ERROR; \
44 } \
Derek Lambertid6cb30e2020-04-28 13:31:29 +010045 } while(0)
46
47
48
49template<typename TContainer>
50inline armnn::InputTensors MakeInputTensors(const std::vector<armnn::BindingPointInfo>& inputBindings,
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +010051 const std::vector<std::reference_wrapper<TContainer>>& inputDataContainers)
Derek Lambertid6cb30e2020-04-28 13:31:29 +010052{
53 armnn::InputTensors inputTensors;
54
55 const size_t numInputs = inputBindings.size();
56 if (numInputs != inputDataContainers.size())
57 {
58 throw armnn::Exception("Mismatching vectors");
59 }
60
61 for (size_t i = 0; i < numInputs; i++)
62 {
63 const armnn::BindingPointInfo& inputBinding = inputBindings[i];
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +010064 const TContainer& inputData = inputDataContainers[i].get();
Derek Lambertid6cb30e2020-04-28 13:31:29 +010065
66 armnn::ConstTensor inputTensor(inputBinding.second, inputData.data());
67 inputTensors.push_back(std::make_pair(inputBinding.first, inputTensor));
68 }
69
70 return inputTensors;
71}
72
73template<typename TContainer>
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +010074inline armnn::OutputTensors MakeOutputTensors(
75 const std::vector<armnn::BindingPointInfo>& outputBindings,
76 const std::vector<std::reference_wrapper<TContainer>>& outputDataContainers)
Derek Lambertid6cb30e2020-04-28 13:31:29 +010077{
78 armnn::OutputTensors outputTensors;
79
80 const size_t numOutputs = outputBindings.size();
81 if (numOutputs != outputDataContainers.size())
82 {
83 throw armnn::Exception("Mismatching vectors");
84 }
85
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +010086 outputTensors.reserve(numOutputs);
87
Derek Lambertid6cb30e2020-04-28 13:31:29 +010088 for (size_t i = 0; i < numOutputs; i++)
89 {
90 const armnn::BindingPointInfo& outputBinding = outputBindings[i];
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +010091 const TContainer& outputData = outputDataContainers[i].get();
Derek Lambertid6cb30e2020-04-28 13:31:29 +010092
93 armnn::Tensor outputTensor(outputBinding.second, const_cast<float*>(outputData.data()));
94 outputTensors.push_back(std::make_pair(outputBinding.first, outputTensor));
95 }
96
97 return outputTensors;
98}
99
Derek Lambertifbab30b2020-09-17 13:58:18 +0100100#define S_BOOL(name) enum class name {False=0, True=1};
101
102S_BOOL(ImportMemory)
103S_BOOL(DumpToDot)
104S_BOOL(ExpectFile)
105S_BOOL(OptionalArg)
106
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100107int LoadModel(const char* filename,
108 ITfLiteParser& parser,
109 IRuntime& runtime,
110 NetworkId& networkId,
Narumol Prangnawaratef6f3002020-08-17 17:02:12 +0100111 const std::vector<BackendId>& backendPreferences,
Derek Lambertifbab30b2020-09-17 13:58:18 +0100112 ImportMemory enableImport,
113 DumpToDot dumpToDot)
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100114{
115 std::ifstream stream(filename, std::ios::in | std::ios::binary);
116 if (!stream.is_open())
117 {
118 ARMNN_LOG(error) << "Could not open model: " << filename;
119 return OPEN_FILE_ERROR;
120 }
121
122 std::vector<uint8_t> contents((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
123 stream.close();
124
125 auto model = parser.CreateNetworkFromBinary(contents);
126 contents.clear();
127 ARMNN_LOG(debug) << "Model loaded ok: " << filename;
128
129 // Optimize backbone model
Narumol Prangnawarata2493a02020-08-19 14:39:07 +0100130 OptimizerOptions options;
Derek Lambertifbab30b2020-09-17 13:58:18 +0100131 options.m_ImportEnabled = enableImport != ImportMemory::False;
Narumol Prangnawarata2493a02020-08-19 14:39:07 +0100132 auto optimizedModel = Optimize(*model, backendPreferences, runtime.GetDeviceSpec(), options);
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100133 if (!optimizedModel)
134 {
135 ARMNN_LOG(fatal) << "Could not optimize the model:" << filename;
136 return OPTIMIZE_NETWORK_ERROR;
137 }
138
Derek Lambertifbab30b2020-09-17 13:58:18 +0100139 if (dumpToDot != DumpToDot::False)
140 {
141 std::stringstream ss;
142 ss << filename << ".dot";
143 std::ofstream dotStream(ss.str().c_str(), std::ofstream::out);
144 optimizedModel->SerializeToDot(dotStream);
145 dotStream.close();
146 }
Narumol Prangnawaratef6f3002020-08-17 17:02:12 +0100147 // Load model into runtime
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100148 {
149 std::string errorMessage;
Francis Murtagh73d3e2e2021-04-29 14:23:04 +0100150
151 armnn::MemorySource memSource = options.m_ImportEnabled ? armnn::MemorySource::Malloc
152 : armnn::MemorySource::Undefined;
153 INetworkProperties modelProps(false, memSource, memSource);
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100154 Status status = runtime.LoadNetwork(networkId, std::move(optimizedModel), errorMessage, modelProps);
155 if (status != Status::Success)
156 {
157 ARMNN_LOG(fatal) << "Could not load " << filename << " model into runtime: " << errorMessage;
158 return LOAD_NETWORK_ERROR;
159 }
160 }
161
162 return 0;
163}
164
165std::vector<float> LoadImage(const char* filename)
166{
Derek Lambertifbab30b2020-09-17 13:58:18 +0100167 if (strlen(filename) == 0)
168 {
169 return std::vector<float>(1920*10180*3, 0.0f);
170 }
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100171 struct Memory
172 {
173 ~Memory() {stbi_image_free(m_Data);}
174 bool IsLoaded() const { return m_Data != nullptr;}
175
176 unsigned char* m_Data;
177 };
178
179 std::vector<float> image;
180
181 int width;
182 int height;
183 int channels;
184
185 Memory mem = {stbi_load(filename, &width, &height, &channels, 3)};
186 if (!mem.IsLoaded())
187 {
188 ARMNN_LOG(error) << "Could not load input image file: " << filename;
189 return image;
190 }
191
192 if (width != 1920 || height != 1080 || channels != 3)
193 {
194 ARMNN_LOG(error) << "Input image has wong dimension: " << width << "x" << height << "x" << channels << ". "
195 " Expected 1920x1080x3.";
196 return image;
197 }
198
199 image.resize(1920*1080*3);
200
201 // Expand to float. Does this need de-gamma?
202 for (unsigned int idx=0; idx <= 1920*1080*3; idx++)
203 {
204 image[idx] = static_cast<float>(mem.m_Data[idx]) /255.0f;
205 }
206
207 return image;
208}
209
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100210
Derek Lambertifbab30b2020-09-17 13:58:18 +0100211bool ValidateFilePath(std::string& file, ExpectFile expectFile)
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100212{
213 if (!ghc::filesystem::exists(file))
214 {
215 std::cerr << "Given file path " << file << " does not exist" << std::endl;
216 return false;
217 }
Derek Lambertifbab30b2020-09-17 13:58:18 +0100218 if (!ghc::filesystem::is_regular_file(file) && expectFile == ExpectFile::True)
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100219 {
220 std::cerr << "Given file path " << file << " is not a regular file" << std::endl;
221 return false;
222 }
223 return true;
224}
225
Ryan OShea74af0932020-08-07 16:27:34 +0100226void CheckAccuracy(std::vector<float>* toDetector0, std::vector<float>* toDetector1,
227 std::vector<float>* toDetector2, std::vector<float>* detectorOutput,
228 const std::vector<yolov3::Detection>& nmsOut, const std::vector<std::string>& filePaths)
229{
230 std::ifstream pathStream;
231 std::vector<float> expected;
232 std::vector<std::vector<float>*> outputs;
233 float compare = 0;
234 unsigned int count = 0;
235
236 //Push back output vectors from inference for use in loop
237 outputs.push_back(toDetector0);
238 outputs.push_back(toDetector1);
239 outputs.push_back(toDetector2);
240 outputs.push_back(detectorOutput);
241
242 for (unsigned int i = 0; i < outputs.size(); ++i)
243 {
244 // Reading expected output files and assigning them to @expected. Close and Clear to reuse stream and clean RAM
245 pathStream.open(filePaths[i]);
246 if (!pathStream.is_open())
247 {
248 ARMNN_LOG(error) << "Expected output file can not be opened: " << filePaths[i];
249 continue;
250 }
251
252 expected.assign(std::istream_iterator<float>(pathStream), {});
253 pathStream.close();
254 pathStream.clear();
255
256 // Ensure each vector is the same length
257 if (expected.size() != outputs[i]->size())
258 {
259 ARMNN_LOG(error) << "Expected output size does not match actual output size: " << filePaths[i];
260 }
261 else
262 {
263 count = 0;
264
265 // Compare abs(difference) with tolerance to check for value by value equality
266 for (unsigned int j = 0; j < outputs[i]->size(); ++j)
267 {
Keith Mok16fb1a22021-03-19 08:58:44 -0700268 compare = std::abs(expected[j] - outputs[i]->at(j));
Ryan OShea74af0932020-08-07 16:27:34 +0100269 if (compare > 0.001f)
270 {
271 count++;
272 }
273 }
274 if (count > 0)
275 {
276 ARMNN_LOG(error) << count << " output(s) do not match expected values in: " << filePaths[i];
277 }
278 }
279 }
280
281 pathStream.open(filePaths[4]);
282 if (!pathStream.is_open())
283 {
284 ARMNN_LOG(error) << "Expected output file can not be opened: " << filePaths[4];
285 }
286 else
287 {
288 expected.assign(std::istream_iterator<float>(pathStream), {});
289 pathStream.close();
290 pathStream.clear();
291 unsigned int y = 0;
292 unsigned int numOfMember = 6;
293 std::vector<float> intermediate;
294
295 for (auto& detection: nmsOut)
296 {
297 for (unsigned int x = y * numOfMember; x < ((y * numOfMember) + numOfMember); ++x)
298 {
299 intermediate.push_back(expected[x]);
300 }
301 if (!yolov3::compare_detection(detection, intermediate))
302 {
303 ARMNN_LOG(error) << "Expected NMS output does not match: Detection " << y + 1;
304 }
305 intermediate.clear();
306 y++;
307 }
308 }
309}
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100310
311struct ParseArgs
312{
313 ParseArgs(int ac, char *av[]) : options{"TfLiteYoloV3Big-Armnn",
314 "Executes YoloV3Big using ArmNN. YoloV3Big consists "
315 "of 3 parts: A backbone TfLite model, a detector TfLite "
316 "model, and None Maximum Suppression. All parts are "
317 "executed successively."}
318 {
319 options.add_options()
320 ("b,backbone-path",
321 "File path where the TfLite model for the yoloV3big backbone "
322 "can be found e.g. mydir/yoloV3big_backbone.tflite",
323 cxxopts::value<std::string>())
324
Ryan OShea74af0932020-08-07 16:27:34 +0100325 ("c,comparison-files",
326 "Defines the expected outputs for the model "
327 "of yoloV3big e.g. 'mydir/file1.txt,mydir/file2.txt,mydir/file3.txt,mydir/file4.txt'->InputToDetector1"
328 " will be tried first then InputToDetector2 then InputToDetector3 then the Detector Output and finally"
329 " the NMS output. NOTE: Files are passed as comma separated list without whitespaces.",
Derek Lambertifa4c1d12020-12-07 13:56:40 +0000330 cxxopts::value<std::vector<std::string>>()->default_value({}))
Ryan OShea74af0932020-08-07 16:27:34 +0100331
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100332 ("d,detector-path",
333 "File path where the TfLite model for the yoloV3big "
334 "detector can be found e.g.'mydir/yoloV3big_detector.tflite'",
335 cxxopts::value<std::string>())
336
337 ("h,help", "Produce help message")
338
339 ("i,image-path",
340 "File path to a 1080x1920 jpg image that should be "
341 "processed e.g. 'mydir/example_img_180_1920.jpg'",
342 cxxopts::value<std::string>())
343
344 ("B,preferred-backends-backbone",
345 "Defines the preferred backends to run the backbone model "
346 "of yoloV3big e.g. 'GpuAcc,CpuRef' -> GpuAcc will be tried "
347 "first before falling back to CpuRef. NOTE: Backends are passed "
348 "as comma separated list without whitespaces.",
349 cxxopts::value<std::vector<std::string>>()->default_value("GpuAcc,CpuRef"))
350
351 ("D,preferred-backends-detector",
352 "Defines the preferred backends to run the detector model "
353 "of yoloV3big e.g. 'CpuAcc,CpuRef' -> CpuAcc will be tried "
354 "first before falling back to CpuRef. NOTE: Backends are passed "
355 "as comma separated list without whitespaces.",
Derek Lambertifbab30b2020-09-17 13:58:18 +0100356 cxxopts::value<std::vector<std::string>>()->default_value("CpuAcc,CpuRef"))
357
358 ("M, model-to-dot",
359 "Dump the optimized model to a dot file for debugging/analysis",
360 cxxopts::value<bool>()->default_value("false"))
361
362 ("Y, dynamic-backends-path",
363 "Define a path from which to load any dynamic backends.",
364 cxxopts::value<std::string>());
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100365
366 auto result = options.parse(ac, av);
367
368 if (result.count("help"))
369 {
370 std::cout << options.help() << "\n";
371 exit(EXIT_SUCCESS);
372 }
373
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100374
Derek Lambertifbab30b2020-09-17 13:58:18 +0100375 backboneDir = GetPathArgument(result, "backbone-path", ExpectFile::True, OptionalArg::False);
Ryan OShea74af0932020-08-07 16:27:34 +0100376
Derek Lambertifbab30b2020-09-17 13:58:18 +0100377 comparisonFiles = GetPathArgument(result["comparison-files"].as<std::vector<std::string>>(), OptionalArg::True);
378
379 detectorDir = GetPathArgument(result, "detector-path", ExpectFile::True, OptionalArg::False);
380
381 imageDir = GetPathArgument(result, "image-path", ExpectFile::True, OptionalArg::True);
382
383 dynamicBackendPath = GetPathArgument(result, "dynamic-backends-path", ExpectFile::False, OptionalArg::True);
Ryan OShea74af0932020-08-07 16:27:34 +0100384
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100385 prefBackendsBackbone = GetBackendIDs(result["preferred-backends-backbone"].as<std::vector<std::string>>());
386 LogBackendsInfo(prefBackendsBackbone, "Backbone");
387 prefBackendsDetector = GetBackendIDs(result["preferred-backends-detector"].as<std::vector<std::string>>());
388 LogBackendsInfo(prefBackendsDetector, "detector");
Derek Lambertifbab30b2020-09-17 13:58:18 +0100389
390 dumpToDot = result["model-to-dot"].as<bool>() ? DumpToDot::True : DumpToDot::False;
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100391 }
392
393 /// Takes a vector of backend strings and returns a vector of backendIDs
394 std::vector<BackendId> GetBackendIDs(const std::vector<std::string>& backendStrings)
395 {
396 std::vector<BackendId> backendIDs;
397 for (const auto& b : backendStrings)
398 {
399 backendIDs.push_back(BackendId(b));
400 }
401 return backendIDs;
402 }
403
404 /// Verifies if the program argument with the name argName contains a valid file path.
405 /// Returns the valid file path string if given argument is associated a valid file path.
406 /// Otherwise throws an exception.
Derek Lambertifbab30b2020-09-17 13:58:18 +0100407 std::string GetPathArgument(cxxopts::ParseResult& result,
408 std::string&& argName,
409 ExpectFile expectFile,
410 OptionalArg isOptionalArg)
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100411 {
412 if (result.count(argName))
413 {
Derek Lambertifbab30b2020-09-17 13:58:18 +0100414 std::string path = result[argName].as<std::string>();
415 if (!ValidateFilePath(path, expectFile))
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100416 {
Derek Lambertifbab30b2020-09-17 13:58:18 +0100417 std::stringstream ss;
418 ss << "Argument given to" << argName << "is not a valid file path";
419 throw cxxopts::option_syntax_exception(ss.str().c_str());
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100420 }
Derek Lambertifbab30b2020-09-17 13:58:18 +0100421 return path;
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100422 }
423 else
424 {
Derek Lambertifbab30b2020-09-17 13:58:18 +0100425 if (isOptionalArg == OptionalArg::True)
426 {
427 return "";
428 }
429
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100430 throw cxxopts::missing_argument_exception(argName);
431 }
432 }
433
Ryan OShea74af0932020-08-07 16:27:34 +0100434 /// Assigns vector of strings to struct member variable
Derek Lambertifbab30b2020-09-17 13:58:18 +0100435 std::vector<std::string> GetPathArgument(const std::vector<std::string>& pathStrings, OptionalArg isOptional)
Ryan OShea74af0932020-08-07 16:27:34 +0100436 {
437 if (pathStrings.size() < 5){
Derek Lambertifbab30b2020-09-17 13:58:18 +0100438 if (isOptional == OptionalArg::True)
439 {
440 return std::vector<std::string>();
441 }
Ryan OShea74af0932020-08-07 16:27:34 +0100442 throw cxxopts::option_syntax_exception("Comparison files requires 5 file paths.");
443 }
444
445 std::vector<std::string> filePaths;
446 for (auto& path : pathStrings)
447 {
448 filePaths.push_back(path);
Derek Lambertifbab30b2020-09-17 13:58:18 +0100449 if (!ValidateFilePath(filePaths.back(), ExpectFile::True))
Ryan OShea74af0932020-08-07 16:27:34 +0100450 {
451 throw cxxopts::option_syntax_exception("Argument given to Comparison Files is not a valid file path");
452 }
453 }
454 return filePaths;
455 }
456
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100457 /// Log info about assigned backends
458 void LogBackendsInfo(std::vector<BackendId>& backends, std::string&& modelName)
459 {
460 std::string info;
461 info = "Preferred backends for " + modelName + " set to [ ";
462 for (auto const &backend : backends)
463 {
464 info = info + std::string(backend) + " ";
465 }
466 ARMNN_LOG(info) << info << "]";
467 }
468
469 // Member variables
470 std::string backboneDir;
Ryan OShea74af0932020-08-07 16:27:34 +0100471 std::vector<std::string> comparisonFiles;
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100472 std::string detectorDir;
473 std::string imageDir;
Derek Lambertifbab30b2020-09-17 13:58:18 +0100474 std::string dynamicBackendPath;
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100475
476 std::vector<BackendId> prefBackendsBackbone;
477 std::vector<BackendId> prefBackendsDetector;
478
479 cxxopts::Options options;
Derek Lambertifbab30b2020-09-17 13:58:18 +0100480
481 DumpToDot dumpToDot;
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100482};
483
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100484int main(int argc, char* argv[])
485{
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100486 // Configure logging
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100487 SetAllLoggingSinks(true, true, true);
488 SetLogFilter(LogSeverity::Trace);
489
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100490 // Check and get given program arguments
491 ParseArgs progArgs = ParseArgs(argc, argv);
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100492
493 // Create runtime
494 IRuntime::CreationOptions runtimeOptions; // default
Derek Lambertifbab30b2020-09-17 13:58:18 +0100495
496 if (!progArgs.dynamicBackendPath.empty())
497 {
498 std::cout << "Loading backends from" << progArgs.dynamicBackendPath << "\n";
499 runtimeOptions.m_DynamicBackendsPath = progArgs.dynamicBackendPath;
500 }
501
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100502 auto runtime = IRuntime::Create(runtimeOptions);
503 if (!runtime)
504 {
505 ARMNN_LOG(fatal) << "Could not create runtime.";
506 return -1;
507 }
508
509 // Create TfLite Parsers
510 ITfLiteParser::TfLiteParserOptions parserOptions;
511 auto parser = ITfLiteParser::Create(parserOptions);
512
513 // Load backbone model
514 ARMNN_LOG(info) << "Loading backbone...";
515 NetworkId backboneId;
Derek Lambertifbab30b2020-09-17 13:58:18 +0100516 const DumpToDot dumpToDot = progArgs.dumpToDot;
517 CHECK_OK(LoadModel(progArgs.backboneDir.c_str(),
518 *parser,
519 *runtime,
520 backboneId,
521 progArgs.prefBackendsBackbone,
522 ImportMemory::False,
523 dumpToDot));
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100524 auto inputId = parser->GetNetworkInputBindingInfo(0, "inputs");
525 auto bbOut0Id = parser->GetNetworkOutputBindingInfo(0, "input_to_detector_1");
526 auto bbOut1Id = parser->GetNetworkOutputBindingInfo(0, "input_to_detector_2");
527 auto bbOut2Id = parser->GetNetworkOutputBindingInfo(0, "input_to_detector_3");
528 auto backboneProfile = runtime->GetProfiler(backboneId);
529 backboneProfile->EnableProfiling(true);
530
Derek Lambertifbab30b2020-09-17 13:58:18 +0100531
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100532 // Load detector model
533 ARMNN_LOG(info) << "Loading detector...";
534 NetworkId detectorId;
Derek Lambertifbab30b2020-09-17 13:58:18 +0100535 CHECK_OK(LoadModel(progArgs.detectorDir.c_str(),
536 *parser,
537 *runtime,
538 detectorId,
539 progArgs.prefBackendsDetector,
540 ImportMemory::True,
541 dumpToDot));
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100542 auto detectIn0Id = parser->GetNetworkInputBindingInfo(0, "input_to_detector_1");
543 auto detectIn1Id = parser->GetNetworkInputBindingInfo(0, "input_to_detector_2");
544 auto detectIn2Id = parser->GetNetworkInputBindingInfo(0, "input_to_detector_3");
545 auto outputBoxesId = parser->GetNetworkOutputBindingInfo(0, "output_boxes");
546 auto detectorProfile = runtime->GetProfiler(detectorId);
547
548 // Load input from file
549 ARMNN_LOG(info) << "Loading test image...";
Jan Eilerse7cc7c32020-06-25 13:18:47 +0100550 auto image = LoadImage(progArgs.imageDir.c_str());
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100551 if (image.empty())
552 {
553 return LOAD_IMAGE_ERROR;
554 }
555
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100556 // Allocate the intermediate tensors
557 std::vector<float> intermediateMem0(bbOut0Id.second.GetNumElements());
558 std::vector<float> intermediateMem1(bbOut1Id.second.GetNumElements());
559 std::vector<float> intermediateMem2(bbOut2Id.second.GetNumElements());
560 std::vector<float> intermediateMem3(outputBoxesId.second.GetNumElements());
561
562 // Setup inputs and outputs
563 using BindingInfos = std::vector<armnn::BindingPointInfo>;
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +0100564 using FloatTensors = std::vector<std::reference_wrapper<std::vector<float>>>;
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100565
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +0100566 InputTensors bbInputTensors = MakeInputTensors(BindingInfos{ inputId },
567 FloatTensors{ image });
568 OutputTensors bbOutputTensors = MakeOutputTensors(BindingInfos{ bbOut0Id, bbOut1Id, bbOut2Id },
569 FloatTensors{ intermediateMem0,
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100570 intermediateMem1,
Narumol Prangnawarat2adf5f02020-07-21 10:21:19 +0100571 intermediateMem2 });
572 InputTensors detectInputTensors = MakeInputTensors(BindingInfos{ detectIn0Id,
573 detectIn1Id,
574 detectIn2Id } ,
575 FloatTensors{ intermediateMem0,
576 intermediateMem1,
577 intermediateMem2 });
578 OutputTensors detectOutputTensors = MakeOutputTensors(BindingInfos{ outputBoxesId },
579 FloatTensors{ intermediateMem3 });
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100580
581 static const int numIterations=2;
582 using DurationUS = std::chrono::duration<double, std::micro>;
583 std::vector<DurationUS> nmsDurations(0);
Ryan OShea74af0932020-08-07 16:27:34 +0100584 std::vector<yolov3::Detection> filtered_boxes;
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100585 nmsDurations.reserve(numIterations);
586 for (int i=0; i < numIterations; i++)
587 {
588 // Execute backbone
589 ARMNN_LOG(info) << "Running backbone...";
590 runtime->EnqueueWorkload(backboneId, bbInputTensors, bbOutputTensors);
591
592 // Execute detector
593 ARMNN_LOG(info) << "Running detector...";
594 runtime->EnqueueWorkload(detectorId, detectInputTensors, detectOutputTensors);
595
596 // Execute NMS
597 ARMNN_LOG(info) << "Running nms...";
598 using clock = std::chrono::steady_clock;
599 auto nmsStartTime = clock::now();
600 yolov3::NMSConfig config;
601 config.num_boxes = 127800;
602 config.num_classes = 80;
603 config.confidence_threshold = 0.9f;
604 config.iou_threshold = 0.5f;
Ryan OShea74af0932020-08-07 16:27:34 +0100605 filtered_boxes = yolov3::nms(config, intermediateMem3);
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100606 auto nmsEndTime = clock::now();
607
608 // Enable the profiling after the warm-up run
609 if (i>0)
610 {
611 print_detection(std::cout, filtered_boxes);
612
613 const auto nmsDuration = DurationUS(nmsStartTime - nmsEndTime);
614 nmsDurations.push_back(nmsDuration);
615 }
616 backboneProfile->EnableProfiling(true);
617 detectorProfile->EnableProfiling(true);
618 }
619 // Log timings to file
620 std::ofstream backboneProfileStream("backbone.json");
621 backboneProfile->Print(backboneProfileStream);
622 backboneProfileStream.close();
623
624 std::ofstream detectorProfileStream("detector.json");
625 detectorProfile->Print(detectorProfileStream);
626 detectorProfileStream.close();
627
628 // Manually construct the json output
629 std::ofstream nmsProfileStream("nms.json");
630 nmsProfileStream << "{" << "\n";
631 nmsProfileStream << R"( "NmsTimings": {)" << "\n";
632 nmsProfileStream << R"( "raw": [)" << "\n";
633 bool isFirst = true;
634 for (auto duration : nmsDurations)
635 {
636 if (!isFirst)
637 {
638 nmsProfileStream << ",\n";
639 }
640
641 nmsProfileStream << " " << duration.count();
642 isFirst = false;
643 }
644 nmsProfileStream << "\n";
645 nmsProfileStream << R"( "units": "us")" << "\n";
646 nmsProfileStream << " ]" << "\n";
647 nmsProfileStream << " }" << "\n";
648 nmsProfileStream << "}" << "\n";
649 nmsProfileStream.close();
650
Derek Lambertifbab30b2020-09-17 13:58:18 +0100651 if (progArgs.comparisonFiles.size() > 0)
652 {
653 CheckAccuracy(&intermediateMem0,
654 &intermediateMem1,
655 &intermediateMem2,
656 &intermediateMem3,
657 filtered_boxes,
658 progArgs.comparisonFiles);
659 }
Ryan OShea74af0932020-08-07 16:27:34 +0100660
Derek Lambertid6cb30e2020-04-28 13:31:29 +0100661 ARMNN_LOG(info) << "Run completed";
662 return 0;
Keith Mok16fb1a22021-03-19 08:58:44 -0700663}