diff --git a/0003-add-more-test-command-line-arguments.patch b/0003-add-more-test-command-line-arguments.patch new file mode 100644 index 0000000..515c01e --- /dev/null +++ b/0003-add-more-test-command-line-arguments.patch @@ -0,0 +1,74 @@ +From 964cb82f3b811aec6663255ab0aa589f0a3be0ee Mon Sep 17 00:00:00 2001 +From: Qin Su +Date: Fri, 22 Feb 2019 14:10:07 -0500 +Subject: [PATCH] add more test command line arguments + +Upstream-Status: Inappropriate [TI only test code] +Signed-off-by: Qin Su +--- + tests/InferenceTest.inl | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 49 insertions(+) + +diff --git a/tests/InferenceTest.inl b/tests/InferenceTest.inl +index 538720b..6fd21b8 100644 +--- a/tests/InferenceTest.inl ++++ b/tests/InferenceTest.inl +@@ -326,6 +326,55 @@ int ClassifierInferenceTestMain(int argc, + BOOST_ASSERT(modelFilename); + BOOST_ASSERT(inputBindingName); + BOOST_ASSERT(outputBindingName); ++ int count; ++ const char *p_input; ++ char inmodelname[500]; ++ char outtensorname[500]; ++ ++ /* parse command line */ ++ for (count = 1; count < argc; count++) ++ { ++ if (*(argv[count]) == '+') ++ { ++ p_input = argv[count] + 1; ++ switch (*(p_input)) ++ { ++ case 'i': ++ case 'I': ++ strcpy(inmodelname, p_input + 2); ++ modelFilename = &inmodelname[0]; ++ std::cout << "Input model = " << modelFilename << std::endl; ++ break; ++ case 'o': ++ case 'O': ++ strcpy(outtensorname, p_input + 2); ++ outputBindingName = &outtensorname[0]; ++ std::cout << "out tensor name = " << outputBindingName << std::endl; ++ break; ++ default: ++ break; ++ } ++ } ++ else if (*(argv[count]) == '-') ++ { ++ p_input = argv[count] + 1; ++ switch (*(p_input)) ++ { ++ case '-': ++ p_input = argv[count] + 2; ++ case 'h': ++ case 'H': ++ std::cout <<"\nAdditional Options: " << std::endl; ++ std::cout <<" +i Set user specified inference model name." << std::endl; ++ std::cout <<" If not set, default name is used." << std::endl; ++ std::cout <<" +o Set user specified output tensor name." << std::endl; ++ std::cout <<" If not set, default name is used.\n" << std::endl; ++ break; ++ default: ++ break; ++ } ++ } ++ } + + return InferenceTestMain(argc, argv, defaultTestCaseIds, + [=] +-- +1.9.1 + diff --git a/0005-add-armnn-mobilenet-test-example.patch b/0005-add-armnn-mobilenet-test-example.patch new file mode 100644 index 0000000..df29462 --- /dev/null +++ b/0005-add-armnn-mobilenet-test-example.patch @@ -0,0 +1,68 @@ +From 99a6c339f1828d3cd1b193cf702bada9011d900b Mon Sep 17 00:00:00 2001 +From: Djordje Senicic +Date: Mon, 24 Jun 2019 14:29:19 -0400 +Subject: [PATCH] add armnn mobilenet test example + +Upstream-Status: Inappropriate [TI only test code] +Signed-off-by: Qin Su +Signed-off-by: Djordje Senicic +--- + tests/CMakeLists.txt | 41 +++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 41 insertions(+) + +diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt +index dfcf4b48..5a78d3a6 100644 +--- a/tests/CMakeLists.txt ++++ b/tests/CMakeLists.txt +@@ -1,3 +1,6 @@ ++find_package( OpenCV REQUIRED ) ++include_directories( ${OpenCV_INCLUDE_DIRS} ) ++ + # UnitTests + include(CheckIncludeFiles) + +@@ -348,3 +351,41 @@ if(BUILD_ARMNN_QUANTIZER) + add_executable_ex(ImageCSVFileGenerator ${ImageCSVFileGenerator_sources}) + ImageTensorExecutor(ImageCSVFileGenerator) + endif() ++ ++if (BUILD_ARMNN_EXAMPLES) ++ set(ArmnnExamples_sources ++ ArmnnExamples/ArmnnExamples.cpp) ++ ++ add_executable_ex(ArmnnExamples ${ArmnnExamples_sources}) ++ ++ target_include_directories(ArmnnExamples PRIVATE ../src/armnnUtils) ++ target_include_directories(ArmnnExamples PRIVATE ../src/armnn) ++ target_include_directories(ArmnnExamples PRIVATE ../src/backends) ++ ++ if (BUILD_CAFFE_PARSER) ++ target_link_libraries(ArmnnExamples armnnCaffeParser) ++ endif() ++ if (BUILD_TF_PARSER) ++ target_link_libraries(ArmnnExamples armnnTfParser) ++ endif() ++ ++ if (BUILD_TF_LITE_PARSER) ++ target_link_libraries(ArmnnExamples armnnTfLiteParser) ++ endif() ++ if (BUILD_ONNX_PARSER) ++ target_link_libraries(ArmnnExamples armnnOnnxParser) ++ endif() ++ ++ target_link_libraries(ArmnnExamples armnn) ++ target_link_libraries(ArmnnExamples ${CMAKE_THREAD_LIBS_INIT}) ++ if(OPENCL_LIBRARIES) ++ target_link_libraries(ArmnnExamples ${OPENCL_LIBRARIES}) ++ endif() ++ ++ target_link_libraries(ArmnnExamples ++ ${Boost_SYSTEM_LIBRARY} ++ ${Boost_FILESYSTEM_LIBRARY} ++ ${Boost_PROGRAM_OPTIONS_LIBRARY} ++ ${OpenCV_LIBS}) ++ addDllCopyCommands(ArmnnExamples) ++endif() +-- +2.17.1 + diff --git a/0006-armnn-mobilenet-test-example.patch b/0006-armnn-mobilenet-test-example.patch new file mode 100644 index 0000000..e226c60 --- /dev/null +++ b/0006-armnn-mobilenet-test-example.patch @@ -0,0 +1,675 @@ +From 4d5e7db268a4f816e24449e8ad011e35890f0c7e Mon Sep 17 00:00:00 2001 +From: Qin Su +Date: Fri, 22 Feb 2019 13:39:09 -0500 +Subject: [PATCH] armnn mobilenet test example + +Upstream-Status: Inappropriate [TI only test code] +Signed-off-by: Qin Su +--- + tests/ArmnnExamples/ArmnnExamples.cpp | 654 ++++++++++++++++++++++++++++++++++ + 1 file changed, 654 insertions(+) + create mode 100644 tests/ArmnnExamples/ArmnnExamples.cpp + +diff --git a/tests/ArmnnExamples/ArmnnExamples.cpp b/tests/ArmnnExamples/ArmnnExamples.cpp +new file mode 100644 +index 0000000..53a11cc +--- /dev/null ++++ b/tests/ArmnnExamples/ArmnnExamples.cpp +@@ -0,0 +1,654 @@ ++/****************************************************************************** ++ * Copyright (c) 2018, Texas Instruments Incorporated - http://www.ti.com/ ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * * Neither the name of Texas Instruments Incorporated nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ++ * THE POSSIBILITY OF SUCH DAMAGE. ++ *****************************************************************************/// ++// Copyright © 2017 Arm Ltd. All rights reserved. ++// See LICENSE file in the project root for full license information. ++// ++#include ++ ++#include ++#include ++ ++#if defined(ARMNN_CAFFE_PARSER) ++#include "armnnCaffeParser/ICaffeParser.hpp" ++#endif ++#if defined(ARMNN_TF_PARSER) ++#include "armnnTfParser/ITfParser.hpp" ++#endif ++#if defined(ARMNN_TF_LITE_PARSER) ++#include "armnnTfLiteParser/ITfLiteParser.hpp" ++#endif ++#if defined(ARMNN_ONNX_PARSER) ++#include "armnnOnnxParser/IOnnxParser.hpp" ++#endif ++#include "CsvReader.hpp" ++#include "../InferenceTest.hpp" ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include "opencv2/core.hpp" ++#include "opencv2/imgproc.hpp" ++#include "opencv2/highgui.hpp" ++#include "opencv2/videoio.hpp" ++#include ++ ++using namespace cv; ++ ++#define INPUT_IMAGE 0 ++#define INPUT_VIDEO 1 ++#define INPUT_CAMERA 2 ++ ++Mat test_image; ++Rect rectCrop; ++ ++time_point predictStart; ++time_point predictEnd; ++ ++void imagenetCallBackFunc(int event, int x, int y, int flags, void* userdata) ++{ ++ if ( event == EVENT_RBUTTONDOWN ) ++ { ++ std::cout << "Right button of the mouse is clicked - position (" << x << ", " << y << ")" << " ... prepare to exit!" << std::endl; ++ exit(0); ++ } ++} ++ ++inline float Lerpfloat(float a, float b, float w) ++{ ++ return w * b + (1.f - w) * a; ++} ++ ++// Load a single image ++struct ImageData ++{ ++ unsigned int m_width; ++ unsigned int m_height; ++ unsigned int m_chnum; ++ unsigned int m_size; ++ std::vector m_image; ++}; ++// Load a single image ++std::unique_ptr loadImageData(std::string image_path, VideoCapture &cap, cv::Mat img, int input_type) ++{ ++ //cv::Mat img; ++ if (input_type == INPUT_IMAGE) ++ { ++ /* use OpenCV to get the image */ ++ img = cv::imread(image_path, CV_LOAD_IMAGE_COLOR); ++ } ++ cv::cvtColor(img, img, CV_BGR2RGB); //convert image format from BGR(openCV format) to RGB (armnn required format). ++ ++ // store image and label in output Image ++ std::unique_ptr ret(new ImageData); ++ ret->m_width = static_cast(img.cols); ++ ret->m_height = static_cast(img.rows); ++ ret->m_chnum = static_cast(img.channels()); ++ ret->m_size = static_cast(img.cols*img.rows*img.channels()); ++ ret->m_image.resize(ret->m_size); ++ ++ for (unsigned int i = 0; i < ret->m_size; i++) ++ { ++ ret->m_image[i] = static_cast(img.data[i]); ++ } ++ return ret; ++} ++// to resize input tensor size ++std::vector ResizeBilinear(std::vector input, ++ const unsigned int inWidth, ++ const unsigned int inHeight, ++ const unsigned int inChnum, ++ const unsigned int outputWidth, ++ const unsigned int outputHeight) ++{ ++ std::vector out; ++ out.resize(outputWidth * outputHeight * 3); ++ ++ // We follow the definition of TensorFlow and AndroidNN: the top-left corner of a texel in the output ++ // image is projected into the input image to figure out the interpolants and weights. Note that this ++ // will yield different results than if projecting the centre of output texels. ++ ++ const unsigned int inputWidth = inWidth; ++ const unsigned int inputHeight = inHeight; ++ ++ // How much to scale pixel coordinates in the output image to get the corresponding pixel coordinates ++ // in the input image. ++ const float scaleY = boost::numeric_cast(inputHeight) / boost::numeric_cast(outputHeight); ++ const float scaleX = boost::numeric_cast(inputWidth) / boost::numeric_cast(outputWidth); ++ ++ uint8_t rgb_x0y0[3]; ++ uint8_t rgb_x1y0[3]; ++ uint8_t rgb_x0y1[3]; ++ uint8_t rgb_x1y1[3]; ++ unsigned int pixelOffset00, pixelOffset10, pixelOffset01, pixelOffset11; ++ for (unsigned int y = 0; y < outputHeight; ++y) ++ { ++ // Corresponding real-valued height coordinate in input image. ++ const float iy = boost::numeric_cast(y) * scaleY; ++ // Discrete height coordinate of top-left texel (in the 2x2 texel area used for interpolation). ++ const float fiy = floorf(iy); ++ const unsigned int y0 = boost::numeric_cast(fiy); ++ ++ // Interpolation weight (range [0,1]) ++ const float yw = iy - fiy; ++ ++ for (unsigned int x = 0; x < outputWidth; ++x) ++ { ++ // Real-valued and discrete width coordinates in input image. ++ const float ix = boost::numeric_cast(x) * scaleX; ++ const float fix = floorf(ix); ++ const unsigned int x0 = boost::numeric_cast(fix); ++ ++ // Interpolation weight (range [0,1]). ++ const float xw = ix - fix; ++ ++ // Discrete width/height coordinates of texels below and to the right of (x0, y0). ++ const unsigned int x1 = std::min(x0 + 1, inputWidth - 1u); ++ const unsigned int y1 = std::min(y0 + 1, inputHeight - 1u); ++ ++ pixelOffset00 = x0 * inChnum + y0 * inputWidth * inChnum; ++ pixelOffset10 = x1 * inChnum + y0 * inputWidth * inChnum; ++ pixelOffset01 = x0 * inChnum + y1 * inputWidth * inChnum; ++ pixelOffset11 = x1 * inChnum + y1 * inputWidth * inChnum; ++ for (unsigned int c = 0; c < 3; ++c) ++ { ++ rgb_x0y0[c] = input[pixelOffset00+c]; ++ rgb_x1y0[c] = input[pixelOffset10+c]; ++ rgb_x0y1[c] = input[pixelOffset01+c]; ++ rgb_x1y1[c] = input[pixelOffset11+c]; ++ } ++ ++ for (unsigned c=0; c<3; ++c) ++ { ++ const float ly0 = Lerpfloat(float(rgb_x0y0[c]), float(rgb_x1y0[c]), xw); ++ const float ly1 = Lerpfloat(float(rgb_x0y1[c]), float(rgb_x1y1[c]), xw); ++ const float l = Lerpfloat(ly0, ly1, yw); ++ out[(3*((y*outputWidth)+x)) + c] = static_cast(l)/255.0f; ++ } ++ } ++ } ++ return out; ++} ++ ++namespace ++{ ++ ++ // Configure boost::program_options for command-line parsing and validation. ++ namespace po = boost::program_options; ++ ++ template ++ std::vector ParseArrayImpl(std::istream& stream, TParseElementFunc parseElementFunc) ++ { ++ std::vector result; ++ // Processes line-by-line. ++ std::string line; ++ while (std::getline(stream, line)) ++ { ++ std::vector tokens; ++ try ++ { ++ // Coverity fix: boost::split() may throw an exception of type boost::bad_function_call. ++ boost::split(tokens, line, boost::algorithm::is_any_of("\t ,;:"), boost::token_compress_on); ++ } ++ catch (const std::exception& e) ++ { ++ BOOST_LOG_TRIVIAL(error) << "An error occurred when splitting tokens: " << e.what(); ++ continue; ++ } ++ for (const std::string& token : tokens) ++ { ++ if (!token.empty()) ++ { ++ try ++ { ++ result.push_back(parseElementFunc(token)); ++ } ++ catch (const std::exception&) ++ { ++ BOOST_LOG_TRIVIAL(error) << "'" << token << "' is not a valid number. It has been ignored."; ++ } ++ } ++ } ++ } ++ ++ return result; ++ } ++ ++ template ++ std::vector ParseArray(std::istream& stream); ++ template<> ++ std::vector ParseArray(std::istream& stream) ++ { ++ return ParseArrayImpl(stream, ++ [](const std::string& s) { return boost::numeric_cast(std::stoi(s)); }); ++ } ++ void RemoveDuplicateDevices(std::vector& computeDevices) ++ { ++ // Mark the duplicate devices as 'Undefined'. ++ for (auto i = computeDevices.begin(); i != computeDevices.end(); ++i) ++ { ++ for (auto j = std::next(i); j != computeDevices.end(); ++j) ++ { ++ if (*j == *i) ++ { ++ *j = armnn::Compute::Undefined; ++ } ++ } ++ } ++ ++ // Remove 'Undefined' devices. ++ computeDevices.erase(std::remove(computeDevices.begin(), computeDevices.end(), armnn::Compute::Undefined), ++ computeDevices.end()); ++ } ++} // namespace ++ ++template ++int MainImpl(const char* modelPath, ++ bool isModelBinary, ++ const std::vector& computeDevices, ++ const char* inputName, ++ const armnn::TensorShape* inputTensorShape, ++ const char* inputTensorDataFilePath, ++ const char* outputName, ++ bool enableProfiling, ++ const size_t number_frame, ++ const std::shared_ptr& runtime = nullptr) ++{ ++ // Loads input tensor. ++ std::vector input; ++ std::vector input_resized; ++ using TContainer = boost::variant, std::vector, std::vector>; ++ ++ try ++ { ++ // Creates an InferenceModel, which will parse the model and load it into an IRuntime. ++ typename InferenceModel::Params params; ++ //const armnn::TensorShape inputTensorShape({ 1, 224, 224 3}); ++ ++ params.m_ModelPath = modelPath; ++ params.m_IsModelBinary = isModelBinary; ++ params.m_ComputeDevices = computeDevices; ++ params.m_InputBindings = { inputName }; ++ params.m_InputShapes = { *inputTensorShape }; ++ params.m_OutputBindings = { outputName }; ++ //params.m_EnableProfiling = enableProfiling; ++ params.m_SubgraphId = 0; ++ InferenceModel model(params, enableProfiling, runtime); ++ ++ VideoCapture cap; ++ int input_type = INPUT_IMAGE; ++ std::string filename = inputTensorDataFilePath; ++ ++ size_t i = filename.rfind("camera_live_input", filename.length()); ++ if (i != string::npos) ++ { ++ cap = VideoCapture(1); ++ namedWindow("ARMNN MobileNet Example", WINDOW_AUTOSIZE | CV_GUI_NORMAL); ++ input_type = INPUT_CAMERA; //camera input ++ } ++ else if((filename.substr(filename.find_last_of(".") + 1) == "mp4") || ++ (filename.substr(filename.find_last_of(".") + 1) == "mov") || ++ (filename.substr(filename.find_last_of(".") + 1) == "avi") ) ++ { ++ cap = VideoCapture(inputTensorDataFilePath); ++ if (! cap.isOpened()) ++ { ++ std::cout << "Cannot open video input: " << inputTensorDataFilePath << std::endl; ++ return (-1); ++ } ++ ++ namedWindow("ARMNN MobileNet Example", WINDOW_AUTOSIZE | CV_GUI_NORMAL); ++ input_type = INPUT_VIDEO; //video clip input ++ } ++ if (input_type != INPUT_IMAGE) ++ { ++ //set the callback function for any mouse event. Used for right click mouse to exit the program. ++ setMouseCallback("ARMNN MobileNet Example", imagenetCallBackFunc, NULL); ++ } ++ ++ for (unsigned int i=0; i < number_frame; i++) ++ { ++ if (input_type != INPUT_IMAGE) ++ { ++ cap.grab(); ++ cap.retrieve(test_image); ++ } ++ std::unique_ptr inputData = loadImageData(inputTensorDataFilePath, cap, test_image, input_type); ++ input.resize(inputData->m_size); ++ ++ input = std::move(inputData->m_image); ++ input_resized = ResizeBilinear(input, inputData->m_width, inputData->m_height, inputData->m_chnum, 224, 224); ++ ++ // Set up input data container ++ std::vector inputDataContainer(1, std::move(input_resized)); ++ ++ // Set up output data container ++ std::vector outputDataContainers; ++ outputDataContainers.push_back(std::vector(model.GetOutputSize())); ++ ++ //profile start ++ predictStart = high_resolution_clock::now(); ++ // Execute model ++ model.Run(inputDataContainer, outputDataContainers); ++ //profile end ++ predictEnd = high_resolution_clock::now(); ++ ++ double timeTakenS = duration(predictEnd - predictStart).count(); ++ double preformance_ret = static_cast(1.0/timeTakenS); ++ ++ //retrieve output ++ std::vector& outputData = (boost::get>(outputDataContainers[0])); ++ //output TOP predictions ++ std::string predict_target_name; ++ // find the out with the highest confidence ++ int label = static_cast(std::distance(outputData.begin(), std::max_element(outputData.begin(), outputData.end()))); ++ std::fstream file("/usr/share/arm/armnn/models/labels.txt"); ++ //std::string predict_target_name; ++ for (int i=0; i <= label; i++) ++ { ++ std::getline(file, predict_target_name); ++ } ++ //get the probability of the top prediction ++ float prob = 100*outputData.data()[label]; ++ //clean the top one so as to find the second top prediction ++ outputData.data()[label] = 0; ++ std::cout << "Top(1) prediction is " << predict_target_name << " with confidence: " << prob << "%" << std::endl; ++ //output next TOP 4 predictions ++ for (int ii=1; ii<5; ii++) ++ { ++ std::string predict_target_name_n; ++ // find the out with the highest confidence ++ int label = static_cast(std::distance(outputData.begin(), std::max_element(outputData.begin(), outputData.end()))); ++ std::fstream file("/usr/share/arm/armnn/models/labels.txt"); ++ //std::string predict_target_name; ++ for (int i=0; i <= label; i++) ++ { ++ std::getline(file, predict_target_name_n); ++ } ++ //get the probability of the prediction ++ float prob = 100*outputData.data()[label]; ++ //clean the top one so as to find the second top prediction ++ outputData.data()[label] = 0; ++ ++ std::cout << "Top(" << (ii+1) << ") prediction is " << predict_target_name_n << " with confidence: " << prob << "%" << std::endl; ++ } ++ std::cout << "Performance (FPS): " << preformance_ret << std::endl; ++ ++ if (input_type != INPUT_IMAGE) ++ { ++ //convert image format back to BGR for OpenCV imshow from RGB format required by armnn. ++ cv::cvtColor(test_image, test_image, CV_RGB2BGR); ++ // output identified object name on top of input image ++ cv::putText(test_image, predict_target_name, ++ cv::Point(rectCrop.x + 5,rectCrop.y + 20), // Coordinates ++ cv::FONT_HERSHEY_COMPLEX_SMALL, // Font ++ 1.0, // Scale. 2.0 = 2x bigger ++ cv::Scalar(0,0,255), // Color ++ 1, // Thickness ++ 8); // Line type ++ ++ // output preformance in FPS on top of input image ++ std::string preformance_ret_string = "Performance (FPS): " + boost::lexical_cast(preformance_ret); ++ cv::putText(test_image, preformance_ret_string, ++ cv::Point(rectCrop.x + 5,rectCrop.y + 40), // Coordinates ++ cv::FONT_HERSHEY_COMPLEX_SMALL, // Font ++ 1.0, // Scale. 2.0 = 2x bigger ++ cv::Scalar(0,0,255), // Color ++ 1, // Thickness ++ 8); // Line type ++ ++ cv::imshow("ARMNN MobileNet Example", test_image); ++ waitKey(2); ++ } ++ } ++ } ++ catch (armnn::Exception const& e) ++ { ++ BOOST_LOG_TRIVIAL(fatal) << "Armnn Error: " << e.what(); ++ return EXIT_FAILURE; ++ } ++ return EXIT_SUCCESS; ++} ++ ++// This will run a test ++int RunTest(const std::string& modelFormat, ++ const std::string& inputTensorShapeStr, ++ const vector& computeDevice, ++ const std::string& modelPath, ++ const std::string& inputName, ++ const std::string& inputTensorDataFilePath, ++ const std::string& outputName, ++ bool enableProfiling, ++ const size_t subgraphId, ++ const std::shared_ptr& runtime = nullptr) ++{ ++ // Parse model binary flag from the model-format string we got from the command-line ++ bool isModelBinary; ++ if (modelFormat.find("bin") != std::string::npos) ++ { ++ isModelBinary = true; ++ } ++ else if (modelFormat.find("txt") != std::string::npos || modelFormat.find("text") != std::string::npos) ++ { ++ isModelBinary = false; ++ } ++ else ++ { ++ BOOST_LOG_TRIVIAL(fatal) << "Unknown model format: '" << modelFormat << "'. Please include 'binary' or 'text'"; ++ return EXIT_FAILURE; ++ } ++ ++ // Parse input tensor shape from the string we got from the command-line. ++ std::unique_ptr inputTensorShape; ++ if (!inputTensorShapeStr.empty()) ++ { ++ std::stringstream ss(inputTensorShapeStr); ++ std::vector dims = ParseArray(ss); ++ try ++ { ++ // Coverity fix: An exception of type armnn::InvalidArgumentException is thrown and never caught. ++ inputTensorShape = std::make_unique(dims.size(), dims.data()); ++ } ++ catch (const armnn::InvalidArgumentException& e) ++ { ++ BOOST_LOG_TRIVIAL(fatal) << "Cannot create tensor shape: " << e.what(); ++ return EXIT_FAILURE; ++ } ++ } ++ // Forward to implementation based on the parser type ++ if (modelFormat.find("caffe") != std::string::npos) ++ { ++#if defined(ARMNN_CAFFE_PARSER) ++ return MainImpl(modelPath.c_str(), isModelBinary, computeDevice, ++ inputName.c_str(), inputTensorShape.get(), ++ inputTensorDataFilePath.c_str(), outputName.c_str(), ++ enableProfiling, subgraphId, runtime); ++#else ++ BOOST_LOG_TRIVIAL(fatal) << "Not built with Caffe parser support."; ++ return EXIT_FAILURE; ++#endif ++ } ++ else if (modelFormat.find("onnx") != std::string::npos) ++ { ++#if defined(ARMNN_ONNX_PARSER) ++ return MainImpl(modelPath.c_str(), isModelBinary, computeDevice, ++ inputName.c_str(), inputTensorShape.get(), ++ inputTensorDataFilePath.c_str(), outputName.c_str(), ++ enableProfiling, subgraphId, runtime); ++#else ++ BOOST_LOG_TRIVIAL(fatal) << "Not built with Onnx parser support."; ++ return EXIT_FAILURE; ++#endif ++ } ++ else if (modelFormat.find("tensorflow") != std::string::npos) ++ { ++#if defined(ARMNN_TF_PARSER) ++ return MainImpl(modelPath.c_str(), isModelBinary, computeDevice, ++ inputName.c_str(), inputTensorShape.get(), ++ inputTensorDataFilePath.c_str(), outputName.c_str(), ++ enableProfiling, subgraphId, runtime); ++#else ++ BOOST_LOG_TRIVIAL(fatal) << "Not built with Tensorflow parser support."; ++ return EXIT_FAILURE; ++#endif ++ } ++ else if(modelFormat.find("tflite") != std::string::npos) ++ { ++#if defined(ARMNN_TF_LITE_PARSER) ++ if (! isModelBinary) ++ { ++ BOOST_LOG_TRIVIAL(fatal) << "Unknown model format: '" << modelFormat << "'. Only 'binary' format supported \ ++ for tflite files"; ++ return EXIT_FAILURE; ++ } ++ return MainImpl(modelPath.c_str(), isModelBinary, computeDevice, ++ inputName.c_str(), inputTensorShape.get(), ++ inputTensorDataFilePath.c_str(), outputName.c_str(), ++ enableProfiling, subgraphId, runtime); ++#else ++ BOOST_LOG_TRIVIAL(fatal) << "Unknown model format: '" << modelFormat << ++ "'. Please include 'caffe', 'tensorflow', 'tflite' or 'onnx'"; ++ return EXIT_FAILURE; ++#endif ++ } ++ else ++ { ++ BOOST_LOG_TRIVIAL(fatal) << "Unknown model format: '" << modelFormat << ++ "'. Please include 'caffe', 'tensorflow', 'tflite' or 'onnx'"; ++ return EXIT_FAILURE; ++ } ++} ++ ++int main(int argc, const char* argv[]) ++{ ++ // Configures logging for both the ARMNN library and this test program. ++#ifdef NDEBUG ++ armnn::LogSeverity level = armnn::LogSeverity::Info; ++#else ++ armnn::LogSeverity level = armnn::LogSeverity::Debug; ++#endif ++ armnn::ConfigureLogging(true, true, level); ++ armnnUtils::ConfigureLogging(boost::log::core::get().get(), true, true, level); ++ ++ std::string testCasesFile; ++ ++ std::string modelFormat = "tensorflow-binary"; ++ std::string modelPath = "/usr/share/arm/armnn/models/mobilenet_v1_1.0_224_frozen.pb"; ++ std::string inputName = "input"; ++ std::string inputTensorShapeStr = "1 224 224 3"; ++ std::string inputTensorDataFilePath = "/usr/share/arm/armnn/testvecs/test2.mp4"; ++ std::string outputName = "MobilenetV1/Predictions/Reshape_1"; ++ std::vector computeDevices = {armnn::Compute::CpuAcc}; ++ // Catch ctrl-c to ensure a clean exit ++ signal(SIGABRT, exit); ++ signal(SIGTERM, exit); ++ ++ if (argc == 1) ++ { ++ return RunTest(modelFormat, inputTensorShapeStr, computeDevices, ++ modelPath, inputName, inputTensorDataFilePath, outputName, false, 1000); ++ } ++ else ++ { ++ size_t subgraphId = 0; ++ po::options_description desc("Options"); ++ try ++ { ++ desc.add_options() ++ ("help", "Display usage information") ++ ("test-cases,t", po::value(&testCasesFile), "Path to a CSV file containing test cases to run. " ++ "If set, further parameters -- with the exception of compute device and concurrency -- will be ignored, " ++ "as they are expected to be defined in the file for each test in particular.") ++ ("concurrent,n", po::bool_switch()->default_value(false), ++ "Whether or not the test cases should be executed in parallel") ++ ("model-format,f", po::value(&modelFormat), ++ "caffe-binary, caffe-text, onnx-binary, onnx-text, tflite-binary, tensorflow-binary or tensorflow-text.") ++ ("model-path,m", po::value(&modelPath), "Path to model file, e.g. .caffemodel, .prototxt," ++ " .tflite, .onnx") ++ ("compute,c", po::value>()->multitoken(), ++ "The preferred order of devices to run layers on by default. Possible choices: CpuAcc, CpuRef, GpuAcc") ++ ("input-name,i", po::value(&inputName), "Identifier of the input tensor in the network.") ++ ("input-tensor-shape,s", po::value(&inputTensorShapeStr), ++ "The shape of the input tensor in the network as a flat array of integers separated by whitespace. " ++ "This parameter is optional, depending on the network.") ++ ("input-tensor-data,d", po::value(&inputTensorDataFilePath), ++ "Input test file name. It can be image/video clip file name or use 'camera_live_input' to select camera input.") ++ ("output-name,o", po::value(&outputName), "Identifier of the output tensor in the network.") ++ ("event-based-profiling,e", po::bool_switch()->default_value(false), ++ "Enables built in profiler. If unset, defaults to off.") ++ ("number_frame", po::value(&subgraphId)->default_value(1), "Number of frames to process."); ++ } ++ catch (const std::exception& e) ++ { ++ // Coverity points out that default_value(...) can throw a bad_lexical_cast, ++ // and that desc.add_options() can throw boost::io::too_few_args. ++ // They really won't in any of these cases. ++ BOOST_ASSERT_MSG(false, "Caught unexpected exception"); ++ BOOST_LOG_TRIVIAL(fatal) << "Fatal internal error: " << e.what(); ++ return EXIT_FAILURE; ++ } ++ ++ // Parses the command-line. ++ po::variables_map vm; ++ try ++ { ++ po::store(po::parse_command_line(argc, argv, desc), vm); ++ po::notify(vm); ++ } ++ catch (const po::error& e) ++ { ++ std::cerr << e.what() << std::endl << std::endl; ++ std::cerr << desc << std::endl; ++ return EXIT_FAILURE; ++ } ++ ++ // Run single test ++ // Get the preferred order of compute devices. ++ std::vector computeDevices = vm["compute"].as>(); ++ bool enableProfiling = vm["event-based-profiling"].as(); ++ ++ // Remove duplicates from the list of compute devices. ++ RemoveDuplicateDevices(computeDevices); ++ ++ return RunTest(modelFormat, inputTensorShapeStr, computeDevices, ++ modelPath, inputName, inputTensorDataFilePath, outputName, enableProfiling, subgraphId); ++ } ++} ++ +-- +1.9.1 + diff --git a/0009-command-line-options-for-video-port-selection.patch b/0009-command-line-options-for-video-port-selection.patch new file mode 100644 index 0000000..bb6b505 --- /dev/null +++ b/0009-command-line-options-for-video-port-selection.patch @@ -0,0 +1,60 @@ +From ee152f3b68f91c5fff336306d011becdcf3a6b17 Mon Sep 17 00:00:00 2001 +From: Djordje Senicic +Date: Sat, 24 Aug 2019 17:58:38 -0400 +Subject: [PATCH] command line options for video port selection + +- Add command line selection <0|1|2|3> of video port used for live camera input + +Upstream-Status: Inappropriate [TI only test code] + +Signed-off-by: Djordje Senicic +--- + tests/ArmnnExamples/ArmnnExamples.cpp | 23 ++++++++++++++++++++--- + 1 file changed, 20 insertions(+), 3 deletions(-) + +diff --git a/tests/ArmnnExamples/ArmnnExamples.cpp b/tests/ArmnnExamples/ArmnnExamples.cpp +index 638fc145..d1526539 100644 +--- a/tests/ArmnnExamples/ArmnnExamples.cpp ++++ b/tests/ArmnnExamples/ArmnnExamples.cpp +@@ -316,10 +316,27 @@ int MainImpl(const char* modelPath, + int input_type = INPUT_IMAGE; + std::string filename = inputTensorDataFilePath; + +- size_t i = filename.rfind("camera_live_input", filename.length()); ++ size_t i = filename.rfind("camera_live_input", filename.length()); + if (i != string::npos) + { +- cap = VideoCapture(1); ++ int vport = 1; ++ size_t loc_i = filename.rfind("camera_live_input0", filename.length()); ++ if(loc_i != string::npos) vport = 0; ++ else { ++ loc_i = filename.rfind("camera_live_input1", filename.length()); ++ if(loc_i != string::npos) vport = 1; ++ else { ++ loc_i = filename.rfind("camera_live_input2", filename.length()); ++ if(loc_i != string::npos) vport = 2; ++ else { ++ loc_i = filename.rfind("camera_live_input3", filename.length()); ++ if(loc_i != string::npos) vport = 3; ++ else std::cout << "Setting ports beyond 3 not supported - using default!" << std::endl; ++ } ++ } ++ } ++ std::cout << "Using video" << vport << std::endl; ++ cap = VideoCapture(vport); + namedWindow("ARMNN MobileNet Example", WINDOW_AUTOSIZE | CV_GUI_NORMAL); + input_type = INPUT_CAMERA; //camera input + } +@@ -609,7 +626,7 @@ int main(int argc, const char* argv[]) + "The shape of the input tensor in the network as a flat array of integers separated by whitespace. " + "This parameter is optional, depending on the network.") + ("input-tensor-data,d", po::value(&inputTensorDataFilePath), +- "Input test file name. It can be image/video clip file name or use 'camera_live_input' to select camera input.") ++ "Input test file name. It can be image/video clip file name or 'camera_live_input or camera_live_input<0|1|2|3>' to select camera input.") + ("output-name,o", po::value(&outputName), "Identifier of the output tensor in the network.") + ("event-based-profiling,e", po::bool_switch()->default_value(false), + "Enables built in profiler. If unset, defaults to off.") +-- +2.17.1 + diff --git a/0010-armnnexamples-update-for-19.08-modifications.patch b/0010-armnnexamples-update-for-19.08-modifications.patch new file mode 100644 index 0000000..60f36a4 --- /dev/null +++ b/0010-armnnexamples-update-for-19.08-modifications.patch @@ -0,0 +1,28 @@ +From a3e266a2de7c45116428f4e21645a2657534191b Mon Sep 17 00:00:00 2001 +From: Djordje Senicic +Date: Mon, 26 Aug 2019 03:51:39 -0400 +Subject: [PATCH] armnnexamples: update for 19.08 modifications + +Upstream-Status: Inappropriate [TI only test code] + +Signed-off-by: Djordje Senicic +--- + tests/ArmnnExamples/ArmnnExamples.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/ArmnnExamples/ArmnnExamples.cpp b/tests/ArmnnExamples/ArmnnExamples.cpp +index d1526539..c10a4fc0 100644 +--- a/tests/ArmnnExamples/ArmnnExamples.cpp ++++ b/tests/ArmnnExamples/ArmnnExamples.cpp +@@ -310,7 +310,7 @@ int MainImpl(const char* modelPath, + params.m_OutputBindings = { outputName }; + //params.m_EnableProfiling = enableProfiling; + params.m_SubgraphId = 0; +- InferenceModel model(params, enableProfiling, runtime); ++ InferenceModel model(params, enableProfiling, "", runtime); + + VideoCapture cap; + int input_type = INPUT_IMAGE; +-- +2.17.1 + diff --git a/armnn-fix_find_opencv.patch b/armnn-fix_find_opencv.patch new file mode 100644 index 0000000..68d6b94 --- /dev/null +++ b/armnn-fix_find_opencv.patch @@ -0,0 +1,19 @@ +--- armnn-19.08.orig/tests/CMakeLists.txt 2019-10-17 09:11:02.836949176 +0200 ++++ armnn-19.08/tests/CMakeLists.txt 2019-10-17 09:10:50.384869262 +0200 +@@ -1,6 +1,3 @@ +-find_package( OpenCV REQUIRED ) +-include_directories( ${OpenCV_INCLUDE_DIRS} ) +- + # UnitTests + include(CheckIncludeFiles) + +@@ -368,6 +365,9 @@ if(BUILD_ARMNN_QUANTIZER) + endif() + + if (BUILD_ARMNN_EXAMPLES) ++ find_package( OpenCV REQUIRED ) ++ include_directories( ${OpenCV_INCLUDE_DIRS} ) ++ + set(ArmnnExamples_sources + ArmnnExamples/ArmnnExamples.cpp) + diff --git a/armnn.changes b/armnn.changes index de7510f..2d4d3df 100644 --- a/armnn.changes +++ b/armnn.changes @@ -1,3 +1,22 @@ +------------------------------------------------------------------- +Mon Oct 28 09:56:16 UTC 2019 - Guillaume GARDET + +- Enable ONNX for Tumbleweed + +------------------------------------------------------------------- +Thu Oct 17 06:57:00 UTC 2019 - Guillaume GARDET + +- Add downstream ArmnnExamples in a separate '-extratests' package + with patches: + * 0003-add-more-test-command-line-arguments.patch + * 0005-add-armnn-mobilenet-test-example.patch + * 0006-armnn-mobilenet-test-example.patch + * 0007-enable-use-of-arm-compute-shared-library.patch + * 0009-command-line-options-for-video-port-selection.patch + * 0010-armnnexamples-update-for-19.08-modifications.patch +- Fix build when extratests are disabled + * armnn-fix_find_opencv.patch + ------------------------------------------------------------------- Mon Oct 7 13:00:59 UTC 2019 - Guillaume GARDET diff --git a/armnn.spec b/armnn.spec index f7827ac..b604ba3 100644 --- a/armnn.spec +++ b/armnn.spec @@ -38,14 +38,21 @@ %endif # stb-devel is available on Leap 15.1+ -%if 0%{?suse_version} > 1500 || 0%{?sle_version} > 150000 && 0%{?is_opensuse} +%if 0%{?suse_version} > 1500 || ( 0%{?sle_version} > 150000 && 0%{?is_opensuse} ) %bcond_without armnn_tests %else %bcond_with armnn_tests %endif -# flatbuffers-devel is available on TW only +# Extra tests require opencv(3)-devel, but it is broken for Leap 15.x - boo#1154091 %if 0%{?suse_version} > 1500 +%bcond_without armnn_extra_tests +%else +%bcond_with armnn_extra_tests +%endif + +# flatbuffers-devel is available on Leap 15.2+ +%if 0%{?suse_version} > 1500 || ( 0%{?sle_version} >= 150200 && 0%{?is_opensuse} ) %bcond_without armnn_flatbuffers %else %bcond_with armnn_flatbuffers @@ -54,7 +61,7 @@ # Enable CAFFE %bcond_without armnn_caffe -# Enable TensorFlow only on TW aarch64 and x86_64 (TF fails to build on Leap 15.x) +# Enable TensorFlow only on TW aarch64 and x86_64 (TF fails to build on Leap 15.x and on armv7 TW) %if 0%{?suse_version} > 1500 %ifarch aarch64 x86_64 %bcond_without armnn_tf @@ -65,8 +72,12 @@ %bcond_with armnn_tf %endif # suse_version -# Disable ONNX +# ONNX is available on Tumbleweed only +%if 0%{?suse_version} > 1500 +%bcond_without armnn_onnx +%else %bcond_with armnn_onnx +%endif %define version_major 19 %define version_minor 08 @@ -91,7 +102,14 @@ Patch4: armnn-fix_arm32_dep.patch Patch5: armnn-fix_arm32.patch # https://github.com/ARM-software/armnn/issues/207 # FIXME: remove this patch once *.pb.cc files are packaged properly in tensorflow-devel - https://github.com/ARM-software/armnn/issues/269 -Patch100: armnn-fix_tensorflow_link.patch +Patch100: armnn-fix_tensorflow_link.patch +# PATCHES to add downstream ArmnnExamples binary - https://layers.openembedded.org/layerindex/recipe/87610/ +Patch200: 0003-add-more-test-command-line-arguments.patch +Patch201: 0005-add-armnn-mobilenet-test-example.patch +Patch202: 0006-armnn-mobilenet-test-example.patch +Patch203: 0009-command-line-options-for-video-port-selection.patch +Patch204: 0010-armnnexamples-update-for-19.08-modifications.patch +Patch205: armnn-fix_find_opencv.patch %if 0%{?suse_version} < 1330 BuildRequires: boost-devel >= 1.59 %else @@ -118,6 +136,13 @@ BuildRequires: Mesa-libOpenCL BuildRequires: ocl-icd-devel BuildRequires: opencl-cpp-headers %endif +%if %{with armnn_extra_tests} +%if 0%{?suse_version} > 1500 +BuildRequires: opencv3-devel +%else +BuildRequires: opencv-devel +%endif +%endif %if %{with armnn_onnx} BuildRequires: python3-onnx-devel %endif @@ -191,6 +216,22 @@ modification – across Arm Cortex CPUs and Arm Mali GPUs. This package contains the development libraries and headers for armnn. +%if %{with armnn_extra_tests} +%package -n %{name}-extratests +Summary: Additionnal downstream tests for Arm NN +Group: Development/Libraries/C and C++ +Requires: %{name} + +%description -n %{name}-extratests +Arm NN is an inference engine for CPUs, GPUs and NPUs. +It bridges the gap between existing NN frameworks and the underlying IP. +It enables efficient translation of existing neural network frameworks, +such as TensorFlow and Caffe, allowing them to run efficiently – without +modification – across Arm Cortex CPUs and Arm Mali GPUs. + +This package contains additionnal downstream tests for armnn. +%endif + %package -n libarmnn%{version_major}%{?package_suffix} %if "%{target}" == "opencl" Conflicts: libarmnn%{version_major} @@ -315,6 +356,12 @@ This package contains the libarmnnOnnxParser library from armnn. %patch4 -p1 %patch5 -p1 %patch100 -p1 +%patch200 -p1 +%patch201 -p1 +%patch202 -p1 +%patch203 -p1 +%patch204 -p1 +%patch205 -p1 # Boost fixes for dynamic linking sed -i 's/add_definitions("-DBOOST_ALL_NO_LIB")/add_definitions("-DBOOST_ALL_DYN_LINK")/' ./cmake/GlobalConfig.cmake sed -i 's/set(Boost_USE_STATIC_LIBS ON)/set(Boost_USE_STATIC_LIBS OFF)/' ./cmake/GlobalConfig.cmake @@ -386,11 +433,17 @@ protoc $PROTO --proto_path=. --proto_path=%{_includedir} --proto_path=$(dirname %endif %if %{with armnn_tests} -DBUILD_UNIT_TESTS=ON \ - -DBUILD_TESTS=ON + -DBUILD_TESTS=ON \ %else -DBUILD_UNIT_TESTS=OFF \ - -DBUILD_TESTS=OFF + -DBUILD_TESTS=OFF \ %endif +%if %{with armnn_extra_tests} + -DBUILD_ARMNN_EXAMPLES=ON +%else + -DBUILD_ARMNN_EXAMPLES=OFF +%endif + %if %{suse_version} > 1500 %cmake_build %else @@ -479,6 +532,11 @@ LD_LIBRARY_PATH="$(pwd)/build/" \ %endif %endif +%if %{with armnn_extra_tests} +%files -n %{name}-extratests +%{_bindir}/ArmnnExamples +%endif + %files -n libarmnn%{version_major}%{?package_suffix} %{_libdir}/libarmnn.so.*