Commit b5e6686b authored by Edwin Carlinet's avatar Edwin Carlinet
Browse files

Implement opening/closing/hit or miss operators

parent 96399c86
{}
\ No newline at end of file
{
}
\ No newline at end of file
3b29ff3e51f04aac090f78e7fd96b8a6
#include <mln/core/algorithm/transform.hpp>
#include <mln/core/colors.hpp>
#include <mln/core/image/experimental/ndimage.hpp>
#include <mln/io/experimental/imread.hpp>
#include <benchmark/benchmark.h>
#include <mln/core/se/disc.hpp>
#include <mln/core/se/rect2d.hpp>
#include <mln/core/se/mask2d.hpp>
#include <mln/io/experimental/imread.hpp>
#include <mln/morpho/experimental/closing.hpp>
#include <mln/morpho/experimental/dilation.hpp>
#include <mln/morpho/experimental/erosion.hpp>
#include <mln/morpho/experimental/hit_or_miss.hpp>
#include <mln/morpho/experimental/opening.hpp>
#include <fixtures/ImagePath/image_path.hpp>
#include <benchmark/benchmark.h>
#include <fixtures/ImagePath/image_path.hpp>
class BMDilation : public benchmark::Fixture
class BMMorpho : public benchmark::Fixture
{
public:
BMDilation()
using image_t = mln::experimental::image2d<uint8_t>;
BMMorpho()
{
mln::io::experimental::imread(fixtures::ImagePath::concat_with_filename("lena.pgm"), m_input);
if (!g_loaded)
{
const char* filename = "Aerial_view_of_Olbia.jpg";
mln::experimental::image2d<mln::rgb8> input;
mln::io::experimental::imread(filename, input);
g_input = mln::transform(input, [](mln::rgb8 x) -> uint8_t { return x[0]; });
g_loaded = true;
}
m_input = g_input;
int nr = m_input.width();
int nc = m_input.height();
mln::resize(m_output, m_input);
......@@ -25,19 +39,24 @@ public:
}
void run(benchmark::State& st, std::function<void()> callback)
void run(benchmark::State& st, std::function<void(const image_t& input, image_t& output)> callback)
{
for (auto _ : st)
callback();
callback(m_input, m_output);
st.SetBytesProcessed(int64_t(st.iterations()) * int64_t(m_size));
}
protected:
mln::experimental::image2d<uint8_t> m_input;
mln::experimental::image2d<uint8_t> m_output;
std::size_t m_size;
static bool g_loaded;
static mln::experimental::image2d<uint8_t> g_input;
mln::experimental::image2d<uint8_t> m_input;
mln::experimental::image2d<uint8_t> m_output;
std::size_t m_size;
};
bool BMMorpho::g_loaded = false;
mln::experimental::image2d<uint8_t> BMMorpho::g_input;
class slow_disc : public mln::se_facade<slow_disc>
{
using Base = mln::experimental::se::disc;
......@@ -75,48 +94,81 @@ private:
};
BENCHMARK_DEFINE_F(BMDilation, EuclideanDisc_naive)(benchmark::State& st)
BENCHMARK_DEFINE_F(BMMorpho, Dilation_EuclideanDisc_naive)(benchmark::State& st)
{
int radius = st.range(0);
slow_disc se(radius, mln::experimental::se::disc::EXACT);
auto f = [&]() { mln::morpho::experimental::dilation(m_input, se, m_output); };
auto f = [se](const image_t& input, image_t& output) { mln::morpho::experimental::dilation(input, se, output); };
this->run(st, f);
}
BENCHMARK_DEFINE_F(BMDilation, EuclideanDisc_incremental)(benchmark::State& st)
BENCHMARK_DEFINE_F(BMMorpho, Dilation_EuclideanDisc_incremental)(benchmark::State& st)
{
int radius = st.range(0);
auto se = mln::experimental::se::disc(radius, mln::experimental::se::disc::EXACT);
auto f = [&]() { mln::morpho::experimental::dilation(m_input, se, m_output); };
auto f = [se](const image_t& input, image_t& output) { mln::morpho::experimental::dilation(input, se, output); };
this->run(st, f);
}
BENCHMARK_DEFINE_F(BMDilation, ApproximatedDisc)(benchmark::State& st)
BENCHMARK_DEFINE_F(BMMorpho, Dilation_ApproximatedDisc)(benchmark::State& st)
{
int radius = st.range(0);
auto se = mln::experimental::se::disc(radius, mln::experimental::se::disc::PERIODIC_LINES_8);
auto f = [&]() { mln::morpho::experimental::dilation(m_input, se, m_output); };
auto f = [se](const image_t& input, image_t& output) { mln::morpho::experimental::dilation(input, se, output); };
this->run(st, f);
}
BENCHMARK_DEFINE_F(BMDilation, Square)(benchmark::State& st)
BENCHMARK_DEFINE_F(BMMorpho, Dilation_Square)(benchmark::State& st)
{
int radius = st.range(0);
auto se = mln::experimental::se::rect2d(2 * radius + 1, 2 * radius + 1);
auto f = [&]() { mln::morpho::experimental::dilation(m_input, se, m_output); };
auto f = [se](const image_t& input, image_t& output) { mln::morpho::experimental::dilation(input, se, output); };
this->run(st, f);
}
constexpr int max_range = 2 << 6;
constexpr int max_range = 128;
BENCHMARK_REGISTER_F(BMMorpho, Dilation_ApproximatedDisc)->RangeMultiplier(2)->Range(2, max_range);
BENCHMARK_REGISTER_F(BMMorpho, Dilation_EuclideanDisc_naive)->RangeMultiplier(2)->Range(2, 16);
BENCHMARK_REGISTER_F(BMMorpho, Dilation_EuclideanDisc_incremental)->RangeMultiplier(2)->Range(2, max_range);
BENCHMARK_REGISTER_F(BMMorpho, Dilation_Square)->RangeMultiplier(2)->Range(2, max_range);
BENCHMARK_REGISTER_F(BMDilation, ApproximatedDisc)->RangeMultiplier(2)->Range(2, max_range);
BENCHMARK_REGISTER_F(BMDilation, EuclideanDisc_naive)->RangeMultiplier(2)->Range(2, max_range);
BENCHMARK_REGISTER_F(BMDilation, EuclideanDisc_incremental)->RangeMultiplier(2)->Range(2, max_range);
BENCHMARK_REGISTER_F(BMDilation, Square)->RangeMultiplier(2)->Range(2, max_range);
BENCHMARK_F(BMMorpho, Opening_Disc)(benchmark::State& st)
{
int radius = 32;
auto se = mln::experimental::se::disc(radius);
auto f = [se](const image_t& input, image_t& output) { mln::morpho::experimental::opening(input, se, output); };
this->run(st, f);
}
BENCHMARK_F(BMMorpho, Hit_or_miss_corner)(benchmark::State& st)
{
mln::se::experimental::mask2d se_hit = {
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 1, 1, 1},
{0, 0, 1, 1, 1},
{0, 0, 1, 1, 1},
};
mln::se::experimental::mask2d se_miss = {
{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1},
{1, 1, 0, 0, 0},
{1, 1, 0, 0, 0},
{1, 1, 0, 0, 0},
};
auto f = [se_hit, se_miss](const image_t& input, image_t& output) { mln::morpho::experimental::hit_or_miss(input, se_hit, se_miss, output); };
this->run(st, f);
}
BENCHMARK_MAIN();
#include <mln/core/algorithm/transform.hpp>
#include <mln/core/colors.hpp>
#include <mln/core/image/experimental/ndimage.hpp>
#include <mln/core/se/disc.hpp>
#include <mln/core/se/rect2d.hpp>
#include <mln/core/se/mask2d.hpp>
#include <mln/io/experimental/imread.hpp>
#include <mln/morpho/structural/closing.hpp>
#include <mln/morpho/structural/dilate.hpp>
#include <mln/morpho/structural/erode.hpp>
#include <mln/morpho/hit_or_miss.hpp>
#include <mln/morpho/structural/opening.hpp>
// [legacy]
#include <mln/core/image/image2d.hpp>
#include <benchmark/benchmark.h>
class BMMorpho : public benchmark::Fixture
{
public:
using image_t = mln::image2d<uint8_t>;
BMMorpho()
{
if (!g_loaded)
{
const char* filename = "Aerial_view_of_Olbia.jpg";
mln::experimental::image2d<mln::rgb8> input;
mln::io::experimental::imread(filename, input);
g_input = mln::transform(input, [](mln::rgb8 x) -> uint8_t { return x[0]; });
g_loaded = true;
}
m_input = g_input;
int nr = m_input.width();
int nc = m_input.height();
mln::resize(m_output, m_input);
m_size = nr * nc;
m_input.to(m_input_, false);
m_output.to(m_output_, false);
}
void run(benchmark::State& st, std::function<void(const image_t& input, image_t& output)> callback)
{
for (auto _ : st)
callback(m_input_, m_output_);
st.SetBytesProcessed(int64_t(st.iterations()) * int64_t(m_size));
}
protected:
static bool g_loaded;
static mln::experimental::image2d<uint8_t> g_input;
mln::experimental::image2d<uint8_t> m_input;
mln::experimental::image2d<uint8_t> m_output;
mln::image2d<uint8_t> m_input_;
mln::image2d<uint8_t> m_output_;
std::size_t m_size;
};
bool BMMorpho::g_loaded = false;
mln::experimental::image2d<uint8_t> BMMorpho::g_input;
BENCHMARK_DEFINE_F(BMMorpho, Dilation_EuclideanDisc_incremental)(benchmark::State& st)
{
int radius = st.range(0);
auto se = mln::se::disc(radius, 0);
auto f = [se](const image_t& input, image_t& output) { mln::morpho::structural::dilate(input, se, output, mln::productorder_less<uint8_t>()); };
this->run(st, f);
}
BENCHMARK_DEFINE_F(BMMorpho, Dilation_ApproximatedDisc)(benchmark::State& st)
{
int radius = st.range(0);
auto se = mln::se::disc(radius, 8);
auto f = [se](const image_t& input, image_t& output) { mln::morpho::structural::dilate(input, se, output, mln::productorder_less<uint8_t>()); };
this->run(st, f);
}
BENCHMARK_DEFINE_F(BMMorpho, Dilation_Square)(benchmark::State& st)
{
int radius = st.range(0);
auto se = mln::se::rect2d(2 * radius + 1, 2 * radius + 1);
auto f = [se](const image_t& input, image_t& output) { mln::morpho::structural::dilate(input, se, output, mln::productorder_less<uint8_t>()); };
this->run(st, f);
}
constexpr int max_range = 128;
BENCHMARK_REGISTER_F(BMMorpho, Dilation_ApproximatedDisc)->RangeMultiplier(2)->Range(2, max_range);
BENCHMARK_REGISTER_F(BMMorpho, Dilation_EuclideanDisc_incremental)->RangeMultiplier(2)->Range(2, max_range);
BENCHMARK_REGISTER_F(BMMorpho, Dilation_Square)->RangeMultiplier(2)->Range(2, max_range);
BENCHMARK_F(BMMorpho, Opening_Disc)(benchmark::State& st)
{
int radius = 32;
auto se = mln::se::disc(radius);
auto f = [se](const image_t& input, image_t& output) { mln::morpho::structural::opening(input, se, mln::productorder_less<uint8_t>(), output); };
this->run(st, f);
}
BENCHMARK_F(BMMorpho, Hit_or_miss_corner)(benchmark::State& st)
{
mln::se::mask2d se_hit = {
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 1, 1, 1},
{0, 0, 1, 1, 1},
{0, 0, 1, 1, 1},
};
mln::se::mask2d se_miss = {
{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1},
{1, 1, 0, 0, 0},
{1, 1, 0, 0, 0},
{1, 1, 0, 0, 0},
};
auto f = [se_hit, se_miss](const image_t& input, image_t& output) { mln::morpho::hit_or_miss(input, se_hit, se_miss, output); };
this->run(st, f);
}
BENCHMARK_MAIN();
......@@ -18,6 +18,7 @@ ExternalData_Expand_Arguments(
fetch-external-data
images
DATA{Space1_20MB.jpg}
DATA{Aerial_view_of_Olbia.jpg}
)
# Extra compiler options
......@@ -65,7 +66,8 @@ set_source_files_properties(${src_standalone} PROPERTIES COMPILE_FLAGS ${STANDAL
add_benchmark(BMAlgorithms BMAlgorithms.cpp BMAlgorithms_main.cpp)
add_benchmark(BMNeighborhood BMNeighborhood.cpp BMNeighborhood_main.cpp)
add_benchmark(BMRotation BMRotation.cpp)
add_benchmark(BMDilation BMDilation.cpp)
add_benchmark(BMMorphoBase BMMorphoBase.cpp)
add_benchmark(BMMorphoBaseRef BMMorphoBaseRef.cpp)
add_benchmark(BMMorphers BMMorphers.cpp BMMorphers_main.cpp)
add_benchmark(BMReference_Linear BMReference_Linear.cpp BMReference_Linear_Reversed.cpp BMReference_Linear_main.cpp)
add_benchmark(BMReference_Neighborhood BMReference_Neighborhood_main.cpp)
......
......@@ -3,19 +3,13 @@ Closing
Include :file:`<mln/morpho/structural/closing.hpp>`
.. cpp:namespace:: mln::morpho::structural
.. cpp:namespace:: mln::morpho
#. .. cpp:function:: \
template <class InputImage, class StructuringElement> \
concrete_t<InputImage> closing(const InputImage& ima, const StructuringElement& se)
#. .. cpp:function:: \
template <class InputImage, class StructuringElement, class Compare> \
concrete_t<InputImage> closing(const InputImage& ima, const StructuringElement& se, Compare cmp)
#. .. cpp:function:: \
template <class InputImage, class StructuringElement, class OutputImage, class Compare> \
void closing(const InputImage& ima, const StructuringElement& se, Compare cmp, OutputImage& output)
.. cpp:function:: \
Image{I} concrete_t<I> closing(I image, StructuringElement se)
Image{I} concrete_t<I> closing(I image, StructuringElement se, BorderManager bm)
void closing(Image image, StructuringElement se, OutputImage out)
void closing(Image image, StructuringElement se, BorderManager bm, OutputImage out)
Closing by a structuring element.
......@@ -25,21 +19,20 @@ Include :file:`<mln/morpho/structural/closing.hpp>`
.. math::
\gamma(f) = \varepsilon_\mathcal{B}(\delta_\mathcal{B}(f))
* (2,3) If a optional \p cmp function is provided, the algorithm will internally do
an unqualified call to ``inf(x, y,cmp)``.The default is the product-order so
that it works for vectorial type as well.
* (3) If the optional ``output`` image is provided, it must be wide enough to store
the results (the function does not perform any resizing).
* An optional border management may be used to manage border side-effects.
Only *fill* and *user* are currently supported.
* If the optional ``output`` image is provided, it must be wide enough to store
the result (the function does not perform any resizing).
:param ima: Input image 𝑓
:param se: Structuring element 𝐵
:param cmp (optional): Comparison function
:param bm (optional): Border management policy
:param output (optional): Output image
:return:
* (1,2) An image whose type is deduced from the input image
* (3\) Nothing (the output image is passed as an argument)
* (3,4) Nothing (the output image is passed as an argument)
:exception: N/A
......@@ -58,13 +51,13 @@ Example 1 : Closing by a square on a gray-level image
.. code-block:: cpp
#include <mln/morpho/structural/closing.hpp>
#include <mln/core/wind2d.hpp>
#include <mln/morpho/closing.hpp>
#include <mln/core/se/rect2d.hpp>
// Define a square SE of size 21x21
auto input = ...;
auto rect = mln::make_rectangle2d(21,21);
auto output = mln::morpho::structural::closing(input, rect);
auto rect = mln::se::rect2d(21,21);
auto output = mln::morpho::closing(input, rect);
.. image:: /images/lena_gray.jpg
......
......@@ -57,7 +57,7 @@ Example 1 : Dilation by a square on a gray-level image
.. code-block:: cpp
#include <mln/morpho/dilation.hpp>
#include <mln/core/wind2d.hpp>
#include <mln/core/se/rect2d.hpp>
// Define a square SE of size 21x21
auto input = ...;
......
Hit or Miss
===========
#. .. cpp:function:: \
template <class InputImage, class StructuringElement1, class StructuringElement2> \
concrete_t<InputImage> hit_or_miss(const InputImage& ima, const StructuringElement1& se_hit, const StructuringElement1& se_miss)
#. .. cpp:function:: \
template <class InputImage, class StructuringElement1, class StructuringElement2, class OutputImage> \
void hit_or_miss(const InputImage& ima, const StructuringElement1& se_hit, const StructuringElement1& se_miss, OutputImage& output)
Include :file:`<mln/morpho/hit_or_miss.hpp>`
.. cpp:namespace:: mln::morpho
.. cpp:function:: \
Image{I} concrete_t<I> hit_or_miss(I image, StructuringElement se_hit, StructuringElement se_miss)
void hit_or_miss(Image image, StructuringElement se_hit, StructuringElement se_miss, OutputImage out)
The hit-or-miss transform is non-linerar filter used to detect pattern in
images. It is defined as:
......
......@@ -3,19 +3,13 @@ Opening
Include :file:`<mln/morpho/structural/opening.hpp>`
.. cpp:namespace:: mln::morpho::structural
.. cpp:namespace:: mln::morpho
#. .. cpp:function:: \
template <class InputImage, class StructuringElement> \
concrete_t<InputImage> opening(const InputImage& ima, const StructuringElement& se)
#. .. cpp:function:: \
template <class InputImage, class StructuringElement, class Compare> \
concrete_t<InputImage> opening(const InputImage& ima, const StructuringElement& se, Compare cmp)
#. .. cpp:function:: \
template <class InputImage, class StructuringElement, class OutputImage, class Compare> \
void opening(const InputImage& ima, const StructuringElement& se, Compare cmp, OutputImage& output)
.. cpp:function:: \
Image{I} concrete_t<I> opening(I image, StructuringElement se)
Image{I} concrete_t<I> opening(I image, StructuringElement se, BorderManager bm)
void opening(Image image, StructuringElement se, OutputImage out)
void opening(Image image, StructuringElement se, BorderManager bm, OutputImage out)
Opening by a structuring element.
......@@ -25,22 +19,21 @@ Include :file:`<mln/morpho/structural/opening.hpp>`
.. math::
\gamma(f) = \delta_\mathcal{B}(\varepsilon_\mathcal{B}(f))
* (2,3) If a optional \p cmp function is provided, the algorithm will internally do
an unqualified call to ``inf(x, y,cmp)``.The default is the product-order so
that it works for vectorial type as well.
* (3) If the optional ``output`` image is provided, it must be wide enough to store
the results (the function does not perform any resizing).
* An optional border management may be used to manage border side-effects.
Only *fill* and *user* are currently supported.
* If the optional ``output`` image is provided, it must be wide enough to store
the result (the function does not perform any resizing).
:param ima: Input image 𝑓
:param se: Structuring element 𝐵
:param cmp (optional): Comparison function
:param bm (optional): Border management policy
:param output (optional): Output image
:return:
* (1,2) An image whose type is deduced from the input image
* (3\) Nothing (the output image is passed as an argument)
* (3,4) Nothing (the output image is passed as an argument)
:exception: N/A
......@@ -58,13 +51,13 @@ Example 1 : Opening by a square on a gray-level image
.. code-block:: cpp
#include <mln/morpho/structural/opening.hpp>
#include <mln/core/wind2d.hpp>
#include <mln/morpho/opening.hpp>
#include <mln/core/se/rect2d.hpp>
// Define a square SE of size 21x21
auto input = ...;
auto rect = mln::make_rectangle2d(21,21);
auto output = mln::morpho::structural::opening(input, rect);
auto rect = mln::se::rect2d(21,21);
auto output = mln::morpho::opening(input, rect);
.. image:: /images/lena_gray.jpg
......
......@@ -3,6 +3,8 @@
#include <mln/core/canvas/local_algorithm.hpp>
#include <mln/core/box.hpp>
//#include <boost/container/small_vector.hpp>
namespace mln::canvas
{
......@@ -12,12 +14,15 @@ namespace mln::canvas
template <class Accu, class SE, class I, class J>
class LocalAccumulation<Accu, SE, I, J, false> : public LocalAlgorithm<SE, I, J>
class LocalAccumulation<Accu, SE, I, J, false>
: public LocalAlgorithm<SE, I, J, LocalAccumulation<Accu, SE, I, J, false>>
{
private:
using base_t = LocalAlgorithm<SE, I, J>;
Accu m_accu;
using self_t = LocalAccumulation;
using base_t = LocalAlgorithm<SE, I, J, self_t>;
Accu m_accu;
friend base_t;
public:
LocalAccumulation(Accu accu, SE se, I& f, J& g)
......@@ -42,14 +47,58 @@ namespace mln::canvas
{
m_accu.take(nval_i);
}
public:
/*
void ExecuteWithIndexes()
{
mln_entering("LocalAlgorithm::Execute (Non-incremental)");
std::size_t se_size = ::ranges::size(this->m_se.offsets());
// Make a local copy to prevent aliasing
std::vector<std::ptrdiff_t> offsets(se_size);
std::vector<image_point_t<I>> dps(se_size);
{
std::size_t i = 0;
for (auto dp : this->m_se.offsets())
{
offsets[i] = this->m_i.delta_index(dp);
dps[i] = dp;
i++;
}
}
auto zz = ranges::view::zip(this->m_i.new_pixels(), this->m_j.new_pixels());
for (auto rows : ranges::rows(zz))
{
this->ExecuteAtLineStart();
for (auto [px_i, px_j] : rows)
{
this->EvalBeforeLocalLoop(px_i.val(), px_j.val());
for (size_t i = 0; i < se_size; ++i)
{
auto tmp = px_i; tmp.ishift(dps[i], offsets[i]);
this->EvalInLocalLoop(tmp.val(), px_i.val(), px_j.val());
}
this->EvalAfterLocalLoop(px_i.val(), px_j.val());
}
}
}
*/
};
template <class Accu, class SE, class I, class J>
class LocalAccumulation<Accu, SE, I, J, true> : public IncrementalLocalAlgorithm<SE, I, J>
class LocalAccumulation<Accu, SE, I, J, true> : public IncrementalLocalAlgorithm<SE, I, J,
LocalAccumulation<Accu, SE, I, J, true>>
{
private:
using base_t = IncrementalLocalAlgorithm<SE, I, J>;
using self_t = LocalAccumulation;
using base_t = IncrementalLocalAlgorithm<SE, I, J, LocalAccumulation>;
using base_2_t = LocalAlgorithm<SE, I, J, LocalAccumulation>;
Accu m_accu;
public:
......@@ -59,6 +108,8 @@ namespace mln::canvas
{
}
friend base_t;
friend base_2_t;