commit 3fede9d004850b70fa3332b35538534eb188f3a1
parent 8646a39f76f0865c6b7fd91e3655793c6670a3b0
Author: Mohammad-Reza Nabipoor <m.nabipoor@yahoo.com>
Date: Wed, 15 Apr 2020 06:10:16 +0430
Add new test case (using `MobileNetV2` network)
The path of `MobileNetV2` pre-trained model file (`.pb` file) is
determined by `MODEL_FILE` environment variable.
The test uses two input images (`dog.bmp` and `panda.bmp`); the
address of original files are in the `test/test-input-files.txt`.
Diffstat:
8 files changed, 241 insertions(+), 0 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -44,6 +44,9 @@ target_compile_options(tfrun
add_executable(tfrun.test
test/tfrun.test.cpp
+
+ test/bmpread.cpp
+ test/bmpread.hpp
)
target_link_libraries(tfrun.test
PRIVATE
@@ -54,6 +57,16 @@ set_property(
PROPERTY CXX_STANDARD 14
)
+set(IMGFILES
+ ${CMAKE_SOURCE_DIR}/test/panda.bmp
+ ${CMAKE_SOURCE_DIR}/test/dog.bmp
+)
+add_custom_command(
+ TARGET tfrun.test
+ PRE_BUILD
+ COMMAND cp ${IMGFILES} ${CMAKE_CURRENT_BINARY_DIR}
+)
+
install(
TARGETS tfrun
DESTINATION lib
diff --git a/test/bmpread.cpp b/test/bmpread.cpp
@@ -0,0 +1,113 @@
+// Copyright 2017 The TensorFlow Authors. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+// tensorflow/lite/examples/label_image/bitmap_helpers.cc
+
+#include "bmpread.hpp"
+
+#include <fstream>
+#include <stdexcept>
+
+using namespace std::literals;
+
+namespace {
+
+std::vector<uint8_t>
+bmpdecode(const uint8_t* input,
+ int row_size,
+ int width,
+ int height,
+ int channels,
+ bool top_down)
+{
+ std::vector<uint8_t> output(height * width * channels);
+
+ for (int i = 0; i < height; i++) {
+ int src_pos;
+ int dst_pos;
+
+ for (int j = 0; j < width; j++) {
+ if (!top_down)
+ src_pos = ((height - 1 - i) * row_size) + j * channels;
+ else
+ src_pos = i * row_size + j * channels;
+ dst_pos = (i * width + j) * channels;
+
+ switch (channels) {
+ case 1:
+ output[dst_pos] = input[src_pos];
+ break;
+
+ case 3:
+ // BGR -> RGB
+ output[dst_pos] = input[src_pos + 2];
+ output[dst_pos + 1] = input[src_pos + 1];
+ output[dst_pos + 2] = input[src_pos];
+ break;
+
+ case 4:
+ // BGRA -> RGBA
+ output[dst_pos] = input[src_pos + 2];
+ output[dst_pos + 1] = input[src_pos + 1];
+ output[dst_pos + 2] = input[src_pos];
+ output[dst_pos + 3] = input[src_pos + 3];
+ break;
+
+ default:
+ throw std::runtime_error{ "Unexpected number of channels: "s +
+ std::to_string(channels) };
+ }
+ }
+ }
+ return output;
+}
+
+} // namespace
+
+std::vector<uint8_t>
+bmpread(const std::string& input_bmp_name,
+ int* width,
+ int* height,
+ int* channels)
+{
+ int begin, end;
+
+ std::ifstream file(input_bmp_name, std::ios::in | std::ios::binary);
+
+ if (!file)
+ throw std::runtime_error{ "input file "s + input_bmp_name +
+ " not found\n" };
+
+ begin = file.tellg();
+ file.seekg(0, std::ios::end);
+ end = file.tellg();
+ size_t len = end - begin;
+
+ std::vector<uint8_t> img_bytes(len);
+
+ file.seekg(0, std::ios::beg);
+ file.read(reinterpret_cast<char*>(img_bytes.data()), len);
+
+ const int32_t header_size =
+ *(reinterpret_cast<const int32_t*>(img_bytes.data() + 10));
+
+ *width = *(reinterpret_cast<const int32_t*>(img_bytes.data() + 18));
+ *height = *(reinterpret_cast<const int32_t*>(img_bytes.data() + 22));
+
+ const int32_t bpp =
+ *(reinterpret_cast<const int32_t*>(img_bytes.data() + 28));
+
+ *channels = bpp / 8;
+
+ // there may be padding bytes when the width is not a multiple of 4 bytes
+ // 8 * channels == bits per pixel
+ const int row_size = (8 * *channels * *width + 31) / 32 * 4;
+
+ // if height is negative, data layout is top down
+ // otherwise, it's bottom up
+ bool top_down = (*height < 0);
+
+ // Decode image, allocating tensor once the image size is known
+ const uint8_t* bmp_pixels = &img_bytes[header_size];
+ return bmpdecode(
+ bmp_pixels, row_size, *width, abs(*height), *channels, top_down);
+}
diff --git a/test/bmpread.hpp b/test/bmpread.hpp
@@ -0,0 +1,14 @@
+// Copyright 2019-2020 Mohammad-Reza Nabipoor
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+std::vector<uint8_t>
+bmpread(const std::string& input_bmp_name,
+ int* width,
+ int* height,
+ int* channels);
diff --git a/test/dog.bmp b/test/dog.bmp
Binary files differ.
diff --git a/test/model/mobilenet-v2-1.4-224.md b/test/model/mobilenet-v2-1.4-224.md
@@ -0,0 +1,45 @@
+# `mobilenet-v2-1.4-224`
+
+**NOTE** This file is adapted from [here](https://github.com/opencv/open_model_zoo/blob/master/models/public/mobilenet-v2-1.4-224/mobilenet-v2-1.4-224.md).
+
+## Use Case and High-Level Description
+
+`mobilenet-v2-1.4-224` is one of MobileNets - small, low-latency, low-power
+models parameterized to meet the resource constraints of a variety of use
+cases. They can be built upon for classification, detection, embeddings and
+segmentation similar to how other popular large scale models are used.
+For details, see the [paper](https://arxiv.org/abs/1704.04861).
+
+## Example
+
+## Specification
+
+| Metric | Value |
+|---------------------------------|-------------------------------------------|
+| Type | Classification |
+| GFlops | 1.183 |
+| MParams | 6.087 |
+
+## Performance
+
+## Input
+
+Image, name: `input` , shape: [1x224x224x3], format: [BxHxWxC], where:
+
+ - B - batch size
+ - H - image height
+ - W - image width
+ - C - number of channels
+
+ Expected color order: RGB.
+ Mean values: [127.5, 127.5, 127.5], scale factor for each channel: 127.5
+
+## Output
+
+Probabilities for all dataset classes (0 class is background). Probabilities
+are represented in logits format. Name: `MobilenetV1/Predictions/Reshape_1`.
+
+## Legal Information
+
+The original model is distributed under the
+[Apache License, Version 2.0](https://raw.githubusercontent.com/tensorflow/models/master/LICENSE).
diff --git a/test/panda.bmp b/test/panda.bmp
Binary files differ.
diff --git a/test/test-input-files.txt b/test/test-input-files.txt
@@ -0,0 +1,2 @@
+https://en.wikipedia.org/wiki/File:Giant_Panda_in_Beijing_Zoo_1.JPG
+https://commons.wikimedia.org/wiki/File:Beagle_001.jpg
diff --git a/test/tfrun.test.cpp b/test/tfrun.test.cpp
@@ -9,6 +9,8 @@
#include <array>
#include <cassert>
+#include "bmpread.hpp"
+
std::vector<float>
image(const std::vector<uint8_t>& in)
{
@@ -25,3 +27,55 @@ TEST_CASE("default ctor")
REQUIRE_THROWS(net.run(nullptr, 0, nullptr, 0));
}
+
+TEST_CASE("MobileNet v2")
+{
+ const char* mpath = getenv("MODEL_FILE");
+
+ REQUIRE(mpath != nullptr);
+
+ tfrun::siso net{ mpath, "input", "MobilenetV2/Predictions/Reshape_1" };
+ enum
+ {
+ ndims = 4,
+ nclasses = 1000 + 1 /*background*/,
+ nbatch = 1,
+ };
+ std::array<int64_t, ndims> dims{ nbatch, 224, 224, 3 };
+ int width;
+ int height;
+ int chan;
+ std::vector<float> img;
+
+ //--- beagle
+
+ img = image(bmpread("dog.bmp", &width, &height, &chan));
+ assert(img.size() == nbatch * 224 * 224 * 3);
+
+ auto out =
+ net.run(dims.data(), ndims, img.data(), img.size() * sizeof(img[0]));
+ auto it = std::max_element(out.data.begin(), out.data.end());
+ auto classIdx = it - out.data.begin();
+
+ REQUIRE(out.dims == std::vector<int64_t>{
+ nbatch,
+ nclasses,
+ });
+ REQUIRE(classIdx == 1 + 162 /*beagle*/);
+
+ //--- panda
+
+ img = image(bmpread("panda.bmp", &width, &height, &chan));
+ assert(img.size() == nbatch * 224 * 224 * 3);
+
+ out = net.run(dims.data(), ndims, img.data(), img.size() * sizeof(img[0]));
+ it = std::max_element(out.data.begin(), out.data.end());
+ classIdx = it - out.data.begin();
+
+ REQUIRE(out.dims == std::vector<int64_t>{
+ nbatch,
+ nclasses,
+ });
+ REQUIRE(classIdx == 1 +
+ 388 /*giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca*/);
+}