mirror of
https://github.com/Qortal/Brooklyn.git
synced 2025-02-07 06:44:18 +00:00
238 lines
8.6 KiB
C++
238 lines
8.6 KiB
C++
//
|
|
// Copyright © 2017 Arm Ltd. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
#pragma once
|
|
|
|
#include "InferenceTest.hpp"
|
|
#include "YoloDatabase.hpp"
|
|
|
|
#include <armnn/utility/Assert.hpp>
|
|
#include <armnn/utility/IgnoreUnused.hpp>
|
|
#include <armnnUtils/FloatingPointComparison.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <utility>
|
|
|
|
constexpr size_t YoloOutputSize = 1470;
|
|
|
|
template <typename Model>
|
|
class YoloTestCase : public InferenceModelTestCase<Model>
|
|
{
|
|
public:
|
|
YoloTestCase(Model& model,
|
|
unsigned int testCaseId,
|
|
YoloTestCaseData& testCaseData)
|
|
: InferenceModelTestCase<Model>(model, testCaseId, { std::move(testCaseData.m_InputImage) }, { YoloOutputSize })
|
|
, m_TopObjectDetections(std::move(testCaseData.m_TopObjectDetections))
|
|
{
|
|
}
|
|
|
|
virtual TestCaseResult ProcessResult(const InferenceTestOptions& options) override
|
|
{
|
|
armnn::IgnoreUnused(options);
|
|
|
|
const std::vector<float>& output = mapbox::util::get<std::vector<float>>(this->GetOutputs()[0]);
|
|
ARMNN_ASSERT(output.size() == YoloOutputSize);
|
|
|
|
constexpr unsigned int gridSize = 7;
|
|
constexpr unsigned int numClasses = 20;
|
|
constexpr unsigned int numScales = 2;
|
|
|
|
const float* outputPtr = output.data();
|
|
|
|
// Range 0-980. Class probabilities. 7x7x20
|
|
vector<vector<vector<float>>> classProbabilities(gridSize, vector<vector<float>>(gridSize,
|
|
vector<float>(numClasses)));
|
|
for (unsigned int y = 0; y < gridSize; ++y)
|
|
{
|
|
for (unsigned int x = 0; x < gridSize; ++x)
|
|
{
|
|
for (unsigned int c = 0; c < numClasses; ++c)
|
|
{
|
|
classProbabilities[y][x][c] = *outputPtr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Range 980-1078. Scales. 7x7x2
|
|
vector<vector<vector<float>>> scales(gridSize, vector<vector<float>>(gridSize, vector<float>(numScales)));
|
|
for (unsigned int y = 0; y < gridSize; ++y)
|
|
{
|
|
for (unsigned int x = 0; x < gridSize; ++x)
|
|
{
|
|
for (unsigned int s = 0; s < numScales; ++s)
|
|
{
|
|
scales[y][x][s] = *outputPtr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Range 1078-1469. Bounding boxes. 7x7x2x4
|
|
constexpr float imageWidthAsFloat = static_cast<float>(YoloImageWidth);
|
|
constexpr float imageHeightAsFloat = static_cast<float>(YoloImageHeight);
|
|
|
|
vector<vector<vector<vector<float>>>> boxes(gridSize, vector<vector<vector<float>>>
|
|
(gridSize, vector<vector<float>>(numScales, vector<float>(4))));
|
|
for (unsigned int y = 0; y < gridSize; ++y)
|
|
{
|
|
for (unsigned int x = 0; x < gridSize; ++x)
|
|
{
|
|
for (unsigned int s = 0; s < numScales; ++s)
|
|
{
|
|
float bx = *outputPtr++;
|
|
float by = *outputPtr++;
|
|
float bw = *outputPtr++;
|
|
float bh = *outputPtr++;
|
|
|
|
boxes[y][x][s][0] = ((bx + static_cast<float>(x)) / 7.0f) * imageWidthAsFloat;
|
|
boxes[y][x][s][1] = ((by + static_cast<float>(y)) / 7.0f) * imageHeightAsFloat;
|
|
boxes[y][x][s][2] = bw * bw * static_cast<float>(imageWidthAsFloat);
|
|
boxes[y][x][s][3] = bh * bh * static_cast<float>(imageHeightAsFloat);
|
|
}
|
|
}
|
|
}
|
|
ARMNN_ASSERT(output.data() + YoloOutputSize == outputPtr);
|
|
|
|
std::vector<YoloDetectedObject> detectedObjects;
|
|
detectedObjects.reserve(gridSize * gridSize * numScales * numClasses);
|
|
|
|
for (unsigned int y = 0; y < gridSize; ++y)
|
|
{
|
|
for (unsigned int x = 0; x < gridSize; ++x)
|
|
{
|
|
for (unsigned int s = 0; s < numScales; ++s)
|
|
{
|
|
for (unsigned int c = 0; c < numClasses; ++c)
|
|
{
|
|
// Resolved confidence: class probabilities * scales.
|
|
const float confidence = classProbabilities[y][x][c] * scales[y][x][s];
|
|
|
|
// Resolves bounding box and stores.
|
|
YoloBoundingBox box;
|
|
box.m_X = boxes[y][x][s][0];
|
|
box.m_Y = boxes[y][x][s][1];
|
|
box.m_W = boxes[y][x][s][2];
|
|
box.m_H = boxes[y][x][s][3];
|
|
|
|
detectedObjects.emplace_back(c, box, confidence);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sorts detected objects by confidence.
|
|
std::sort(detectedObjects.begin(), detectedObjects.end(),
|
|
[](const YoloDetectedObject& a, const YoloDetectedObject& b)
|
|
{
|
|
// Sorts by largest confidence first, then by class.
|
|
return a.m_Confidence > b.m_Confidence
|
|
|| (a.m_Confidence == b.m_Confidence && a.m_Class > b.m_Class);
|
|
});
|
|
|
|
// Checks the top N detections.
|
|
auto outputIt = detectedObjects.begin();
|
|
auto outputEnd = detectedObjects.end();
|
|
|
|
for (const YoloDetectedObject& expectedDetection : m_TopObjectDetections)
|
|
{
|
|
if (outputIt == outputEnd)
|
|
{
|
|
// Somehow expected more things to check than detections found by the model.
|
|
return TestCaseResult::Abort;
|
|
}
|
|
|
|
const YoloDetectedObject& detectedObject = *outputIt;
|
|
if (detectedObject.m_Class != expectedDetection.m_Class)
|
|
{
|
|
ARMNN_LOG(error) << "Prediction for test case " << this->GetTestCaseId() <<
|
|
" is incorrect: Expected (" << expectedDetection.m_Class << ")" <<
|
|
" but predicted (" << detectedObject.m_Class << ")";
|
|
return TestCaseResult::Failed;
|
|
}
|
|
|
|
if (!armnnUtils::within_percentage_tolerance(detectedObject.m_Box.m_X, expectedDetection.m_Box.m_X) ||
|
|
!armnnUtils::within_percentage_tolerance(detectedObject.m_Box.m_Y, expectedDetection.m_Box.m_Y) ||
|
|
!armnnUtils::within_percentage_tolerance(detectedObject.m_Box.m_W, expectedDetection.m_Box.m_W) ||
|
|
!armnnUtils::within_percentage_tolerance(detectedObject.m_Box.m_H, expectedDetection.m_Box.m_H) ||
|
|
!armnnUtils::within_percentage_tolerance(detectedObject.m_Confidence, expectedDetection.m_Confidence))
|
|
{
|
|
ARMNN_LOG(error) << "Detected bounding box for test case " << this->GetTestCaseId() <<
|
|
" is incorrect";
|
|
return TestCaseResult::Failed;
|
|
}
|
|
|
|
++outputIt;
|
|
}
|
|
|
|
return TestCaseResult::Ok;
|
|
}
|
|
|
|
private:
|
|
std::vector<YoloDetectedObject> m_TopObjectDetections;
|
|
};
|
|
|
|
template <typename Model>
|
|
class YoloTestCaseProvider : public IInferenceTestCaseProvider
|
|
{
|
|
public:
|
|
template <typename TConstructModelCallable>
|
|
explicit YoloTestCaseProvider(TConstructModelCallable constructModel)
|
|
: m_ConstructModel(constructModel)
|
|
{
|
|
}
|
|
|
|
virtual void AddCommandLineOptions(cxxopts::Options& options, std::vector<std::string>& required) override
|
|
{
|
|
options
|
|
.allow_unrecognised_options()
|
|
.add_options()
|
|
("d,data-dir", "Path to directory containing test data", cxxopts::value<std::string>(m_DataDir));
|
|
|
|
Model::AddCommandLineOptions(options, m_ModelCommandLineOptions, required);
|
|
}
|
|
|
|
virtual bool ProcessCommandLineOptions(const InferenceTestOptions& commonOptions) override
|
|
{
|
|
if (!ValidateDirectory(m_DataDir))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_Model = m_ConstructModel(commonOptions, m_ModelCommandLineOptions);
|
|
if (!m_Model)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_Database = std::make_unique<YoloDatabase>(m_DataDir.c_str());
|
|
if (!m_Database)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual std::unique_ptr<IInferenceTestCase> GetTestCase(unsigned int testCaseId) override
|
|
{
|
|
std::unique_ptr<YoloTestCaseData> testCaseData = m_Database->GetTestCaseData(testCaseId);
|
|
if (!testCaseData)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return std::make_unique<YoloTestCase<Model>>(*m_Model, testCaseId, *testCaseData);
|
|
}
|
|
|
|
private:
|
|
typename Model::CommandLineOptions m_ModelCommandLineOptions;
|
|
std::function<std::unique_ptr<Model>(const InferenceTestOptions&,
|
|
typename Model::CommandLineOptions)> m_ConstructModel;
|
|
std::unique_ptr<Model> m_Model;
|
|
|
|
std::string m_DataDir;
|
|
std::unique_ptr<YoloDatabase> m_Database;
|
|
};
|