From 3c25fd1ba59504c563cb00134f8785cf4d10811a Mon Sep 17 00:00:00 2001 From: Christos Malliaridis Date: Tue, 14 Jul 2020 18:44:12 +0200 Subject: [PATCH] Update and expand erosion / dilation tutorial - Add python explanation for erosion and dilation - Add java explanation for erosion and dilation - Restructure and reword specific sections --- .../erosion_dilatation/erosion_dilatation.markdown | 216 +++++++++++++++++---- samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp | 2 + .../erosion_dilatation/MorphologyDemo1.java | 14 ++ .../imgProc/erosion_dilatation/morphology_1.py | 99 ++++++---- 4 files changed, 252 insertions(+), 79 deletions(-) diff --git a/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown b/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown index ddb7d9e..adcedb2 100644 --- a/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown +++ b/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown @@ -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) diff --git a/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp b/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp index 48b0c2e..33e0062 100644 --- a/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp +++ b/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp @@ -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] /** diff --git a/samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java b/samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java index 7a5f60f..e71400f 100644 --- a/samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java +++ b/samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java @@ -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] } diff --git a/samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py b/samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py index 502457b..3645eab 100644 --- a/samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py +++ b/samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py @@ -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) -- 2.7.4