Skip to content

Commit 479bab0

Browse files
author
pradeep
committed
Remove kernel size limit for b8 inputs of erode/dilate
b8(binary images) don't have any size limitations for structuring-element/kernel starting with this change. For such larger kernels, convolution(fft) based implementation is used.
1 parent 5b05f1b commit 479bab0

3 files changed

Lines changed: 119 additions & 16 deletions

File tree

src/api/c/morph.cpp

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,36 @@
77
* http://arrayfire.com/licenses/BSD-3-Clause
88
********************************************************/
99

10+
#include <arith.hpp>
1011
#include <backend.hpp>
12+
#include <cast.hpp>
1113
#include <common/err_common.hpp>
14+
#include <common/indexing_helpers.hpp>
15+
#include <copy.hpp>
16+
#include <fftconvolve.hpp>
1217
#include <handle.hpp>
18+
#include <logic.hpp>
19+
#include <math.hpp>
1320
#include <morph.hpp>
21+
#include <unary.hpp>
1422
#include <af/defines.h>
1523
#include <af/dim4.hpp>
1624
#include <af/image.h>
1725

1826
using af::dim4;
27+
using common::flip;
28+
using detail::arithOp;
1929
using detail::Array;
30+
using detail::cast;
2031
using detail::cdouble;
2132
using detail::cfloat;
2233
using detail::createEmptyArray;
34+
using detail::createValueArray;
35+
using detail::logicOp;
36+
using detail::scalar;
2337
using detail::uchar;
2438
using detail::uint;
39+
using detail::unaryOp;
2540
using detail::ushort;
2641

2742
template<typename T, bool isDilation>
@@ -32,6 +47,62 @@ static inline af_array morph(const af_array &in, const af_array &mask) {
3247
return getHandle(out);
3348
}
3449

50+
template<bool isDilation>
51+
static inline af_array morph(const af_array &input, const af_array &mask) {
52+
using detail::fftconvolve;
53+
54+
#if defined(AF_CPU)
55+
#if defined(USE_MKL)
56+
constexpr unsigned fftMethodThreshold = 11;
57+
#else
58+
constexpr unsigned fftMethodThreshold = 27;
59+
#endif // defined(USE_MKL)
60+
#elif defined(AF_CUDA)
61+
constexpr unsigned fftMethodThreshold = 17;
62+
#elif defined(AF_OPENCL)
63+
constexpr unsigned fftMethodThreshold = 19;
64+
#endif // defined(AF_CPU)
65+
66+
const Array<float> se = castArray<float>(mask);
67+
const dim4 &seDims = se.dims();
68+
69+
if (seDims[0] <= fftMethodThreshold) {
70+
return morph<char, isDilation>(input, mask);
71+
}
72+
73+
DIM_ASSERT(2, (seDims[0] == seDims[1]));
74+
75+
const Array<char> in = getArray<char>(input);
76+
const dim4 &inDims = in.dims();
77+
const auto paddedSe =
78+
padArrayBorders(se,
79+
{static_cast<dim_t>(seDims[0] % 2 == 0),
80+
static_cast<dim_t>(seDims[1] % 2 == 0), 0, 0},
81+
{0, 0, 0, 0}, AF_PAD_ZERO);
82+
83+
auto fftConv = fftconvolve<float, float, cfloat, false, false, 2>;
84+
85+
if (isDilation) {
86+
Array<float> dft =
87+
fftConv(cast<float>(in), paddedSe, false, AF_BATCH_LHS);
88+
89+
return getHandle(cast<char>(unaryOp<float, af_round_t>(dft)));
90+
} else {
91+
const Array<char> ONES = createValueArray(inDims, scalar<char>(1));
92+
const Array<float> ZEROS = createValueArray(inDims, scalar<float>(0));
93+
const Array<char> inv = arithOp<char, af_sub_t>(ONES, in, inDims);
94+
95+
Array<float> dft =
96+
fftConv(cast<float>(inv), paddedSe, false, AF_BATCH_LHS);
97+
98+
Array<float> rounded = unaryOp<float, af_round_t>(dft);
99+
Array<char> thrshd = logicOp<float, af_gt_t>(rounded, ZEROS, inDims);
100+
Array<char> inverted = arithOp<char, af_sub_t>(ONES, thrshd, inDims);
101+
102+
return getHandle(inverted);
103+
}
104+
}
105+
35106
template<typename T, bool isDilation>
36107
static inline af_array morph3d(const af_array &in, const af_array &mask) {
37108
const Array<T> input = getArray<T>(in);
@@ -58,7 +129,7 @@ static af_err morph(af_array *out, const af_array &in, const af_array &mask) {
58129
switch (type) {
59130
case f32: output = morph<float, isDilation>(in, mask); break;
60131
case f64: output = morph<double, isDilation>(in, mask); break;
61-
case b8: output = morph<char, isDilation>(in, mask); break;
132+
case b8: output = morph<isDilation>(in, mask); break;
62133
case s32: output = morph<int, isDilation>(in, mask); break;
63134
case u32: output = morph<uint, isDilation>(in, mask); break;
64135
case s16: output = morph<short, isDilation>(in, mask); break;

test/data

Submodule data updated from 6a48c88 to 408f440

test/morph.cpp

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ TYPED_TEST(Morph, Erode4x4x4) {
134134
}
135135

136136
template<typename T, bool isDilation, bool isColor>
137-
void morphImageTest(string pTestFile) {
137+
void morphImageTest(string pTestFile, dim_t seLen) {
138138
SUPPORTED_TYPE_CHECK(T);
139139
if (noImageIOTests()) return;
140140

@@ -148,23 +148,31 @@ void morphImageTest(string pTestFile) {
148148
size_t testCount = inDims.size();
149149

150150
for (size_t testId = 0; testId < testCount; ++testId) {
151-
af_array inArray = 0;
152-
af_array maskArray = 0;
153-
af_array outArray = 0;
154-
af_array goldArray = 0;
155-
dim_t nElems = 0;
151+
af_array _inArray = 0;
152+
af_array inArray = 0;
153+
af_array maskArray = 0;
154+
af_array outArray = 0;
155+
af_array _goldArray = 0;
156+
af_array goldArray = 0;
157+
dim_t nElems = 0;
156158

157159
inFiles[testId].insert(0, string(TEST_DIR "/morph/"));
158160
outFiles[testId].insert(0, string(TEST_DIR "/morph/"));
159161

160-
dim4 mdims(3, 3, 1, 1);
162+
af_dtype targetType = static_cast<af_dtype>(dtype_traits<T>::af_type);
163+
164+
dim4 mdims(seLen, seLen, 1, 1);
161165
ASSERT_SUCCESS(af_constant(&maskArray, 1.0, mdims.ndims(), mdims.get(),
162-
(af_dtype)dtype_traits<T>::af_type));
166+
targetType));
163167

164168
ASSERT_SUCCESS(
165-
af_load_image(&inArray, inFiles[testId].c_str(), isColor));
169+
af_load_image(&_inArray, inFiles[testId].c_str(), isColor));
170+
ASSERT_SUCCESS(af_cast(&inArray, _inArray, targetType));
171+
166172
ASSERT_SUCCESS(
167-
af_load_image(&goldArray, outFiles[testId].c_str(), isColor));
173+
af_load_image(&_goldArray, outFiles[testId].c_str(), isColor));
174+
ASSERT_SUCCESS(af_cast(&goldArray, _goldArray, targetType));
175+
168176
ASSERT_SUCCESS(af_get_elements(&nElems, goldArray));
169177

170178
if (isDilation)
@@ -181,20 +189,44 @@ void morphImageTest(string pTestFile) {
181189
ASSERT_EQ(true, compareArraysRMSD(nElems, goldData.data(),
182190
outData.data(), 0.018f));
183191

192+
ASSERT_SUCCESS(af_release_array(_inArray));
184193
ASSERT_SUCCESS(af_release_array(inArray));
185194
ASSERT_SUCCESS(af_release_array(maskArray));
186195
ASSERT_SUCCESS(af_release_array(outArray));
196+
ASSERT_SUCCESS(af_release_array(_goldArray));
187197
ASSERT_SUCCESS(af_release_array(goldArray));
188198
}
189199
}
190200

191-
TEST(Morph, Grayscale) {
192-
morphImageTest<float, true, false>(string(TEST_DIR "/morph/gray.test"));
201+
TEST(Morph, GrayscaleDilation3x3StructuringElement) {
202+
morphImageTest<float, true, false>(string(TEST_DIR "/morph/gray.test"), 3);
203+
}
204+
205+
TEST(Morph, ColorImageErosion3x3StructuringElement) {
206+
morphImageTest<float, false, true>(string(TEST_DIR "/morph/color.test"), 3);
207+
}
208+
209+
TEST(Morph, BinaryImageDilationBy33x33Kernel) {
210+
morphImageTest<char, true, false>(
211+
string(TEST_DIR "/morph/zag_dilation.test"), 33);
212+
}
213+
214+
TEST(Morph, BinaryImageErosionBy33x33Kernel) {
215+
morphImageTest<char, false, false>(
216+
string(TEST_DIR "/morph/zag_erosion.test"), 33);
217+
}
218+
219+
#if defined(AF_CPU) // float image 33x33 morph ops
220+
TEST(Morph, DilationBy33x33Kernel) {
221+
morphImageTest<float, true, true>(
222+
string(TEST_DIR "/morph/baboon_dilation.test"), 33);
193223
}
194224

195-
TEST(Morph, ColorImage) {
196-
morphImageTest<float, false, true>(string(TEST_DIR "/morph/color.test"));
225+
TEST(Morph, ErosionBy33x33Kernel) {
226+
morphImageTest<float, false, true>(
227+
string(TEST_DIR "/morph/baboon_erosion.test"), 33);
197228
}
229+
#endif // AF_CPU float image 33x33 morph ops
198230

199231
template<typename T, bool isDilation>
200232
void morphInputTest(void) {

0 commit comments

Comments
 (0)