IVGCVSW-4707 - Add AlignCorners and HalfPixelCenters to Resize
authorDavid Monahan <david.monahan@arm.com>
Fri, 19 Jun 2020 15:43:48 +0000 (16:43 +0100)
committerDavid Monahan <david.monahan@arm.com>
Sat, 20 Jun 2020 09:46:26 +0000 (09:46 +0000)
 * Added AlignCorners and HalfPixelCenters Parameters to Resize
 * Added Unit Tests

Signed-off-by: David Monahan <david.monahan@arm.com>
Change-Id: I83420a9bcb7beec9073d201448f64eb53090e1f1

src/armnn/SerializeLayerParameters.cpp
src/armnn/layers/ResizeLayer.cpp
src/backends/backendsCommon/test/layerTests/ResizeTestImpl.cpp
src/backends/backendsCommon/test/layerTests/ResizeTestImpl.hpp
src/backends/reference/test/RefLayerTests.cpp
src/backends/reference/workloads/RefResizeWorkload.cpp
src/backends/reference/workloads/Resize.cpp
src/backends/reference/workloads/Resize.hpp

index 76b92f3..e4bf094 100644 (file)
@@ -293,6 +293,8 @@ void StringifyLayerParameters<ResizeBilinearDescriptor>::Serialize(ParameterStri
     fn("TargetWidth", std::to_string(desc.m_TargetWidth));
     fn("TargetHeight", std::to_string(desc.m_TargetHeight));
     fn("DataLayout", GetDataLayoutName(desc.m_DataLayout));
+    fn("AlignCorners", std::to_string(desc.m_AlignCorners));
+    fn("HalfPixelCenters", std::to_string(desc.m_HalfPixelCenters));
 }
 
 void StringifyLayerParameters<ResizeDescriptor>::Serialize(ParameterStringifyFunction& fn,
@@ -302,6 +304,8 @@ void StringifyLayerParameters<ResizeDescriptor>::Serialize(ParameterStringifyFun
     fn("TargetHeight", std::to_string(desc.m_TargetHeight));
     fn("ResizeMethod", GetResizeMethodAsCString(desc.m_Method));
     fn("DataLayout", GetDataLayoutName(desc.m_DataLayout));
+    fn("AlignCorners", std::to_string(desc.m_AlignCorners));
+    fn("HalfPixelCenters", std::to_string(desc.m_HalfPixelCenters));
 }
 
 void StringifyLayerParameters<SpaceToBatchNdDescriptor>::Serialize(ParameterStringifyFunction& fn,
index 9654e58..b16adeb 100644 (file)
@@ -50,6 +50,11 @@ std::vector<TensorShape> ResizeLayer::InferOutputShapes(const std::vector<Tensor
         TensorShape( { outBatch, outHeight, outWidth, outChannels } ) :
         TensorShape( { outBatch, outChannels, outHeight, outWidth });
 
+    if (m_Param.m_HalfPixelCenters && m_Param.m_AlignCorners)
+    {
+        throw LayerValidationException("ResizeLayer: AlignCorners cannot be true when HalfPixelCenters is true");
+    }
+
     return std::vector<TensorShape>({ tensorShape });
 }
 
index f12f53c..72507d3 100644 (file)
@@ -29,7 +29,9 @@ struct ResizeTestParams
         , m_InQuantScale(1.0f)
         , m_InQuantOffset(0)
         , m_OutQuantScale(1.0f)
-        , m_OutQuantOffset(0) {}
+        , m_OutQuantOffset(0)
+        , m_AlignCorners(false)
+        , m_HalfPixelCenters(false) {}
 
     armnn::ResizeMethod m_ResizeMethod;
     armnn::DataLayout   m_DataLayout;
@@ -46,6 +48,9 @@ struct ResizeTestParams
     float               m_OutQuantScale;
     int32_t             m_OutQuantOffset;
 
+    bool m_AlignCorners;
+    bool m_HalfPixelCenters;
+
     void SetInQuantParams(float quantScale, int32_t quantOffset)
     {
         m_InQuantScale   = quantScale;
@@ -111,6 +116,8 @@ LayerTestResult<T, NumDims> ResizeTestImpl(
     armnn::ResizeQueueDescriptor descriptor;
     descriptor.m_Parameters.m_Method     = params.m_ResizeMethod;
     descriptor.m_Parameters.m_DataLayout = params.m_DataLayout;
+    descriptor.m_Parameters.m_AlignCorners = params.m_AlignCorners;
+    descriptor.m_Parameters.m_HalfPixelCenters = params.m_HalfPixelCenters;
 
     armnnUtils::DataLayoutIndexed dataLayoutIndexed(params.m_DataLayout);
     descriptor.m_Parameters.m_TargetWidth  = params.m_OutputShape[dataLayoutIndexed.GetWidthIndex()];
@@ -528,6 +535,129 @@ LayerTestResult<T, 4> ResizeNearestNeighborMagTest(
     return ResizeTestImpl<4, ArmnnType>(workloadFactory, memoryManager, testParams);
 }
 
+template<armnn::DataType ArmnnType, typename T>
+LayerTestResult<T, 4> HalfPixelCentersResizeBilinearTest(
+    armnn::IWorkloadFactory& workloadFactory,
+    const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+    const armnn::DataLayout dataLayout)
+{
+    ResizeTestParams testParams;
+    testParams.m_ResizeMethod = armnn::ResizeMethod::Bilinear;
+    testParams.m_DataLayout   = dataLayout;
+    testParams.m_HalfPixelCenters = true;
+
+    testParams.m_InputShape  = { 2, 1, 2, 2 };
+    testParams.m_OutputShape = { 2, 1, 3, 3 };
+
+    testParams.m_InputData =
+    {
+          1.0f, 2.0f,
+          3.0f, 4.0f,
+
+          1.0f, 2.0f,
+          3.0f, 4.0f
+    };
+
+    testParams.m_ExpectedOutputData =
+    {
+          1.0f, 1.5f, 2.0f,
+          2.0f, 2.5f, 3.0f,
+          3.0f, 3.5f, 4.0f,
+
+          1.0f, 1.5f, 2.0f,
+          2.0f, 2.5f, 3.0f,
+          3.0f, 3.5f, 4.0f,
+    };
+
+    return ResizeTestImpl<4, ArmnnType>(workloadFactory, memoryManager, testParams);
+}
+
+template<armnn::DataType ArmnnType, typename T>
+LayerTestResult<T, 4> AlignCornersResizeBilinearTest(
+    armnn::IWorkloadFactory& workloadFactory,
+    const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+    const armnn::DataLayout dataLayout)
+{
+    ResizeTestParams testParams;
+    testParams.m_ResizeMethod = armnn::ResizeMethod::Bilinear;
+    testParams.m_DataLayout   = dataLayout;
+    testParams.m_AlignCorners = true;
+
+    testParams.m_InputShape  = { 1, 1, 2, 2 };
+    testParams.m_OutputShape = { 1, 1, 1, 1 };
+
+    testParams.m_InputData =
+    {
+          1.0f, 2.0f,
+          3.0f, 4.0f,
+    };
+
+    testParams.m_ExpectedOutputData =
+    {
+          1.0f
+    };
+
+    return ResizeTestImpl<4, ArmnnType>(workloadFactory, memoryManager, testParams);
+}
+
+template<armnn::DataType ArmnnType, typename T>
+LayerTestResult<T, 4> HalfPixelCentersResizeNearestNeighbourTest(
+    armnn::IWorkloadFactory& workloadFactory,
+    const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+    const armnn::DataLayout dataLayout)
+{
+    ResizeTestParams testParams;
+    testParams.m_ResizeMethod = armnn::ResizeMethod::NearestNeighbor;
+    testParams.m_DataLayout   = dataLayout;
+    testParams.m_HalfPixelCenters = true;
+
+    testParams.m_InputShape  = { 1, 1, 2, 5 };
+    testParams.m_OutputShape = { 1, 1, 2, 2 };
+
+    testParams.m_InputData =
+    {
+          1.0f, 2.0f, 3.0f, 4.0f, 5.0f,
+
+          1.0f, 2.0f, 3.0f, 4.0f, 5.0f
+    };
+
+    testParams.m_ExpectedOutputData =
+    {
+          2.0f, 4.0f,
+          2.0f, 4.0f
+    };
+
+    return ResizeTestImpl<4, ArmnnType>(workloadFactory, memoryManager, testParams);
+}
+
+template<armnn::DataType ArmnnType, typename T>
+LayerTestResult<T, 4> AlignCornersResizeNearestNeighbourTest(
+    armnn::IWorkloadFactory& workloadFactory,
+    const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+    const armnn::DataLayout dataLayout)
+{
+    ResizeTestParams testParams;
+    testParams.m_ResizeMethod = armnn::ResizeMethod::NearestNeighbor;
+    testParams.m_DataLayout   = dataLayout;
+    testParams.m_AlignCorners = true;
+
+    testParams.m_InputShape  = { 1, 1, 2, 2 };
+    testParams.m_OutputShape = { 1, 1, 1, 1 };
+
+    testParams.m_InputData =
+    {
+          1.0f, 2.0f,
+          3.0f, 4.0f,
+    };
+
+    testParams.m_ExpectedOutputData =
+    {
+          1.0f
+    };
+
+    return ResizeTestImpl<4, ArmnnType>(workloadFactory, memoryManager, testParams);
+}
+
 //
 // Explicit template instantiations
 //
@@ -597,6 +727,30 @@ ResizeNearestNeighborMagTest<armnn::DataType::Float32>(
     float outQuantScale,
     int32_t outQuantOffset);
 
+template LayerTestResult<armnn::ResolveType<armnn::DataType::Float32>, 4>
+HalfPixelCentersResizeBilinearTest<armnn::DataType::Float32>(
+    armnn::IWorkloadFactory& workloadFactory,
+    const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+    const armnn::DataLayout dataLayout);
+
+template LayerTestResult<armnn::ResolveType<armnn::DataType::Float32>, 4>
+AlignCornersResizeBilinearTest<armnn::DataType::Float32>(
+    armnn::IWorkloadFactory& workloadFactory,
+    const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+    const armnn::DataLayout dataLayout);
+
+template LayerTestResult<armnn::ResolveType<armnn::DataType::Float32>, 4>
+HalfPixelCentersResizeNearestNeighbourTest<armnn::DataType::Float32>(
+    armnn::IWorkloadFactory& workloadFactory,
+    const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+    const armnn::DataLayout dataLayout);
+
+template LayerTestResult<armnn::ResolveType<armnn::DataType::Float32>, 4>
+AlignCornersResizeNearestNeighbourTest<armnn::DataType::Float32>(
+    armnn::IWorkloadFactory& workloadFactory,
+    const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+    const armnn::DataLayout dataLayout);
+
 // Float16
 template LayerTestResult<armnn::ResolveType<armnn::DataType::Float16>, 4>
 ResizeBilinearNopTest<armnn::DataType::Float16>(
index 538a64c..b70ae85 100644 (file)
@@ -79,3 +79,27 @@ LayerTestResult<T, 4> ResizeNearestNeighborMagTest(
     int32_t inQuantOffset,
     float outQuantScale,
     int32_t outQuantOffset);
+
+template<armnn::DataType ArmnnType, typename T = armnn::ResolveType<ArmnnType>>
+LayerTestResult<T, 4> HalfPixelCentersResizeBilinearTest(
+    armnn::IWorkloadFactory& workloadFactory,
+    const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+    const armnn::DataLayout dataLayout);
+
+template<armnn::DataType ArmnnType, typename T = armnn::ResolveType<ArmnnType>>
+LayerTestResult<T, 4> AlignCornersResizeBilinearTest(
+    armnn::IWorkloadFactory& workloadFactory,
+    const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+    const armnn::DataLayout dataLayout);
+
+template<armnn::DataType ArmnnType, typename T = armnn::ResolveType<ArmnnType>>
+LayerTestResult<T, 4> HalfPixelCentersResizeNearestNeighbourTest(
+    armnn::IWorkloadFactory& workloadFactory,
+    const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+    const armnn::DataLayout dataLayout);
+
+template<armnn::DataType ArmnnType, typename T = armnn::ResolveType<ArmnnType>>
+LayerTestResult<T, 4> AlignCornersResizeNearestNeighbourTest(
+    armnn::IWorkloadFactory& workloadFactory,
+    const armnn::IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
+    const armnn::DataLayout dataLayout);
\ No newline at end of file
index 93bfb90..4d347ca 100644 (file)
@@ -873,6 +873,12 @@ ARMNN_AUTO_TEST_CASE(ResizeBilinearMagUint8,
 ARMNN_AUTO_TEST_CASE(ResizeBilinearMagUint16,
                      SimpleResizeBilinearTest<DataType::QSymmS16>,
                      DataLayout::NCHW)
+ARMNN_AUTO_TEST_CASE(HalfPixelCentersResizeBilinear,
+                     HalfPixelCentersResizeBilinearTest<DataType::Float32>,
+                     DataLayout::NCHW)
+ARMNN_AUTO_TEST_CASE(AlignCornersResizeBilinear,
+                     AlignCornersResizeBilinearTest<DataType::Float32>,
+                     DataLayout::NCHW)
 
 // Resize Bilinear - NHWC
 ARMNN_AUTO_TEST_CASE(ResizeBilinearNopNhwc,
@@ -1012,6 +1018,12 @@ ARMNN_AUTO_TEST_CASE(ResizeNearestNeighborMagUint8,
 ARMNN_AUTO_TEST_CASE(ResizeNearestNeighborMagUint16,
                      SimpleResizeNearestNeighborTest<DataType::QSymmS16>,
                      DataLayout::NCHW)
+ARMNN_AUTO_TEST_CASE(HalfPixelCentersResizeNearestNeighbour,
+                     HalfPixelCentersResizeNearestNeighbourTest<DataType::Float32>,
+                     DataLayout::NCHW)
+ARMNN_AUTO_TEST_CASE(AlignCornersResizeNearestNeighbour,
+                     AlignCornersResizeNearestNeighbourTest<DataType::Float32>,
+                     DataLayout::NCHW)
 
 // Resize NearestNeighbor - NHWC
 ARMNN_AUTO_TEST_CASE(ResizeNearestNeighborNopNhwc,
index 49e4f36..21ff852 100644 (file)
@@ -35,7 +35,8 @@ void RefResizeWorkload::Execute() const
            outputInfo,
            m_Data.m_Parameters.m_DataLayout,
            m_Data.m_Parameters.m_Method,
-           m_Data.m_Parameters.m_AlignCorners);
+           m_Data.m_Parameters.m_AlignCorners,
+           m_Data.m_Parameters.m_HalfPixelCenters);
 }
 
 } //namespace armnn
index 407774e..16cdd4a 100644 (file)
@@ -30,6 +30,36 @@ inline double EuclideanDistance(float Xa, float Ya, const unsigned int Xb, const
     return std::sqrt(pow(Xa - boost::numeric_cast<float>(Xb), 2) + pow(Ya - boost::numeric_cast<float>(Yb), 2));
 }
 
+inline float CalculateResizeScale(const unsigned int& InputSize,
+                                  const unsigned int& OutputSize,
+                                  const bool& AlignCorners)
+{
+    return (AlignCorners && OutputSize > 1)
+            ?  boost::numeric_cast<float>(InputSize - 1) / boost::numeric_cast<float>(OutputSize - 1)
+            :  boost::numeric_cast<float>(InputSize) / boost::numeric_cast<float>(OutputSize);
+}
+
+inline float PixelScaler(const unsigned int& Pixel,
+                         const float& Scale,
+                         const bool& HalfPixelCenters,
+                         armnn::ResizeMethod& resizeMethod)
+{
+    // For Half Pixel Centers the Top Left texel is assumed to be at 0.5,0.5
+    if (HalfPixelCenters && resizeMethod == armnn::ResizeMethod::Bilinear)
+    {
+        return (static_cast<float>(Pixel) + 0.5f) * Scale - 0.5f;
+    }
+    // Nearest Neighbour doesn't need to have 0.5f trimmed off as it will floor the values later
+    else if (HalfPixelCenters && resizeMethod == armnn::ResizeMethod::NearestNeighbor)
+    {
+        return (static_cast<float>(Pixel) + 0.5f) * Scale;
+    }
+    else
+    {
+        return static_cast<float>(Pixel) * Scale;
+    }
+}
+
 }// anonymous namespace
 
 void Resize(Decoder<float>&   in,
@@ -38,8 +68,12 @@ void Resize(Decoder<float>&   in,
             const TensorInfo& outputInfo,
             DataLayoutIndexed dataLayout,
             armnn::ResizeMethod resizeMethod,
-            bool alignCorners)
+            bool alignCorners,
+            bool halfPixelCenters)
 {
+    // alignCorners and halfPixelCenters cannot both be true
+    ARMNN_ASSERT(!(alignCorners && halfPixelCenters));
+
     // 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.
@@ -52,14 +86,10 @@ void Resize(Decoder<float>&   in,
     const unsigned int outputHeight = outputInfo.GetShape()[dataLayout.GetHeightIndex()];
     const unsigned int outputWidth = outputInfo.GetShape()[dataLayout.GetWidthIndex()];
 
-    const unsigned int sizeOffset = resizeMethod == armnn::ResizeMethod::Bilinear && alignCorners ? 1 : 0;
-
     // 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<float>(inputHeight - sizeOffset)
-                       / boost::numeric_cast<float>(outputHeight - sizeOffset);
-    const float scaleX = boost::numeric_cast<float>(inputWidth - sizeOffset)
-                       / boost::numeric_cast<float>(outputWidth - sizeOffset);
+    const float scaleY = CalculateResizeScale(inputHeight, outputHeight, alignCorners);
+    const float scaleX = CalculateResizeScale(inputWidth, outputWidth, alignCorners);
 
     TensorShape inputShape =  inputInfo.GetShape();
     TensorShape outputShape =  outputInfo.GetShape();
@@ -71,11 +101,13 @@ void Resize(Decoder<float>&   in,
             for (unsigned int y = 0; y < outputHeight; ++y)
             {
                 // Corresponding real-valued height coordinate in input image.
-                const float iy = boost::numeric_cast<float>(y) * scaleY;
+                float iy = PixelScaler(y, scaleY, halfPixelCenters, resizeMethod);
 
                 // 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<unsigned int>(fiy);
+                const float fiy = (resizeMethod == armnn::ResizeMethod::NearestNeighbor && alignCorners) ?
+                                  roundf(iy) : floorf(iy);
+                // Pixel scaling a value with Half Pixel Centers can be negative, if so set to 0
+                const unsigned int y0 = static_cast<unsigned int>(std::max(fiy, 0.0f));
 
                 // Interpolation weight (range [0,1]).
                 const float yw = iy - fiy;
@@ -83,16 +115,31 @@ void Resize(Decoder<float>&   in,
                 for (unsigned int x = 0; x < outputWidth; ++x)
                 {
                     // Real-valued and discrete width coordinates in input image.
-                    const float ix = boost::numeric_cast<float>(x) * scaleX;
-                    const float fix = floorf(ix);
-                    const unsigned int x0 = boost::numeric_cast<unsigned int>(fix);
+                    float ix = PixelScaler(x, scaleX, halfPixelCenters, resizeMethod);
+
+                    // Nearest Neighbour uses rounding to align to corners
+                    const float fix = resizeMethod == armnn::ResizeMethod::NearestNeighbor && alignCorners ?
+                                      roundf(ix) : floorf(ix);
+                    // Pixel scaling a value with Half Pixel Centers can be negative, if so set to 0
+                    const unsigned int x0 = static_cast<unsigned int>(std::max(fix, 0.0f));
 
                     // Interpolation weight (range [0,1]).
                     const float xw = ix - fix;
 
+                    unsigned int x1;
+                    unsigned int y1;
+                    // Half Pixel Centers uses the scaling to compute a weighted parameter for nearby pixels
+                    if (halfPixelCenters)
+                    {
+                        x1 = std::min(static_cast<unsigned int>(std::ceil(ix)), inputWidth - 1u);
+                        y1 = std::min(static_cast<unsigned int>(std::ceil(iy)), inputHeight - 1u);
+                    }
                     // 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);
+                    else
+                    {
+                        x1 = std::min(x0 + 1, inputWidth - 1u);
+                        y1 = std::min(y0 + 1, inputHeight - 1u);
+                    }
 
                     float interpolatedValue;
                     switch (resizeMethod)
index 58ec7df..99362af 100644 (file)
@@ -20,6 +20,7 @@ void Resize(Decoder<float>&               in,
             const TensorInfo&             outputInfo,
             armnnUtils::DataLayoutIndexed dataLayout = DataLayout::NCHW,
             ResizeMethod                  resizeMethod = ResizeMethod::NearestNeighbor,
-            bool                          alignConers = false);
+            bool                          alignCorners = false,
+            bool                          halfPixelCenters = false);
 
 } // namespace armnn