Commit 243fdbc6 by Edwin Carlinet

### Migrate morphological gradients

parent ae1dc3b3
 ... @@ -14,6 +14,7 @@ Structural Morphological Operation ... @@ -14,6 +14,7 @@ Structural Morphological Operation morpho/hit_or_miss morpho/hit_or_miss morpho/rank_filter morpho/rank_filter morpho/median_filter morpho/median_filter morpho/gradient morpho/opening_by_reconstruction morpho/opening_by_reconstruction ... ...
 Morphological Gradients ======================= Include :file: .. cpp:namespace:: mln::morpho .. cpp:function:: \ Image{I} concrete_t gradient(I f, StructuringElement se) Image{I} concrete_t external_gradient(I f, StructuringElement se) Image{I} concrete_t internal_gradient(I f, StructuringElement se) Compute the morphological gradients. Morphological gradients are operators enhancing variations of pixel intensity in a neighborhood determined by a structuring element. Three combinations are currently used: #. arithmetic difference between the dilation and the erosion; this is basic morphological gradient, also called **Beucher** gradient. :math:\rho_B = \delta_B - \varepsilon_B #. arithmetic difference between the dilation and the original image; also called **external gradient**: :math:\rho_B^{+} = \delta_B - \mathrm{id} #. arithmetic difference between the original image and its erosion; also called the **internal_gradient**: :math:\rho_B^{-} = \mathrm{id} - \varepsilon_B If input is not integral, the marginal ordering is considered and the 𝑙₂ of the vector difference is returned: :math:\rho_B = \left| \delta_B - \varepsilon_B \right| :param f: Input image 𝑓 :param se: Elementary structuring element. :return: An image whose type is deduced from the input image. :precondition: :exception: N/A Example 1 : Gradient by a square on a gray-level image ------------------------------------------------------ .. code-block:: cpp #include #include // Define a square SE of size 7x7 auto input = ...; auto rect = mln::se::rect2d(7,7); auto grad1 = mln::morpho::gradient(input, rect); auto grad2 = mln::morpho::internal_gradient(input, rect); auto grad3 = mln::morpho::external_gradient(input, rect); .. list-table:: * - .. figure:: /images/lena_gray.jpg - .. figure:: /images/morpho_gradient_1.png Beucher (thick) gradient * - .. figure:: /images/morpho_int_gradient_1.png Internal gradient - .. figure:: /images/morpho_ext_gradient_1.png External Gradient \ No newline at end of file
 ... @@ -27,6 +27,10 @@ add_image("erosion-cli;dilation;square;21" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho ... @@ -27,6 +27,10 @@ add_image("erosion-cli;dilation;square;21" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho add_image("erosion-cli;opening;square;21" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho_opening_1.png) add_image("erosion-cli;opening;square;21" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho_opening_1.png) 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("erosion-cli;gradient;square;7" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho_gradient_1.png) add_image("erosion-cli;ext_gradient;square;7" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho_ext_gradient_1.png) add_image("erosion-cli;int_gradient;square;7" "${PYLENE_IMAGE_DIR}/lena.pgm" morpho_int_gradient_1.png) add_image("cdt;1" "${DOCUMENTATION_IMAGE_DIR}/F.png" F-4.png) add_image("cdt;1" "${DOCUMENTATION_IMAGE_DIR}/F.png" F-4.png) add_image("cdt;2" "${DOCUMENTATION_IMAGE_DIR}/F.png" F-8.png) add_image("cdt;2" "${DOCUMENTATION_IMAGE_DIR}/F.png" F-8.png) add_image("cdt;3" "${DOCUMENTATION_IMAGE_DIR}/F.png" F-2-3.png) add_image("cdt;3" "${DOCUMENTATION_IMAGE_DIR}/F.png" F-2-3.png) ... ...  ... @@ -7,6 +7,7 @@ ... @@ -7,6 +7,7 @@ #include #include #include #include #include #include #include #include #include #include #include ... @@ -56,6 +57,9 @@ enum morpho_op_type ... @@ -56,6 +57,9 @@ enum morpho_op_type kOpening, kOpening, kClosing, kClosing, kMedian, kMedian, kGradient, kInternalGradient, kExternalGradient }; }; std::istream& operator>>(std::istream& in, morpho_op_type& se) std::istream& operator>>(std::istream& in, morpho_op_type& se) ... @@ -73,6 +77,12 @@ std::istream& operator>>(std::istream& in, morpho_op_type& se) ... @@ -73,6 +77,12 @@ std::istream& operator>>(std::istream& in, morpho_op_type& se) se = kClosing; se = kClosing; else if (token == "median") else if (token == "median") se = kMedian; se = kMedian; else if (token == "gradient") se = kGradient; else if (token == "ext_gradient") se = kExternalGradient; else if (token == "int_gradient") se = kInternalGradient; else else throw po::invalid_option_value("Invalid Operator"); throw po::invalid_option_value("Invalid Operator"); return in; return in; ... @@ -115,7 +125,7 @@ int main(int argc, char** argv) ... @@ -115,7 +125,7 @@ int main(int argc, char** argv) { { std::cout << "Usage: " << argv[0] std::cout << "Usage: " << argv[0] << " [-h,--help] OPERATOR SE size input output\n" << " [-h,--help] OPERATOR SE size input output\n" "OPERATOR\t Morphological operation to perform [erosion | dilation]\n" "OPERATOR\t Morphological operation to perform [erosion | dilation | opening | closing | median | gradient | ext_gradient | int_gradient]\n" "SE\t Structuring element to use [square | disc | diamond]\n" "SE\t Structuring element to use [square | disc | diamond]\n" "size\t Size of the SE\n" "size\t Size of the SE\n" "input\t Input image (u8)\n" "input\t Input image (u8)\n" ... @@ -146,6 +156,15 @@ int main(int argc, char** argv) ... @@ -146,6 +156,15 @@ int main(int argc, char** argv) case kMedian: case kMedian: output = mln::morpho::experimental::median_filter(input, nbh, mln::extension::bm::mirror{}); output = mln::morpho::experimental::median_filter(input, nbh, mln::extension::bm::mirror{}); break; break; case kGradient: output = mln::morpho::experimental::gradient(input, nbh); break; case kExternalGradient: output = mln::morpho::experimental::external_gradient(input, nbh); break; case kInternalGradient: output = mln::morpho::experimental::internal_gradient(input, nbh); break; } } mln::io::experimental::imsave(output, vm["output"].as()); mln::io::experimental::imsave(output, vm["output"].as()); ... ...  ... @@ -32,10 +32,14 @@ namespace mln ... @@ -32,10 +32,14 @@ namespace mln template template void transform(InputImage in, const Image& out, UnaryFunction f); void transform(InputImage in, const Image& out, UnaryFunction f); template void transform(InputImage1 in1, InputImage2 in2, OutputImage out, BinaryFunction f); template template image_ch_value_t>>> image_ch_value_t>>> transform(InputImage in, UnaryFunction f); transform(InputImage in, UnaryFunction f); /******************************************/ /******************************************/ ... @@ -62,9 +66,36 @@ namespace mln ... @@ -62,9 +66,36 @@ namespace mln ::ranges::transform(r1, ::ranges::begin(r2), f); ::ranges::transform(r1, ::ranges::begin(r2), f); } } template void transform(InputImage1 in1, InputImage2 in2, OutputImage out, BinaryFunction f) { static_assert(mln::is_a()); static_assert(mln::is_a()); static_assert(mln::is_a()); static_assert( ::ranges::Invocable, image_reference_t>()); static_assert( std::is_convertible_v< std::invoke_result_t, image_reference_t>, image_value_t>, "The result of the function is not implicitely convertible to the output image value type"); mln_entering("mln::transform"); // FIXME: disabled becaused some domain (e.g. morphed) do not implement comparison for now // mln_precondition(in2.domain() == out.domain()); // mln_precondition(in1.domain() == out.domain()); auto&& ivals1 = in1.new_values(); auto&& ivals2 = in2.new_values(); auto&& ovals = out.new_values(); for (auto [r1, r2, r3] : ranges::view::zip(ranges::rows(ivals1), ranges::rows(ivals2), ranges::rows(ovals))) ::ranges::transform(r1, ::ranges::begin(r2), ::ranges::begin(r3), f); } template template image_ch_value_t>>> image_ch_value_t>>> transform(InputImage in, UnaryFunction f) transform(InputImage in, UnaryFunction f) { { static_assert(mln::is_a()); static_assert(mln::is_a()); static_assert(::ranges::Invocable>()); static_assert(::ranges::Invocable>()); ... ...  #pragma once #include #include #include #include #include #include #include /// \file namespace mln::morpho::experimental { /// \ingroup morpho /// \brief Compute the Beucher gradient. /// /// The beucher gradient is defined as: /// \f[ /// |\nabla u(p)| = \left| \bigvee_{q \in \mathcal{N}(p)} f(q) - /// \bigwedge_{q \in \mathcal{N}(p)} f(q) \right| /// \f] /// /// + If the optional \p out image is provided, it must be wide enough to store /// the results (the function does not perform any resizing). /// /// + If a optional \p cmp function is provided, the algorithm /// will internally do an unqualified call to sup(x, y, cmp) /// and inf(x, y,cmp). The default is the product-order so /// that it works for vectorial type as well. /// /// \param[in] input Input image. /// \param[in] se Neighborhood/SE/Window to look around. /// \param[in] cmp (optional) Comparaison function. The method internally does an /// unqualified call to inf(x, y, cmp) and sup(x, y, cmp). Default /// is the product-order. /// \param[in] norm (optional) Norm function used in \f$|x - y|\f\$. The default is /// the ℓ₂-norm. /// \param[out] out (optional) Output image to write in. /// /// FIXME: This can be done in a single pass using kernels but you /// we have to consider: /// * an incrementable accumlator /// * an extension that is mirrorizable namespace details { template struct grad_op { using result_type = decltype(mln::l2norm(std::declval())); result_type operator()(T a, T b) const noexcept { return mln::l2norm(T(a - b)); } }; template struct grad_op>> { using result_type = T; T operator()(T a, T b) const noexcept { return a - b; } }; template using gradient_result_t = image_ch_value_t>::result_type>; } template details::gradient_result_t> gradient(InputImage&& input, const SE& se); template details::gradient_result_t> external_gradient(InputImage&& input, const SE& se); template details::gradient_result_t> internal_gradient(InputImage&& input, const SE& se); /*************************/ /*** Implementation ***/ /*************************/ template details::gradient_result_t> gradient(InputImage&& ima, const SE& se) { using I = std::remove_reference_t; static_assert(mln::is_a()); static_assert(mln::is_a()); mln_entering("mln::morpho::gradient"); auto d = morpho::experimental::dilation(ima, se); auto e = morpho::experimental::erosion(ima, se); using R = typename details::grad_op>::result_type; details::gradient_result_t out = imchvalue(ima); mln::transform(d, e, out, details::grad_op>()); return out; } template details::gradient_result_t> external_gradient(InputImage&& ima, const SE& se) { using I = std::remove_reference_t; static_assert(mln::is_a()); static_assert(mln::is_a()); mln_entering("mln::morpho::external_gradient"); auto d = morpho::experimental::dilation(ima, se); using R = typename details::grad_op>::result_type; details::gradient_result_t out = imchvalue(ima); mln::transform(d, ima, out, details::grad_op>()); return out; } template details::gradient_result_t> internal_gradient(InputImage&& ima, const SE& se) { using I = std::remove_reference_t; static_assert(mln::is_a()); static_assert(mln::is_a()); mln_entering("mln::morpho::internal_gradient"); auto e = morpho::experimental::erosion(ima, se); using R = typename details::grad_op>::result_type; details::gradient_result_t out = imchvalue(ima); mln::transform(ima, e, out, details::grad_op>()); return out; } } // namespace mln::morpho::experimental