mirror of
https://github.com/opencv/opencv.git
synced 2026-07-29 15:23:05 +04:00
Merge pull request #9406 from Cartucho:update_core_tutorials
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
Adding (blending) two images using OpenCV {#tutorial_adding_images}
|
||||
=========================================
|
||||
|
||||
@prev_tutorial{tutorial_mat_operations}
|
||||
@next_tutorial{tutorial_basic_linear_transform}
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
In this tutorial you will learn:
|
||||
|
||||
- what is *linear blending* and why it is useful;
|
||||
- how to add two images using @ref cv::addWeighted
|
||||
- how to add two images using **addWeighted()**
|
||||
|
||||
Theory
|
||||
------
|
||||
@@ -28,33 +31,83 @@ eh?)
|
||||
Source Code
|
||||
-----------
|
||||
|
||||
@add_toggle_cpp
|
||||
Download the source code from
|
||||
[here](https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/core/AddingImages/AddingImages.cpp).
|
||||
[here](https://raw.githubusercontent.com/opencv/opencv/master/samples/cpp/tutorial_code/core/AddingImages/AddingImages.cpp).
|
||||
@include cpp/tutorial_code/core/AddingImages/AddingImages.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
Download the source code from
|
||||
[here](https://raw.githubusercontent.com/opencv/opencv/master/samples/java/tutorial_code/core/AddingImages/AddingImages.java).
|
||||
@include java/tutorial_code/core/AddingImages/AddingImages.java
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
Download the source code from
|
||||
[here](https://raw.githubusercontent.com/opencv/opencv/master/samples/python/tutorial_code/core/AddingImages/adding_images.py).
|
||||
@include python/tutorial_code/core/AddingImages/adding_images.py
|
||||
@end_toggle
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
-# Since we are going to perform:
|
||||
Since we are going to perform:
|
||||
|
||||
\f[g(x) = (1 - \alpha)f_{0}(x) + \alpha f_{1}(x)\f]
|
||||
\f[g(x) = (1 - \alpha)f_{0}(x) + \alpha f_{1}(x)\f]
|
||||
|
||||
We need two source images (\f$f_{0}(x)\f$ and \f$f_{1}(x)\f$). So, we load them in the usual way:
|
||||
@snippet cpp/tutorial_code/core/AddingImages/AddingImages.cpp load
|
||||
We need two source images (\f$f_{0}(x)\f$ and \f$f_{1}(x)\f$). So, we load them in the usual way:
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/AddingImages/AddingImages.cpp load
|
||||
@end_toggle
|
||||
|
||||
**warning**
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/AddingImages/AddingImages.java load
|
||||
@end_toggle
|
||||
|
||||
Since we are *adding* *src1* and *src2*, they both have to be of the same size (width and
|
||||
height) and type.
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/AddingImages/adding_images.py load
|
||||
@end_toggle
|
||||
|
||||
-# Now we need to generate the `g(x)` image. For this, the function @ref cv::addWeighted comes quite handy:
|
||||
@snippet cpp/tutorial_code/core/AddingImages/AddingImages.cpp blend_images
|
||||
since @ref cv::addWeighted produces:
|
||||
\f[dst = \alpha \cdot src1 + \beta \cdot src2 + \gamma\f]
|
||||
In this case, `gamma` is the argument \f$0.0\f$ in the code above.
|
||||
We used the following images: [LinuxLogo.jpg](https://raw.githubusercontent.com/opencv/opencv/master/samples/data/LinuxLogo.jpg) and [WindowsLogo.jpg](https://raw.githubusercontent.com/opencv/opencv/master/samples/data/WindowsLogo.jpg)
|
||||
|
||||
-# Create windows, show the images and wait for the user to end the program.
|
||||
@snippet cpp/tutorial_code/core/AddingImages/AddingImages.cpp display
|
||||
@warning Since we are *adding* *src1* and *src2*, they both have to be of the same size
|
||||
(width and height) and type.
|
||||
|
||||
Now we need to generate the `g(x)` image. For this, the function **addWeighted()** comes quite handy:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/AddingImages/AddingImages.cpp blend_images
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/AddingImages/AddingImages.java blend_images
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/AddingImages/adding_images.py blend_images
|
||||
Numpy version of above line (but cv2 function is around 2x faster):
|
||||
\code{.py}
|
||||
dst = np.uint8(alpha*(img1)+beta*(img2))
|
||||
\endcode
|
||||
@end_toggle
|
||||
|
||||
since **addWeighted()** produces:
|
||||
\f[dst = \alpha \cdot src1 + \beta \cdot src2 + \gamma\f]
|
||||
In this case, `gamma` is the argument \f$0.0\f$ in the code above.
|
||||
|
||||
Create windows, show the images and wait for the user to end the program.
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/AddingImages/AddingImages.cpp display
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/AddingImages/AddingImages.java display
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/AddingImages/adding_images.py display
|
||||
@end_toggle
|
||||
|
||||
Result
|
||||
------
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
Basic Drawing {#tutorial_basic_geometric_drawing}
|
||||
=============
|
||||
|
||||
@prev_tutorial{tutorial_basic_linear_transform}
|
||||
@next_tutorial{tutorial_random_generator_and_text}
|
||||
|
||||
Goals
|
||||
-----
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
- Use @ref cv::Point to define 2D points in an image.
|
||||
- Use @ref cv::Scalar and why it is useful
|
||||
- Draw a **line** by using the OpenCV function @ref cv::line
|
||||
- Draw an **ellipse** by using the OpenCV function @ref cv::ellipse
|
||||
- Draw a **rectangle** by using the OpenCV function @ref cv::rectangle
|
||||
- Draw a **circle** by using the OpenCV function @ref cv::circle
|
||||
- Draw a **filled polygon** by using the OpenCV function @ref cv::fillPoly
|
||||
- Draw a **line** by using the OpenCV function **line()**
|
||||
- Draw an **ellipse** by using the OpenCV function **ellipse()**
|
||||
- Draw a **rectangle** by using the OpenCV function **rectangle()**
|
||||
- Draw a **circle** by using the OpenCV function **circle()**
|
||||
- Draw a **filled polygon** by using the OpenCV function **fillPoly()**
|
||||
|
||||
@add_toggle_cpp
|
||||
OpenCV Theory
|
||||
-------------
|
||||
|
||||
@@ -42,86 +44,217 @@ Point pt = Point(10, 8);
|
||||
Scalar( a, b, c )
|
||||
@endcode
|
||||
We would be defining a BGR color such as: *Blue = a*, *Green = b* and *Red = c*
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
OpenCV Theory
|
||||
-------------
|
||||
|
||||
For this tutorial, we will heavily use two structures: @ref cv::Point and @ref cv::Scalar :
|
||||
|
||||
### Point
|
||||
|
||||
It represents a 2D point, specified by its image coordinates \f$x\f$ and \f$y\f$. We can define it as:
|
||||
@code{.java}
|
||||
Point pt = new Point();
|
||||
pt.x = 10;
|
||||
pt.y = 8;
|
||||
@endcode
|
||||
or
|
||||
@code{.java}
|
||||
Point pt = new Point(10, 8);
|
||||
@endcode
|
||||
### Scalar
|
||||
|
||||
- Represents a 4-element vector. The type Scalar is widely used in OpenCV for passing pixel
|
||||
values.
|
||||
- In this tutorial, we will use it extensively to represent BGR color values (3 parameters). It is
|
||||
not necessary to define the last argument if it is not going to be used.
|
||||
- Let's see an example, if we are asked for a color argument and we give:
|
||||
@code{.java}
|
||||
Scalar( a, b, c )
|
||||
@endcode
|
||||
We would be defining a BGR color such as: *Blue = a*, *Green = b* and *Red = c*
|
||||
@end_toggle
|
||||
|
||||
Code
|
||||
----
|
||||
|
||||
@add_toggle_cpp
|
||||
- This code is in your OpenCV sample folder. Otherwise you can grab it from
|
||||
[here](https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/core/Matrix/Drawing_1.cpp)
|
||||
[here](https://raw.githubusercontent.com/opencv/opencv/master/samples/cpp/tutorial_code/core/Matrix/Drawing_1.cpp)
|
||||
@include samples/cpp/tutorial_code/core/Matrix/Drawing_1.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
- This code is in your OpenCV sample folder. Otherwise you can grab it from
|
||||
[here](https://raw.githubusercontent.com/opencv/opencv/master/samples/java/tutorial_code/core/BasicGeometricDrawing/BasicGeometricDrawing.java)
|
||||
@include samples/java/tutorial_code/core/BasicGeometricDrawing/BasicGeometricDrawing.java
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
- This code is in your OpenCV sample folder. Otherwise you can grab it from
|
||||
[here](https://raw.githubusercontent.com/opencv/opencv/master/samples/python/tutorial_code/core/BasicGeometricDrawing/basic_geometric_drawing.py)
|
||||
@include samples/python/tutorial_code/core/BasicGeometricDrawing/basic_geometric_drawing.py
|
||||
@end_toggle
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
-# Since we plan to draw two examples (an atom and a rook), we have to create two images and two
|
||||
windows to display them.
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp create_images
|
||||
Since we plan to draw two examples (an atom and a rook), we have to create two images and two
|
||||
windows to display them.
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp create_images
|
||||
@end_toggle
|
||||
|
||||
-# We created functions to draw different geometric shapes. For instance, to draw the atom we used
|
||||
*MyEllipse* and *MyFilledCircle*:
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp draw_atom
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/BasicGeometricDrawing/BasicGeometricDrawing.java create_images
|
||||
@end_toggle
|
||||
|
||||
-# And to draw the rook we employed *MyLine*, *rectangle* and a *MyPolygon*:
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp draw_rook
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/BasicGeometricDrawing/basic_geometric_drawing.py create_images
|
||||
@end_toggle
|
||||
|
||||
-# Let's check what is inside each of these functions:
|
||||
- *MyLine*
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp myline
|
||||
We created functions to draw different geometric shapes. For instance, to draw the atom we used
|
||||
**MyEllipse** and **MyFilledCircle**:
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp draw_atom
|
||||
@end_toggle
|
||||
|
||||
As we can see, *MyLine* just call the function @ref cv::line , which does the following:
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/BasicGeometricDrawing/BasicGeometricDrawing.java draw_atom
|
||||
@end_toggle
|
||||
|
||||
- Draw a line from Point **start** to Point **end**
|
||||
- The line is displayed in the image **img**
|
||||
- The line color is defined by **Scalar( 0, 0, 0)** which is the RGB value correspondent
|
||||
to **Black**
|
||||
- The line thickness is set to **thickness** (in this case 2)
|
||||
- The line is a 8-connected one (**lineType** = 8)
|
||||
- *MyEllipse*
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp myellipse
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/BasicGeometricDrawing/basic_geometric_drawing.py draw_atom
|
||||
@end_toggle
|
||||
|
||||
From the code above, we can observe that the function @ref cv::ellipse draws an ellipse such
|
||||
that:
|
||||
And to draw the rook we employed **MyLine**, **rectangle** and a **MyPolygon**:
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp draw_rook
|
||||
@end_toggle
|
||||
|
||||
- The ellipse is displayed in the image **img**
|
||||
- The ellipse center is located in the point **(w/2, w/2)** and is enclosed in a box
|
||||
of size **(w/4, w/16)**
|
||||
- The ellipse is rotated **angle** degrees
|
||||
- The ellipse extends an arc between **0** and **360** degrees
|
||||
- The color of the figure will be **Scalar( 255, 0, 0)** which means blue in BGR value.
|
||||
- The ellipse's **thickness** is 2.
|
||||
- *MyFilledCircle*
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp myfilledcircle
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/BasicGeometricDrawing/BasicGeometricDrawing.java draw_rook
|
||||
@end_toggle
|
||||
|
||||
Similar to the ellipse function, we can observe that *circle* receives as arguments:
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/BasicGeometricDrawing/basic_geometric_drawing.py draw_rook
|
||||
@end_toggle
|
||||
|
||||
- The image where the circle will be displayed (**img**)
|
||||
- The center of the circle denoted as the Point **center**
|
||||
- The radius of the circle: **w/32**
|
||||
- The color of the circle: **Scalar(0, 0, 255)** which means *Red* in BGR
|
||||
- Since **thickness** = -1, the circle will be drawn filled.
|
||||
- *MyPolygon*
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp mypolygon
|
||||
|
||||
To draw a filled polygon we use the function @ref cv::fillPoly . We note that:
|
||||
Let's check what is inside each of these functions:
|
||||
@add_toggle_cpp
|
||||
@end_toggle
|
||||
|
||||
- The polygon will be drawn on **img**
|
||||
- The vertices of the polygon are the set of points in **ppt**
|
||||
- The total number of vertices to be drawn are **npt**
|
||||
- The number of polygons to be drawn is only **1**
|
||||
- The color of the polygon is defined by **Scalar( 255, 255, 255)**, which is the BGR
|
||||
value for *white*
|
||||
- *rectangle*
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp rectangle
|
||||
<H4>MyLine</H4>
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp my_line
|
||||
@end_toggle
|
||||
|
||||
Finally we have the @ref cv::rectangle function (we did not create a special function for
|
||||
this guy). We note that:
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/BasicGeometricDrawing/BasicGeometricDrawing.java my_line
|
||||
@end_toggle
|
||||
|
||||
- The rectangle will be drawn on **rook_image**
|
||||
- Two opposite vertices of the rectangle are defined by *\* Point( 0, 7*w/8 )*\*
|
||||
andPoint( w, w)*\*
|
||||
- The color of the rectangle is given by **Scalar(0, 255, 255)** which is the BGR value
|
||||
for *yellow*
|
||||
- Since the thickness value is given by **FILLED (-1)**, the rectangle will be filled.
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/BasicGeometricDrawing/basic_geometric_drawing.py my_line
|
||||
@end_toggle
|
||||
|
||||
- As we can see, **MyLine** just call the function **line()** , which does the following:
|
||||
- Draw a line from Point **start** to Point **end**
|
||||
- The line is displayed in the image **img**
|
||||
- The line color is defined by <B>( 0, 0, 0 )</B> which is the RGB value correspondent
|
||||
to **Black**
|
||||
- The line thickness is set to **thickness** (in this case 2)
|
||||
- The line is a 8-connected one (**lineType** = 8)
|
||||
|
||||
<H4>MyEllipse</H4>
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp my_ellipse
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/BasicGeometricDrawing/BasicGeometricDrawing.java my_ellipse
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/BasicGeometricDrawing/basic_geometric_drawing.py my_ellipse
|
||||
@end_toggle
|
||||
|
||||
- From the code above, we can observe that the function **ellipse()** draws an ellipse such
|
||||
that:
|
||||
|
||||
- The ellipse is displayed in the image **img**
|
||||
- The ellipse center is located in the point <B>(w/2, w/2)</B> and is enclosed in a box
|
||||
of size <B>(w/4, w/16)</B>
|
||||
- The ellipse is rotated **angle** degrees
|
||||
- The ellipse extends an arc between **0** and **360** degrees
|
||||
- The color of the figure will be <B>( 255, 0, 0 )</B> which means blue in BGR value.
|
||||
- The ellipse's **thickness** is 2.
|
||||
|
||||
<H4>MyFilledCircle</H4>
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp my_filled_circle
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/BasicGeometricDrawing/BasicGeometricDrawing.java my_filled_circle
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/BasicGeometricDrawing/basic_geometric_drawing.py my_filled_circle
|
||||
@end_toggle
|
||||
|
||||
- Similar to the ellipse function, we can observe that *circle* receives as arguments:
|
||||
|
||||
- The image where the circle will be displayed (**img**)
|
||||
- The center of the circle denoted as the point **center**
|
||||
- The radius of the circle: **w/32**
|
||||
- The color of the circle: <B>( 0, 0, 255 )</B> which means *Red* in BGR
|
||||
- Since **thickness** = -1, the circle will be drawn filled.
|
||||
|
||||
<H4>MyPolygon</H4>
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp my_polygon
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/BasicGeometricDrawing/BasicGeometricDrawing.java my_polygon
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/BasicGeometricDrawing/basic_geometric_drawing.py my_polygon
|
||||
@end_toggle
|
||||
|
||||
- To draw a filled polygon we use the function **fillPoly()** . We note that:
|
||||
|
||||
- The polygon will be drawn on **img**
|
||||
- The vertices of the polygon are the set of points in **ppt**
|
||||
- The color of the polygon is defined by <B>( 255, 255, 255 )</B>, which is the BGR
|
||||
value for *white*
|
||||
|
||||
<H4>rectangle</H4>
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/Matrix/Drawing_1.cpp rectangle
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/BasicGeometricDrawing/BasicGeometricDrawing.java rectangle
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/BasicGeometricDrawing/basic_geometric_drawing.py rectangle
|
||||
@end_toggle
|
||||
|
||||
- Finally we have the @ref cv::rectangle function (we did not create a special function for
|
||||
this guy). We note that:
|
||||
|
||||
- The rectangle will be drawn on **rook_image**
|
||||
- Two opposite vertices of the rectangle are defined by <B>( 0, 7*w/8 )</B>
|
||||
and <B>( w, w )</B>
|
||||
- The color of the rectangle is given by <B>( 0, 255, 255 )</B> which is the BGR value
|
||||
for *yellow*
|
||||
- Since the thickness value is given by **FILLED (-1)**, the rectangle will be filled.
|
||||
|
||||
Result
|
||||
------
|
||||
|
||||
+163
-81
@@ -1,6 +1,9 @@
|
||||
Discrete Fourier Transform {#tutorial_discrete_fourier_transform}
|
||||
==========================
|
||||
|
||||
@prev_tutorial{tutorial_random_generator_and_text}
|
||||
@next_tutorial{tutorial_file_input_output_with_xml_yml}
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
@@ -8,21 +11,49 @@ We'll seek answers for the following questions:
|
||||
|
||||
- What is a Fourier transform and why use it?
|
||||
- How to do it in OpenCV?
|
||||
- Usage of functions such as: @ref cv::copyMakeBorder() , @ref cv::merge() , @ref cv::dft() , @ref
|
||||
cv::getOptimalDFTSize() , @ref cv::log() and @ref cv::normalize() .
|
||||
- Usage of functions such as: **copyMakeBorder()** , **merge()** , **dft()** ,
|
||||
**getOptimalDFTSize()** , **log()** and **normalize()** .
|
||||
|
||||
Source code
|
||||
-----------
|
||||
|
||||
@add_toggle_cpp
|
||||
You can [download this from here
|
||||
](https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp) or
|
||||
](https://raw.githubusercontent.com/opencv/opencv/master/samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp) or
|
||||
find it in the
|
||||
`samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp` of the
|
||||
OpenCV source code library.
|
||||
@end_toggle
|
||||
|
||||
Here's a sample usage of @ref cv::dft() :
|
||||
@add_toggle_java
|
||||
You can [download this from here
|
||||
](https://raw.githubusercontent.com/opencv/opencv/master/samples/java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java) or
|
||||
find it in the
|
||||
`samples/java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java` of the
|
||||
OpenCV source code library.
|
||||
@end_toggle
|
||||
|
||||
@includelineno cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp
|
||||
@add_toggle_python
|
||||
You can [download this from here
|
||||
](https://raw.githubusercontent.com/opencv/opencv/master/samples/python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py) or
|
||||
find it in the
|
||||
`samples/python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py` of the
|
||||
OpenCV source code library.
|
||||
@end_toggle
|
||||
|
||||
Here's a sample usage of **dft()** :
|
||||
|
||||
@add_toggle_cpp
|
||||
@include cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@include java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@include python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py
|
||||
@end_toggle
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
@@ -49,89 +80,140 @@ Fourier Transform too needs to be of a discrete type resulting in a Discrete Fou
|
||||
(*DFT*). You'll want to use this whenever you need to determine the structure of an image from a
|
||||
geometrical point of view. Here are the steps to follow (in case of a gray scale input image *I*):
|
||||
|
||||
-# **Expand the image to an optimal size**. The performance of a DFT is dependent of the image
|
||||
size. It tends to be the fastest for image sizes that are multiple of the numbers two, three and
|
||||
five. Therefore, to achieve maximal performance it is generally a good idea to pad border values
|
||||
to the image to get a size with such traits. The @ref cv::getOptimalDFTSize() returns this
|
||||
optimal size and we can use the @ref cv::copyMakeBorder() function to expand the borders of an
|
||||
image:
|
||||
@code{.cpp}
|
||||
Mat padded; //expand input image to optimal size
|
||||
int m = getOptimalDFTSize( I.rows );
|
||||
int n = getOptimalDFTSize( I.cols ); // on the border add zero pixels
|
||||
copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
|
||||
@endcode
|
||||
The appended pixels are initialized with zero.
|
||||
#### Expand the image to an optimal size
|
||||
|
||||
-# **Make place for both the complex and the real values**. The result of a Fourier Transform is
|
||||
complex. This implies that for each image value the result is two image values (one per
|
||||
component). Moreover, the frequency domains range is much larger than its spatial counterpart.
|
||||
Therefore, we store these usually at least in a *float* format. Therefore we'll convert our
|
||||
input image to this type and expand it with another channel to hold the complex values:
|
||||
@code{.cpp}
|
||||
Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
|
||||
Mat complexI;
|
||||
merge(planes, 2, complexI); // Add to the expanded another plane with zeros
|
||||
@endcode
|
||||
-# **Make the Discrete Fourier Transform**. It's possible an in-place calculation (same input as
|
||||
output):
|
||||
@code{.cpp}
|
||||
dft(complexI, complexI); // this way the result may fit in the source matrix
|
||||
@endcode
|
||||
-# **Transform the real and complex values to magnitude**. A complex number has a real (*Re*) and a
|
||||
complex (imaginary - *Im*) part. The results of a DFT are complex numbers. The magnitude of a
|
||||
DFT is:
|
||||
The performance of a DFT is dependent of the image
|
||||
size. It tends to be the fastest for image sizes that are multiple of the numbers two, three and
|
||||
five. Therefore, to achieve maximal performance it is generally a good idea to pad border values
|
||||
to the image to get a size with such traits. The **getOptimalDFTSize()** returns this
|
||||
optimal size and we can use the **copyMakeBorder()** function to expand the borders of an
|
||||
image (the appended pixels are initialized with zero):
|
||||
|
||||
\f[M = \sqrt[2]{ {Re(DFT(I))}^2 + {Im(DFT(I))}^2}\f]
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp expand
|
||||
@end_toggle
|
||||
|
||||
Translated to OpenCV code:
|
||||
@code{.cpp}
|
||||
split(complexI, planes); // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
|
||||
magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude
|
||||
Mat magI = planes[0];
|
||||
@endcode
|
||||
-# **Switch to a logarithmic scale**. It turns out that the dynamic range of the Fourier
|
||||
coefficients is too large to be displayed on the screen. We have some small and some high
|
||||
changing values that we can't observe like this. Therefore the high values will all turn out as
|
||||
white points, while the small ones as black. To use the gray scale values to for visualization
|
||||
we can transform our linear scale to a logarithmic one:
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java expand
|
||||
@end_toggle
|
||||
|
||||
\f[M_1 = \log{(1 + M)}\f]
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py expand
|
||||
@end_toggle
|
||||
|
||||
Translated to OpenCV code:
|
||||
@code{.cpp}
|
||||
magI += Scalar::all(1); // switch to logarithmic scale
|
||||
log(magI, magI);
|
||||
@endcode
|
||||
-# **Crop and rearrange**. Remember, that at the first step, we expanded the image? Well, it's time
|
||||
to throw away the newly introduced values. For visualization purposes we may also rearrange the
|
||||
quadrants of the result, so that the origin (zero, zero) corresponds with the image center.
|
||||
@code{.cpp}
|
||||
magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));
|
||||
int cx = magI.cols/2;
|
||||
int cy = magI.rows/2;
|
||||
#### Make place for both the complex and the real values
|
||||
|
||||
Mat q0(magI, Rect(0, 0, cx, cy)); // Top-Left - Create a ROI per quadrant
|
||||
Mat q1(magI, Rect(cx, 0, cx, cy)); // Top-Right
|
||||
Mat q2(magI, Rect(0, cy, cx, cy)); // Bottom-Left
|
||||
Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right
|
||||
The result of a Fourier Transform is
|
||||
complex. This implies that for each image value the result is two image values (one per
|
||||
component). Moreover, the frequency domains range is much larger than its spatial counterpart.
|
||||
Therefore, we store these usually at least in a *float* format. Therefore we'll convert our
|
||||
input image to this type and expand it with another channel to hold the complex values:
|
||||
|
||||
Mat tmp; // swap quadrants (Top-Left with Bottom-Right)
|
||||
q0.copyTo(tmp);
|
||||
q3.copyTo(q0);
|
||||
tmp.copyTo(q3);
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp complex_and_real
|
||||
@end_toggle
|
||||
|
||||
q1.copyTo(tmp); // swap quadrant (Top-Right with Bottom-Left)
|
||||
q2.copyTo(q1);
|
||||
tmp.copyTo(q2);
|
||||
@endcode
|
||||
-# **Normalize**. This is done again for visualization purposes. We now have the magnitudes,
|
||||
however this are still out of our image display range of zero to one. We normalize our values to
|
||||
this range using the @ref cv::normalize() function.
|
||||
@code{.cpp}
|
||||
normalize(magI, magI, 0, 1, NORM_MINMAX); // Transform the matrix with float values into a
|
||||
// viewable image form (float between values 0 and 1).
|
||||
@endcode
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java complex_and_real
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py complex_and_real
|
||||
@end_toggle
|
||||
|
||||
#### Make the Discrete Fourier Transform
|
||||
It's possible an in-place calculation (same input as
|
||||
output):
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp dft
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java dft
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py dft
|
||||
@end_toggle
|
||||
|
||||
#### Transform the real and complex values to magnitude
|
||||
A complex number has a real (*Re*) and a
|
||||
complex (imaginary - *Im*) part. The results of a DFT are complex numbers. The magnitude of a
|
||||
DFT is:
|
||||
|
||||
\f[M = \sqrt[2]{ {Re(DFT(I))}^2 + {Im(DFT(I))}^2}\f]
|
||||
|
||||
Translated to OpenCV code:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp magnitude
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java magnitude
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py magnitude
|
||||
@end_toggle
|
||||
|
||||
#### Switch to a logarithmic scale
|
||||
It turns out that the dynamic range of the Fourier
|
||||
coefficients is too large to be displayed on the screen. We have some small and some high
|
||||
changing values that we can't observe like this. Therefore the high values will all turn out as
|
||||
white points, while the small ones as black. To use the gray scale values to for visualization
|
||||
we can transform our linear scale to a logarithmic one:
|
||||
|
||||
\f[M_1 = \log{(1 + M)}\f]
|
||||
|
||||
Translated to OpenCV code:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp log
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java log
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py log
|
||||
@end_toggle
|
||||
|
||||
#### Crop and rearrange
|
||||
Remember, that at the first step, we expanded the image? Well, it's time
|
||||
to throw away the newly introduced values. For visualization purposes we may also rearrange the
|
||||
quadrants of the result, so that the origin (zero, zero) corresponds with the image center.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp crop_rearrange
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java crop_rearrange
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py crop_rearrange
|
||||
@end_toggle
|
||||
|
||||
#### Normalize
|
||||
This is done again for visualization purposes. We now have the magnitudes,
|
||||
however this are still out of our image display range of zero to one. We normalize our values to
|
||||
this range using the @ref cv::normalize() function.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp normalize
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet java/tutorial_code/core/discrete_fourier_transform/DiscreteFourierTransform.java normalize
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet python/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.py normalize
|
||||
@end_toggle
|
||||
|
||||
Result
|
||||
------
|
||||
@@ -140,7 +222,7 @@ An application idea would be to determine the geometrical orientation present in
|
||||
example, let us find out if a text is horizontal or not? Looking at some text you'll notice that the
|
||||
text lines sort of form also horizontal lines and the letters form sort of vertical lines. These two
|
||||
main components of a text snippet may be also seen in case of the Fourier transform. Let us use
|
||||
[this horizontal ](https://github.com/opencv/opencv/tree/master/samples/data/imageTextN.png) and [this rotated](https://github.com/opencv/opencv/tree/master/samples/data/imageTextR.png)
|
||||
[this horizontal ](https://raw.githubusercontent.com/opencv/opencv/master/samples/data/imageTextN.png) and [this rotated](https://raw.githubusercontent.com/opencv/opencv/master/samples/data/imageTextR.png)
|
||||
image about a text.
|
||||
|
||||
In case of the horizontal text:
|
||||
|
||||
@@ -28,24 +28,39 @@ the zero-zero index) on the pixel you want to calculate and sum up the pixel val
|
||||
the overlapped matrix values. It's the same thing, however in case of large matrices the latter
|
||||
notation is a lot easier to look over.
|
||||
|
||||
Code
|
||||
----
|
||||
|
||||
@add_toggle_cpp
|
||||
Now let us see how we can make this happen by using the basic pixel access method or by using the
|
||||
@ref cv::filter2D function.
|
||||
You can download this source code from [here
|
||||
](https://raw.githubusercontent.com/opencv/opencv/master/samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp) or look in the
|
||||
OpenCV source code libraries sample directory at
|
||||
`samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp`.
|
||||
@include samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
Now let us see how we can make this happen by using the basic pixel access method or by using the
|
||||
**Imgproc.filter2D()** function.
|
||||
You can download this source code from [here
|
||||
](https://raw.githubusercontent.com/opencv/opencv/master/samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java) or look in the
|
||||
OpenCV source code libraries sample directory at
|
||||
`samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java`.
|
||||
@include samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
Now let us see how we can make this happen by using the basic pixel access method or by using the
|
||||
**cv2.filter2D()** function.
|
||||
You can download this source code from [here
|
||||
](https://raw.githubusercontent.com/opencv/opencv/master/samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py) or look in the
|
||||
OpenCV source code libraries sample directory at
|
||||
`samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py`.
|
||||
@include samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py
|
||||
@end_toggle
|
||||
|
||||
The Basic Method
|
||||
----------------
|
||||
|
||||
Now let us see how we can make this happen by using the basic pixel access method or by using the
|
||||
**filter2D()** function.
|
||||
|
||||
Here's a function that will do this:
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp basic_method
|
||||
@@ -132,37 +147,38 @@ The filter2D function
|
||||
Applying such filters are so common in image processing that in OpenCV there exist a function that
|
||||
will take care of applying the mask (also called a kernel in some places). For this you first need
|
||||
to define an object that holds the mask:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp kern
|
||||
|
||||
Then call the @ref cv::filter2D function specifying the input, the output image and the kernel to
|
||||
use:
|
||||
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp filter2D
|
||||
|
||||
The function even has a fifth optional argument to specify the center of the kernel, a sixth
|
||||
for adding an optional value to the filtered pixels before storing them in K and a seventh one
|
||||
for determining what to do in the regions where the operation is undefined (borders).
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java kern
|
||||
|
||||
Then call the **Imgproc.filter2D()** function specifying the input, the output image and the kernel to
|
||||
use:
|
||||
@snippet samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java filter2D
|
||||
The function even has a fifth optional argument to specify the center of the kernel, a sixth
|
||||
for adding an optional value to the filtered pixels before storing them in K and a seventh one
|
||||
for determining what to do in the regions where the operation is undefined (borders).
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py kern
|
||||
@end_toggle
|
||||
|
||||
Then call the **cv2.filter2D()** function specifying the input, the output image and the kernell to
|
||||
Then call the **filter2D()** function specifying the input, the output image and the kernel to
|
||||
use:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp filter2D
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
@snippet samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java filter2D
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py filter2D
|
||||
@end_toggle
|
||||
|
||||
The function even has a fifth optional argument to specify the center of the kernel, a sixth
|
||||
for adding an optional value to the filtered pixels before storing them in K and a seventh one
|
||||
for determining what to do in the regions where the operation is undefined (borders).
|
||||
|
||||
This function is shorter, less verbose and, because there are some optimizations, it is usually faster
|
||||
than the *hand-coded method*. For example in my test while the second one took only 13
|
||||
milliseconds the first took around 31 milliseconds. Quite some difference.
|
||||
@@ -172,22 +188,7 @@ For example:
|
||||

|
||||
|
||||
@add_toggle_cpp
|
||||
You can download this source code from [here
|
||||
](https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp) or look in the
|
||||
OpenCV source code libraries sample directory at
|
||||
`samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp`.
|
||||
|
||||
Check out an instance of running the program on our [YouTube
|
||||
channel](http://www.youtube.com/watch?v=7PF1tAU9se4) .
|
||||
@youtube{7PF1tAU9se4}
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_java
|
||||
You can look in the OpenCV source code libraries sample directory at
|
||||
`samples/java/tutorial_code/core/mat_mask_operations/MatMaskOperations.java`.
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
You can look in the OpenCV source code libraries sample directory at
|
||||
`samples/python/tutorial_code/core/mat_mask_operations/mat_mask_operations.py`.
|
||||
@end_toggle
|
||||
|
||||
@@ -40,6 +40,8 @@ understanding how to manipulate the images on a pixel level.
|
||||
|
||||
- @subpage tutorial_adding_images
|
||||
|
||||
*Languages:* C++, Java, Python
|
||||
|
||||
*Compatibility:* \> OpenCV 2.0
|
||||
|
||||
*Author:* Ana Huamán
|
||||
@@ -56,6 +58,8 @@ understanding how to manipulate the images on a pixel level.
|
||||
|
||||
- @subpage tutorial_basic_geometric_drawing
|
||||
|
||||
*Languages:* C++, Java, Python
|
||||
|
||||
*Compatibility:* \> OpenCV 2.0
|
||||
|
||||
*Author:* Ana Huamán
|
||||
@@ -72,6 +76,8 @@ understanding how to manipulate the images on a pixel level.
|
||||
|
||||
- @subpage tutorial_discrete_fourier_transform
|
||||
|
||||
*Languages:* C++, Java, Python
|
||||
|
||||
*Compatibility:* \> OpenCV 2.0
|
||||
|
||||
*Author:* Bernát Gábor
|
||||
|
||||
Reference in New Issue
Block a user