Commit 9f3025ec authored by Edwin Carlinet's avatar Edwin Carlinet
Browse files

Implement Opening By Reconstruction

parent 8d7c47c2
#include <mln/core/algorithm/transform.hpp> #include <mln/core/algorithm/transform.hpp>
#include <mln/core/colors.hpp> #include <mln/core/colors.hpp>
#include <mln/core/image/experimental/ndimage.hpp> #include <mln/core/image/experimental/ndimage.hpp>
#include <mln/core/se/disc.hpp> #include <mln/core/se/disc.hpp>
#include <mln/core/se/rect2d.hpp> #include <mln/core/se/rect2d.hpp>
#include <mln/core/se/mask2d.hpp> #include <mln/core/se/mask2d.hpp>
#include <mln/core/neighborhood/c4.hpp>
#include <mln/io/experimental/imread.hpp> #include <mln/io/experimental/imread.hpp>
#include <mln/morpho/experimental/closing.hpp> #include <mln/morpho/experimental/closing.hpp>
#include <mln/morpho/experimental/dilation.hpp> #include <mln/morpho/experimental/dilation.hpp>
#include <mln/morpho/experimental/erosion.hpp> #include <mln/morpho/experimental/erosion.hpp>
#include <mln/morpho/experimental/hit_or_miss.hpp> #include <mln/morpho/experimental/hit_or_miss.hpp>
#include <mln/morpho/experimental/opening.hpp>
#include <mln/morpho/experimental/median_filter.hpp> #include <mln/morpho/experimental/median_filter.hpp>
#include <mln/morpho/experimental/opening.hpp>
#include <mln/morpho/experimental/reconstruction.hpp>
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
...@@ -151,15 +155,31 @@ BENCHMARK_F(BMMorpho, Opening_Disc)(benchmark::State& st) ...@@ -151,15 +155,31 @@ BENCHMARK_F(BMMorpho, Opening_Disc)(benchmark::State& st)
} }
BENCHMARK_F(BMMorpho, Median_Filter_Disc)(benchmark::State& st) BENCHMARK_F(BMMorpho, Median_Filter_Disc)(benchmark::State& st)
{
int radius = 32;
auto se = mln::experimental::se::rect2d(2 * radius + 1, 2 * radius + 1);
auto f = [se](const image_t& input, image_t& output) {
mln::morpho::experimental::median_filter(input, se, mln::extension::bm::mirror{}, output);
};
this->run(st, f);
}
BENCHMARK_F(BMMorpho, Opening_By_Reconstruction_Disc)(benchmark::State& st)
{ {
int radius = 32; int radius = 32;
auto se = mln::experimental::se::disc(radius); auto se = mln::experimental::se::disc(radius);
auto f = [se](const image_t& input, image_t& output) { mln::morpho::experimental::median_filter(input, se, mln::extension::bm::mirror{}, output); }; auto markers = mln::morpho::experimental::erosion(m_input, se);
auto f = [m = std::move(markers)](const image_t& input, image_t& output) {
output = mln::morpho::experimental::opening_by_reconstruction(input, m, mln::experimental::c4);
};
this->run(st, f); this->run(st, f);
} }
BENCHMARK_F(BMMorpho, Hit_or_miss_corner)(benchmark::State& st) BENCHMARK_F(BMMorpho, Hit_or_miss_corner)(benchmark::State& st)
{ {
mln::se::experimental::mask2d se_hit = { mln::se::experimental::mask2d se_hit = {
......
...@@ -4,11 +4,16 @@ ...@@ -4,11 +4,16 @@
#include <mln/core/se/disc.hpp> #include <mln/core/se/disc.hpp>
#include <mln/core/se/rect2d.hpp> #include <mln/core/se/rect2d.hpp>
#include <mln/core/se/mask2d.hpp> #include <mln/core/se/mask2d.hpp>
#include <mln/core/neighb2d.hpp>
#include <mln/io/experimental/imread.hpp> #include <mln/io/experimental/imread.hpp>
#include <mln/morpho/hit_or_miss.hpp>
#include <mln/morpho/median_filter.hpp>
#include <mln/morpho/opening_by_reconstruction.hpp>
#include <mln/morpho/structural/closing.hpp> #include <mln/morpho/structural/closing.hpp>
#include <mln/morpho/structural/dilate.hpp> #include <mln/morpho/structural/dilate.hpp>
#include <mln/morpho/structural/erode.hpp> #include <mln/morpho/structural/erode.hpp>
#include <mln/morpho/hit_or_miss.hpp>
#include <mln/morpho/structural/opening.hpp> #include <mln/morpho/structural/opening.hpp>
// [legacy] // [legacy]
...@@ -103,12 +108,26 @@ BENCHMARK_REGISTER_F(BMMorpho, Dilation_Square)->RangeMultiplier(2)->Range(2, ma ...@@ -103,12 +108,26 @@ BENCHMARK_REGISTER_F(BMMorpho, Dilation_Square)->RangeMultiplier(2)->Range(2, ma
BENCHMARK_F(BMMorpho, Opening_Disc)(benchmark::State& st) BENCHMARK_F(BMMorpho, Opening_Disc)(benchmark::State& st)
{ {
int radius = 32; int radius = 32;
auto se = mln::se::rect2d(2 * radius + 1, 2 * radius + 1);
auto f = [se](const image_t& input, image_t& output) {
output = mln::morpho::structural::opening(input, se, mln::productorder_less<uint8_t>());
};
this->run(st, f);
}
BENCHMARK_F(BMMorpho, Opening_By_Reconstruction_Disc)(benchmark::State& st)
{
int radius = 32;
auto se = mln::se::disc(radius); 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); }; auto markers = mln::morpho::structural::erode(m_input_, se);
auto f = [m = std::move(markers)](const image_t& input, image_t& output) {
output = mln::morpho::opening_by_reconstruction(input, m, mln::c4);
};
this->run(st, f); this->run(st, f);
} }
BENCHMARK_F(BMMorpho, Hit_or_miss_corner)(benchmark::State& st) BENCHMARK_F(BMMorpho, Hit_or_miss_corner)(benchmark::State& st)
{ {
mln::se::mask2d se_hit = { mln::se::mask2d se_hit = {
......
...@@ -15,7 +15,6 @@ Structural Morphological Operation ...@@ -15,7 +15,6 @@ Structural Morphological Operation
morpho/rank_filter morpho/rank_filter
morpho/median_filter morpho/median_filter
morpho/opening_by_reconstruction morpho/opening_by_reconstruction
morpho/closing_by_reconstruction
Segmentation Segmentation
......
Closing by reconstruction
=========================
Include :file:`<mln/morpho/closing_by_reconstruction.hpp>`
.. cpp:namespace:: mln::morpho
.. cpp:function:: \
template <class InputImage1, class InputImage2, class Neighborhood> \
concrete_t<InputImage1> closing_by_reconstruction(const InputImage1& f, const InputImage2& markers, const Neighborhood& nbh)
.. cpp:function:: \
template <class InputImage1, class InputImage2, class Neighborhood, class Compare> \
concrete_t<InputImage1> closing_by_reconstruction(const InputImage1& f, const InputImage2& markers, const Neighborhood& nbh, Compare cmp)
Perform the reconstruction of the image ``markers`` over the constrain
image ``f``. Contrary to :ref:`opening-by-reconstruction`, the *background* is
reconstructed instead of the *foreground*.
:param f: Input image 𝑓
:param markers: Marker image (must have the same value type of 𝑓)
:param nbh: Fundamental structuring element.
:param cmp (optional): Comparaison function defining a strict weak ordering of values.
:return: An image whose type is deduced from the input image
:precondition: :math:`markers > f`
:exception: N/A
Notes
-----
Complexity
----------
...@@ -94,11 +94,10 @@ Hit or miss transform to detect horizontal 2px-thick line, with pattern:: ...@@ -94,11 +94,10 @@ Hit or miss transform to detect horizontal 2px-thick line, with pattern::
:width: 49% :width: 49%
Logical or between the two previous images: Logical or between the two previous images::
.. literalinclude:: /snippets/staff_lines.cpp using mln::view::ops;
:lines: 50 auto markers = markers1 || markers2;
:language: cpp
.. image:: /images/staff_lines.png .. image:: /images/staff_lines.png
:width: 49% :width: 49%
......
...@@ -43,7 +43,7 @@ Complexity ...@@ -43,7 +43,7 @@ Complexity
Example 1 : Median-filter by a square on a gray-level image Example 1 : Median-filter by a square on a gray-level image
--------------------------------------------------------- -----------------------------------------------------------
.. code-block:: cpp .. code-block:: cpp
......
.. _opening-by-reconstruction: .. _opening-by-reconstruction:
Opening by reconstruction Opening & Closing by reconstruction
========================= ===================================
Include :file:`<mln/morpho/opening_by_reconstruction.hpp>` Include :file:`<mln/morpho/reconstruction.hpp>`
.. cpp:namespace:: mln::morpho .. cpp:namespace:: mln::morpho
.. cpp:function:: \ .. cpp:function:: \
template <class InputImage1, class InputImage2, class Neighborhood> \ Image{I1}\
concrete_t<InputImage1> opening_by_reconstruction(const InputImage1& f, const InputImage2& markers, const Neighborhood& nbh) concrete_t<I1> opening_by_reconstruction(I1 f, I2 markers, Neighborhood nbh, Compare cmp)
Image{I1}\
Perform the reconstruction of the image ``markers`` under the constrain concrete_t<I1> opening_by_reconstruction(I1 f, I2 markers, Neighborhood nbh)
image ``f``. The markers designate the parts of the image we want to Image{I1}\
retain. In binary, it is equivalent to perform the *conditional* concrete_t<I1> closing_by_reconstruction(I1 f, I2 markers, Neighborhood nbh)
dilation of the ``markers`` :math:`X`, by a structuring element 𝑩, using
the *reference* 𝑓 until reaching stability. The opening by reconstruction performs the reconstruction of the image
``markers`` under the constrain image ``f``. The markers designate the parts
of the image we want to retain. In binary, it is equivalent to perform the
*conditional* dilation of the ``markers`` :math:`X`, by a structuring element
𝑩, using the *reference* 𝑓 until reaching stability.
.. math:: .. math::
\delta^{n+1}_{f,\mathcal{B}}(X) = \delta_\mathcal{B}(\delta^{n}_{f,\mathcal{B}}(X)) \cap f \delta^{n+1}_{f,\mathcal{B}}(X) = \delta_\mathcal{B}(\delta^{n}_{f,\mathcal{B}}(X)) \cap f
Similarly, the closing by reconstruction reconstructs the *background* instead of the *foreground*.
:param f: Input image 𝑓 :param f: Input image 𝑓
:param markers: Marker image (must have the same value type of 𝑓) :param markers: Marker image (must have the same value type of 𝑓)
:param nbh: Fundamental structuring element. :param nbh: Elementary structuring element.
:return: An image whose type is deduced from the input image :return: An image whose type is deduced from the input image
:precondition: :math:`markers < f` :precondition: :math:`markers \le f` (for (3) :math:`markers \ge f`)
:exception: N/A :exception: N/A
...@@ -43,37 +50,88 @@ Complexity ...@@ -43,37 +50,88 @@ Complexity
Example 1 : Staff lines reconstruction Example 1 : Staff lines reconstruction
-------------------------------------- --------------------------------------
.. image:: /images/staff_lines.png .. list-table::
:width: 49%
* - .. figure:: /images/staff_lines.png
.. image:: /images/staff_lines_markers.png Original image
:width: 49%
The markers have been obtained with the :cpp:func:`hit_or_miss`. - .. figure:: /images/staff_lines_markers.png
* Reconstruction of the objects touching staff lines; with the foundamental SE Markers obtained by the :doc:`hit_or_miss` transform.
(4-connection). All objects that do not touch the staff lines are removed.
Given an original image and some markers obtained with the :doc:`hit_or_miss`
transform. The geodesic reconstruction (with the 4-connection) of the original
image by the markers give the objects touching staff lines. All objects that do
not touch the staff lines are removed.
.. literalinclude:: /snippets/staff_lines.cpp .. literalinclude:: /snippets/staff_lines.cpp
:lines: 56 :start-after: M3_START
:end-before: M3_END
:language: cpp :language: cpp
.. image:: /images/staff_lines.png .. figure:: /images/morpho_reconstruction_1.png
:width: 49% :figwidth: 49%
:figclass: align-center
.. image:: /images/morpho_reconstruction_1.png Geodesic reconstruction from the markers.
:width: 49%
* Reconstruction of the lines only; with an horizontal SE `x-o-x`.
If we want to reconstruct only the staff line only, use an horizontal SE `x-o-x`.
.. literalinclude:: /snippets/staff_lines.cpp .. literalinclude:: /snippets/staff_lines.cpp
:lines: 59 :start-after: M4_START
:end-before: M4_END
:language: cpp :language: cpp
.. image:: /images/staff_lines.png .. figure:: /images/morpho_reconstruction_2.png
:width: 49% :figwidth: 49%
:figclass: align-center
Horizontal reconstruction from the markers.
Example 2 : Dense region reconstruction
---------------------------------------
.. list-table::
* - .. figure:: /images/blobs2_binary.png
(a) Original image
- .. figure:: /images/morpho_reconstruction_markers.png
(b) Markers from the :doc:`rank_filter`
- .. figure:: /images/morpho_reconstruction_dilated.png
(c) Dilated of the original image (a)
.. literalinclude:: /snippets/reconstruction.cpp
:start-after: M2_START
:end-before: M2_END
:language: cpp
Given an original image. We first start with a :doc:`rank_filter` to locate
dense region (regions with much more foreground pixels that background pixels)
that gives us markers. Then a dilation with a small disc allows to connect
objects. The reconstruction of the dilated image with a the markers gives a mask
for the dense region. Finally, we just have to mask the input with the mask to
get the objects in dense regions::
auto out = mln::clone(rec && input);
.. list-table::
* - .. figure:: /images/morpho_reconstruction_rec.png
(d) Reconstruction of (c) from the markers (b)
- .. figure:: /images/morpho_reconstruction_out.png
Input (a) restricted to the mask (d)
.. image:: /images/morpho_reconstruction_2.png
:width: 49%
...@@ -6,7 +6,7 @@ Include :file:`<mln/morpho/rank_filter.hpp>` ...@@ -6,7 +6,7 @@ Include :file:`<mln/morpho/rank_filter.hpp>`
.. cpp:function:: \ .. cpp:function:: \
template <class Ratio> void rank_filter(Image image, StructuringElement se, BorderManager bm, OutputImage out) template <class Ratio> void rank_filter(Image image, StructuringElement se, BorderManager bm, OutputImage out)
template <class Ratio> Image{I} concrete_t<I> rank_filter(I image, StructuringElement se, BorderManager bm) template <class Ratio, Image I> concrete_t<I> rank_filter(I image, StructuringElement se, BorderManager bm)
The rank filter is non-linear filter that assigns the 𝑟-th value in a given structuring element 𝐵. The rank filter is non-linear filter that assigns the 𝑟-th value in a given structuring element 𝐵.
......
...@@ -28,7 +28,13 @@ add_image("erosion-cli;opening;square;21" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho_ ...@@ -28,7 +28,13 @@ add_image("erosion-cli;opening;square;21" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho_
add_image("erosion-cli;closing;square;21" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho_closing_1.png) add_image("erosion-cli;closing;square;21" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho_closing_1.png)
add_image("erosion-cli;median;square;21" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho_median_1.png) add_image("erosion-cli;median;square;21" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho_median_1.png)
add_image("staff_lines" "${DOCUMENTATION_IMAGE_DIR}/staff_lines.pbm" add_image("staff_lines" "${DOCUMENTATION_IMAGE_DIR}/staff_lines.pbm"
morpho_hitormiss_1.png morpho_hitormiss_2.png staff_lines_markers.png morpho_reconstruction_1.png morpho_reconstruction_2.png) morpho_hitormiss_1.png morpho_hitormiss_2.png staff_lines_markers.png morpho_reconstruction_1.png morpho_reconstruction_2.png)
add_image("reconstruction"
"${DOCUMENTATION_IMAGE_DIR}/blobs2_binary.png"
morpho_reconstruction_dilated.png
morpho_reconstruction_markers.png
morpho_reconstruction_rec.png
morpho_reconstruction_out.png)
add_image("blobs_watershed" "${DOCUMENTATION_IMAGE_DIR}/blobs_binary.png" blobs_distance_transform.png blobs_segmentation.png) add_image("blobs_watershed" "${DOCUMENTATION_IMAGE_DIR}/blobs_binary.png" blobs_distance_transform.png blobs_segmentation.png)
...@@ -42,4 +48,4 @@ add_executable(erosion-cli erosion-cli.cpp) ...@@ -42,4 +48,4 @@ add_executable(erosion-cli erosion-cli.cpp)
add_executable(staff_lines staff_lines.cpp) add_executable(staff_lines staff_lines.cpp)
add_executable(component_tree_1 component_tree_1.cpp) add_executable(component_tree_1 component_tree_1.cpp)
add_executable(blobs_watershed blobs_watershed.cpp) add_executable(blobs_watershed blobs_watershed.cpp)
add_executable(reconstruction reconstruction.cpp)
#include <mln/io/experimental/imread.hpp>
#include <mln/io/experimental/imsave.hpp>
#include <mln/core/image/experimental/ndimage.hpp>
#include <mln/core/neighborhood/c8.hpp>
#include <mln/core/se/disc.hpp>
#include <mln/core/se/rect2d.hpp>
#include <mln/morpho/experimental/dilation.hpp>
#include <mln/morpho/experimental/erosion.hpp>
#include <mln/morpho/experimental/rank_filter.hpp>
#include <mln/morpho/experimental/reconstruction.hpp>
#include <mln/core/image/view/operators.hpp>
#include <ratio>
int main(int argc, char** argv)
{
if (argc < 6)
{
std::cerr << "Usage: " << argv[0] << " dilated.pbm markers.pbm reconstruction.pbm out.pbm\n";
return 1;
}
using namespace mln::view::ops;
mln::experimental::image2d<bool> input;
mln::io::experimental::imread(argv[1], input);
// #M2_START
// Make blobs connected
auto disc = mln::experimental::se::disc(4);
auto dil = mln::morpho::experimental::dilation(input, disc);
// Get markers for large connected components
auto rect = mln::experimental::se::rect2d(20, 20);
auto markers = mln::morpho::experimental::rank_filter<std::ratio<1,4>>(input, rect, mln::extension::bm::fill(false));
// Reconstruction of the large CC
auto rec = mln::morpho::experimental::opening_by_reconstruction(dil, markers, mln::experimental::c8);
// #M2_END
// Mask
auto out = mln::clone(rec && input);
// Save
mln::io::experimental::imsave(dil, argv[2]);
mln::io::experimental::imsave(markers, argv[3]);
mln::io::experimental::imsave(rec, argv[4]);
mln::io::experimental::imsave(out, argv[5]);
}
#include <mln/core/image/image2d.hpp> #include <mln/core/image/experimental/ndimage.hpp>
#include <mln/core/image/view/operators.hpp> #include <mln/core/image/view/operators.hpp>
#include <mln/core/neighb2d.hpp> #include <mln/core/neighborhood/c4.hpp>
#include <mln/core/neighborhood/c2_h.hpp>
#include <mln/core/se/mask2d.hpp> #include <mln/core/se/mask2d.hpp>
#include <mln/io/imread.hpp> #include <mln/io/experimental/imread.hpp>
#include <mln/io/imsave.hpp> #include <mln/io/experimental/imsave.hpp>
#include <mln/morpho/hit_or_miss.hpp> #include <mln/morpho/experimental/hit_or_miss.hpp>
#include <mln/morpho/opening_by_reconstruction.hpp> #include <mln/morpho/experimental/reconstruction.hpp>
int main(int argc, char** argv) int main(int argc, char** argv)
...@@ -18,40 +19,43 @@ int main(int argc, char** argv) ...@@ -18,40 +19,43 @@ int main(int argc, char** argv)
return 1; return 1;
} }
mln::image2d<bool> input; mln::experimental::image2d<bool> input;
mln::io::imread(argv[1], input); mln::io::experimental::imread(argv[1], input);
// Make a dual neighborhood corresponding to // Make a dual neighborhood corresponding to
// x x x // x x x
// o o o // o o o
// x x x // x x x
mln::image2d<bool> markers1, markers2; mln::experimental::image2d<bool> markers1, markers2;
{ // #M1_START { // #M1_START
mln::se::mask2d se_hit = {{0, 0, 0}, {1, 1, 1}, {0, 0, 0}}; mln::se::experimental::mask2d se_hit = {{0, 0, 0}, {1, 1, 1}, {0, 0, 0}};
mln::se::mask2d se_miss = {{1, 1, 1}, {0, 0, 0}, {1, 1, 1}}; mln::se::experimental::mask2d se_miss = {{1, 1, 1}, {0, 0, 0}, {1, 1, 1}};
auto output = mln::morpho::hit_or_miss(input, se_hit, se_miss);
markers1 = output; markers1 = mln::morpho::experimental::hit_or_miss(input, se_hit, se_miss);
mln::io::imsave(mln::lnot(markers1), argv[2]); mln::io::experimental::imsave(not markers1, argv[2]);
} // #M1_END } // #M1_END
{ // #M2_START { // #M2_START
mln::se::mask2d se_hit = {{0, 0, 0}, {0, 0, 0}, {1, 1, 1}, {1, 1, 1}, {0, 0, 0}}; mln::se::experimental::mask2d se_hit = {{0, 0, 0}, {0, 0, 0}, {1, 1, 1}, {1, 1, 1}, {0, 0, 0}};
mln::se::mask2d se_miss = {{0, 0, 0}, {1, 1, 1}, {0, 0, 0}, {0, 0, 0}, {1, 1, 1}}; mln::se::experimental::mask2d se_miss = {{0, 0, 0}, {1, 1, 1}, {0, 0, 0}, {0, 0, 0}, {1, 1, 1}};
auto output = mln::morpho::hit_or_miss(input, se_hit, se_miss);
markers2 = output; markers2 = mln::morpho::experimental::hit_or_miss(input, se_hit, se_miss);
mln::io::imsave(mln::lnot(markers2), argv[3]); mln::io::experimental::imsave(not markers2, argv[3]);
} // #M2_END } // #M2_END
auto markers = mln::lor(markers1, markers2); auto markers = markers1 or markers2;
mln::io::imsave(mln::lnot(markers), argv[4]); mln::io::experimental::imsave(not markers, argv[4]);
// FIXME: add experimental version // #M3_START
auto markers_ = mln::lor(markers1, markers2); auto markers_ = markers1 or markers2;
auto all_touching = mln::morpho::opening_by_reconstruction(input, markers_, mln::c4); auto all_touching = mln::morpho::experimental::opening_by_reconstruction(input, markers_, mln::experimental::c4);
mln::io::imsave(mln::lnot(all_touching), argv[5]); mln::io::experimental::imsave(not all_touching, argv[5]);
// #M3_END
auto lines_only = mln::morpho::opening_by_reconstruction(input, markers_, mln::c2_h); // #M4_START
mln::io::imsave(mln::lnot(lines_only), argv[6]); auto lines_only = mln::morpho::experimental::opening_by_reconstruction(input, markers_, mln::experimental::c2_h);
mln::io::experimental::imsave(not lines_only, argv[6]);
// #M4_END