aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsabella Gottardi <isabella.gottardi@arm.com>2017-07-04 11:21:28 +0100
committerAnthony Barbier <anthony.barbier@arm.com>2018-09-17 14:17:31 +0100
commit6203153b25fda2c4b8d94fe71337d1145a36c132 (patch)
treeaee1e7db73d09eabf01ed8fb7680f98f97e07c93
parentd6253602ec7ebcbe6adb0acc51a97a5d8c167495 (diff)
downloadComputeLibrary-6203153b25fda2c4b8d94fe71337d1145a36c132.tar.gz
COMPMID-424 Implemented reference implementation and validation tests (NEON and CL) for Warp Perspective
Changed the behaviour in NEWarpKernel for border mode replicate and constant to stick with the VX specs. When the new coords are out of the valid region, the output will be computed using the values from the border. In the validation tests the validate will be called with tolerance_value 1 and tolerance_number 0.2%, due to some float arithmetic related mismatches. Change-Id: Id4f9d0ef87178f8f8fd38ee17fee0e6f4beb85cd Reviewed-on: http://mpd-gerrit.cambridge.arm.com/80283 Tested-by: Kaizen <jeremy.johnson+kaizengerrit@arm.com> Reviewed-by: Moritz Pflanzer <moritz.pflanzer@arm.com> Reviewed-by: Steven Niu <steven.niu@arm.com>
-rw-r--r--arm_compute/core/Helpers.inl4
-rw-r--r--src/core/NEON/kernels/NEWarpKernel.cpp63
-rw-r--r--tests/validation/CL/WarpPerspective.cpp208
-rw-r--r--tests/validation/Helpers.h25
-rw-r--r--tests/validation/NEON/WarpPerspective.cpp209
-rw-r--r--tests/validation/Reference.cpp16
-rw-r--r--tests/validation/Reference.h15
-rw-r--r--tests/validation/ReferenceCPP.cpp10
-rw-r--r--tests/validation/ReferenceCPP.h12
-rw-r--r--tests/validation/TensorOperations.h128
-rw-r--r--tests/validation/Validation.cpp34
-rw-r--r--tests/validation/Validation.h12
12 files changed, 725 insertions, 11 deletions
diff --git a/arm_compute/core/Helpers.inl b/arm_compute/core/Helpers.inl
index f885810078..d342a60987 100644
--- a/arm_compute/core/Helpers.inl
+++ b/arm_compute/core/Helpers.inl
@@ -53,8 +53,8 @@ inline uint8_t pixel_bilinear_c1u8(const uint8_t *first_pixel_ptr, size_t stride
{
ARM_COMPUTE_ERROR_ON(first_pixel_ptr == nullptr);
- const int32_t xi = x;
- const int32_t yi = y;
+ const int32_t xi = std::floor(x);
+ const int32_t yi = std::floor(y);
const float dx = x - xi;
const float dy = y - yi;
diff --git a/src/core/NEON/kernels/NEWarpKernel.cpp b/src/core/NEON/kernels/NEWarpKernel.cpp
index 8f8c852672..5ca1395b47 100644
--- a/src/core/NEON/kernels/NEWarpKernel.cpp
+++ b/src/core/NEON/kernels/NEWarpKernel.cpp
@@ -521,7 +521,6 @@ void NEWarpPerspectiveKernel<interpolation>::warp_constant(const Window &window)
const float yn = y0 / z0;
// Only use input values if xn and yn are within the valid region.
- // Otherwise write the constant border value.
if((min_y <= yn) && (yn < max_y) && (min_x <= xn) && (xn < max_x))
{
switch(interpolation)
@@ -538,7 +537,34 @@ void NEWarpPerspectiveKernel<interpolation>::warp_constant(const Window &window)
}
else
{
- *out.ptr() = _constant_border_value;
+ switch(interpolation)
+ {
+ case InterpolationPolicy::NEAREST_NEIGHBOR:
+ *out.ptr() = _constant_border_value;
+ break;
+ case InterpolationPolicy::BILINEAR:
+ {
+ const auto xi = clamp<int>(std::floor(xn), min_x - 1, max_x);
+ const auto yi = clamp<int>(std::floor(yn), min_y - 1, max_y);
+ const auto xi_1 = clamp<int>(std::floor(xn + 1), min_x - 1, max_x);
+ const auto yi_1 = clamp<int>(std::floor(yn + 1), min_y - 1, max_y);
+
+ const float dx = xn - std::floor(xn);
+ const float dy = yn - std::floor(yn);
+ const float dx1 = 1.0f - dx;
+ const float dy1 = 1.0f - dy;
+
+ const float a00 = *(in.ptr() + xi + yi * stride);
+ const float a01 = *(in.ptr() + xi_1 + yi * stride);
+ const float a10 = *(in.ptr() + xi + yi_1 * stride);
+ const float a11 = *(in.ptr() + xi_1 + yi_1 * stride);
+
+ *out.ptr() = a00 * (dx1 * dy1) + a01 * (dx * dy1) + a10 * (dx1 * dy) + a11 * (dx * dy);
+ }
+ break;
+ default:
+ ARM_COMPUTE_ERROR("Interpolation not supported");
+ }
}
x0 += M00;
@@ -619,7 +645,6 @@ void NEWarpPerspectiveKernel<interpolation>::warp_replicate(const Window &window
const float yn = y0 / z0;
// Only load from (x0, y0) if the point is within the valid region.
- // Otherwise load from the edge of the valid region.
if((min_y <= yn) && (yn < max_y) && (min_x <= xn) && (xn < max_x))
{
switch(interpolation)
@@ -637,10 +662,34 @@ void NEWarpPerspectiveKernel<interpolation>::warp_replicate(const Window &window
else
{
// Clamp coordinates
- const auto xi = clamp<int>(x0, min_x, max_x - 1);
- const auto yi = clamp<int>(y0, min_y, max_y - 1);
-
- *out.ptr() = *(in.ptr() + xi + yi * stride);
+ const auto xi = clamp<int>(std::floor(xn), min_x, max_x - 1);
+ const auto yi = clamp<int>(std::floor(yn), min_y, max_y - 1);
+ switch(interpolation)
+ {
+ case InterpolationPolicy::NEAREST_NEIGHBOR:
+ *out.ptr() = *(in.ptr() + xi + yi * stride);
+ break;
+ case InterpolationPolicy::BILINEAR:
+ {
+ const auto xi_1 = clamp<int>(std::floor(xn + 1), min_x, max_x - 1);
+ const auto yi_1 = clamp<int>(std::floor(yn + 1), min_y, max_y - 1);
+
+ const float dx = xn - std::floor(xn);
+ const float dy = yn - std::floor(yn);
+ const float dx1 = 1.0f - dx;
+ const float dy1 = 1.0f - dy;
+
+ const float a00 = *(in.ptr() + xi + yi * stride);
+ const float a01 = *(in.ptr() + xi_1 + yi * stride);
+ const float a10 = *(in.ptr() + xi + yi_1 * stride);
+ const float a11 = *(in.ptr() + xi_1 + yi_1 * stride);
+
+ *out.ptr() = a00 * (dx1 * dy1) + a01 * (dx * dy1) + a10 * (dx1 * dy) + a11 * (dx * dy);
+ }
+ break;
+ default:
+ ARM_COMPUTE_ERROR("Interpolation not supported");
+ }
}
x0 += M00;
diff --git a/tests/validation/CL/WarpPerspective.cpp b/tests/validation/CL/WarpPerspective.cpp
new file mode 100644
index 0000000000..260b22be03
--- /dev/null
+++ b/tests/validation/CL/WarpPerspective.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2017 ARM Limited.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "AssetsLibrary.h"
+#include "CL/CLAccessor.h"
+#include "Globals.h"
+#include "PaddingCalculator.h"
+#include "TypePrinter.h"
+#include "Utils.h"
+#include "validation/Datasets.h"
+#include "validation/Helpers.h"
+#include "validation/Reference.h"
+#include "validation/Validation.h"
+#include "validation/ValidationUserConfiguration.h"
+
+#include "arm_compute/core/Helpers.h"
+#include "arm_compute/core/Types.h"
+#include "arm_compute/runtime/CL/functions/CLWarpPerspective.h"
+#include "arm_compute/runtime/Tensor.h"
+#include "arm_compute/runtime/TensorAllocator.h"
+
+#include "boost_wrapper.h"
+
+#include <random>
+#include <string>
+
+using namespace arm_compute;
+using namespace arm_compute::test;
+using namespace arm_compute::test::validation;
+
+namespace
+{
+/** Compute Warp Perspective function.
+ *
+ * @param[in] input Shape of the input and output tensors.
+ * @param[in] matrix The perspective matrix. Must be 3x3 of type float.
+ * @param[in] policy The interpolation type.
+ * @param[in] border_mode Strategy to use for borders.
+ * @param[in] constant_border_value Constant value to use for borders if border_mode is set to CONSTANT.
+ *
+ * @return Computed output tensor.
+ */
+CLTensor compute_warp_perspective(const TensorShape &shape, const float *matrix, InterpolationPolicy policy,
+ BorderMode border_mode, uint8_t constant_border_value)
+{
+ // Create tensors
+ CLTensor src = create_tensor<CLTensor>(shape, DataType::U8);
+ CLTensor dst = create_tensor<CLTensor>(shape, DataType::U8);
+
+ // Create and configure function
+ CLWarpPerspective warp_perspective;
+ warp_perspective.configure(&src, &dst, matrix, policy, border_mode, constant_border_value);
+
+ // Allocate tensors
+ src.allocator()->allocate();
+ dst.allocator()->allocate();
+
+ BOOST_TEST(!src.info()->is_resizable());
+ BOOST_TEST(!dst.info()->is_resizable());
+
+ // Fill tensors
+ library->fill_tensor_uniform(CLAccessor(src), 0);
+
+ // Compute function
+ warp_perspective.run();
+
+ return dst;
+}
+} // namespace
+
+#ifndef DOXYGEN_SKIP_THIS
+BOOST_AUTO_TEST_SUITE(CL)
+BOOST_AUTO_TEST_SUITE(WarpPerspective)
+
+BOOST_TEST_DECORATOR(*boost::unit_test::label("precommit") * boost::unit_test::label("nightly"))
+BOOST_DATA_TEST_CASE(Configuration, (SmallShapes() + LargeShapes())
+ * boost::unit_test::data::make({ InterpolationPolicy::BILINEAR, InterpolationPolicy::NEAREST_NEIGHBOR }) * BorderModes(),
+ shape, policy, border_mode)
+{
+ uint8_t constant_border_value = 0;
+
+ // Generate a random constant value if border_mode is constant
+ if(border_mode == BorderMode::CONSTANT)
+ {
+ std::mt19937 gen(user_config.seed.get());
+ std::uniform_int_distribution<uint8_t> distribution_u8(0, 255);
+ constant_border_value = distribution_u8(gen);
+ }
+
+ std::array<float, 9> matrix;
+ fill_warp_matrix<9>(matrix, 3, 3);
+
+ // Create tensors
+ CLTensor src = create_tensor<CLTensor>(shape, DataType::U8);
+ CLTensor dst = create_tensor<CLTensor>(shape, DataType::U8);
+
+ BOOST_TEST(src.info()->is_resizable());
+ BOOST_TEST(dst.info()->is_resizable());
+
+ // Create and configure function
+ CLWarpPerspective warp_perspective;
+ warp_perspective.configure(&src, &dst, matrix.data(), policy, border_mode, constant_border_value);
+
+ // Validate valid region
+ const ValidRegion valid_region = shape_to_valid_region(shape);
+
+ validate(src.info()->valid_region(), valid_region);
+ validate(dst.info()->valid_region(), valid_region);
+
+ // Validate padding
+ PaddingCalculator calculator(shape.x(), 4);
+ calculator.set_border_mode(border_mode);
+
+ const PaddingSize read_padding(1);
+ const PaddingSize write_padding = calculator.required_padding(PaddingCalculator::Option::EXCLUDE_BORDER);
+
+ validate(src.info()->padding(), read_padding);
+ validate(dst.info()->padding(), write_padding);
+}
+
+BOOST_TEST_DECORATOR(*boost::unit_test::label("precommit"))
+BOOST_DATA_TEST_CASE(RunSmall, SmallShapes()
+ * boost::unit_test::data::make({ InterpolationPolicy::BILINEAR, InterpolationPolicy::NEAREST_NEIGHBOR })
+ * BorderModes(),
+ shape, policy, border_mode)
+{
+ uint8_t constant_border_value = 0;
+
+ // Generate a random constant value if border_mode is constant
+ if(border_mode == BorderMode::CONSTANT)
+ {
+ std::mt19937 gen(user_config.seed.get());
+ std::uniform_int_distribution<uint8_t> distribution_u8(0, 255);
+ constant_border_value = distribution_u8(gen);
+ }
+
+ // Create the valid mask Tensor
+ RawTensor valid_mask(shape, DataType::U8);
+
+ // Create the matrix
+ std::array<float, 9> matrix;
+ fill_warp_matrix<9>(matrix, 3, 3);
+
+ // Compute function
+ CLTensor dst = compute_warp_perspective(shape, matrix.data(), policy, border_mode, constant_border_value);
+
+ // Compute reference
+ RawTensor ref_dst = Reference::compute_reference_warp_perspective(shape, valid_mask, matrix.data(), policy, border_mode, constant_border_value);
+
+ // Validate output
+ validate(CLAccessor(dst), ref_dst, valid_mask, 1, 0.2f);
+}
+
+BOOST_TEST_DECORATOR(*boost::unit_test::label("nightly"))
+BOOST_DATA_TEST_CASE(RunLarge, LargeShapes()
+ * boost::unit_test::data::make({ InterpolationPolicy::NEAREST_NEIGHBOR, InterpolationPolicy::BILINEAR }) * BorderModes(),
+ shape, policy, border_mode)
+{
+ uint8_t constant_border_value = 0;
+
+ // Generate a random constant value if border_mode is constant
+ if(border_mode == BorderMode::CONSTANT)
+ {
+ std::mt19937 gen(user_config.seed.get());
+ std::uniform_int_distribution<uint8_t> distribution_u8(0, 255);
+ constant_border_value = distribution_u8(gen);
+ }
+
+ // Create the valid mask Tensor
+ RawTensor valid_mask(shape, DataType::U8);
+
+ // Create the matrix
+ std::array<float, 9> matrix;
+ fill_warp_matrix<9>(matrix, 3, 3);
+
+ // Compute function
+ CLTensor dst = compute_warp_perspective(shape, matrix.data(), policy, border_mode, constant_border_value);
+
+ // Compute reference
+ RawTensor ref_dst = Reference::compute_reference_warp_perspective(shape, valid_mask, matrix.data(), policy, border_mode, constant_border_value);
+
+ // Validate output
+ validate(CLAccessor(dst), ref_dst, valid_mask, 1, 0.2f);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_SUITE_END()
+#endif /* DOXYGEN_SKIP_THIS */
diff --git a/tests/validation/Helpers.h b/tests/validation/Helpers.h
index 8d70de6958..09ffda8957 100644
--- a/tests/validation/Helpers.h
+++ b/tests/validation/Helpers.h
@@ -225,6 +225,31 @@ inline TensorShape calculate_depth_concatenate_shape(std::vector<TensorShape> in
return out_shape;
}
+/** Fill matrix random.
+ *
+ * @param[in,out] matrix Matrix
+ * @param[in] cols Columns (width) of matrix
+ * @param[in] rows Rows (height) of matrix
+ */
+template <std::size_t SIZE>
+inline void fill_warp_matrix(std::array<float, SIZE> &matrix, int cols, int rows)
+{
+ std::mt19937 gen(user_config.seed.get());
+ std::uniform_real_distribution<float> dist(-1, 1);
+
+ for(int v = 0, r = 0; r < rows; ++r)
+ {
+ for(int c = 0; c < cols; ++c, ++v)
+ {
+ matrix[v] = dist(gen);
+ }
+ }
+ if(SIZE == 9)
+ {
+ matrix[(cols * rows) - 1] = 1;
+ }
+}
+
/** Create a vector of random ROIs.
*
* @param[in] shape The shape of the input tensor.
diff --git a/tests/validation/NEON/WarpPerspective.cpp b/tests/validation/NEON/WarpPerspective.cpp
new file mode 100644
index 0000000000..2c102ea37e
--- /dev/null
+++ b/tests/validation/NEON/WarpPerspective.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2017 ARM Limited.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "AssetsLibrary.h"
+#include "Globals.h"
+#include "NEON/Accessor.h"
+#include "PaddingCalculator.h"
+#include "TypePrinter.h"
+#include "Utils.h"
+#include "validation/Datasets.h"
+#include "validation/Helpers.h"
+#include "validation/Reference.h"
+#include "validation/Validation.h"
+#include "validation/ValidationUserConfiguration.h"
+
+#include "arm_compute/core/Helpers.h"
+#include "arm_compute/core/Types.h"
+#include "arm_compute/runtime/NEON/functions/NEWarpPerspective.h"
+#include "arm_compute/runtime/Tensor.h"
+#include "arm_compute/runtime/TensorAllocator.h"
+
+#include "boost_wrapper.h"
+
+#include <random>
+#include <string>
+
+using namespace arm_compute;
+using namespace arm_compute::test;
+using namespace arm_compute::test::validation;
+
+namespace
+{
+/** Compute Warp Perspective function.
+ *
+ * @param[in] input Shape of the input and output tensors.
+ * @param[in] matrix The perspective matrix. Must be 3x3 of type float.
+ * @param[in] policy The interpolation type.
+ * @param[in] border_mode Strategy to use for borders.
+ * @param[in] constant_border_value Constant value to use for borders if border_mode is set to CONSTANT.
+ *
+ * @return Computed output tensor.
+ */
+Tensor compute_warp_perspective(const TensorShape &shape, const float *matrix, InterpolationPolicy policy,
+ BorderMode border_mode, uint8_t constant_border_value)
+{
+ // Create tensors
+ Tensor src = create_tensor<Tensor>(shape, DataType::U8);
+ Tensor dst = create_tensor<Tensor>(shape, DataType::U8);
+
+ // Create and configure function
+ NEWarpPerspective warp_perspective;
+ warp_perspective.configure(&src, &dst, matrix, policy, border_mode, constant_border_value);
+
+ // Allocate tensors
+ src.allocator()->allocate();
+ dst.allocator()->allocate();
+
+ BOOST_TEST(!src.info()->is_resizable());
+ BOOST_TEST(!dst.info()->is_resizable());
+
+ // Fill tensors
+ library->fill_tensor_uniform(Accessor(src), 0);
+
+ // Compute function
+ warp_perspective.run();
+
+ return dst;
+}
+} // namespace
+
+#ifndef DOXYGEN_SKIP_THIS
+BOOST_AUTO_TEST_SUITE(NEON)
+BOOST_AUTO_TEST_SUITE(WarpPerspective)
+
+BOOST_TEST_DECORATOR(*boost::unit_test::label("precommit") * boost::unit_test::label("nightly"))
+BOOST_DATA_TEST_CASE(Configuration, (SmallShapes() + LargeShapes())
+ * boost::unit_test::data::make({ InterpolationPolicy::BILINEAR, InterpolationPolicy::NEAREST_NEIGHBOR }) * BorderModes(),
+ shape, policy, border_mode)
+{
+ uint8_t constant_border_value = 0;
+
+ // Generate a random constant value if border_mode is constant
+ if(border_mode == BorderMode::CONSTANT)
+ {
+ std::mt19937 gen(user_config.seed.get());
+ std::uniform_int_distribution<uint8_t> distribution_u8(0, 255);
+ constant_border_value = distribution_u8(gen);
+ }
+
+ // Create the matrix
+ std::array<float, 9> matrix;
+ fill_warp_matrix<9>(matrix, 3, 3);
+
+ // Create tensors
+ Tensor src = create_tensor<Tensor>(shape, DataType::U8);
+ Tensor dst = create_tensor<Tensor>(shape, DataType::U8);
+
+ BOOST_TEST(src.info()->is_resizable());
+ BOOST_TEST(dst.info()->is_resizable());
+
+ // Create and configure function
+ NEWarpPerspective warp_perspective;
+ warp_perspective.configure(&src, &dst, matrix.data(), policy, border_mode, constant_border_value);
+
+ // Validate valid region
+ const ValidRegion valid_region = shape_to_valid_region(shape);
+
+ validate(src.info()->valid_region(), valid_region);
+ validate(dst.info()->valid_region(), valid_region);
+
+ // Validate padding
+ PaddingCalculator calculator(shape.x(), 1);
+ calculator.set_border_mode(border_mode);
+ calculator.set_border_size(1);
+
+ const PaddingSize read_padding(1);
+ const PaddingSize write_padding = calculator.required_padding();
+
+ validate(src.info()->padding(), read_padding);
+ validate(dst.info()->padding(), write_padding);
+}
+
+BOOST_TEST_DECORATOR(*boost::unit_test::label("precommit"))
+BOOST_DATA_TEST_CASE(RunSmall, SmallShapes()
+ * boost::unit_test::data::make({ InterpolationPolicy::BILINEAR, InterpolationPolicy::NEAREST_NEIGHBOR })
+ * BorderModes(),
+ shape, policy, border_mode)
+{
+ uint8_t constant_border_value = 0;
+
+ // Generate a random constant value if border_mode is constant
+ if(border_mode == BorderMode::CONSTANT)
+ {
+ std::mt19937 gen(user_config.seed.get());
+ std::uniform_int_distribution<uint8_t> distribution_u8(0, 255);
+ constant_border_value = distribution_u8(gen);
+ }
+
+ // Create the valid mask Tensor
+ RawTensor valid_mask(shape, DataType::U8);
+
+ // Create the matrix
+ std::array<float, 9> matrix;
+ fill_warp_matrix<9>(matrix, 3, 3);
+
+ // Compute function
+ Tensor dst = compute_warp_perspective(shape, matrix.data(), policy, border_mode, constant_border_value);
+
+ // Compute reference
+ RawTensor ref_dst = Reference::compute_reference_warp_perspective(shape, valid_mask, matrix.data(), policy, border_mode, constant_border_value);
+
+ // Validate output
+ validate(Accessor(dst), ref_dst, valid_mask, 1, 0.2f);
+}
+BOOST_TEST_DECORATOR(*boost::unit_test::label("nightly"))
+BOOST_DATA_TEST_CASE(RunLarge, LargeShapes()
+ * boost::unit_test::data::make({ InterpolationPolicy::NEAREST_NEIGHBOR, InterpolationPolicy::BILINEAR }) * BorderModes(),
+ shape, policy, border_mode)
+{
+ uint8_t constant_border_value = 0;
+
+ // Generate a random constant value if border_mode is constant
+ if(border_mode == BorderMode::CONSTANT)
+ {
+ std::mt19937 gen(user_config.seed.get());
+ std::uniform_int_distribution<uint8_t> distribution_u8(0, 255);
+ constant_border_value = distribution_u8(gen);
+ }
+
+ // Create the valid mask Tensor
+ RawTensor valid_mask(shape, DataType::U8);
+
+ // Create the matrix
+ std::array<float, 9> matrix;
+ fill_warp_matrix<9>(matrix, 3, 3);
+
+ // Compute function
+ Tensor dst = compute_warp_perspective(shape, matrix.data(), policy, border_mode, constant_border_value);
+
+ // Compute reference
+ RawTensor ref_dst = Reference::compute_reference_warp_perspective(shape, valid_mask, matrix.data(), policy, border_mode, constant_border_value);
+
+ // Validate output
+ validate(Accessor(dst), ref_dst, valid_mask, 1, 0.2f);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_SUITE_END()
+#endif /* DOXYGEN_SKIP_THIS */
diff --git a/tests/validation/Reference.cpp b/tests/validation/Reference.cpp
index 621158a80e..0fca661dc4 100644
--- a/tests/validation/Reference.cpp
+++ b/tests/validation/Reference.cpp
@@ -390,6 +390,22 @@ RawTensor Reference::compute_reference_threshold(const TensorShape &shape, uint8
return ref_dst;
}
+RawTensor Reference::compute_reference_warp_perspective(const TensorShape &shape, RawTensor &valid_mask, const float *matrix, InterpolationPolicy policy, BorderMode border_mode,
+ uint8_t constant_border_value)
+{
+ // Create reference
+ RawTensor ref_src(shape, DataType::U8);
+ RawTensor ref_dst(shape, DataType::U8);
+
+ // Fill reference
+ library->fill_tensor_uniform(ref_src, 0);
+
+ // Compute reference
+ ReferenceCPP::warp_perspective(ref_src, ref_dst, valid_mask, matrix, policy, border_mode, constant_border_value);
+
+ return ref_dst;
+}
+
RawTensor Reference::compute_reference_batch_normalization_layer(const TensorShape &shape0, const TensorShape &shape1, DataType dt, float epsilon, int fixed_point_position)
{
// Create reference
diff --git a/tests/validation/Reference.h b/tests/validation/Reference.h
index e00a5b95d5..13e43d3d30 100644
--- a/tests/validation/Reference.h
+++ b/tests/validation/Reference.h
@@ -266,6 +266,21 @@ public:
* @return Computed raw tensor.
*/
static RawTensor compute_reference_threshold(const TensorShape &shape, uint8_t threshold, uint8_t false_value, uint8_t true_value, ThresholdType type, uint8_t upper);
+
+ /** Compute reference Warp Perspective.
+ *
+ * @param[in] shape Shape of the input and output tensors.
+ * @param[out] valid_mask Valid mask tensor.
+ * @param[in] matrix The perspective matrix. Must be 3x3 of type float.
+ * @param[in] policy The interpolation type.
+ * @param[in] border_mode Strategy to use for borders.
+ * @param[in] constant_border_value Constant value to use for borders if border_mode is set to CONSTANT.
+ *
+ * @return Computed raw tensor.
+ */
+ static RawTensor compute_reference_warp_perspective(const TensorShape &shape, RawTensor &valid_mask, const float *matrix, InterpolationPolicy policy, BorderMode border_mode,
+ uint8_t constant_border_value);
+
/** Compute reference batch normalization layer.
*
* @param[in] shape0 Shape of the input and output tensors.
diff --git a/tests/validation/ReferenceCPP.cpp b/tests/validation/ReferenceCPP.cpp
index 69100ad9bb..069cc1d871 100644
--- a/tests/validation/ReferenceCPP.cpp
+++ b/tests/validation/ReferenceCPP.cpp
@@ -252,6 +252,16 @@ void ReferenceCPP::threshold(const RawTensor &src, RawTensor &dst, uint8_t thres
tensor_operations::threshold(s, d, threshold, false_value, true_value, type, upper);
}
+// Warp perspective
+void ReferenceCPP::warp_perspective(const RawTensor &src, RawTensor &dst, RawTensor &valid_mask, const float *matrix, InterpolationPolicy policy, BorderMode border_mode, uint8_t constant_border_value)
+{
+ ARM_COMPUTE_ERROR_ON(src.data_type() != DataType::U8 || dst.data_type() != DataType::U8);
+ const Tensor<uint8_t> s(src.shape(), src.data_type(), src.fixed_point_position(), reinterpret_cast<const uint8_t *>(src.data()));
+ Tensor<uint8_t> d(dst.shape(), dst.data_type(), dst.fixed_point_position(), reinterpret_cast<uint8_t *>(dst.data()));
+ Tensor<uint8_t> vmask(valid_mask.shape(), valid_mask.data_type(), valid_mask.fixed_point_position(), reinterpret_cast<uint8_t *>(valid_mask.data()));
+ tensor_operations::warp_perspective(s, d, vmask, matrix, policy, border_mode, constant_border_value);
+}
+
// Batch Normalization Layer
void ReferenceCPP::batch_normalization_layer(const RawTensor &src, RawTensor &dst, const RawTensor &mean, const RawTensor &var, const RawTensor &beta, const RawTensor &gamma, float epsilon,
int fixed_point_position)
diff --git a/tests/validation/ReferenceCPP.h b/tests/validation/ReferenceCPP.h
index 2118ea942e..2d35fa9590 100644
--- a/tests/validation/ReferenceCPP.h
+++ b/tests/validation/ReferenceCPP.h
@@ -228,6 +228,18 @@ public:
* @param[in] upper Upper threshold. Only used when the thresholding type is RANGE.
*/
static void threshold(const RawTensor &src, RawTensor &dst, uint8_t threshold, uint8_t false_value, uint8_t true_value, ThresholdType type, uint8_t upper);
+ /** Warp perspective of@p src to @p dst
+ *
+ * @param[in] src First tensor.
+ * @param[out] dst Result tensor.
+ * @param[out] valid_mask Valid mask tensor.
+ * @param[in] matrix The perspective matrix. Must be 3x3 of type float.
+ * @param[in] policy The interpolation type.
+ * @param[in] border_mode Strategy to use for borders.
+ * @param[in] constant_border_value Constant value to use for borders if border_mode is set to CONSTANT.
+ */
+ static void warp_perspective(const RawTensor &src, RawTensor &dst, RawTensor &valid_mask, const float *matrix, InterpolationPolicy policy, BorderMode border_mode, uint8_t constant_border_value);
+
/** Batch Normalization of @p src based on the information from @p norm_info.
*
* @param[in] src Input tensor.
diff --git a/tests/validation/TensorOperations.h b/tests/validation/TensorOperations.h
index 317d22934e..3220d80a04 100644
--- a/tests/validation/TensorOperations.h
+++ b/tests/validation/TensorOperations.h
@@ -388,7 +388,7 @@ void accumulate_squared(const Tensor<T1> &in, Tensor<T2> &out, uint32_t shift)
}
}
-// Accumulate weighted
+// Accumulate weighted total_size = init_auto_padding(tensor_shape, num_channels, type);
template <typename T>
void accumulate_weighted(const Tensor<T> &in, Tensor<T> &out, float alpha)
{
@@ -748,6 +748,132 @@ void threshold(const Tensor<T> &in, Tensor<T> &out, uint8_t threshold, uint8_t f
}
}
+template <typename T>
+T bilinear_policy(const Tensor<T> &in, Coordinates id, float xn, float yn, BorderMode border_mode, uint8_t constant_border_value)
+{
+ int idx = std::floor(xn);
+ int idy = std::floor(yn);
+
+ const float dx = xn - idx;
+ const float dy = yn - idy;
+ const float dx_1 = 1.0f - dx;
+ const float dy_1 = 1.0f - dy;
+
+ id.set(0, idx);
+ id.set(1, idy);
+ const T tl = tensor_elem_at(in, id, border_mode, constant_border_value);
+ id.set(0, idx + 1);
+ id.set(1, idy);
+ const T tr = tensor_elem_at(in, id, border_mode, constant_border_value);
+ id.set(0, idx);
+ id.set(1, idy + 1);
+ const T bl = tensor_elem_at(in, id, border_mode, constant_border_value);
+ id.set(0, idx + 1);
+ id.set(1, idy + 1);
+ const T br = tensor_elem_at(in, id, border_mode, constant_border_value);
+
+ return tl * (dx_1 * dy_1) + tr * (dx * dy_1) + bl * (dx_1 * dy) + br * (dx * dy);
+}
+
+bool valid_bilinear_policy(float xn, float yn, int width, int height, BorderMode border_mode)
+{
+ if(border_mode != BorderMode::UNDEFINED)
+ {
+ return true;
+ }
+ if((0 <= yn + 1) && (yn + 1 < height) && (0 <= xn + 1) && (xn + 1 < width))
+ {
+ return true;
+ }
+ return false;
+}
+
+// Warp Perspective
+template <typename T>
+void warp_perspective(const Tensor<T> &in, Tensor<T> &out, Tensor<T> &valid_mask, const float *matrix, InterpolationPolicy policy, BorderMode border_mode, uint8_t constant_border_value)
+{
+ // x0 = M00 * x + M01 * y + M02
+ // y0 = M10 * x + M11 * y + M12
+ // z0 = M20 * x + M21 * y + M22
+ // xn = x0 / z0
+ // yn = y0 / z0
+ const float M00 = matrix[0];
+ const float M10 = matrix[1];
+ const float M20 = matrix[2];
+ const float M01 = matrix[0 + 1 * 3];
+ const float M11 = matrix[1 + 1 * 3];
+ const float M21 = matrix[2 + 1 * 3];
+ const float M02 = matrix[0 + 2 * 3];
+ const float M12 = matrix[1 + 2 * 3];
+ const float M22 = matrix[2 + 2 * 3];
+
+ const int width = in.shape().x();
+ const int height = in.shape().y();
+
+ for(int element_idx = 0; element_idx < in.num_elements(); ++element_idx)
+ {
+ valid_mask[element_idx] = 1;
+ Coordinates id = index2coord(in.shape(), element_idx);
+ int idx = id.x();
+ int idy = id.y();
+ const float z0 = M20 * idx + M21 * idy + M22;
+
+ float x0 = (M00 * idx + M01 * idy + M02);
+ float y0 = (M10 * idx + M11 * idy + M12);
+
+ float xn = x0 / z0;
+ float yn = y0 / z0;
+ id.set(0, static_cast<int>(std::floor(xn)));
+ id.set(1, static_cast<int>(std::floor(yn)));
+ if((0 <= yn) && (yn < height) && (0 <= xn) && (xn < width))
+ {
+ switch(policy)
+ {
+ case InterpolationPolicy::NEAREST_NEIGHBOR:
+ out[element_idx] = tensor_elem_at(in, id, border_mode, constant_border_value);
+ break;
+ case InterpolationPolicy::BILINEAR:
+ (valid_bilinear_policy(xn, yn, width, height, border_mode)) ? out[element_idx] = bilinear_policy(in, id, xn, yn, border_mode, constant_border_value) : valid_mask[element_idx] = 0;
+ break;
+ case InterpolationPolicy::AREA:
+ default:
+ ARM_COMPUTE_ERROR("Interpolation not supported");
+ }
+ }
+ else
+ {
+ if(border_mode == BorderMode::UNDEFINED)
+ {
+ valid_mask[element_idx] = 0;
+ }
+ else
+ {
+ switch(policy)
+ {
+ case InterpolationPolicy::NEAREST_NEIGHBOR:
+ if(border_mode == BorderMode::CONSTANT)
+ {
+ out[element_idx] = constant_border_value;
+ }
+ else if(border_mode == BorderMode::REPLICATE)
+ {
+ id.set(0, std::max(0, std::min(static_cast<int>(xn), width - 1)));
+ id.set(1, std::max(0, std::min(static_cast<int>(yn), height - 1)));
+ out[element_idx] = in[coord2index(in.shape(), id)];
+ }
+ break;
+ case InterpolationPolicy::BILINEAR:
+ out[element_idx] = bilinear_policy(in, id, xn, yn, border_mode, constant_border_value);
+ break;
+ case InterpolationPolicy::AREA:
+ default:
+ ARM_COMPUTE_ERROR("Interpolation not supported");
+ }
+ }
+ }
+ }
+}
+
// Batch Normalization Layer for fixed point type
template <typename T, typename std::enable_if<std::is_integral<T>::value, int>::type * = nullptr>
void batch_normalization_layer(const Tensor<T> &in, Tensor<T> &out, const Tensor<T> &mean, const Tensor<T> &var, const Tensor<T> &beta, const Tensor<T> &gamma, float epsilon, int fixed_point_position)
diff --git a/tests/validation/Validation.cpp b/tests/validation/Validation.cpp
index eac4105b21..868bbaac5e 100644
--- a/tests/validation/Validation.cpp
+++ b/tests/validation/Validation.cpp
@@ -193,7 +193,6 @@ void check_single_element(const Coordinates &id, const IAccessor &tensor, const
BOOST_TEST_INFO("reference = " << std::setprecision(5) << ref);
BOOST_TEST_INFO("target = " << std::setprecision(5) << target);
BOOST_TEST_WARN(equal);
-
++num_mismatches;
}
++num_elements;
@@ -264,6 +263,39 @@ void validate(const IAccessor &tensor, const RawTensor &reference, const ValidRe
<< "%) mismatched (maximum tolerated " << std::setprecision(2) << tolerance_number << "%)");
}
+void validate(const IAccessor &tensor, const RawTensor &reference, const RawTensor &valid_mask, float tolerance_value, float tolerance_number, uint64_t wrap_range)
+{
+ int64_t num_mismatches = 0;
+ int64_t num_elements = 0;
+
+ BOOST_TEST(tensor.element_size() == reference.element_size());
+ BOOST_TEST(tensor.format() == reference.format());
+ BOOST_TEST(tensor.data_type() == reference.data_type());
+ BOOST_TEST(tensor.num_channels() == reference.num_channels());
+ BOOST_TEST(compare_dimensions(tensor.shape(), reference.shape()));
+
+ const int min_elements = std::min(tensor.num_elements(), reference.num_elements());
+ const int min_channels = std::min(tensor.num_channels(), reference.num_channels());
+ const size_t channel_size = element_size_from_data_type(reference.data_type());
+
+ // Iterate over all elements within valid region, e.g. U8, S16, RGB888, ...
+ for(int element_idx = 0; element_idx < min_elements; ++element_idx)
+ {
+ const Coordinates id = index2coord(reference.shape(), element_idx);
+ if(valid_mask[element_idx] == 1)
+ {
+ check_single_element(id, tensor, reference, tolerance_value, wrap_range, min_channels, channel_size, num_mismatches, num_elements);
+ }
+ }
+
+ const int64_t absolute_tolerance_number = tolerance_number * num_elements;
+ const float percent_mismatches = static_cast<float>(num_mismatches) / num_elements * 100.f;
+
+ BOOST_TEST(num_mismatches <= absolute_tolerance_number,
+ num_mismatches << " values (" << std::setprecision(2) << percent_mismatches
+ << "%) mismatched (maximum tolerated " << std::setprecision(2) << tolerance_number << "%)");
+}
+
void validate(const IAccessor &tensor, const void *reference_value)
{
BOOST_TEST_REQUIRE((reference_value != nullptr));
diff --git a/tests/validation/Validation.h b/tests/validation/Validation.h
index 66bb2be2ca..43a90f378e 100644
--- a/tests/validation/Validation.h
+++ b/tests/validation/Validation.h
@@ -103,6 +103,18 @@ void validate(const IAccessor &tensor, const RawTensor &reference, float toleran
*/
void validate(const IAccessor &tensor, const RawTensor &reference, const ValidRegion &valid_region, float tolerance_value = 0.f, float tolerance_number = 0.f, uint64_t wrap_range = 0);
+/** Validate tensors with valid mask.
+ *
+ * - Dimensionality has to be the same.
+ * - All values have to match.
+ *
+ * @note: wrap_range allows cases where reference tensor rounds up to the wrapping point, causing it to wrap around to
+ * zero while the test tensor stays at wrapping point to pass. This may permit true erroneous cases (difference between
+ * reference tensor and test tensor is multiple of wrap_range), but such errors would be detected by
+ * other test cases.
+ */
+void validate(const IAccessor &tensor, const RawTensor &reference, const RawTensor &valid_mask, float tolerance_value = 0.f, float tolerance_number = 0.f, uint64_t wrap_range = 0);
+
/** Validate tensors against constant value.
*
* - All values have to match.