aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAron Virginas-Tar <Aron.Virginas-Tar@arm.com>2019-01-29 11:09:51 +0000
committerMatteo Martincigh <matteo.martincigh@arm.com>2019-01-30 13:05:58 +0000
commitd089b74bebbcc8518fb0f4eacb7e6569ae170199 (patch)
treea86f9ec054d4daad3d20446ced27555768a84862 /tests
parent7cf0eaa26c1fb29ca9df97e4734ec7c1e10f81c4 (diff)
downloadarmnn-d089b74bebbcc8518fb0f4eacb7e6569ae170199.tar.gz
IVGCVSW-2437 Inference test for TensorFlow Lite MobileNet SSD
Change-Id: If7ee1efa3ee79d9eca41c5a6219b3fc42e740efe
Diffstat (limited to 'tests')
-rw-r--r--tests/CMakeLists.txt7
-rw-r--r--tests/MobileNetSsdDatabase.hpp105
-rw-r--r--tests/MobileNetSsdInferenceTest.hpp202
-rw-r--r--tests/ObjectDetectionCommon.hpp49
-rw-r--r--tests/TfLiteMobileNetSsd-Armnn/TfLiteMobileNetSsd-Armnn.cpp76
5 files changed, 439 insertions, 0 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 981553702e..e8f72eb4ee 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -164,6 +164,13 @@ if (BUILD_TF_LITE_PARSER)
ImagePreprocessor.cpp)
TfLiteParserTest(TfLiteMobilenetQuantized-Armnn "${TfLiteMobilenetQuantized-Armnn_sources}")
+ set(TfLiteMobileNetSsd-Armnn_sources
+ TfLiteMobileNetSsd-Armnn/TfLiteMobileNetSsd-Armnn.cpp
+ MobileNetSsdDatabase.hpp
+ MobileNetSsdInferenceTest.hpp
+ ObjectDetectionCommon.hpp)
+ TfLiteParserTest(TfLiteMobileNetSsd-Armnn "${TfLiteMobileNetSsd-Armnn_sources}")
+
set(TfLiteVGG16Quantized-Armnn_sources
TfLiteVGG16Quantized-Armnn/TfLiteVGG16Quantized-Armnn.cpp
ImagePreprocessor.hpp
diff --git a/tests/MobileNetSsdDatabase.hpp b/tests/MobileNetSsdDatabase.hpp
new file mode 100644
index 0000000000..e3a28d13bd
--- /dev/null
+++ b/tests/MobileNetSsdDatabase.hpp
@@ -0,0 +1,105 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+#pragma once
+
+#include "ObjectDetectionCommon.hpp"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <armnn/TypesUtils.hpp>
+
+#include <boost/log/trivial.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+
+#include <array>
+#include <string>
+
+#include "InferenceTestImage.hpp"
+
+namespace
+{
+
+struct MobileNetSsdTestCaseData
+{
+ MobileNetSsdTestCaseData(
+ std::vector<float> inputData,
+ std::vector<DetectedObject> expectedOutput)
+ : m_InputData(std::move(inputData))
+ , m_ExpectedOutput(std::move(expectedOutput))
+ {}
+
+ std::vector<float> m_InputData;
+ std::vector<DetectedObject> m_ExpectedOutput;
+};
+
+class MobileNetSsdDatabase
+{
+public:
+ explicit MobileNetSsdDatabase(const std::string& imageDir);
+
+ std::unique_ptr<MobileNetSsdTestCaseData> GetTestCaseData(unsigned int testCaseId);
+
+private:
+ std::string m_ImageDir;
+};
+
+constexpr unsigned int k_MobileNetSsdImageWidth = 300u;
+constexpr unsigned int k_MobileNetSsdImageHeight = k_MobileNetSsdImageWidth;
+
+// Test cases
+const std::array<ObjectDetectionInput, 1> g_PerTestCaseInput =
+{
+ ObjectDetectionInput
+ {
+ "Cat.jpg",
+ DetectedObject(16, BoundingBox(0.21678525f, 0.0859828f, 0.9271242f, 0.9453231f), 0.79296875f)
+ }
+};
+
+MobileNetSsdDatabase::MobileNetSsdDatabase(const std::string& imageDir)
+ : m_ImageDir(imageDir)
+{}
+
+std::unique_ptr<MobileNetSsdTestCaseData> MobileNetSsdDatabase::GetTestCaseData(unsigned int testCaseId)
+{
+ const unsigned int safeTestCaseId =
+ testCaseId % boost::numeric_cast<unsigned int>(g_PerTestCaseInput.size());
+ const ObjectDetectionInput& testCaseInput = g_PerTestCaseInput[safeTestCaseId];
+
+ // Load test case input
+ const std::string imagePath = m_ImageDir + testCaseInput.first;
+ std::vector<float> imageData;
+ try
+ {
+ InferenceTestImage image(imagePath.c_str());
+
+ // Resize image (if needed)
+ const unsigned int width = image.GetWidth();
+ const unsigned int height = image.GetHeight();
+ if (width != k_MobileNetSsdImageWidth || height != k_MobileNetSsdImageHeight)
+ {
+ image.Resize(k_MobileNetSsdImageWidth, k_MobileNetSsdImageHeight, CHECK_LOCATION());
+ }
+
+ // Get image data as a vector of floats
+ imageData = GetImageDataInArmNnLayoutAsNormalizedFloats(ImageChannelLayout::Rgb, image);
+ }
+ catch (const InferenceTestImageException& e)
+ {
+ BOOST_LOG_TRIVIAL(fatal) << "Failed to load image for test case " << testCaseId << ". Error: " << e.what();
+ return nullptr;
+ }
+
+ // Prepare test case expected output
+ std::vector<DetectedObject> expectedOutput;
+ expectedOutput.reserve(1);
+ expectedOutput.push_back(testCaseInput.second);
+
+ return std::make_unique<MobileNetSsdTestCaseData>(std::move(imageData), std::move(expectedOutput));
+}
+
+} // anonymous namespace
diff --git a/tests/MobileNetSsdInferenceTest.hpp b/tests/MobileNetSsdInferenceTest.hpp
new file mode 100644
index 0000000000..cf00966e4b
--- /dev/null
+++ b/tests/MobileNetSsdInferenceTest.hpp
@@ -0,0 +1,202 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+#pragma once
+
+#include "InferenceTest.hpp"
+#include "MobileNetSsdDatabase.hpp"
+
+#include <boost/assert.hpp>
+#include <boost/log/trivial.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+#include <boost/test/tools/floating_point_comparison.hpp>
+
+#include <vector>
+
+namespace
+{
+
+template<typename Model>
+class MobileNetSsdTestCase : public InferenceModelTestCase<Model>
+{
+public:
+ MobileNetSsdTestCase(Model& model,
+ unsigned int testCaseId,
+ const MobileNetSsdTestCaseData& testCaseData)
+ : InferenceModelTestCase<Model>(model,
+ testCaseId,
+ { std::move(testCaseData.m_InputData) },
+ { k_OutputSize1, k_OutputSize2, k_OutputSize3, k_OutputSize4 })
+ , m_FloatComparer(boost::math::fpc::percent_tolerance(1.0f))
+ , m_DetectedObjects(testCaseData.m_ExpectedOutput)
+ {}
+
+ TestCaseResult ProcessResult(const InferenceTestOptions& options) override
+ {
+ const std::vector<float>& output1 = this->GetOutputs()[0]; // bounding boxes
+ BOOST_ASSERT(output1.size() == k_OutputSize1);
+
+ const std::vector<float>& output2 = this->GetOutputs()[1]; // classes
+ BOOST_ASSERT(output2.size() == k_OutputSize2);
+
+ const std::vector<float>& output3 = this->GetOutputs()[2]; // scores
+ BOOST_ASSERT(output3.size() == k_OutputSize3);
+
+ const std::vector<float>& output4 = this->GetOutputs()[3]; // number of valid detections
+ BOOST_ASSERT(output4.size() == k_OutputSize4);
+
+ // Extract detected objects from output data
+ std::vector<DetectedObject> detectedObjects;
+ const float* outputData = output1.data();
+ for (unsigned int i = 0u; i < k_NumDetections; i++)
+ {
+ // NOTE: Order of coordinates in output data is yMin, xMin, yMax, xMax
+ float yMin = *outputData++;
+ float xMin = *outputData++;
+ float yMax = *outputData++;
+ float xMax = *outputData++;
+
+ DetectedObject detectedObject(
+ static_cast<unsigned int>(output2.at(i)),
+ BoundingBox(xMin, yMin, xMax, yMax),
+ output3.at(i));
+
+ detectedObjects.push_back(detectedObject);
+ }
+
+ // Sort detected objects by confidence
+ std::sort(detectedObjects.begin(), detectedObjects.end(),
+ [](const DetectedObject& a, const DetectedObject& b)
+ {
+ return a.m_Confidence > b.m_Confidence ||
+ (a.m_Confidence == b.m_Confidence && a.m_Class > b.m_Class);
+ });
+
+ // Check if number of valid detections matches expectations
+ const size_t numValidDetections = boost::numeric_cast<size_t>(output4[0]);
+ if (numValidDetections != m_DetectedObjects.size())
+ {
+ BOOST_LOG_TRIVIAL(error) << "Number of valid detections is incorrect: Expected (" <<
+ m_DetectedObjects.size() << ")" << " but got (" << numValidDetections << ")";
+ return TestCaseResult::Failed;
+ }
+
+ // Compare detected objects with expected results
+ std::vector<DetectedObject>::const_iterator it = detectedObjects.begin();
+ for (const DetectedObject& expectedDetection : m_DetectedObjects)
+ {
+ if (it == detectedObjects.end())
+ {
+ BOOST_LOG_TRIVIAL(info) << "No more detected objects to compare";
+ return TestCaseResult::Abort;
+ }
+
+ const DetectedObject& detectedObject = *it;
+ if (detectedObject.m_Class != expectedDetection.m_Class)
+ {
+ BOOST_LOG_TRIVIAL(error) << "Prediction for test case " << this->GetTestCaseId() <<
+ " is incorrect: Expected (" << expectedDetection.m_Class << ")" <<
+ " but predicted (" << detectedObject.m_Class << ")";
+ return TestCaseResult::Failed;
+ }
+
+ if(!m_FloatComparer(detectedObject.m_Confidence, expectedDetection.m_Confidence))
+ {
+ BOOST_LOG_TRIVIAL(error) << "Confidence of prediction for test case " << this->GetTestCaseId() <<
+ " is incorrect: Expected (" << expectedDetection.m_Confidence << ") +- 1.0 pc" <<
+ " but predicted (" << detectedObject.m_Confidence << ")";
+ return TestCaseResult::Failed;
+ }
+
+ if (!m_FloatComparer(detectedObject.m_BoundingBox.m_XMin, expectedDetection.m_BoundingBox.m_XMin) ||
+ !m_FloatComparer(detectedObject.m_BoundingBox.m_YMin, expectedDetection.m_BoundingBox.m_YMin) ||
+ !m_FloatComparer(detectedObject.m_BoundingBox.m_XMax, expectedDetection.m_BoundingBox.m_XMax) ||
+ !m_FloatComparer(detectedObject.m_BoundingBox.m_YMax, expectedDetection.m_BoundingBox.m_YMax))
+ {
+ BOOST_LOG_TRIVIAL(error) << "Detected bounding box for test case " << this->GetTestCaseId() <<
+ " is incorrect";
+ return TestCaseResult::Failed;
+ }
+
+ ++it;
+ }
+
+ return TestCaseResult::Ok;
+ }
+
+private:
+ static constexpr unsigned int k_NumDetections = 10u;
+
+ static constexpr unsigned int k_OutputSize1 = k_NumDetections * 4u;
+ static constexpr unsigned int k_OutputSize2 = k_NumDetections;
+ static constexpr unsigned int k_OutputSize3 = k_NumDetections;
+ static constexpr unsigned int k_OutputSize4 = 1u;
+
+ boost::math::fpc::close_at_tolerance<float> m_FloatComparer;
+ std::vector<DetectedObject> m_DetectedObjects;
+};
+
+template <typename Model>
+class MobileNetSsdTestCaseProvider : public IInferenceTestCaseProvider
+{
+public:
+ template <typename TConstructModelCallable>
+ explicit MobileNetSsdTestCaseProvider(TConstructModelCallable constructModel)
+ : m_ConstructModel(constructModel)
+ {}
+
+ virtual void AddCommandLineOptions(boost::program_options::options_description& options) override
+ {
+ namespace po = boost::program_options;
+
+ options.add_options()
+ ("data-dir,d", po::value<std::string>(&m_DataDir)->required(),
+ "Path to directory containing test data");
+
+ Model::AddCommandLineOptions(options, m_ModelCommandLineOptions);
+ }
+
+ virtual bool ProcessCommandLineOptions() override
+ {
+ if (!ValidateDirectory(m_DataDir))
+ {
+ return false;
+ }
+
+ m_Model = m_ConstructModel(m_ModelCommandLineOptions);
+ if (!m_Model)
+ {
+ return false;
+ }
+
+ m_Database = std::make_unique<MobileNetSsdDatabase>(m_DataDir.c_str());
+ if (!m_Database)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ std::unique_ptr<IInferenceTestCase> GetTestCase(unsigned int testCaseId) override
+ {
+ std::unique_ptr<MobileNetSsdTestCaseData> testCaseData = m_Database->GetTestCaseData(testCaseId);
+ if (!testCaseData)
+ {
+ return nullptr;
+ }
+
+ return std::make_unique<MobileNetSsdTestCase<Model>>(*m_Model, testCaseId, *testCaseData);
+ }
+
+private:
+ typename Model::CommandLineOptions m_ModelCommandLineOptions;
+ std::function<std::unique_ptr<Model>(typename Model::CommandLineOptions)> m_ConstructModel;
+ std::unique_ptr<Model> m_Model;
+
+ std::string m_DataDir;
+ std::unique_ptr<MobileNetSsdDatabase> m_Database;
+};
+
+} // anonymous namespace \ No newline at end of file
diff --git a/tests/ObjectDetectionCommon.hpp b/tests/ObjectDetectionCommon.hpp
new file mode 100644
index 0000000000..85b54c255f
--- /dev/null
+++ b/tests/ObjectDetectionCommon.hpp
@@ -0,0 +1,49 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+#pragma once
+
+#include <string>
+#include <utility>
+
+namespace
+{
+
+struct BoundingBox
+{
+ BoundingBox()
+ : BoundingBox(0.0f, 0.0f, 0.0f, 0.0f)
+ {}
+
+ BoundingBox(float xMin, float yMin, float xMax, float yMax)
+ : m_XMin(xMin)
+ , m_YMin(yMin)
+ , m_XMax(xMax)
+ , m_YMax(yMax)
+ {}
+
+ float m_XMin;
+ float m_YMin;
+ float m_XMax;
+ float m_YMax;
+};
+
+struct DetectedObject
+{
+ DetectedObject(unsigned int detectedClass,
+ const BoundingBox& boundingBox,
+ float confidence)
+ : m_Class(detectedClass)
+ , m_BoundingBox(boundingBox)
+ , m_Confidence(confidence)
+ {}
+
+ unsigned int m_Class;
+ BoundingBox m_BoundingBox;
+ float m_Confidence;
+};
+
+using ObjectDetectionInput = std::pair<std::string, DetectedObject>;
+
+} // anonymous namespace \ No newline at end of file
diff --git a/tests/TfLiteMobileNetSsd-Armnn/TfLiteMobileNetSsd-Armnn.cpp b/tests/TfLiteMobileNetSsd-Armnn/TfLiteMobileNetSsd-Armnn.cpp
new file mode 100644
index 0000000000..b1bc0f6120
--- /dev/null
+++ b/tests/TfLiteMobileNetSsd-Armnn/TfLiteMobileNetSsd-Armnn.cpp
@@ -0,0 +1,76 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+#include "../MobileNetSsdInferenceTest.hpp"
+
+#include "armnnTfLiteParser/ITfLiteParser.hpp"
+
+#include <algorithm>
+#include <iterator>
+
+using namespace armnnTfLiteParser;
+
+int main(int argc, char* argv[])
+{
+ int retVal = EXIT_FAILURE;
+ try
+ {
+ using DataType = float;
+ using Parser = armnnTfLiteParser::ITfLiteParser;
+ using Model = InferenceModel<Parser, DataType>;
+
+ armnn::TensorShape inputTensorShape({ 1, 300, 300, 3 });
+
+ std::vector<const char*> inputLayerNames =
+ {
+ "normalized_input_image_tensor"
+ };
+
+ std::vector<const char*> outputLayerNames =
+ {
+ "TFLite_Detection_PostProcess",
+ "TFLite_Detection_PostProcess:1",
+ "TFLite_Detection_PostProcess:2",
+ "TFLite_Detection_PostProcess:3"
+ };
+
+ retVal = InferenceTestMain(argc, argv, { 0 },
+ [&inputTensorShape, inputLayerNames, outputLayerNames]()
+ {
+ return make_unique<MobileNetSsdTestCaseProvider<Model>>(
+ [&]
+ (typename Model::CommandLineOptions modelOptions)
+ {
+ if (!ValidateDirectory(modelOptions.m_ModelDir))
+ {
+ return std::unique_ptr<Model>();
+ }
+
+ typename Model::Params modelParams;
+ modelParams.m_ModelPath =
+ modelOptions.m_ModelDir + "ssd_mobilenet_v1.tflite";
+
+ std::copy(inputLayerNames.begin(), inputLayerNames.end(),
+ std::back_inserter(modelParams.m_InputBindings));
+
+ std::copy(outputLayerNames.begin(), outputLayerNames.end(),
+ std::back_inserter(modelParams.m_OutputBindings));
+
+ modelParams.m_InputShapes = { inputTensorShape };
+ modelParams.m_IsModelBinary = true;
+ modelParams.m_ComputeDevice = modelOptions.m_ComputeDevice;
+ modelParams.m_VisualizePostOptimizationModel = modelOptions.m_VisualizePostOptimizationModel;
+ modelParams.m_EnableFp16TurboMode = modelOptions.m_EnableFp16TurboMode;
+
+ return std::make_unique<Model>(modelParams);
+ });
+ });
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "WARNING: " << *argv << ": An error has occurred when running "
+ "the classifier inference tests: " << e.what() << std::endl;
+ }
+ return retVal;
+}