blob: 7e99a1bbb30fce4077008765a2751f855207f925 [file] [log] [blame]
/*
* Copyright (c) 2017-2020 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 "arm_compute/runtime/CL/functions/CLCannyEdge.h"
#include "arm_compute/core/CL/ICLTensor.h"
#include "arm_compute/core/CL/OpenCL.h"
#include "arm_compute/core/Error.h"
#include "arm_compute/core/Validate.h"
#include "arm_compute/runtime/CL/CLScheduler.h"
#include "arm_compute/runtime/CL/functions/CLSobel3x3.h"
#include "arm_compute/runtime/CL/functions/CLSobel5x5.h"
#include "arm_compute/runtime/CL/functions/CLSobel7x7.h"
#include "src/core/CL/kernels/CLCannyEdgeKernel.h"
#include "src/core/CL/kernels/CLFillBorderKernel.h"
#include "src/core/CL/kernels/CLSobel5x5Kernel.h"
#include "src/core/CL/kernels/CLSobel7x7Kernel.h"
using namespace arm_compute;
CLCannyEdge::CLCannyEdge(std::shared_ptr<IMemoryManager> memory_manager) // NOLINT
: _memory_group(std::move(memory_manager)),
_sobel(),
_gradient(std::make_unique<CLGradientKernel>()),
_border_mag_gradient(std::make_unique<CLFillBorderKernel>()),
_non_max_suppr(std::make_unique<CLEdgeNonMaxSuppressionKernel>()),
_edge_trace(std::make_unique<CLEdgeTraceKernel>()),
_gx(),
_gy(),
_mag(),
_phase(),
_nonmax(),
_visited(),
_recorded(),
_l1_list_counter(),
_l1_stack(),
_output(nullptr)
{
}
CLCannyEdge::~CLCannyEdge() = default;
void CLCannyEdge::configure(ICLTensor *input, ICLTensor *output, int32_t upper_thr, int32_t lower_thr, int32_t gradient_size, int32_t norm_type, BorderMode border_mode,
uint8_t constant_border_value)
{
configure(CLKernelLibrary::get().get_compile_context(), input, output, upper_thr, lower_thr, gradient_size, norm_type, border_mode, constant_border_value);
}
void CLCannyEdge::configure(const CLCompileContext &compile_context, ICLTensor *input, ICLTensor *output, int32_t upper_thr, int32_t lower_thr, int32_t gradient_size, int32_t norm_type,
BorderMode border_mode,
uint8_t constant_border_value)
{
ARM_COMPUTE_ERROR_ON_NULLPTR(input, output);
ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(input, 1, DataType::U8);
ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(output, 1, DataType::U8);
ARM_COMPUTE_ERROR_ON((1 != norm_type) && (2 != norm_type));
ARM_COMPUTE_ERROR_ON((gradient_size != 3) && (gradient_size != 5) && (gradient_size != 7));
ARM_COMPUTE_ERROR_ON((lower_thr < 0) || (lower_thr >= upper_thr));
_output = output;
const unsigned int L1_hysteresis_stack_size = 8;
const TensorShape shape = input->info()->tensor_shape();
TensorInfo gradient_info;
TensorInfo info;
// Initialize images
if(gradient_size < 7)
{
gradient_info.init(shape, 1, arm_compute::DataType::S16);
info.init(shape, 1, arm_compute::DataType::U16);
}
else
{
gradient_info.init(shape, 1, arm_compute::DataType::S32);
info.init(shape, 1, arm_compute::DataType::U32);
}
_gx.allocator()->init(gradient_info);
_gy.allocator()->init(gradient_info);
_mag.allocator()->init(info);
_nonmax.allocator()->init(info);
TensorInfo info_u8(shape, 1, arm_compute::DataType::U8);
_phase.allocator()->init(info_u8);
_l1_list_counter.allocator()->init(info_u8);
TensorInfo info_u32(shape, 1, arm_compute::DataType::U32);
_visited.allocator()->init(info_u32);
_recorded.allocator()->init(info_u32);
TensorShape shape_l1_stack = input->info()->tensor_shape();
shape_l1_stack.set(0, input->info()->dimension(0) * L1_hysteresis_stack_size);
TensorInfo info_s32(shape_l1_stack, 1, arm_compute::DataType::S32);
_l1_stack.allocator()->init(info_s32);
// Manage intermediate buffers
_memory_group.manage(&_gx);
_memory_group.manage(&_gy);
// Configure/Init sobelNxN
if(gradient_size == 3)
{
auto k = std::make_unique<CLSobel3x3>();
k->configure(compile_context, input, &_gx, &_gy, border_mode, constant_border_value);
_sobel = std::move(k);
}
else if(gradient_size == 5)
{
auto k = std::make_unique<CLSobel5x5>();
k->configure(compile_context, input, &_gx, &_gy, border_mode, constant_border_value);
_sobel = std::move(k);
}
else if(gradient_size == 7)
{
auto k = std::make_unique<CLSobel7x7>();
k->configure(compile_context, input, &_gx, &_gy, border_mode, constant_border_value);
_sobel = std::move(k);
}
else
{
ARM_COMPUTE_ERROR_VAR("Gradient size %d not supported", gradient_size);
}
// Manage intermediate buffers
_memory_group.manage(&_mag);
_memory_group.manage(&_phase);
// Configure gradient
_gradient->configure(compile_context, &_gx, &_gy, &_mag, &_phase, norm_type);
// Allocate intermediate buffers
_gx.allocator()->allocate();
_gy.allocator()->allocate();
// Manage intermediate buffers
_memory_group.manage(&_nonmax);
// Configure non-maxima suppression
_non_max_suppr->configure(compile_context, &_mag, &_phase, &_nonmax, lower_thr, border_mode == BorderMode::UNDEFINED);
// Allocate intermediate buffers
_phase.allocator()->allocate();
// Fill border around magnitude image as non-maxima suppression will access
// it. If border mode is undefined filling the border is a nop.
_border_mag_gradient->configure(compile_context, &_mag, _non_max_suppr->border_size(), border_mode, constant_border_value);
// Allocate intermediate buffers
_mag.allocator()->allocate();
// Manage intermediate buffers
_memory_group.manage(&_visited);
_memory_group.manage(&_recorded);
_memory_group.manage(&_l1_stack);
_memory_group.manage(&_l1_list_counter);
// Configure edge tracing
_edge_trace->configure(compile_context, &_nonmax, output, upper_thr, lower_thr, &_visited, &_recorded, &_l1_stack, &_l1_list_counter);
// Allocate intermediate buffers
_visited.allocator()->allocate();
_recorded.allocator()->allocate();
_l1_stack.allocator()->allocate();
_l1_list_counter.allocator()->allocate();
_nonmax.allocator()->allocate();
}
void CLCannyEdge::run()
{
MemoryGroupResourceScope scope_mg(_memory_group);
// Run sobel
_sobel->run();
// Run phase and magnitude calculation
CLScheduler::get().enqueue(*_gradient, false);
// Fill border before non-maxima suppression. Nop for border mode undefined.
CLScheduler::get().enqueue(*_border_mag_gradient, false);
// Run non max suppresion
_nonmax.clear(CLScheduler::get().queue());
CLScheduler::get().enqueue(*_non_max_suppr, false);
// Clear temporary structures and run edge trace
_output->clear(CLScheduler::get().queue());
_visited.clear(CLScheduler::get().queue());
_recorded.clear(CLScheduler::get().queue());
_l1_list_counter.clear(CLScheduler::get().queue());
_l1_stack.clear(CLScheduler::get().queue());
CLScheduler::get().enqueue(*_edge_trace, true);
}