blob: c96d1f28d0f269a4d19030130e0b47110db512b4 [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//
5//#include "../InferenceTest.hpp"
6//#include "../ImagePreprocessor.hpp"
7#include "armnnTfLiteParser/ITfLiteParser.hpp"
8
9#include "NMS.hpp"
10
11#include <stb/stb_image.h>
12
13#include <armnn/INetwork.hpp>
14#include <armnn/IRuntime.hpp>
15#include <armnn/Logging.hpp>
16#include <armnn/utility/IgnoreUnused.hpp>
17
18#include <chrono>
19#include <iostream>
20#include <fstream>
21
22using namespace armnnTfLiteParser;
23using namespace armnn;
24
25static const int OPEN_FILE_ERROR = -2;
26static const int OPTIMIZE_NETWORK_ERROR = -3;
27static const int LOAD_NETWORK_ERROR = -4;
28static const int LOAD_IMAGE_ERROR = -5;
29static const int GENERAL_ERROR = -100;
30
31#define CHECK_OK(v) \
32 do { \
33 try { \
34 auto r_local = v; \
35 if (r_local != 0) { return r_local;} \
36 } \
37 catch(armnn::Exception e) \
38 { \
39 ARMNN_LOG(error) << "Oops: " << e.what(); \
40 return GENERAL_ERROR; \
41 } \
42 } while(0)
43
44
45
46template<typename TContainer>
47inline armnn::InputTensors MakeInputTensors(const std::vector<armnn::BindingPointInfo>& inputBindings,
48 const std::vector<TContainer>& inputDataContainers)
49{
50 armnn::InputTensors inputTensors;
51
52 const size_t numInputs = inputBindings.size();
53 if (numInputs != inputDataContainers.size())
54 {
55 throw armnn::Exception("Mismatching vectors");
56 }
57
58 for (size_t i = 0; i < numInputs; i++)
59 {
60 const armnn::BindingPointInfo& inputBinding = inputBindings[i];
61 const TContainer& inputData = inputDataContainers[i];
62
63 armnn::ConstTensor inputTensor(inputBinding.second, inputData.data());
64 inputTensors.push_back(std::make_pair(inputBinding.first, inputTensor));
65 }
66
67 return inputTensors;
68}
69
70template<typename TContainer>
71inline armnn::OutputTensors MakeOutputTensors(const std::vector<armnn::BindingPointInfo>& outputBindings,
72 const std::vector<TContainer>& outputDataContainers)
73{
74 armnn::OutputTensors outputTensors;
75
76 const size_t numOutputs = outputBindings.size();
77 if (numOutputs != outputDataContainers.size())
78 {
79 throw armnn::Exception("Mismatching vectors");
80 }
81
82 for (size_t i = 0; i < numOutputs; i++)
83 {
84 const armnn::BindingPointInfo& outputBinding = outputBindings[i];
85 const TContainer& outputData = outputDataContainers[i];
86
87 armnn::Tensor outputTensor(outputBinding.second, const_cast<float*>(outputData.data()));
88 outputTensors.push_back(std::make_pair(outputBinding.first, outputTensor));
89 }
90
91 return outputTensors;
92}
93
94int LoadModel(const char* filename,
95 ITfLiteParser& parser,
96 IRuntime& runtime,
97 NetworkId& networkId,
98 const std::vector<BackendId>& backendPreferences)
99{
100 std::ifstream stream(filename, std::ios::in | std::ios::binary);
101 if (!stream.is_open())
102 {
103 ARMNN_LOG(error) << "Could not open model: " << filename;
104 return OPEN_FILE_ERROR;
105 }
106
107 std::vector<uint8_t> contents((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
108 stream.close();
109
110 auto model = parser.CreateNetworkFromBinary(contents);
111 contents.clear();
112 ARMNN_LOG(debug) << "Model loaded ok: " << filename;
113
114 // Optimize backbone model
115 auto optimizedModel = Optimize(*model, backendPreferences, runtime.GetDeviceSpec());
116 if (!optimizedModel)
117 {
118 ARMNN_LOG(fatal) << "Could not optimize the model:" << filename;
119 return OPTIMIZE_NETWORK_ERROR;
120 }
121
122 // Load backbone model into runtime
123 {
124 std::string errorMessage;
125 INetworkProperties modelProps;
126 Status status = runtime.LoadNetwork(networkId, std::move(optimizedModel), errorMessage, modelProps);
127 if (status != Status::Success)
128 {
129 ARMNN_LOG(fatal) << "Could not load " << filename << " model into runtime: " << errorMessage;
130 return LOAD_NETWORK_ERROR;
131 }
132 }
133
134 return 0;
135}
136
137std::vector<float> LoadImage(const char* filename)
138{
139 struct Memory
140 {
141 ~Memory() {stbi_image_free(m_Data);}
142 bool IsLoaded() const { return m_Data != nullptr;}
143
144 unsigned char* m_Data;
145 };
146
147 std::vector<float> image;
148
149 int width;
150 int height;
151 int channels;
152
153 Memory mem = {stbi_load(filename, &width, &height, &channels, 3)};
154 if (!mem.IsLoaded())
155 {
156 ARMNN_LOG(error) << "Could not load input image file: " << filename;
157 return image;
158 }
159
160 if (width != 1920 || height != 1080 || channels != 3)
161 {
162 ARMNN_LOG(error) << "Input image has wong dimension: " << width << "x" << height << "x" << channels << ". "
163 " Expected 1920x1080x3.";
164 return image;
165 }
166
167 image.resize(1920*1080*3);
168
169 // Expand to float. Does this need de-gamma?
170 for (unsigned int idx=0; idx <= 1920*1080*3; idx++)
171 {
172 image[idx] = static_cast<float>(mem.m_Data[idx]) /255.0f;
173 }
174
175 return image;
176}
177
178int main(int argc, char* argv[])
179{
180 if (argc != 3)
181 {
182 ARMNN_LOG(error) << "Expected arguments: {PathToModels} {PathToData}";
183 }
184 std::string modelsPath(argv[1]);
185 std::string imagePath(argv[2]);
186
187 std::string backboneModelFile = modelsPath + "yolov3_1080_1920_backbone_int8.tflite";
188 std::string detectorModelFile = modelsPath + "yolov3_1080_1920_detector_fp32.tflite";
189 std::string imageFile = imagePath + "1080_1920.jpg";
190
191 // Configure the logging
192 SetAllLoggingSinks(true, true, true);
193 SetLogFilter(LogSeverity::Trace);
194
195
196 // Create runtime
197 IRuntime::CreationOptions runtimeOptions; // default
198 auto runtime = IRuntime::Create(runtimeOptions);
199 if (!runtime)
200 {
201 ARMNN_LOG(fatal) << "Could not create runtime.";
202 return -1;
203 }
204
205 // Create TfLite Parsers
206 ITfLiteParser::TfLiteParserOptions parserOptions;
207 auto parser = ITfLiteParser::Create(parserOptions);
208
209 // Load backbone model
210 ARMNN_LOG(info) << "Loading backbone...";
211 NetworkId backboneId;
212 CHECK_OK(LoadModel(backboneModelFile.c_str(), *parser, *runtime, backboneId, {"GpuAcc", "CpuRef"}));
213 auto inputId = parser->GetNetworkInputBindingInfo(0, "inputs");
214 auto bbOut0Id = parser->GetNetworkOutputBindingInfo(0, "input_to_detector_1");
215 auto bbOut1Id = parser->GetNetworkOutputBindingInfo(0, "input_to_detector_2");
216 auto bbOut2Id = parser->GetNetworkOutputBindingInfo(0, "input_to_detector_3");
217 auto backboneProfile = runtime->GetProfiler(backboneId);
218 backboneProfile->EnableProfiling(true);
219
220 // Load detector model
221 ARMNN_LOG(info) << "Loading detector...";
222 NetworkId detectorId;
223 CHECK_OK(LoadModel(detectorModelFile.c_str(), *parser, *runtime, detectorId, {"CpuAcc", "CpuRef"}));
224 auto detectIn0Id = parser->GetNetworkInputBindingInfo(0, "input_to_detector_1");
225 auto detectIn1Id = parser->GetNetworkInputBindingInfo(0, "input_to_detector_2");
226 auto detectIn2Id = parser->GetNetworkInputBindingInfo(0, "input_to_detector_3");
227 auto outputBoxesId = parser->GetNetworkOutputBindingInfo(0, "output_boxes");
228 auto detectorProfile = runtime->GetProfiler(detectorId);
229
230 // Load input from file
231 ARMNN_LOG(info) << "Loading test image...";
232 auto image = LoadImage(imageFile.c_str());
233 if (image.empty())
234 {
235 return LOAD_IMAGE_ERROR;
236 }
237
238
239 // Allocate the intermediate tensors
240 std::vector<float> intermediateMem0(bbOut0Id.second.GetNumElements());
241 std::vector<float> intermediateMem1(bbOut1Id.second.GetNumElements());
242 std::vector<float> intermediateMem2(bbOut2Id.second.GetNumElements());
243 std::vector<float> intermediateMem3(outputBoxesId.second.GetNumElements());
244
245 // Setup inputs and outputs
246 using BindingInfos = std::vector<armnn::BindingPointInfo>;
247 using FloatTensors = std::vector<std::vector<float>>;
248
249 InputTensors bbInputTensors = MakeInputTensors(BindingInfos{inputId},
250 FloatTensors{std::move(image)});
251 OutputTensors bbOutputTensors = MakeOutputTensors(BindingInfos{bbOut0Id, bbOut1Id, bbOut2Id},
252 FloatTensors{intermediateMem0,
253 intermediateMem1,
254 intermediateMem2});
255 InputTensors detectInputTensors = MakeInputTensors(BindingInfos{detectIn0Id,
256 detectIn1Id,
257 detectIn2Id},
258 FloatTensors{intermediateMem0,
259 intermediateMem1,
260 intermediateMem2});
261 OutputTensors detectOutputTensors = MakeOutputTensors(BindingInfos{outputBoxesId},
262 FloatTensors{intermediateMem3});
263
264 static const int numIterations=2;
265 using DurationUS = std::chrono::duration<double, std::micro>;
266 std::vector<DurationUS> nmsDurations(0);
267 nmsDurations.reserve(numIterations);
268 for (int i=0; i < numIterations; i++)
269 {
270 // Execute backbone
271 ARMNN_LOG(info) << "Running backbone...";
272 runtime->EnqueueWorkload(backboneId, bbInputTensors, bbOutputTensors);
273
274 // Execute detector
275 ARMNN_LOG(info) << "Running detector...";
276 runtime->EnqueueWorkload(detectorId, detectInputTensors, detectOutputTensors);
277
278 // Execute NMS
279 ARMNN_LOG(info) << "Running nms...";
280 using clock = std::chrono::steady_clock;
281 auto nmsStartTime = clock::now();
282 yolov3::NMSConfig config;
283 config.num_boxes = 127800;
284 config.num_classes = 80;
285 config.confidence_threshold = 0.9f;
286 config.iou_threshold = 0.5f;
287 auto filtered_boxes = yolov3::nms(config, intermediateMem3);
288 auto nmsEndTime = clock::now();
289
290 // Enable the profiling after the warm-up run
291 if (i>0)
292 {
293 print_detection(std::cout, filtered_boxes);
294
295 const auto nmsDuration = DurationUS(nmsStartTime - nmsEndTime);
296 nmsDurations.push_back(nmsDuration);
297 }
298 backboneProfile->EnableProfiling(true);
299 detectorProfile->EnableProfiling(true);
300 }
301 // Log timings to file
302 std::ofstream backboneProfileStream("backbone.json");
303 backboneProfile->Print(backboneProfileStream);
304 backboneProfileStream.close();
305
306 std::ofstream detectorProfileStream("detector.json");
307 detectorProfile->Print(detectorProfileStream);
308 detectorProfileStream.close();
309
310 // Manually construct the json output
311 std::ofstream nmsProfileStream("nms.json");
312 nmsProfileStream << "{" << "\n";
313 nmsProfileStream << R"( "NmsTimings": {)" << "\n";
314 nmsProfileStream << R"( "raw": [)" << "\n";
315 bool isFirst = true;
316 for (auto duration : nmsDurations)
317 {
318 if (!isFirst)
319 {
320 nmsProfileStream << ",\n";
321 }
322
323 nmsProfileStream << " " << duration.count();
324 isFirst = false;
325 }
326 nmsProfileStream << "\n";
327 nmsProfileStream << R"( "units": "us")" << "\n";
328 nmsProfileStream << " ]" << "\n";
329 nmsProfileStream << " }" << "\n";
330 nmsProfileStream << "}" << "\n";
331 nmsProfileStream.close();
332
333 ARMNN_LOG(info) << "Run completed";
334 return 0;
335}