ArmNN  NotReleased
YoloInferenceTest.hpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #pragma once
6 
7 #include "InferenceTest.hpp"
8 #include "YoloDatabase.hpp"
9 
10 #include <algorithm>
11 #include <array>
12 #include <utility>
13 
14 #include <boost/assert.hpp>
15 #include <boost/multi_array.hpp>
16 #include <boost/test/tools/floating_point_comparison.hpp>
17 
18 constexpr size_t YoloOutputSize = 1470;
19 
20 template <typename Model>
21 class YoloTestCase : public InferenceModelTestCase<Model>
22 {
23 public:
25  unsigned int testCaseId,
26  YoloTestCaseData& testCaseData)
27  : InferenceModelTestCase<Model>(model, testCaseId, { std::move(testCaseData.m_InputImage) }, { YoloOutputSize })
28  , m_FloatComparer(boost::math::fpc::percent_tolerance(1.0f))
29  , m_TopObjectDetections(std::move(testCaseData.m_TopObjectDetections))
30  {
31  }
32 
34  {
35  boost::ignore_unused(options);
36 
37  using Boost3dArray = boost::multi_array<float, 3>;
38 
39  const std::vector<float>& output = boost::get<std::vector<float>>(this->GetOutputs()[0]);
40  BOOST_ASSERT(output.size() == YoloOutputSize);
41 
42  constexpr Boost3dArray::index gridSize = 7;
43  constexpr Boost3dArray::index numClasses = 20;
44  constexpr Boost3dArray::index numScales = 2;
45 
46  const float* outputPtr = output.data();
47 
48  // Range 0-980. Class probabilities. 7x7x20
49  Boost3dArray classProbabilities(boost::extents[gridSize][gridSize][numClasses]);
50  for (Boost3dArray::index y = 0; y < gridSize; ++y)
51  {
52  for (Boost3dArray::index x = 0; x < gridSize; ++x)
53  {
54  for (Boost3dArray::index c = 0; c < numClasses; ++c)
55  {
56  classProbabilities[y][x][c] = *outputPtr++;
57  }
58  }
59  }
60 
61  // Range 980-1078. Scales. 7x7x2
62  Boost3dArray scales(boost::extents[gridSize][gridSize][numScales]);
63  for (Boost3dArray::index y = 0; y < gridSize; ++y)
64  {
65  for (Boost3dArray::index x = 0; x < gridSize; ++x)
66  {
67  for (Boost3dArray::index s = 0; s < numScales; ++s)
68  {
69  scales[y][x][s] = *outputPtr++;
70  }
71  }
72  }
73 
74  // Range 1078-1469. Bounding boxes. 7x7x2x4
75  constexpr float imageWidthAsFloat = static_cast<float>(YoloImageWidth);
76  constexpr float imageHeightAsFloat = static_cast<float>(YoloImageHeight);
77 
78  boost::multi_array<float, 4> boxes(boost::extents[gridSize][gridSize][numScales][4]);
79  for (Boost3dArray::index y = 0; y < gridSize; ++y)
80  {
81  for (Boost3dArray::index x = 0; x < gridSize; ++x)
82  {
83  for (Boost3dArray::index s = 0; s < numScales; ++s)
84  {
85  float bx = *outputPtr++;
86  float by = *outputPtr++;
87  float bw = *outputPtr++;
88  float bh = *outputPtr++;
89 
90  boxes[y][x][s][0] = ((bx + static_cast<float>(x)) / 7.0f) * imageWidthAsFloat;
91  boxes[y][x][s][1] = ((by + static_cast<float>(y)) / 7.0f) * imageHeightAsFloat;
92  boxes[y][x][s][2] = bw * bw * static_cast<float>(imageWidthAsFloat);
93  boxes[y][x][s][3] = bh * bh * static_cast<float>(imageHeightAsFloat);
94  }
95  }
96  }
97  BOOST_ASSERT(output.data() + YoloOutputSize == outputPtr);
98 
99  std::vector<YoloDetectedObject> detectedObjects;
100  detectedObjects.reserve(gridSize * gridSize * numScales * numClasses);
101 
102  for (Boost3dArray::index y = 0; y < gridSize; ++y)
103  {
104  for (Boost3dArray::index x = 0; x < gridSize; ++x)
105  {
106  for (Boost3dArray::index s = 0; s < numScales; ++s)
107  {
108  for (Boost3dArray::index c = 0; c < numClasses; ++c)
109  {
110  // Resolved confidence: class probabilities * scales.
111  const float confidence = classProbabilities[y][x][c] * scales[y][x][s];
112 
113  // Resolves bounding box and stores.
114  YoloBoundingBox box;
115  box.m_X = boxes[y][x][s][0];
116  box.m_Y = boxes[y][x][s][1];
117  box.m_W = boxes[y][x][s][2];
118  box.m_H = boxes[y][x][s][3];
119 
120  detectedObjects.emplace_back(c, box, confidence);
121  }
122  }
123  }
124  }
125 
126  // Sorts detected objects by confidence.
127  std::sort(detectedObjects.begin(), detectedObjects.end(),
128  [](const YoloDetectedObject& a, const YoloDetectedObject& b)
129  {
130  // Sorts by largest confidence first, then by class.
131  return a.m_Confidence > b.m_Confidence
132  || (a.m_Confidence == b.m_Confidence && a.m_Class > b.m_Class);
133  });
134 
135  // Checks the top N detections.
136  auto outputIt = detectedObjects.begin();
137  auto outputEnd = detectedObjects.end();
138 
139  for (const YoloDetectedObject& expectedDetection : m_TopObjectDetections)
140  {
141  if (outputIt == outputEnd)
142  {
143  // Somehow expected more things to check than detections found by the model.
144  return TestCaseResult::Abort;
145  }
146 
147  const YoloDetectedObject& detectedObject = *outputIt;
148  if (detectedObject.m_Class != expectedDetection.m_Class)
149  {
150  ARMNN_LOG(error) << "Prediction for test case " << this->GetTestCaseId() <<
151  " is incorrect: Expected (" << expectedDetection.m_Class << ")" <<
152  " but predicted (" << detectedObject.m_Class << ")";
153  return TestCaseResult::Failed;
154  }
155 
156  if (!m_FloatComparer(detectedObject.m_Box.m_X, expectedDetection.m_Box.m_X) ||
157  !m_FloatComparer(detectedObject.m_Box.m_Y, expectedDetection.m_Box.m_Y) ||
158  !m_FloatComparer(detectedObject.m_Box.m_W, expectedDetection.m_Box.m_W) ||
159  !m_FloatComparer(detectedObject.m_Box.m_H, expectedDetection.m_Box.m_H) ||
160  !m_FloatComparer(detectedObject.m_Confidence, expectedDetection.m_Confidence))
161  {
162  ARMNN_LOG(error) << "Detected bounding box for test case " << this->GetTestCaseId() <<
163  " is incorrect";
164  return TestCaseResult::Failed;
165  }
166 
167  ++outputIt;
168  }
169 
170  return TestCaseResult::Ok;
171  }
172 
173 private:
174  boost::math::fpc::close_at_tolerance<float> m_FloatComparer;
175  std::vector<YoloDetectedObject> m_TopObjectDetections;
176 };
177 
178 template <typename Model>
180 {
181 public:
182  template <typename TConstructModelCallable>
183  explicit YoloTestCaseProvider(TConstructModelCallable constructModel)
184  : m_ConstructModel(constructModel)
185  {
186  }
187 
188  virtual void AddCommandLineOptions(boost::program_options::options_description& options) override
189  {
190  namespace po = boost::program_options;
191 
192  options.add_options()
193  ("data-dir,d", po::value<std::string>(&m_DataDir)->required(),
194  "Path to directory containing test data");
195 
196  Model::AddCommandLineOptions(options, m_ModelCommandLineOptions);
197  }
198 
199  virtual bool ProcessCommandLineOptions(const InferenceTestOptions &commonOptions) override
200  {
201  if (!ValidateDirectory(m_DataDir))
202  {
203  return false;
204  }
205 
206  m_Model = m_ConstructModel(commonOptions, m_ModelCommandLineOptions);
207  if (!m_Model)
208  {
209  return false;
210  }
211 
212  m_Database = std::make_unique<YoloDatabase>(m_DataDir.c_str());
213  if (!m_Database)
214  {
215  return false;
216  }
217 
218  return true;
219  }
220 
221  virtual std::unique_ptr<IInferenceTestCase> GetTestCase(unsigned int testCaseId) override
222  {
223  std::unique_ptr<YoloTestCaseData> testCaseData = m_Database->GetTestCaseData(testCaseId);
224  if (!testCaseData)
225  {
226  return nullptr;
227  }
228 
229  return std::make_unique<YoloTestCase<Model>>(*m_Model, testCaseId, *testCaseData);
230  }
231 
232 private:
233  typename Model::CommandLineOptions m_ModelCommandLineOptions;
234  std::function<std::unique_ptr<Model>(const InferenceTestOptions&,
235  typename Model::CommandLineOptions)> m_ConstructModel;
236  std::unique_ptr<Model> m_Model;
237 
238  std::string m_DataDir;
239  std::unique_ptr<YoloDatabase> m_Database;
240 };
virtual TestCaseResult ProcessResult(const InferenceTestOptions &options) override
virtual std::unique_ptr< IInferenceTestCase > GetTestCase(unsigned int testCaseId) override
YoloBoundingBox m_Box
constexpr size_t YoloOutputSize
#define ARMNN_LOG(severity)
Definition: Logging.hpp:163
YoloTestCase(Model &model, unsigned int testCaseId, YoloTestCaseData &testCaseData)
virtual bool ProcessCommandLineOptions(const InferenceTestOptions &commonOptions) override
virtual void AddCommandLineOptions(boost::program_options::options_description &options) override
YoloTestCaseProvider(TConstructModelCallable constructModel)
const std::vector< TContainer > & GetOutputs() const
constexpr unsigned int YoloImageHeight
constexpr unsigned int YoloImageWidth
armnn::Runtime::CreationOptions::ExternalProfilingOptions options
std::vector< float > m_InputImage
unsigned int m_Class
bool ValidateDirectory(std::string &dir)