Update and expand erosion / dilation tutorial
authorChristos Malliaridis <c.malliaridis@gmail.com>
Tue, 14 Jul 2020 16:44:12 +0000 (18:44 +0200)
committerChristos Malliaridis <c.malliaridis@gmail.com>
Wed, 18 Nov 2020 12:32:24 +0000 (13:32 +0100)
- Add python explanation for erosion and dilation
- Add java explanation for erosion and dilation
- Restructure and reword specific sections

doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown
samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp
samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java
samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py

index ddb7d9e..adcedb2 100644 (file)
@@ -84,57 +84,198 @@ This tutorial's code is shown below. You can also download it
 Explanation
 -----------
 
--#  Most of the material shown here is trivial (if you have any doubt, please refer to the tutorials in
-    previous sections). Let's check the general structure of the C++ program:
+@add_toggle_cpp
+Most of the material shown here is trivial (if you have any doubt, please refer to the tutorials in
+previous sections). Let's check the general structure of the C++ program:
+
+@snippet cpp/tutorial_code/ImgProc/Morphology_1.cpp main
+
+-#   Load an image (can be BGR or grayscale)
+-#   Create two windows (one for dilation output, the other for erosion)
+-#   Create a set of two Trackbars for each operation:
+    -   The first trackbar "Element" returns either **erosion_elem** or **dilation_elem**
+    -   The second trackbar "Kernel size" return **erosion_size** or **dilation_size** for the
+        corresponding operation.
+-#  Call once erosion and dilation to show the initial image.
+
+
+Every time we move any slider, the user's function **Erosion** or **Dilation** will be
+called and it will update the output image based on the current trackbar values.
+
+Let's analyze these two functions:
+
+#### The erosion function
+
+@snippet cpp/tutorial_code/ImgProc/Morphology_1.cpp erosion
+
+The function that performs the *erosion* operation is @ref cv::erode . As we can see, it
+receives three arguments:
+-   *src*: The source image
+-   *erosion_dst*: The output image
+-   *element*: This is the kernel we will use to perform the operation. If we do not
+    specify, the default is a simple `3x3` matrix. Otherwise, we can specify its
+    shape. For this, we need to use the function cv::getStructuringElement :
+    @snippet cpp/tutorial_code/ImgProc/Morphology_1.cpp kernel
+
+    We can choose any of three shapes for our kernel:
+
+    -   Rectangular box: MORPH_RECT
+    -   Cross: MORPH_CROSS
+    -   Ellipse: MORPH_ELLIPSE
+
+    Then, we just have to specify the size of our kernel and the *anchor point*. If not
+    specified, it is assumed to be in the center.
+
+That is all. We are ready to perform the erosion of our image.
+
+#### The dilation function
+
+The code is below. As you can see, it is completely similar to the snippet of code for **erosion**.
+Here we also have the option of defining our kernel, its anchor point and the size of the operator
+to be used.
+@snippet cpp/tutorial_code/ImgProc/Morphology_1.cpp dilation
+@end_toggle
+
+@add_toggle_java
+Most of the material shown here is trivial (if you have any doubt, please refer to the tutorials in
+previous sections). Let's check however the general structure of the java class. There are 4 main
+parts in the java class:
+
+- the class constructor which setups the window that will be filled with window components
+- the `addComponentsToPane` method, which fills out the window
+- the `update` method, which determines what happens when the user changes any value
+- the `main` method, which is the entry point of the program
+
+In this tutorial we will focus on the `addComponentsToPane` and `update` methods. However, for completion the
+steps followed in the constructor are:
+
+-#  Load an image (can be BGR or grayscale)
+-#  Create a window
+-#  Add various control components with `addComponentsToPane`
+-#  show the window
+
+The components were added by the following method:
+
+@snippet java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java components
+
+In short we
+
+-#  create a panel for the sliders
+-#  create a combo box for the element types
+-#  create a slider for the kernel size
+-#  create a combo box for the morphology function to use (erosion or dilation)
+
+The action and state changed listeners added call at the end the `update` method which updates
+the image based on the current slider values. So every time we move any slider, the `update` method is triggered.
 
-    -   Load an image (can be BGR or grayscale)
-    -   Create two windows (one for dilation output, the other for erosion)
-    -   Create a set of two Trackbars for each operation:
-        -   The first trackbar "Element" returns either **erosion_elem** or **dilation_elem**
-        -   The second trackbar "Kernel size" return **erosion_size** or **dilation_size** for the
-            corresponding operation.
-    -   Every time we move any slider, the user's function **Erosion** or **Dilation** will be
-        called and it will update the output image based on the current trackbar values.
+#### Updating the image
 
-    Let's analyze these two functions:
+To update the image we used the following implementation:
 
--#  **erosion:**
-    @snippet cpp/tutorial_code/ImgProc/Morphology_1.cpp erosion
+@snippet java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java update
 
-    -   The function that performs the *erosion* operation is @ref cv::erode . As we can see, it
-        receives three arguments:
-        -   *src*: The source image
-        -   *erosion_dst*: The output image
-        -   *element*: This is the kernel we will use to perform the operation. If we do not
-            specify, the default is a simple `3x3` matrix. Otherwise, we can specify its
-            shape. For this, we need to use the function cv::getStructuringElement :
-            @snippet cpp/tutorial_code/ImgProc/Morphology_1.cpp kernel
+In other words we
 
-            We can choose any of three shapes for our kernel:
+-# get the structuring element the user chose
+-# execute the **erosion** or **dilation** function based on `doErosion`
+-# reload the image with the morphology applied
+-# repaint the frame
 
-            -   Rectangular box: MORPH_RECT
-            -   Cross: MORPH_CROSS
-            -   Ellipse: MORPH_ELLIPSE
+Let's analyze the `erode` and `dilate` methods:
 
-            Then, we just have to specify the size of our kernel and the *anchor point*. If not
-            specified, it is assumed to be in the center.
+#### The erosion method
 
-    -   That is all. We are ready to perform the erosion of our image.
-@note Additionally, there is another parameter that allows you to perform multiple erosions
-(iterations) at once. However, We haven't used it in this simple tutorial. You can check out the
-reference for more details.
+@snippet java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java erosion
 
--#  **dilation:**
+The function that performs the *erosion* operation is @ref cv::erode . As we can see, it
+receives three arguments:
+-   *src*: The source image
+-   *erosion_dst*: The output image
+-   *element*: This is the kernel we will use to perform the operation. For specifying the shape, we need to use
+    the function cv::getStructuringElement :
+    @snippet java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java kernel
 
-    The code is below. As you can see, it is completely similar to the snippet of code for **erosion**.
-    Here we also have the option of defining our kernel, its anchor point and the size of the operator
-    to be used.
-    @snippet cpp/tutorial_code/ImgProc/Morphology_1.cpp dilation
+    We can choose any of three shapes for our kernel:
+
+    -   Rectangular box: CV_SHAPE_RECT
+    -   Cross: CV_SHAPE_CROSS
+    -   Ellipse: CV_SHAPE_ELLIPSE
+
+    Together with the shape we specify the size of our kernel and the *anchor point*. If the anchor point is not
+    specified, it is assumed to be in the center.
+
+That is all. We are ready to perform the erosion of our image.
+
+#### The dilation function
+
+The code is below. As you can see, it is completely similar to the snippet of code for **erosion**.
+Here we also have the option of defining our kernel, its anchor point and the size of the operator
+to be used.
+@snippet java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java dilation
+@end_toggle
+
+@add_toggle_python
+Most of the material shown here is trivial (if you have any doubt, please refer to the tutorials in
+previous sections). Let's check the general structure of the python script:
+
+@snippet python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py main
+
+-#  Load an image (can be BGR or grayscale)
+-#  Create two windows (one for erosion output, the other for dilation) with a set of trackbars each
+    -   The first trackbar "Element" returns the value for the morphological type that will be mapped
+        (1 = rectangle, 2 = cross, 3 = ellipse)
+    -   The second trackbar "Kernel size" returns the size of the element for the
+        corresponding operation
+-#  Call once erosion and dilation to show the initial image
+
+Every time we move any slider, the user's function **erosion** or **dilation** will be
+called and it will update the output image based on the current trackbar values.
+
+Let's analyze these two functions:
+
+#### The erosion function
+
+@snippet python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py erosion
+
+The function that performs the *erosion* operation is @ref cv::erode . As we can see, it
+receives two arguments and returns the processed image:
+-   *src*: The source image
+-   *element*: The kernel we will use to perform the operation. We can specify its
+    shape by using the function cv::getStructuringElement :
+    @snippet python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py kernel
+
+    We can choose any of three shapes for our kernel:
+
+    -   Rectangular box: MORPH_RECT
+    -   Cross: MORPH_CROSS
+    -   Ellipse: MORPH_ELLIPSE
+
+Then, we just have to specify the size of our kernel and the *anchor point*. If the anchor point not
+specified, it is assumed to be in the center.
+
+That is all. We are ready to perform the erosion of our image.
+
+#### The dilation function
+
+The code is below. As you can see, it is completely similar to the snippet of code for **erosion**.
+Here we also have the option of defining our kernel, its anchor point and the size of the operator
+to be used.
+
+@snippet python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py dilation
+@end_toggle
+
+@note Additionally, there are further parameters that allow you to perform multiple erosions/dilations
+(iterations) at once and also set the border type and value. However, We haven't used those
+in this simple tutorial. You can check out the reference for more details.
 
 Results
 -------
 
-Compile the code above and execute it with an image as argument. For instance, using this image:
+Compile the code above and execute it (or run the script if using python) with an image as argument.
+If you do not provide an image as argument the default sample image
+([LinuxLogo.jpg](https://github.com/opencv/opencv/tree/master/samples/data/LinuxLogo.jpg)) will be used.
+
+For instance, using this image:
 
 ![](images/Morphology_1_Tutorial_Original_Image.jpg)
 
@@ -143,3 +284,4 @@ naturally. Try them out! You can even try to add a third Trackbar to control the
 iterations.
 
 ![](images/Morphology_1_Result.jpg)
+(depending on the programming language the output might vary a little or be only 1 window)
index 48b0c2e..33e0062 100644 (file)
@@ -25,6 +25,7 @@ int const max_kernel_size = 21;
 void Erosion( int, void* );
 void Dilation( int, void* );
 
+//![main]
 /**
  * @function main
  */
@@ -70,6 +71,7 @@ int main( int argc, char** argv )
   waitKey(0);
   return 0;
 }
+//![main]
 
 //![erosion]
 /**
index 7a5f60f..e71400f 100644 (file)
@@ -34,6 +34,7 @@ public class MorphologyDemo1 {
     private JFrame frame;
     private JLabel imgLabel;
 
+    //! [constructor]
     public MorphologyDemo1(String[] args) {
         String imagePath = args.length > 0 ? args[0] : "../data/LinuxLogo.jpg";
         matImgSrc = Imgcodecs.imread(imagePath);
@@ -54,7 +55,9 @@ public class MorphologyDemo1 {
         frame.pack();
         frame.setVisible(true);
     }
+    //! [constructor]
 
+    //! [components]
     private void addComponentsToPane(Container pane, Image img) {
         if (!(pane.getLayout() instanceof BorderLayout)) {
             pane.add(new JLabel("Container doesn't use BorderLayout!"));
@@ -114,21 +117,31 @@ public class MorphologyDemo1 {
         imgLabel = new JLabel(new ImageIcon(img));
         pane.add(imgLabel, BorderLayout.CENTER);
     }
+    //! [components]
 
+    //! [update]
     private void update() {
+        //! [kernel]
         Mat element = Imgproc.getStructuringElement(elementType, new Size(2 * kernelSize + 1, 2 * kernelSize + 1),
                 new Point(kernelSize, kernelSize));
+        //! [kernel]
 
         if (doErosion) {
+            //! [erosion]
             Imgproc.erode(matImgSrc, matImgDst, element);
+            //! [erosion]
         } else {
+            //! [dilation]
             Imgproc.dilate(matImgSrc, matImgDst, element);
+            //! [dilation]
         }
         Image img = HighGui.toBufferedImage(matImgDst);
         imgLabel.setIcon(new ImageIcon(img));
         frame.repaint();
     }
+    //! [update]
 
+    //! [main]
     public static void main(String[] args) {
         // Load the native OpenCV library
         System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
@@ -142,4 +155,5 @@ public class MorphologyDemo1 {
             }
         });
     }
+    //! [main]
 }
index 502457b..3645eab 100644 (file)
@@ -3,61 +3,76 @@ import cv2 as cv
 import numpy as np
 import argparse
 
+src = None
 erosion_size = 0
 max_elem = 2
 max_kernel_size = 21
-title_trackbar_element_type = 'Element:\n 0: Rect \n 1: Cross \n 2: Ellipse'
+title_trackbar_element_shape = 'Element:\n 0: Rect \n 1: Cross \n 2: Ellipse'
 title_trackbar_kernel_size = 'Kernel size:\n 2n +1'
 title_erosion_window = 'Erosion Demo'
-title_dilatation_window = 'Dilation Demo'
+title_dilation_window = 'Dilation Demo'
 
+
+## [main]
+def main(image):
+    global src
+    src = cv.imread(cv.samples.findFile(image))
+    if src is None:
+        print('Could not open or find the image: ', image)
+        exit(0)
+
+    cv.namedWindow(title_erosion_window)
+    cv.createTrackbar(title_trackbar_element_shape, title_erosion_window, 0, max_elem, erosion)
+    cv.createTrackbar(title_trackbar_kernel_size, title_erosion_window, 0, max_kernel_size, erosion)
+
+    cv.namedWindow(title_dilation_window)
+    cv.createTrackbar(title_trackbar_element_shape, title_dilation_window, 0, max_elem, dilatation)
+    cv.createTrackbar(title_trackbar_kernel_size, title_dilation_window, 0, max_kernel_size, dilatation)
+
+    erosion(0)
+    dilatation(0)
+    cv.waitKey()
+## [main]
+
+# optional mapping of values with morphological shapes
+def morph_shape(val):
+    if val == 0:
+        return cv.MORPH_RECT
+    elif val == 1:
+        return cv.MORPH_CROSS
+    elif val == 2:
+        return cv.MORPH_ELLIPSE
+
+
+## [erosion]
 def erosion(val):
     erosion_size = cv.getTrackbarPos(title_trackbar_kernel_size, title_erosion_window)
-    erosion_type = 0
-    val_type = cv.getTrackbarPos(title_trackbar_element_type, title_erosion_window)
-    if val_type == 0:
-        erosion_type = cv.MORPH_RECT
-    elif val_type == 1:
-        erosion_type = cv.MORPH_CROSS
-    elif val_type == 2:
-        erosion_type = cv.MORPH_ELLIPSE
-
-    element = cv.getStructuringElement(erosion_type, (2*erosion_size + 1, 2*erosion_size+1), (erosion_size, erosion_size))
+    erosion_shape = morph_shape(cv.getTrackbarPos(title_trackbar_element_shape, title_erosion_window))
+
+    ## [kernel]
+    element = cv.getStructuringElement(erosion_shape, (2 * erosion_size + 1, 2 * erosion_size + 1),
+                                       (erosion_size, erosion_size))
+    ## [kernel]
     erosion_dst = cv.erode(src, element)
     cv.imshow(title_erosion_window, erosion_dst)
+## [erosion]
 
-def dilatation(val):
-    dilatation_size = cv.getTrackbarPos(title_trackbar_kernel_size, title_dilatation_window)
-    dilatation_type = 0
-    val_type = cv.getTrackbarPos(title_trackbar_element_type, title_dilatation_window)
-    if val_type == 0:
-        dilatation_type = cv.MORPH_RECT
-    elif val_type == 1:
-        dilatation_type = cv.MORPH_CROSS
-    elif val_type == 2:
-        dilatation_type = cv.MORPH_ELLIPSE
-
-    element = cv.getStructuringElement(dilatation_type, (2*dilatation_size + 1, 2*dilatation_size+1), (dilatation_size, dilatation_size))
-    dilatation_dst = cv.dilate(src, element)
-    cv.imshow(title_dilatation_window, dilatation_dst)
 
-parser = argparse.ArgumentParser(description='Code for Eroding and Dilating tutorial.')
-parser.add_argument('--input', help='Path to input image.', default='LinuxLogo.jpg')
-args = parser.parse_args()
+## [dilation]
+def dilatation(val):
+    dilatation_size = cv.getTrackbarPos(title_trackbar_kernel_size, title_dilation_window)
+    dilation_shape = morph_shape(cv.getTrackbarPos(title_trackbar_element_shape, title_dilation_window))
 
-src = cv.imread(cv.samples.findFile(args.input))
-if src is None:
-    print('Could not open or find the image: ', args.input)
-    exit(0)
+    element = cv.getStructuringElement(dilation_shape, (2 * dilatation_size + 1, 2 * dilatation_size + 1),
+                                       (dilatation_size, dilatation_size))
+    dilatation_dst = cv.dilate(src, element)
+    cv.imshow(title_dilation_window, dilatation_dst)
+## [dilation]
 
-cv.namedWindow(title_erosion_window)
-cv.createTrackbar(title_trackbar_element_type, title_erosion_window , 0, max_elem, erosion)
-cv.createTrackbar(title_trackbar_kernel_size, title_erosion_window , 0, max_kernel_size, erosion)
 
-cv.namedWindow(title_dilatation_window)
-cv.createTrackbar(title_trackbar_element_type, title_dilatation_window , 0, max_elem, dilatation)
-cv.createTrackbar(title_trackbar_kernel_size, title_dilatation_window , 0, max_kernel_size, dilatation)
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description='Code for Eroding and Dilating tutorial.')
+    parser.add_argument('--input', help='Path to input image.', default='LinuxLogo.jpg')
+    args = parser.parse_args()
 
-erosion(0)
-dilatation(0)
-cv.waitKey()
+    main(args.input)