Removed Sphinx documentation files
This commit is contained in:
@@ -1,276 +0,0 @@
|
||||
.. _Morphology_1:
|
||||
|
||||
Eroding and Dilating
|
||||
**********************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
* Apply two very common morphology operators: Dilation and Erosion. For this purpose, you will use the following OpenCV functions:
|
||||
|
||||
* :erode:`erode <>`
|
||||
* :dilate:`dilate <>`
|
||||
|
||||
Cool Theory
|
||||
============
|
||||
|
||||
.. note::
|
||||
The explanation below belongs to the book **Learning OpenCV** by Bradski and Kaehler.
|
||||
|
||||
Morphological Operations
|
||||
--------------------------
|
||||
|
||||
* In short: A set of operations that process images based on shapes. Morphological operations apply a *structuring element* to an input image and generate an output image.
|
||||
|
||||
* The most basic morphological operations are two: Erosion and Dilation. They have a wide array of uses, i.e. :
|
||||
|
||||
* Removing noise
|
||||
|
||||
* Isolation of individual elements and joining disparate elements in an image.
|
||||
|
||||
* Finding of intensity bumps or holes in an image
|
||||
|
||||
* We will explain dilation and erosion briefly, using the following image as an example:
|
||||
|
||||
.. image:: images/Morphology_1_Tutorial_Theory_Original_Image.png
|
||||
:alt: Original image
|
||||
:align: center
|
||||
|
||||
Dilation
|
||||
~~~~~~~~
|
||||
|
||||
* This operations consists of convoluting an image :math:`A` with some kernel (:math:`B`), which can have any shape or size, usually a square or circle.
|
||||
|
||||
* The kernel :math:`B` has a defined *anchor point*, usually being the center of the kernel.
|
||||
|
||||
* As the kernel :math:`B` is scanned over the image, we compute the maximal pixel value overlapped by :math:`B` and replace the image pixel in the anchor point position with that maximal value. As you can deduce, this maximizing operation causes bright regions within an image to "grow" (therefore the name *dilation*). Take as an example the image above. Applying dilation we can get:
|
||||
|
||||
.. image:: images/Morphology_1_Tutorial_Theory_Dilation.png
|
||||
:alt: Dilation result - Theory example
|
||||
:align: center
|
||||
|
||||
The background (bright) dilates around the black regions of the letter.
|
||||
|
||||
Erosion
|
||||
~~~~~~~
|
||||
|
||||
* This operation is the sister of dilation. What this does is to compute a local minimum over the area of the kernel.
|
||||
|
||||
* As the kernel :math:`B` is scanned over the image, we compute the minimal pixel value overlapped by :math:`B` and replace the image pixel under the anchor point with that minimal value.
|
||||
|
||||
* Analagously to the example for dilation, we can apply the erosion operator to the original image (shown above). You can see in the result below that the bright areas of the image (the background, apparently), get thinner, whereas the dark zones (the "writing") gets bigger.
|
||||
|
||||
.. image:: images/Morphology_1_Tutorial_Theory_Erosion.png
|
||||
:alt: Erosion result - Theory example
|
||||
:align: center
|
||||
|
||||
|
||||
Code
|
||||
======
|
||||
|
||||
This tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "highgui.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/// Global variables
|
||||
Mat src, erosion_dst, dilation_dst;
|
||||
|
||||
int erosion_elem = 0;
|
||||
int erosion_size = 0;
|
||||
int dilation_elem = 0;
|
||||
int dilation_size = 0;
|
||||
int const max_elem = 2;
|
||||
int const max_kernel_size = 21;
|
||||
|
||||
/* Function Headers */
|
||||
void Erosion( int, void* );
|
||||
void Dilation( int, void* );
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load an image
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
/// Create windows
|
||||
namedWindow( "Erosion Demo", WINDOW_AUTOSIZE );
|
||||
namedWindow( "Dilation Demo", WINDOW_AUTOSIZE );
|
||||
cvMoveWindow( "Dilation Demo", src.cols, 0 );
|
||||
|
||||
/// Create Erosion Trackbar
|
||||
createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Erosion Demo",
|
||||
&erosion_elem, max_elem,
|
||||
Erosion );
|
||||
|
||||
createTrackbar( "Kernel size:\n 2n +1", "Erosion Demo",
|
||||
&erosion_size, max_kernel_size,
|
||||
Erosion );
|
||||
|
||||
/// Create Dilation Trackbar
|
||||
createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Dilation Demo",
|
||||
&dilation_elem, max_elem,
|
||||
Dilation );
|
||||
|
||||
createTrackbar( "Kernel size:\n 2n +1", "Dilation Demo",
|
||||
&dilation_size, max_kernel_size,
|
||||
Dilation );
|
||||
|
||||
/// Default start
|
||||
Erosion( 0, 0 );
|
||||
Dilation( 0, 0 );
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* @function Erosion */
|
||||
void Erosion( int, void* )
|
||||
{
|
||||
int erosion_type;
|
||||
if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }
|
||||
else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }
|
||||
else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }
|
||||
|
||||
Mat element = getStructuringElement( erosion_type,
|
||||
Size( 2*erosion_size + 1, 2*erosion_size+1 ),
|
||||
Point( erosion_size, erosion_size ) );
|
||||
|
||||
/// Apply the erosion operation
|
||||
erode( src, erosion_dst, element );
|
||||
imshow( "Erosion Demo", erosion_dst );
|
||||
}
|
||||
|
||||
/* @function Dilation */
|
||||
void Dilation( int, void* )
|
||||
{
|
||||
int dilation_type;
|
||||
if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }
|
||||
else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }
|
||||
else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }
|
||||
|
||||
Mat element = getStructuringElement( dilation_type,
|
||||
Size( 2*dilation_size + 1, 2*dilation_size+1 ),
|
||||
Point( dilation_size, dilation_size ) );
|
||||
/// Apply the dilation operation
|
||||
dilate( src, dilation_dst, element );
|
||||
imshow( "Dilation Demo", dilation_dst );
|
||||
}
|
||||
|
||||
|
||||
Explanation
|
||||
=============
|
||||
|
||||
#. Most of the stuff shown is known by you (if you have any doubt, please refer to the tutorials in previous sections). Let's check the general structure of the program:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Load an image (can be RGB or grayscale)
|
||||
* Create two windows (one for dilation output, the other for erosion)
|
||||
* Create a set of 02 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.
|
||||
|
||||
Let's analyze these two functions:
|
||||
|
||||
#. **erosion:**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/* @function Erosion */
|
||||
void Erosion( int, void* )
|
||||
{
|
||||
int erosion_type;
|
||||
if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }
|
||||
else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }
|
||||
else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }
|
||||
|
||||
Mat element = getStructuringElement( erosion_type,
|
||||
Size( 2*erosion_size + 1, 2*erosion_size+1 ),
|
||||
Point( erosion_size, erosion_size ) );
|
||||
/// Apply the erosion operation
|
||||
erode( src, erosion_dst, element );
|
||||
imshow( "Erosion Demo", erosion_dst );
|
||||
}
|
||||
|
||||
* The function that performs the *erosion* operation is :erode:`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 :math:`3x3` matrix. Otherwise, we can specify its shape. For this, we need to use the function :get_structuring_element:`getStructuringElement <>`:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat element = getStructuringElement( erosion_type,
|
||||
Size( 2*erosion_size + 1, 2*erosion_size+1 ),
|
||||
Point( erosion_size, erosion_size ) );
|
||||
|
||||
We can choose any of three shapes for our kernel:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ 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.
|
||||
|
||||
.. note::
|
||||
Additionally, there is another parameter that allows you to perform multiple erosions (iterations) at once. We are not using it in this simple tutorial, though. You can check out the Reference for more details.
|
||||
|
||||
|
||||
#. **dilation:**
|
||||
|
||||
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.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/* @function Dilation */
|
||||
void Dilation( int, void* )
|
||||
{
|
||||
int dilation_type;
|
||||
if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }
|
||||
else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }
|
||||
else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }
|
||||
|
||||
Mat element = getStructuringElement( dilation_type,
|
||||
Size( 2*dilation_size + 1, 2*dilation_size+1 ),
|
||||
Point( dilation_size, dilation_size ) );
|
||||
/// Apply the dilation operation
|
||||
dilate( src, dilation_dst, element );
|
||||
imshow( "Dilation Demo", dilation_dst );
|
||||
}
|
||||
|
||||
|
||||
|
||||
Results
|
||||
========
|
||||
|
||||
* Compile the code above and execute it with an image as argument. For instance, using this image:
|
||||
|
||||
.. image:: images/Morphology_1_Tutorial_Original_Image.jpg
|
||||
:alt: Original image
|
||||
:align: center
|
||||
|
||||
We get the results below. Varying the indices in the Trackbars give different output images, naturally. Try them out! You can even try to add a third Trackbar to control the number of iterations.
|
||||
|
||||
.. image:: images/Morphology_1_Result.jpg
|
||||
:alt: Dilation and Erosion application
|
||||
:align: center
|
@@ -1,326 +0,0 @@
|
||||
.. _Smoothing:
|
||||
|
||||
Smoothing Images
|
||||
******************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to apply diverse linear filters to smooth images using OpenCV functions such as:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* :blur:`blur <>`
|
||||
* :gaussian_blur:`GaussianBlur <>`
|
||||
* :median_blur:`medianBlur <>`
|
||||
* :bilateral_filter:`bilateralFilter <>`
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
.. note::
|
||||
The explanation below belongs to the book `Computer Vision: Algorithms and Applications <http://szeliski.org/Book/>`_ by Richard Szeliski and to *LearningOpenCV*
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* *Smoothing*, also called *blurring*, is a simple and frequently used image processing operation.
|
||||
|
||||
* There are many reasons for smoothing. In this tutorial we will focus on smoothing in order to reduce noise (other uses will be seen in the following tutorials).
|
||||
|
||||
* To perform a smoothing operation we will apply a *filter* to our image. The most common type of filters are *linear*, in which an output pixel's value (i.e. :math:`g(i,j)`) is determined as a weighted sum of input pixel values (i.e. :math:`f(i+k,j+l)`) :
|
||||
|
||||
.. math::
|
||||
g(i,j) = \sum_{k,l} f(i+k, j+l) h(k,l)
|
||||
|
||||
:math:`h(k,l)` is called the *kernel*, which is nothing more than the coefficients of the filter.
|
||||
|
||||
|
||||
It helps to visualize a *filter* as a window of coefficients sliding across the image.
|
||||
|
||||
* There are many kind of filters, here we will mention the most used:
|
||||
|
||||
Normalized Box Filter
|
||||
-----------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* This filter is the simplest of all! Each output pixel is the *mean* of its kernel neighbors ( all of them contribute with equal weights)
|
||||
|
||||
* The kernel is below:
|
||||
|
||||
.. math::
|
||||
|
||||
K = \dfrac{1}{K_{width} \cdot K_{height}} \begin{bmatrix}
|
||||
1 & 1 & 1 & ... & 1 \\
|
||||
1 & 1 & 1 & ... & 1 \\
|
||||
. & . & . & ... & 1 \\
|
||||
. & . & . & ... & 1 \\
|
||||
1 & 1 & 1 & ... & 1
|
||||
\end{bmatrix}
|
||||
|
||||
|
||||
Gaussian Filter
|
||||
---------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Probably the most useful filter (although not the fastest). Gaussian filtering is done by convolving each point in the input array with a *Gaussian kernel* and then summing them all to produce the output array.
|
||||
|
||||
* Just to make the picture clearer, remember how a 1D Gaussian kernel look like?
|
||||
|
||||
.. image:: images/Smoothing_Tutorial_theory_gaussian_0.jpg
|
||||
:align: center
|
||||
|
||||
Assuming that an image is 1D, you can notice that the pixel located in the middle would have the biggest weight. The weight of its neighbors decreases as the spatial distance between them and the center pixel increases.
|
||||
|
||||
.. note::
|
||||
|
||||
Remember that a 2D Gaussian can be represented as :
|
||||
|
||||
.. math::
|
||||
|
||||
G_{0}(x, y) = A e^{ \dfrac{ -(x - \mu_{x})^{2} }{ 2\sigma^{2}_{x} } + \dfrac{ -(y - \mu_{y})^{2} }{ 2\sigma^{2}_{y} } }
|
||||
|
||||
where :math:`\mu` is the mean (the peak) and :math:`\sigma` represents the variance (per each of the variables :math:`x` and :math:`y`)
|
||||
|
||||
|
||||
Median Filter
|
||||
--------------
|
||||
|
||||
The median filter run through each element of the signal (in this case the image) and replace each pixel with the **median** of its neighboring pixels (located in a square neighborhood around the evaluated pixel).
|
||||
|
||||
|
||||
Bilateral Filter
|
||||
-----------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* So far, we have explained some filters which main goal is to *smooth* an input image. However, sometimes the filters do not only dissolve the noise, but also smooth away the *edges*. To avoid this (at certain extent at least), we can use a bilateral filter.
|
||||
|
||||
* In an analogous way as the Gaussian filter, the bilateral filter also considers the neighboring pixels with weights assigned to each of them. These weights have two components, the first of which is the same weighting used by the Gaussian filter. The second component takes into account the difference in intensity between the neighboring pixels and the evaluated one.
|
||||
|
||||
* For a more detailed explanation you can check `this link <http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/MANDUCHI1/Bilateral_Filtering.html>`_
|
||||
|
||||
|
||||
Code
|
||||
======
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* **What does this program do?**
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Loads an image
|
||||
* Applies 4 different kinds of filters (explained in Theory) and show the filtered images sequentially
|
||||
|
||||
* **Downloadable code**:
|
||||
Click `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgProc/Smoothing.cpp>`_
|
||||
|
||||
* **Code at glance:**
|
||||
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
/// Global Variables
|
||||
int DELAY_CAPTION = 1500;
|
||||
int DELAY_BLUR = 100;
|
||||
int MAX_KERNEL_LENGTH = 31;
|
||||
|
||||
Mat src; Mat dst;
|
||||
char window_name[] = "Filter Demo 1";
|
||||
|
||||
/// Function headers
|
||||
int display_caption( char* caption );
|
||||
int display_dst( int delay );
|
||||
|
||||
/*
|
||||
* function main
|
||||
*/
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Load the source image
|
||||
src = imread( "../images/lena.jpg", 1 );
|
||||
|
||||
if( display_caption( "Original Image" ) != 0 ) { return 0; }
|
||||
|
||||
dst = src.clone();
|
||||
if( display_dst( DELAY_CAPTION ) != 0 ) { return 0; }
|
||||
|
||||
/// Applying Homogeneous blur
|
||||
if( display_caption( "Homogeneous Blur" ) != 0 ) { return 0; }
|
||||
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ blur( src, dst, Size( i, i ), Point(-1,-1) );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
|
||||
/// Applying Gaussian blur
|
||||
if( display_caption( "Gaussian Blur" ) != 0 ) { return 0; }
|
||||
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ GaussianBlur( src, dst, Size( i, i ), 0, 0 );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
|
||||
/// Applying Median blur
|
||||
if( display_caption( "Median Blur" ) != 0 ) { return 0; }
|
||||
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ medianBlur ( src, dst, i );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
|
||||
/// Applying Bilateral Filter
|
||||
if( display_caption( "Bilateral Blur" ) != 0 ) { return 0; }
|
||||
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ bilateralFilter ( src, dst, i, i*2, i/2 );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
|
||||
/// Wait until user press a key
|
||||
display_caption( "End: Press a key!" );
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int display_caption( char* caption )
|
||||
{
|
||||
dst = Mat::zeros( src.size(), src.type() );
|
||||
putText( dst, caption,
|
||||
Point( src.cols/4, src.rows/2),
|
||||
FONT_HERSHEY_COMPLEX, 1, Scalar(255, 255, 255) );
|
||||
|
||||
imshow( window_name, dst );
|
||||
int c = waitKey( DELAY_CAPTION );
|
||||
if( c >= 0 ) { return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
int display_dst( int delay )
|
||||
{
|
||||
imshow( window_name, dst );
|
||||
int c = waitKey ( delay );
|
||||
if( c >= 0 ) { return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Explanation
|
||||
=============
|
||||
|
||||
#. Let's check the OpenCV functions that involve only the smoothing procedure, since the rest is already known by now.
|
||||
|
||||
#. **Normalized Block Filter:**
|
||||
|
||||
OpenCV offers the function :blur:`blur <>` to perform smoothing with this filter.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ blur( src, dst, Size( i, i ), Point(-1,-1) );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
|
||||
|
||||
We specify 4 arguments (more details, check the Reference):
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ *src*: Source image
|
||||
|
||||
+ *dst*: Destination image
|
||||
|
||||
+ *Size( w,h )*: Defines the size of the kernel to be used ( of width *w* pixels and height *h* pixels)
|
||||
|
||||
+ *Point(-1, -1)*: Indicates where the anchor point (the pixel evaluated) is located with respect to the neighborhood. If there is a negative value, then the center of the kernel is considered the anchor point.
|
||||
|
||||
#. **Gaussian Filter:**
|
||||
|
||||
It is performed by the function :gaussian_blur:`GaussianBlur <>` :
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ GaussianBlur( src, dst, Size( i, i ), 0, 0 );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
|
||||
Here we use 4 arguments (more details, check the OpenCV reference):
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ *src*: Source image
|
||||
|
||||
+ *dst*: Destination image
|
||||
|
||||
+ *Size(w, h)*: The size of the kernel to be used (the neighbors to be considered). :math:`w` and :math:`h` have to be odd and positive numbers otherwise thi size will be calculated using the :math:`\sigma_{x}` and :math:`\sigma_{y}` arguments.
|
||||
|
||||
+ :math:`\sigma_{x}`: The standard deviation in x. Writing :math:`0` implies that :math:`\sigma_{x}` is calculated using kernel size.
|
||||
|
||||
+ :math:`\sigma_{y}`: The standard deviation in y. Writing :math:`0` implies that :math:`\sigma_{y}` is calculated using kernel size.
|
||||
|
||||
|
||||
#. **Median Filter:**
|
||||
|
||||
This filter is provided by the :median_blur:`medianBlur <>` function:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ medianBlur ( src, dst, i );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
|
||||
We use three arguments:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ *src*: Source image
|
||||
|
||||
+ *dst*: Destination image, must be the same type as *src*
|
||||
|
||||
+ *i*: Size of the kernel (only one because we use a square window). Must be odd.
|
||||
|
||||
|
||||
#. **Bilateral Filter**
|
||||
|
||||
Provided by OpenCV function :bilateral_filter:`bilateralFilter <>`
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
|
||||
{ bilateralFilter ( src, dst, i, i*2, i/2 );
|
||||
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
|
||||
|
||||
We use 5 arguments:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ *src*: Source image
|
||||
|
||||
+ *dst*: Destination image
|
||||
|
||||
+ *d*: The diameter of each pixel neighborhood.
|
||||
|
||||
+ :math:`\sigma_{Color}`: Standard deviation in the color space.
|
||||
|
||||
+ :math:`\sigma_{Space}`: Standard deviation in the coordinate space (in pixel terms)
|
||||
|
||||
|
||||
Results
|
||||
========
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* The code opens an image (in this case *lena.jpg*) and display it under the effects of the 4 filters explained.
|
||||
|
||||
* Here is a snapshot of the image smoothed using *medianBlur*:
|
||||
|
||||
.. image:: images/Smoothing_Tutorial_Result_Median_Filter.jpg
|
||||
:alt: Smoothing with a median filter
|
||||
:align: center
|
@@ -1,305 +0,0 @@
|
||||
.. _back_projection:
|
||||
|
||||
Back Projection
|
||||
****************
|
||||
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
In this tutorial you will learn:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* What is Back Projection and why it is useful
|
||||
|
||||
* How to use the OpenCV function :calc_back_project:`calcBackProject <>` to calculate Back Projection
|
||||
|
||||
* How to mix different channels of an image by using the OpenCV function :mix_channels:`mixChannels <>`
|
||||
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
What is Back Projection?
|
||||
---------------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Back Projection is a way of recording how well the pixels of a given image fit the distribution of pixels in a histogram model.
|
||||
|
||||
* To make it simpler: For Back Projection, you calculate the histogram model of a feature and then use it to find this feature in an image.
|
||||
|
||||
* Application example: If you have a histogram of flesh color (say, a Hue-Saturation histogram ), then you can use it to find flesh color areas in an image:
|
||||
|
||||
|
||||
How does it work?
|
||||
------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* We explain this by using the skin example:
|
||||
|
||||
* Let's say you have gotten a skin histogram (Hue-Saturation) based on the image below. The histogram besides is going to be our *model histogram* (which we know represents a sample of skin tonality). You applied some mask to capture only the histogram of the skin area:
|
||||
|
||||
====== ======
|
||||
|T0| |T1|
|
||||
====== ======
|
||||
|
||||
.. |T0| image:: images/Back_Projection_Theory0.jpg
|
||||
:align: middle
|
||||
|
||||
.. |T1| image:: images/Back_Projection_Theory1.jpg
|
||||
:align: middle
|
||||
|
||||
|
||||
* Now, let's imagine that you get another hand image (Test Image) like the one below: (with its respective histogram):
|
||||
|
||||
====== ======
|
||||
|T2| |T3|
|
||||
====== ======
|
||||
|
||||
.. |T2| image:: images/Back_Projection_Theory2.jpg
|
||||
:align: middle
|
||||
|
||||
.. |T3| image:: images/Back_Projection_Theory3.jpg
|
||||
:align: middle
|
||||
|
||||
|
||||
* What we want to do is to use our *model histogram* (that we know represents a skin tonality) to detect skin areas in our Test Image. Here are the steps
|
||||
|
||||
a. In each pixel of our Test Image (i.e. :math:`p(i,j)` ), collect the data and find the correspondent bin location for that pixel (i.e. :math:`( h_{i,j}, s_{i,j} )` ).
|
||||
|
||||
b. Lookup the *model histogram* in the correspondent bin - :math:`( h_{i,j}, s_{i,j} )` - and read the bin value.
|
||||
|
||||
c. Store this bin value in a new image (*BackProjection*). Also, you may consider to normalize the *model histogram* first, so the output for the Test Image can be visible for you.
|
||||
|
||||
d. Applying the steps above, we get the following BackProjection image for our Test Image:
|
||||
|
||||
.. image:: images/Back_Projection_Theory4.jpg
|
||||
:align: center
|
||||
|
||||
e. In terms of statistics, the values stored in *BackProjection* represent the *probability* that a pixel in *Test Image* belongs to a skin area, based on the *model histogram* that we use. For instance in our Test image, the brighter areas are more probable to be skin area (as they actually are), whereas the darker areas have less probability (notice that these "dark" areas belong to surfaces that have some shadow on it, which in turns affects the detection).
|
||||
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* **What does this program do?**
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Loads an image
|
||||
* Convert the original to HSV format and separate only *Hue* channel to be used for the Histogram (using the OpenCV function :mix_channels:`mixChannels <>`)
|
||||
* Let the user to enter the number of bins to be used in the calculation of the histogram.
|
||||
* Calculate the histogram (and update it if the bins change) and the backprojection of the same image.
|
||||
* Display the backprojection and the histogram in windows.
|
||||
|
||||
* **Downloadable code**:
|
||||
|
||||
a. Click `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching/calcBackProject_Demo1.cpp>`_ for the basic version (explained in this tutorial).
|
||||
b. For stuff slightly fancier (using H-S histograms and floodFill to define a mask for the skin area) you can check the `improved demo <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching/calcBackProject_Demo2.cpp>`_
|
||||
c. ...or you can always check out the classical `camshiftdemo <https://github.com/Itseez/opencv/tree/master/samples/cpp/camshiftdemo.cpp>`_ in samples.
|
||||
|
||||
* **Code at glance:**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
/// Global Variables
|
||||
Mat src; Mat hsv; Mat hue;
|
||||
int bins = 25;
|
||||
|
||||
/// Function Headers
|
||||
void Hist_and_Backproj(int, void* );
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Read the image
|
||||
src = imread( argv[1], 1 );
|
||||
/// Transform it to HSV
|
||||
cvtColor( src, hsv, COLOR_BGR2HSV );
|
||||
|
||||
/// Use only the Hue value
|
||||
hue.create( hsv.size(), hsv.depth() );
|
||||
int ch[] = { 0, 0 };
|
||||
mixChannels( &hsv, 1, &hue, 1, ch, 1 );
|
||||
|
||||
/// Create Trackbar to enter the number of bins
|
||||
char* window_image = "Source image";
|
||||
namedWindow( window_image, WINDOW_AUTOSIZE );
|
||||
createTrackbar("* Hue bins: ", window_image, &bins, 180, Hist_and_Backproj );
|
||||
Hist_and_Backproj(0, 0);
|
||||
|
||||
/// Show the image
|
||||
imshow( window_image, src );
|
||||
|
||||
/// Wait until user exits the program
|
||||
waitKey(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @function Hist_and_Backproj
|
||||
* @brief Callback to Trackbar
|
||||
*/
|
||||
void Hist_and_Backproj(int, void* )
|
||||
{
|
||||
MatND hist;
|
||||
int histSize = MAX( bins, 2 );
|
||||
float hue_range[] = { 0, 180 };
|
||||
const float* ranges = { hue_range };
|
||||
|
||||
/// Get the Histogram and normalize it
|
||||
calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
|
||||
normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
/// Get Backprojection
|
||||
MatND backproj;
|
||||
calcBackProject( &hue, 1, 0, hist, backproj, &ranges, 1, true );
|
||||
|
||||
/// Draw the backproj
|
||||
imshow( "BackProj", backproj );
|
||||
|
||||
/// Draw the histogram
|
||||
int w = 400; int h = 400;
|
||||
int bin_w = cvRound( (double) w / histSize );
|
||||
Mat histImg = Mat::zeros( w, h, CV_8UC3 );
|
||||
|
||||
for( int i = 0; i < bins; i ++ )
|
||||
{ rectangle( histImg, Point( i*bin_w, h ), Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ), Scalar( 0, 0, 255 ), -1 ); }
|
||||
|
||||
imshow( "Histogram", histImg );
|
||||
}
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
#. Declare the matrices to store our images and initialize the number of bins to be used by our histogram:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src; Mat hsv; Mat hue;
|
||||
int bins = 25;
|
||||
|
||||
#. Read the input image and transform it to HSV format:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1], 1 );
|
||||
cvtColor( src, hsv, COLOR_BGR2HSV );
|
||||
|
||||
#. For this tutorial, we will use only the Hue value for our 1-D histogram (check out the fancier code in the links above if you want to use the more standard H-S histogram, which yields better results):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
hue.create( hsv.size(), hsv.depth() );
|
||||
int ch[] = { 0, 0 };
|
||||
mixChannels( &hsv, 1, &hue, 1, ch, 1 );
|
||||
|
||||
as you see, we use the function :mix_channels:`mixChannels` to get only the channel 0 (Hue) from the hsv image. It gets the following parameters:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ **&hsv:** The source array from which the channels will be copied
|
||||
+ **1:** The number of source arrays
|
||||
+ **&hue:** The destination array of the copied channels
|
||||
+ **1:** The number of destination arrays
|
||||
+ **ch[] = {0,0}:** The array of index pairs indicating how the channels are copied. In this case, the Hue(0) channel of &hsv is being copied to the 0 channel of &hue (1-channel)
|
||||
+ **1:** Number of index pairs
|
||||
|
||||
#. Create a Trackbar for the user to enter the bin values. Any change on the Trackbar means a call to the **Hist_and_Backproj** callback function.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
char* window_image = "Source image";
|
||||
namedWindow( window_image, WINDOW_AUTOSIZE );
|
||||
createTrackbar("* Hue bins: ", window_image, &bins, 180, Hist_and_Backproj );
|
||||
Hist_and_Backproj(0, 0);
|
||||
|
||||
#. Show the image and wait for the user to exit the program:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
imshow( window_image, src );
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
|
||||
#. **Hist_and_Backproj function:** Initialize the arguments needed for :calc_hist:`calcHist <>`. The number of bins comes from the Trackbar:
|
||||
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
void Hist_and_Backproj(int, void* )
|
||||
{
|
||||
MatND hist;
|
||||
int histSize = MAX( bins, 2 );
|
||||
float hue_range[] = { 0, 180 };
|
||||
const float* ranges = { hue_range };
|
||||
|
||||
#. Calculate the Histogram and normalize it to the range :math:`[0,255]`
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
|
||||
normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
#. Get the Backprojection of the same image by calling the function :calc_back_project:`calcBackProject <>`
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
MatND backproj;
|
||||
calcBackProject( &hue, 1, 0, hist, backproj, &ranges, 1, true );
|
||||
|
||||
all the arguments are known (the same as used to calculate the histogram), only we add the backproj matrix, which will store the backprojection of the source image (&hue)
|
||||
|
||||
#. Display backproj:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
imshow( "BackProj", backproj );
|
||||
|
||||
#. Draw the 1-D Hue histogram of the image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
int w = 400; int h = 400;
|
||||
int bin_w = cvRound( (double) w / histSize );
|
||||
Mat histImg = Mat::zeros( w, h, CV_8UC3 );
|
||||
|
||||
for( int i = 0; i < bins; i ++ )
|
||||
{ rectangle( histImg, Point( i*bin_w, h ), Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ), Scalar( 0, 0, 255 ), -1 ); }
|
||||
|
||||
imshow( "Histogram", histImg );
|
||||
|
||||
|
||||
|
||||
Results
|
||||
=======
|
||||
|
||||
#. Here are the output by using a sample image ( guess what? Another hand ). You can play with the bin values and you will observe how it affects the results:
|
||||
|
||||
====== ====== ======
|
||||
|R0| |R1| |R2|
|
||||
====== ====== ======
|
||||
|
||||
.. |R0| image:: images/Back_Projection1_Source_Image.jpg
|
||||
:align: middle
|
||||
|
||||
.. |R1| image:: images/Back_Projection1_Histogram.jpg
|
||||
:align: middle
|
||||
|
||||
.. |R2| image:: images/Back_Projection1_BackProj.jpg
|
||||
:align: middle
|
@@ -1,331 +0,0 @@
|
||||
.. _histogram_calculation:
|
||||
|
||||
Histogram Calculation
|
||||
*********************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :split:`split <>` to divide an image into its correspondent planes.
|
||||
|
||||
* To calculate histograms of arrays of images by using the OpenCV function :calc_hist:`calcHist <>`
|
||||
|
||||
* To normalize an array by using the function :normalize:`normalize <>`
|
||||
|
||||
|
||||
.. note::
|
||||
In the last tutorial (:ref:`histogram_equalization`) we talked about a particular kind of histogram called *Image histogram*. Now we will considerate it in its more general concept. Read on!
|
||||
|
||||
|
||||
What are histograms?
|
||||
--------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Histograms are collected *counts* of data organized into a set of predefined *bins*
|
||||
|
||||
* When we say *data* we are not restricting it to be intensity values (as we saw in the previous Tutorial). The data collected can be whatever feature you find useful to describe your image.
|
||||
|
||||
* Let's see an example. Imagine that a Matrix contains information of an image (i.e. intensity in the range :math:`0-255`):
|
||||
|
||||
|
||||
.. image:: images/Histogram_Calculation_Theory_Hist0.jpg
|
||||
:align: center
|
||||
|
||||
* What happens if we want to *count* this data in an organized way? Since we know that the *range* of information value for this case is 256 values, we can segment our range in subparts (called **bins**) like:
|
||||
|
||||
.. math::
|
||||
\begin{array}{l}
|
||||
[0, 255] = { [0, 15] \cup [16, 31] \cup ....\cup [240,255] } \\
|
||||
range = { bin_{1} \cup bin_{2} \cup ....\cup bin_{n = 15} }
|
||||
\end{array}
|
||||
|
||||
and we can keep count of the number of pixels that fall in the range of each :math:`bin_{i}`. Applying this to the example above we get the image below ( axis x represents the bins and axis y the number of pixels in each of them).
|
||||
|
||||
|
||||
.. image:: images/Histogram_Calculation_Theory_Hist1.jpg
|
||||
:align: center
|
||||
|
||||
* This was just a simple example of how an histogram works and why it is useful. An histogram can keep count not only of color intensities, but of whatever image features that we want to measure (i.e. gradients, directions, etc).
|
||||
|
||||
* Let's identify some parts of the histogram:
|
||||
|
||||
a. **dims**: The number of parameters you want to collect data of. In our example, **dims = 1** because we are only counting the intensity values of each pixel (in a greyscale image).
|
||||
b. **bins**: It is the number of **subdivisions** in each dim. In our example, **bins = 16**
|
||||
c. **range**: The limits for the values to be measured. In this case: **range = [0,255]**
|
||||
|
||||
* What if you want to count two features? In this case your resulting histogram would be a 3D plot (in which x and y would be :math:`bin_{x}` and :math:`bin_{y}` for each feature and z would be the number of counts for each combination of :math:`(bin_{x}, bin_{y})`. The same would apply for more features (of course it gets trickier).
|
||||
|
||||
|
||||
What OpenCV offers you
|
||||
-----------------------
|
||||
|
||||
For simple purposes, OpenCV implements the function :calc_hist:`calcHist <>`, which calculates the histogram of a set of arrays (usually images or image planes). It can operate with up to 32 dimensions. We will see it in the code below!
|
||||
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* **What does this program do?**
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Loads an image
|
||||
* Splits the image into its R, G and B planes using the function :split:`split <>`
|
||||
* Calculate the Histogram of each 1-channel plane by calling the function :calc_hist:`calcHist <>`
|
||||
* Plot the three histograms in a window
|
||||
|
||||
* **Downloadable code**:
|
||||
Click `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching/calcHist_Demo.cpp>`_
|
||||
|
||||
* **Code at glance:**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
/*
|
||||
* @function main
|
||||
*/
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
Mat src, dst;
|
||||
|
||||
/// Load image
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
/// Separate the image in 3 places ( B, G and R )
|
||||
vector<Mat> bgr_planes;
|
||||
split( src, bgr_planes );
|
||||
|
||||
/// Establish the number of bins
|
||||
int histSize = 256;
|
||||
|
||||
/// Set the ranges ( for B,G,R) )
|
||||
float range[] = { 0, 256 } ;
|
||||
const float* histRange = { range };
|
||||
|
||||
bool uniform = true; bool accumulate = false;
|
||||
|
||||
Mat b_hist, g_hist, r_hist;
|
||||
|
||||
/// Compute the histograms:
|
||||
calcHist( &bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );
|
||||
calcHist( &bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate );
|
||||
calcHist( &bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate );
|
||||
|
||||
// Draw the histograms for B, G and R
|
||||
int hist_w = 512; int hist_h = 400;
|
||||
int bin_w = cvRound( (double) hist_w/histSize );
|
||||
|
||||
Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
|
||||
|
||||
/// Normalize the result to [ 0, histImage.rows ]
|
||||
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
|
||||
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
|
||||
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
/// Draw for each channel
|
||||
for( int i = 1; i < histSize; i++ )
|
||||
{
|
||||
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ) ,
|
||||
Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),
|
||||
Scalar( 255, 0, 0), 2, 8, 0 );
|
||||
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ) ,
|
||||
Point( bin_w*(i), hist_h - cvRound(g_hist.at<float>(i)) ),
|
||||
Scalar( 0, 255, 0), 2, 8, 0 );
|
||||
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ) ,
|
||||
Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),
|
||||
Scalar( 0, 0, 255), 2, 8, 0 );
|
||||
}
|
||||
|
||||
/// Display
|
||||
namedWindow("calcHist Demo", WINDOW_AUTOSIZE );
|
||||
imshow("calcHist Demo", histImage );
|
||||
|
||||
waitKey(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
#. Create the necessary matrices:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src, dst;
|
||||
|
||||
#. Load the source image
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
#. Separate the source image in its three R,G and B planes. For this we use the OpenCV function :split:`split <>`:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
vector<Mat> bgr_planes;
|
||||
split( src, bgr_planes );
|
||||
|
||||
our input is the image to be divided (this case with three channels) and the output is a vector of Mat )
|
||||
|
||||
#. Now we are ready to start configuring the **histograms** for each plane. Since we are working with the B, G and R planes, we know that our values will range in the interval :math:`[0,255]`
|
||||
|
||||
a. Establish number of bins (5, 10...):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
int histSize = 256; //from 0 to 255
|
||||
|
||||
b. Set the range of values (as we said, between 0 and 255 )
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/// Set the ranges ( for B,G,R) )
|
||||
float range[] = { 0, 256 } ; //the upper boundary is exclusive
|
||||
const float* histRange = { range };
|
||||
|
||||
c. We want our bins to have the same size (uniform) and to clear the histograms in the beginning, so:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
bool uniform = true; bool accumulate = false;
|
||||
|
||||
d. Finally, we create the Mat objects to save our histograms. Creating 3 (one for each plane):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat b_hist, g_hist, r_hist;
|
||||
|
||||
e. We proceed to calculate the histograms by using the OpenCV function :calc_hist:`calcHist <>`:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/// Compute the histograms:
|
||||
calcHist( &bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );
|
||||
calcHist( &bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate );
|
||||
calcHist( &bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate );
|
||||
|
||||
where the arguments are:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ **&bgr_planes[0]:** The source array(s)
|
||||
+ **1**: The number of source arrays (in this case we are using 1. We can enter here also a list of arrays )
|
||||
+ **0**: The channel (*dim*) to be measured. In this case it is just the intensity (each array is single-channel) so we just write 0.
|
||||
+ **Mat()**: A mask to be used on the source array ( zeros indicating pixels to be ignored ). If not defined it is not used
|
||||
+ **b_hist**: The Mat object where the histogram will be stored
|
||||
+ **1**: The histogram dimensionality.
|
||||
+ **histSize:** The number of bins per each used dimension
|
||||
+ **histRange:** The range of values to be measured per each dimension
|
||||
+ **uniform** and **accumulate**: The bin sizes are the same and the histogram is cleared at the beginning.
|
||||
|
||||
|
||||
#. Create an image to display the histograms:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// Draw the histograms for R, G and B
|
||||
int hist_w = 512; int hist_h = 400;
|
||||
int bin_w = cvRound( (double) hist_w/histSize );
|
||||
|
||||
Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
|
||||
|
||||
#. Notice that before drawing, we first :normalize:`normalize <>` the histogram so its values fall in the range indicated by the parameters entered:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/// Normalize the result to [ 0, histImage.rows ]
|
||||
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
|
||||
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
|
||||
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
this function receives these arguments:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ **b_hist:** Input array
|
||||
+ **b_hist:** Output normalized array (can be the same)
|
||||
+ **0** and**histImage.rows**: For this example, they are the lower and upper limits to normalize the values of **r_hist**
|
||||
+ **NORM_MINMAX:** Argument that indicates the type of normalization (as described above, it adjusts the values between the two limits set before)
|
||||
+ **-1:** Implies that the output normalized array will be the same type as the input
|
||||
+ **Mat():** Optional mask
|
||||
|
||||
#. Finally, observe that to access the bin (in this case in this 1D-Histogram):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/// Draw for each channel
|
||||
for( int i = 1; i < histSize; i++ )
|
||||
{
|
||||
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ) ,
|
||||
Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),
|
||||
Scalar( 255, 0, 0), 2, 8, 0 );
|
||||
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ) ,
|
||||
Point( bin_w*(i), hist_h - cvRound(g_hist.at<float>(i)) ),
|
||||
Scalar( 0, 255, 0), 2, 8, 0 );
|
||||
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ) ,
|
||||
Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),
|
||||
Scalar( 0, 0, 255), 2, 8, 0 );
|
||||
}
|
||||
|
||||
|
||||
we use the expression:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
b_hist.at<float>(i)
|
||||
|
||||
|
||||
where :math:`i` indicates the dimension. If it were a 2D-histogram we would use something like:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
b_hist.at<float>( i, j )
|
||||
|
||||
|
||||
#. Finally we display our histograms and wait for the user to exit:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow("calcHist Demo", WINDOW_AUTOSIZE );
|
||||
imshow("calcHist Demo", histImage );
|
||||
|
||||
waitKey(0);
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
Result
|
||||
======
|
||||
|
||||
#. Using as input argument an image like the shown below:
|
||||
|
||||
.. image:: images/Histogram_Calculation_Original_Image.jpg
|
||||
:align: center
|
||||
|
||||
#. Produces the following histogram:
|
||||
|
||||
.. image:: images/Histogram_Calculation_Result.jpg
|
||||
:align: center
|
@@ -1,221 +0,0 @@
|
||||
.. _histogram_comparison:
|
||||
|
||||
Histogram Comparison
|
||||
********************
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the function :compare_hist:`compareHist <>` to get a numerical parameter that express how well two histograms match with each other.
|
||||
* Use different metrics to compare histograms
|
||||
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* To compare two histograms ( :math:`H_{1}` and :math:`H_{2}` ), first we have to choose a *metric* (:math:`d(H_{1}, H_{2})`) to express how well both histograms match.
|
||||
|
||||
* OpenCV implements the function :compare_hist:`compareHist <>` to perform a comparison. It also offers 4 different metrics to compute the matching:
|
||||
|
||||
|
||||
a. **Correlation ( CV\_COMP\_CORREL )**
|
||||
|
||||
.. math::
|
||||
|
||||
d(H_1,H_2) = \frac{\sum_I (H_1(I) - \bar{H_1}) (H_2(I) - \bar{H_2})}{\sqrt{\sum_I(H_1(I) - \bar{H_1})^2 \sum_I(H_2(I) - \bar{H_2})^2}}
|
||||
|
||||
where
|
||||
|
||||
.. math::
|
||||
|
||||
\bar{H_k} = \frac{1}{N} \sum _J H_k(J)
|
||||
|
||||
|
||||
and :math:`N` is the total number of histogram bins.
|
||||
|
||||
|
||||
|
||||
b. **Chi-Square ( CV\_COMP\_CHISQR )**
|
||||
|
||||
.. math::
|
||||
|
||||
d(H_1,H_2) = \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)}
|
||||
|
||||
|
||||
c. **Intersection ( method=CV\_COMP\_INTERSECT )**
|
||||
|
||||
.. math::
|
||||
|
||||
d(H_1,H_2) = \sum _I \min (H_1(I), H_2(I))
|
||||
|
||||
|
||||
d. **Bhattacharyya distance ( CV\_COMP\_BHATTACHARYYA )**
|
||||
|
||||
.. math::
|
||||
|
||||
d(H_1,H_2) = \sqrt{1 - \frac{1}{\sqrt{\bar{H_1} \bar{H_2} N^2}} \sum_I \sqrt{H_1(I) \cdot H_2(I)}}
|
||||
|
||||
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* **What does this program do?**
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Loads a *base image* and 2 *test images* to be compared with it.
|
||||
* Generate 1 image that is the lower half of the *base image*
|
||||
* Convert the images to HSV format
|
||||
* Calculate the H-S histogram for all the images and normalize them in order to compare them.
|
||||
* Compare the histogram of the *base image* with respect to the 2 test histograms, the histogram of the lower half base image and with the same base image histogram.
|
||||
* Display the numerical matching parameters obtained.
|
||||
|
||||
* **Downloadable code**:
|
||||
Click `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp>`_
|
||||
|
||||
* **Code at glance:**
|
||||
|
||||
.. literalinclude:: ../../../../../samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp
|
||||
:language: cpp
|
||||
:tab-width: 4
|
||||
|
||||
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
#. Declare variables such as the matrices to store the base image and the two other images to compare ( RGB and HSV )
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src_base, hsv_base;
|
||||
Mat src_test1, hsv_test1;
|
||||
Mat src_test2, hsv_test2;
|
||||
Mat hsv_half_down;
|
||||
|
||||
#. Load the base image (src\_base) and the other two test images:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
if( argc < 4 )
|
||||
{ printf("** Error. Usage: ./compareHist_Demo <image_settings0> <image_setting1> <image_settings2>\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
src_base = imread( argv[1], 1 );
|
||||
src_test1 = imread( argv[2], 1 );
|
||||
src_test2 = imread( argv[3], 1 );
|
||||
|
||||
#. Convert them to HSV format:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
cvtColor( src_base, hsv_base, COLOR_BGR2HSV );
|
||||
cvtColor( src_test1, hsv_test1, COLOR_BGR2HSV );
|
||||
cvtColor( src_test2, hsv_test2, COLOR_BGR2HSV );
|
||||
|
||||
#. Also, create an image of half the base image (in HSV format):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
hsv_half_down = hsv_base( Range( hsv_base.rows/2, hsv_base.rows - 1 ), Range( 0, hsv_base.cols - 1 ) );
|
||||
|
||||
#. Initialize the arguments to calculate the histograms (bins, ranges and channels H and S ).
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
int h_bins = 50; int s_bins = 60;
|
||||
int histSize[] = { h_bins, s_bins };
|
||||
|
||||
float h_ranges[] = { 0, 180 };
|
||||
float s_ranges[] = { 0, 256 };
|
||||
|
||||
const float* ranges[] = { h_ranges, s_ranges };
|
||||
|
||||
int channels[] = { 0, 1 };
|
||||
|
||||
#. Create the MatND objects to store the histograms:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
MatND hist_base;
|
||||
MatND hist_half_down;
|
||||
MatND hist_test1;
|
||||
MatND hist_test2;
|
||||
|
||||
#. Calculate the Histograms for the base image, the 2 test images and the half-down base image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );
|
||||
normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
calcHist( &hsv_half_down, 1, channels, Mat(), hist_half_down, 2, histSize, ranges, true, false );
|
||||
normalize( hist_half_down, hist_half_down, 0, 1, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false );
|
||||
normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false );
|
||||
normalize( hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
|
||||
#. Apply sequentially the 4 comparison methods between the histogram of the base image (hist\_base) and the other histograms:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
for( int i = 0; i < 4; i++ )
|
||||
{ int compare_method = i;
|
||||
double base_base = compareHist( hist_base, hist_base, compare_method );
|
||||
double base_half = compareHist( hist_base, hist_half_down, compare_method );
|
||||
double base_test1 = compareHist( hist_base, hist_test1, compare_method );
|
||||
double base_test2 = compareHist( hist_base, hist_test2, compare_method );
|
||||
|
||||
printf( " Method [%d] Perfect, Base-Half, Base-Test(1), Base-Test(2) : %f, %f, %f, %f \n", i, base_base, base_half , base_test1, base_test2 );
|
||||
}
|
||||
|
||||
|
||||
Results
|
||||
========
|
||||
|
||||
#. We use as input the following images:
|
||||
|
||||
============ ============ ============
|
||||
|Base_0| |Test_1| |Test_2|
|
||||
============ ============ ============
|
||||
|
||||
.. |Base_0| image:: images/Histogram_Comparison_Source_0.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Test_1| image:: images/Histogram_Comparison_Source_1.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Test_2| image:: images/Histogram_Comparison_Source_2.jpg
|
||||
:align: middle
|
||||
|
||||
where the first one is the base (to be compared to the others), the other 2 are the test images. We will also compare the first image with respect to itself and with respect of half the base image.
|
||||
|
||||
#. We should expect a perfect match when we compare the base image histogram with itself. Also, compared with the histogram of half the base image, it should present a high match since both are from the same source. For the other two test images, we can observe that they have very different lighting conditions, so the matching should not be very good:
|
||||
|
||||
#. Here the numeric results:
|
||||
|
||||
=============== =============== =============== =============== ===============
|
||||
*Method* Base - Base Base - Half Base - Test 1 Base - Test 2
|
||||
=============== =============== =============== =============== ===============
|
||||
*Correlation* 1.000000 0.930766 0.182073 0.120447
|
||||
*Chi-square* 0.000000 4.940466 21.184536 49.273437
|
||||
*Intersection* 24.391548 14.959809 3.889029 5.775088
|
||||
*Bhattacharyya* 0.000000 0.222609 0.646576 0.801869
|
||||
=============== =============== =============== =============== ===============
|
||||
|
||||
|
||||
For the *Correlation* and *Intersection* methods, the higher the metric, the more accurate the match. As we can see, the match *base-base* is the highest of all as expected. Also we can observe that the match *base-half* is the second best match (as we predicted). For the other two metrics, the less the result, the better the match. We can observe that the matches between the test 1 and test 2 with respect to the base are worse, which again, was expected.
|
@@ -1,217 +0,0 @@
|
||||
.. _histogram_equalization:
|
||||
|
||||
Histogram Equalization
|
||||
**********************
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
In this tutorial you will learn:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* What an image histogram is and why it is useful
|
||||
|
||||
* To equalize histograms of images by using the OpenCV function:equalize_hist:`equalizeHist <>`
|
||||
|
||||
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
What is an Image Histogram?
|
||||
---------------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* It is a graphical representation of the intensity distribution of an image.
|
||||
|
||||
* It quantifies the number of pixels for each intensity value considered.
|
||||
|
||||
.. image:: images/Histogram_Equalization_Theory_0.jpg
|
||||
:align: center
|
||||
|
||||
|
||||
What is Histogram Equalization?
|
||||
-------------------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* It is a method that improves the contrast in an image, in order to stretch out the intensity range.
|
||||
|
||||
* To make it clearer, from the image above, you can see that the pixels seem clustered around the middle of the available range of intensities. What Histogram Equalization does is to *stretch out* this range. Take a look at the figure below: The green circles indicate the *underpopulated* intensities. After applying the equalization, we get an histogram like the figure in the center. The resulting image is shown in the picture at right.
|
||||
|
||||
.. image:: images/Histogram_Equalization_Theory_1.jpg
|
||||
:align: center
|
||||
|
||||
How does it work?
|
||||
-----------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Equalization implies *mapping* one distribution (the given histogram) to another distribution (a wider and more uniform distribution of intensity values) so the intensity values are spreaded over the whole range.
|
||||
|
||||
* To accomplish the equalization effect, the remapping should be the *cumulative distribution function (cdf)* (more details, refer to *Learning OpenCV*). For the histogram :math:`H(i)`, its *cumulative distribution* :math:`H^{'}(i)` is:
|
||||
|
||||
.. math::
|
||||
|
||||
H^{'}(i) = \sum_{0 \le j < i} H(j)
|
||||
|
||||
To use this as a remapping function, we have to normalize :math:`H^{'}(i)` such that the maximum value is 255 ( or the maximum value for the intensity of the image ). From the example above, the cumulative function is:
|
||||
|
||||
.. image:: images/Histogram_Equalization_Theory_2.jpg
|
||||
:align: center
|
||||
|
||||
* Finally, we use a simple remapping procedure to obtain the intensity values of the equalized image:
|
||||
|
||||
.. math::
|
||||
|
||||
equalized( x, y ) = H^{'}( src(x,y) )
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* **What does this program do?**
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Loads an image
|
||||
* Convert the original image to grayscale
|
||||
* Equalize the Histogram by using the OpenCV function :equalize_hist:`EqualizeHist <>`
|
||||
* Display the source and equalized images in a window.
|
||||
|
||||
* **Downloadable code**:
|
||||
Click `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching/EqualizeHist_Demo.cpp>`_
|
||||
|
||||
* **Code at glance:**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
Mat src, dst;
|
||||
|
||||
char* source_window = "Source image";
|
||||
char* equalized_window = "Equalized Image";
|
||||
|
||||
/// Load image
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
if( !src.data )
|
||||
{ cout<<"Usage: ./Histogram_Demo <path_to_image>"<<endl;
|
||||
return -1;}
|
||||
|
||||
/// Convert to grayscale
|
||||
cvtColor( src, src, COLOR_BGR2GRAY );
|
||||
|
||||
/// Apply Histogram Equalization
|
||||
equalizeHist( src, dst );
|
||||
|
||||
/// Display results
|
||||
namedWindow( source_window, WINDOW_AUTOSIZE );
|
||||
namedWindow( equalized_window, WINDOW_AUTOSIZE );
|
||||
|
||||
imshow( source_window, src );
|
||||
imshow( equalized_window, dst );
|
||||
|
||||
/// Wait until user exits the program
|
||||
waitKey(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
#. Declare the source and destination images as well as the windows names:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src, dst;
|
||||
|
||||
char* source_window = "Source image";
|
||||
char* equalized_window = "Equalized Image";
|
||||
|
||||
#. Load the source image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
if( !src.data )
|
||||
{ cout<<"Usage: ./Histogram_Demo <path_to_image>"<<endl;
|
||||
return -1;}
|
||||
|
||||
#. Convert it to grayscale:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
cvtColor( src, src, COLOR_BGR2GRAY );
|
||||
|
||||
#. Apply histogram equalization with the function :equalize_hist:`equalizeHist <>` :
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
equalizeHist( src, dst );
|
||||
|
||||
As it can be easily seen, the only arguments are the original image and the output (equalized) image.
|
||||
|
||||
#. Display both images (original and equalized) :
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow( source_window, WINDOW_AUTOSIZE );
|
||||
namedWindow( equalized_window, WINDOW_AUTOSIZE );
|
||||
|
||||
imshow( source_window, src );
|
||||
imshow( equalized_window, dst );
|
||||
|
||||
#. Wait until user exists the program
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
|
||||
|
||||
Results
|
||||
=======
|
||||
|
||||
#. To appreciate better the results of equalization, let's introduce an image with not much contrast, such as:
|
||||
|
||||
.. image:: images/Histogram_Equalization_Original_Image.jpg
|
||||
:align: center
|
||||
|
||||
which, by the way, has this histogram:
|
||||
|
||||
.. image:: images/Histogram_Equalization_Original_Histogram.jpg
|
||||
:align: center
|
||||
|
||||
notice that the pixels are clustered around the center of the histogram.
|
||||
|
||||
#. After applying the equalization with our program, we get this result:
|
||||
|
||||
.. image:: images/Histogram_Equalization_Equalized_Image.jpg
|
||||
:align: center
|
||||
|
||||
this image has certainly more contrast. Check out its new histogram like this:
|
||||
|
||||
.. image:: images/Histogram_Equalization_Equalized_Histogram.jpg
|
||||
:align: center
|
||||
|
||||
Notice how the number of pixels is more distributed through the intensity range.
|
||||
|
||||
|
||||
.. note::
|
||||
Are you wondering how did we draw the Histogram figures shown above? Check out the following tutorial!
|
@@ -1,371 +0,0 @@
|
||||
.. _template_matching:
|
||||
|
||||
Template Matching
|
||||
*****************
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :match_template:`matchTemplate <>` to search for matches between an image patch and an input image
|
||||
* Use the OpenCV function :min_max_loc:`minMaxLoc <>` to find the maximum and minimum values (as well as their positions) in a given array.
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
What is template matching?
|
||||
--------------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
Template matching is a technique for finding areas of an image that match (are similar) to a template image (patch).
|
||||
|
||||
|
||||
How does it work?
|
||||
------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* We need two primary components:
|
||||
|
||||
a. **Source image (I):** The image in which we expect to find a match to the template image
|
||||
b. **Template image (T):** The patch image which will be compared to the template image
|
||||
|
||||
our goal is to detect the highest matching area:
|
||||
|
||||
.. image:: images/Template_Matching_Template_Theory_Summary.jpg
|
||||
:align: center
|
||||
|
||||
* To identify the matching area, we have to *compare* the template image against the source image by sliding it:
|
||||
|
||||
.. image:: images/Template_Matching_Template_Theory_Sliding.jpg
|
||||
:align: center
|
||||
|
||||
* By **sliding**, we mean moving the patch one pixel at a time (left to right, up to down). At each location, a metric is calculated so it represents how "good" or "bad" the match at that location is (or how similar the patch is to that particular area of the source image).
|
||||
|
||||
* For each location of **T** over **I**, you *store* the metric in the *result matrix* **(R)**. Each location :math:`(x,y)` in **R** contains the match metric:
|
||||
|
||||
.. image:: images/Template_Matching_Template_Theory_Result.jpg
|
||||
:align: center
|
||||
|
||||
the image above is the result **R** of sliding the patch with a metric **TM_CCORR_NORMED**. The brightest locations indicate the highest matches. As you can see, the location marked by the red circle is probably the one with the highest value, so that location (the rectangle formed by that point as a corner and width and height equal to the patch image) is considered the match.
|
||||
|
||||
* In practice, we use the function :min_max_loc:`minMaxLoc <>` to locate the highest value (or lower, depending of the type of matching method) in the *R* matrix.
|
||||
|
||||
Which are the matching methods available in OpenCV?
|
||||
----------------------------------------------------
|
||||
|
||||
Good question. OpenCV implements Template matching in the function :match_template:`matchTemplate <>`. The available methods are 6:
|
||||
|
||||
a. **method=CV\_TM\_SQDIFF**
|
||||
|
||||
.. math::
|
||||
|
||||
R(x,y)= \sum _{x',y'} (T(x',y')-I(x+x',y+y'))^2
|
||||
|
||||
|
||||
b. **method=CV\_TM\_SQDIFF\_NORMED**
|
||||
|
||||
.. math::
|
||||
|
||||
R(x,y)= \frac{\sum_{x',y'} (T(x',y')-I(x+x',y+y'))^2}{\sqrt{\sum_{x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}}
|
||||
|
||||
|
||||
c. **method=CV\_TM\_CCORR**
|
||||
|
||||
.. math::
|
||||
|
||||
R(x,y)= \sum _{x',y'} (T(x',y') \cdot I(x+x',y+y'))
|
||||
|
||||
|
||||
d. **method=CV\_TM\_CCORR\_NORMED**
|
||||
|
||||
.. math::
|
||||
|
||||
R(x,y)= \frac{\sum_{x',y'} (T(x',y') \cdot I(x+x',y+y'))}{\sqrt{\sum_{x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}}
|
||||
|
||||
|
||||
e. **method=CV\_TM\_CCOEFF**
|
||||
|
||||
.. math::
|
||||
|
||||
R(x,y)= \sum _{x',y'} (T'(x',y') \cdot I(x+x',y+y'))
|
||||
|
||||
where
|
||||
|
||||
.. math::
|
||||
|
||||
\begin{array}{l} T'(x',y')=T(x',y') - 1/(w \cdot h) \cdot \sum _{x'',y''} T(x'',y'') \\ I'(x+x',y+y')=I(x+x',y+y') - 1/(w \cdot h) \cdot \sum _{x'',y''} I(x+x'',y+y'') \end{array}
|
||||
|
||||
|
||||
f. **method=CV\_TM\_CCOEFF\_NORMED**
|
||||
|
||||
.. math::
|
||||
|
||||
R(x,y)= \frac{ \sum_{x',y'} (T'(x',y') \cdot I'(x+x',y+y')) }{ \sqrt{\sum_{x',y'}T'(x',y')^2 \cdot \sum_{x',y'} I'(x+x',y+y')^2} }
|
||||
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* **What does this program do?**
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Loads an input image and a image patch (*template*)
|
||||
* Perform a template matching procedure by using the OpenCV function :match_template:`matchTemplate <>` with any of the 6 matching methods described before. The user can choose the method by entering its selection in the Trackbar.
|
||||
* Normalize the output of the matching procedure
|
||||
* Localize the location with higher matching probability
|
||||
* Draw a rectangle around the area corresponding to the highest match
|
||||
|
||||
* **Downloadable code**:
|
||||
Click `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching/MatchTemplate_Demo.cpp>`_
|
||||
|
||||
* **Code at glance:**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
/// Global Variables
|
||||
Mat img; Mat templ; Mat result;
|
||||
char* image_window = "Source Image";
|
||||
char* result_window = "Result window";
|
||||
|
||||
int match_method;
|
||||
int max_Trackbar = 5;
|
||||
|
||||
/// Function Headers
|
||||
void MatchingMethod( int, void* );
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load image and template
|
||||
img = imread( argv[1], 1 );
|
||||
templ = imread( argv[2], 1 );
|
||||
|
||||
/// Create windows
|
||||
namedWindow( image_window, WINDOW_AUTOSIZE );
|
||||
namedWindow( result_window, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Create Trackbar
|
||||
char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED";
|
||||
createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod );
|
||||
|
||||
MatchingMethod( 0, 0 );
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @function MatchingMethod
|
||||
* @brief Trackbar callback
|
||||
*/
|
||||
void MatchingMethod( int, void* )
|
||||
{
|
||||
/// Source image to display
|
||||
Mat img_display;
|
||||
img.copyTo( img_display );
|
||||
|
||||
/// Create the result matrix
|
||||
int result_cols = img.cols - templ.cols + 1;
|
||||
int result_rows = img.rows - templ.rows + 1;
|
||||
|
||||
result.create( result_cols, result_rows, CV_32FC1 );
|
||||
|
||||
/// Do the Matching and Normalize
|
||||
matchTemplate( img, templ, result, match_method );
|
||||
normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
/// Localizing the best match with minMaxLoc
|
||||
double minVal; double maxVal; Point minLoc; Point maxLoc;
|
||||
Point matchLoc;
|
||||
|
||||
minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );
|
||||
|
||||
/// For SQDIFF and SQDIFF_NORMED, the best matches are lower values. For all the other methods, the higher the better
|
||||
if( match_method == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED )
|
||||
{ matchLoc = minLoc; }
|
||||
else
|
||||
{ matchLoc = maxLoc; }
|
||||
|
||||
/// Show me what you got
|
||||
rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
|
||||
rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
|
||||
|
||||
imshow( image_window, img_display );
|
||||
imshow( result_window, result );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
#. Declare some global variables, such as the image, template and result matrices, as well as the match method and the window names:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat img; Mat templ; Mat result;
|
||||
char* image_window = "Source Image";
|
||||
char* result_window = "Result window";
|
||||
|
||||
int match_method;
|
||||
int max_Trackbar = 5;
|
||||
|
||||
|
||||
#. Load the source image and template:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
img = imread( argv[1], 1 );
|
||||
templ = imread( argv[2], 1 );
|
||||
|
||||
#. Create the windows to show the results:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow( image_window, WINDOW_AUTOSIZE );
|
||||
namedWindow( result_window, WINDOW_AUTOSIZE );
|
||||
|
||||
#. Create the Trackbar to enter the kind of matching method to be used. When a change is detected the callback function **MatchingMethod** is called.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED";
|
||||
createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod );
|
||||
|
||||
#. Wait until user exits the program.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
|
||||
#. Let's check out the callback function. First, it makes a copy of the source image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat img_display;
|
||||
img.copyTo( img_display );
|
||||
|
||||
|
||||
#. Next, it creates the result matrix that will store the matching results for each template location. Observe in detail the size of the result matrix (which matches all possible locations for it)
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
int result_cols = img.cols - templ.cols + 1;
|
||||
int result_rows = img.rows - templ.rows + 1;
|
||||
|
||||
result.create( result_cols, result_rows, CV_32FC1 );
|
||||
|
||||
#. Perform the template matching operation:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
matchTemplate( img, templ, result, match_method );
|
||||
|
||||
the arguments are naturally the input image **I**, the template **T**, the result **R** and the match_method (given by the Trackbar)
|
||||
|
||||
#. We normalize the results:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
#. We localize the minimum and maximum values in the result matrix **R** by using :min_max_loc:`minMaxLoc <>`.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
double minVal; double maxVal; Point minLoc; Point maxLoc;
|
||||
Point matchLoc;
|
||||
|
||||
minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );
|
||||
|
||||
the function calls as arguments:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ **result:** The source array
|
||||
+ **&minVal** and **&maxVal:** Variables to save the minimum and maximum values in **result**
|
||||
+ **&minLoc** and **&maxLoc:** The Point locations of the minimum and maximum values in the array.
|
||||
+ **Mat():** Optional mask
|
||||
|
||||
|
||||
#. For the first two methods ( TM\_SQDIFF and MT\_SQDIFF\_NORMED ) the best match are the lowest values. For all the others, higher values represent better matches. So, we save the corresponding value in the **matchLoc** variable:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
if( match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED )
|
||||
{ matchLoc = minLoc; }
|
||||
else
|
||||
{ matchLoc = maxLoc; }
|
||||
|
||||
#. Display the source image and the result matrix. Draw a rectangle around the highest possible matching area:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
|
||||
rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
|
||||
|
||||
imshow( image_window, img_display );
|
||||
imshow( result_window, result );
|
||||
|
||||
|
||||
Results
|
||||
=======
|
||||
|
||||
#. Testing our program with an input image such as:
|
||||
|
||||
.. image:: images/Template_Matching_Original_Image.jpg
|
||||
:align: center
|
||||
|
||||
and a template image:
|
||||
|
||||
.. image:: images/Template_Matching_Template_Image.jpg
|
||||
:align: center
|
||||
|
||||
#. Generate the following result matrices (first row are the standard methods SQDIFF, CCORR and CCOEFF, second row are the same methods in its normalized version). In the first column, the darkest is the better match, for the other two columns, the brighter a location, the higher the match.
|
||||
|
||||
============ ============ ============
|
||||
|Result_0| |Result_2| |Result_4|
|
||||
============ ============ ============
|
||||
|Result_1| |Result_3| |Result_5|
|
||||
============ ============ ============
|
||||
|
||||
.. |Result_0| image:: images/Template_Matching_Correl_Result_0.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Result_1| image:: images/Template_Matching_Correl_Result_1.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Result_2| image:: images/Template_Matching_Correl_Result_2.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Result_3| image:: images/Template_Matching_Correl_Result_3.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Result_4| image:: images/Template_Matching_Correl_Result_4.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Result_5| image:: images/Template_Matching_Correl_Result_5.jpg
|
||||
:align: middle
|
||||
|
||||
#. The right match is shown below (black rectangle around the face of the guy at the right). Notice that CCORR and CCDEFF gave erroneous best matches, however their normalized version did it right, this may be due to the fact that we are only considering the "highest match" and not the other possible high matches.
|
||||
|
||||
.. image:: images/Template_Matching_Image_Result.jpg
|
||||
:align: center
|
@@ -1,284 +0,0 @@
|
||||
.. _canny_detector:
|
||||
|
||||
Canny Edge Detector
|
||||
********************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :canny:`Canny <>` to implement the Canny Edge Detector.
|
||||
|
||||
Theory
|
||||
=======
|
||||
|
||||
#. The *Canny Edge detector* was developed by John F. Canny in 1986. Also known to many as the *optimal detector*, Canny algorithm aims to satisfy three main criteria:
|
||||
|
||||
* **Low error rate:** Meaning a good detection of only existent edges.
|
||||
* **Good localization:** The distance between edge pixels detected and real edge pixels have to be minimized.
|
||||
* **Minimal response:** Only one detector response per edge.
|
||||
|
||||
Steps
|
||||
------
|
||||
|
||||
#. Filter out any noise. The Gaussian filter is used for this purpose. An example of a Gaussian kernel of :math:`size = 5` that might be used is shown below:
|
||||
|
||||
.. math::
|
||||
|
||||
K = \dfrac{1}{159}\begin{bmatrix}
|
||||
2 & 4 & 5 & 4 & 2 \\
|
||||
4 & 9 & 12 & 9 & 4 \\
|
||||
5 & 12 & 15 & 12 & 5 \\
|
||||
4 & 9 & 12 & 9 & 4 \\
|
||||
2 & 4 & 5 & 4 & 2
|
||||
\end{bmatrix}
|
||||
|
||||
|
||||
#. Find the intensity gradient of the image. For this, we follow a procedure analogous to Sobel:
|
||||
|
||||
a. Apply a pair of convolution masks (in :math:`x` and :math:`y` directions:
|
||||
|
||||
.. math::
|
||||
|
||||
G_{x} = \begin{bmatrix}
|
||||
-1 & 0 & +1 \\
|
||||
-2 & 0 & +2 \\
|
||||
-1 & 0 & +1
|
||||
\end{bmatrix}
|
||||
|
||||
G_{y} = \begin{bmatrix}
|
||||
-1 & -2 & -1 \\
|
||||
0 & 0 & 0 \\
|
||||
+1 & +2 & +1
|
||||
\end{bmatrix}
|
||||
|
||||
b. Find the gradient strength and direction with:
|
||||
|
||||
.. math::
|
||||
\begin{array}{l}
|
||||
G = \sqrt{ G_{x}^{2} + G_{y}^{2} } \\
|
||||
\theta = \arctan(\dfrac{ G_{y} }{ G_{x} })
|
||||
\end{array}
|
||||
|
||||
The direction is rounded to one of four possible angles (namely 0, 45, 90 or 135)
|
||||
|
||||
#. *Non-maximum* suppression is applied. This removes pixels that are not considered to be part of an edge. Hence, only thin lines (candidate edges) will remain.
|
||||
|
||||
#. *Hysteresis*: The final step. Canny does use two thresholds (upper and lower):
|
||||
|
||||
a. If a pixel gradient is higher than the *upper* threshold, the pixel is accepted as an edge
|
||||
b. If a pixel gradient value is below the *lower* threshold, then it is rejected.
|
||||
c. If the pixel gradient is between the two thresholds, then it will be accepted only if it is connected to a pixel that is above the *upper* threshold.
|
||||
|
||||
Canny recommended a *upper*:*lower* ratio between 2:1 and 3:1.
|
||||
|
||||
#. For more details, you can always consult your favorite Computer Vision book.
|
||||
|
||||
Code
|
||||
=====
|
||||
|
||||
#. **What does this program do?**
|
||||
|
||||
* Asks the user to enter a numerical value to set the lower threshold for our *Canny Edge Detector* (by means of a Trackbar)
|
||||
* Applies the *Canny Detector* and generates a **mask** (bright lines representing the edges on a black background).
|
||||
* Applies the mask obtained on the original image and display it in a window.
|
||||
|
||||
#. The tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/CannyDetector_Demo.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/// Global variables
|
||||
|
||||
Mat src, src_gray;
|
||||
Mat dst, detected_edges;
|
||||
|
||||
int edgeThresh = 1;
|
||||
int lowThreshold;
|
||||
int const max_lowThreshold = 100;
|
||||
int ratio = 3;
|
||||
int kernel_size = 3;
|
||||
char* window_name = "Edge Map";
|
||||
|
||||
/*
|
||||
* @function CannyThreshold
|
||||
* @brief Trackbar callback - Canny thresholds input with a ratio 1:3
|
||||
*/
|
||||
void CannyThreshold(int, void*)
|
||||
{
|
||||
/// Reduce noise with a kernel 3x3
|
||||
blur( src_gray, detected_edges, Size(3,3) );
|
||||
|
||||
/// Canny detector
|
||||
Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
|
||||
|
||||
/// Using Canny's output as a mask, we display our result
|
||||
dst = Scalar::all(0);
|
||||
|
||||
src.copyTo( dst, detected_edges);
|
||||
imshow( window_name, dst );
|
||||
}
|
||||
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load an image
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
/// Create a matrix of the same type and size as src (for dst)
|
||||
dst.create( src.size(), src.type() );
|
||||
|
||||
/// Convert the image to grayscale
|
||||
cvtColor( src, src_gray, COLOR_BGR2GRAY );
|
||||
|
||||
/// Create a window
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Create a Trackbar for user to enter threshold
|
||||
createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
|
||||
|
||||
/// Show the image
|
||||
CannyThreshold(0, 0);
|
||||
|
||||
/// Wait until user exit program by pressing a key
|
||||
waitKey(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Explanation
|
||||
============
|
||||
|
||||
#. Create some needed variables:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src, src_gray;
|
||||
Mat dst, detected_edges;
|
||||
|
||||
int edgeThresh = 1;
|
||||
int lowThreshold;
|
||||
int const max_lowThreshold = 100;
|
||||
int ratio = 3;
|
||||
int kernel_size = 3;
|
||||
char* window_name = "Edge Map";
|
||||
|
||||
Note the following:
|
||||
|
||||
a. We establish a ratio of lower:upper threshold of 3:1 (with the variable *ratio*)
|
||||
b. We set the kernel size of :math:`3` (for the Sobel operations to be performed internally by the Canny function)
|
||||
c. We set a maximum value for the lower Threshold of :math:`100`.
|
||||
|
||||
|
||||
#. Loads the source image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/// Load an image
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
#. Create a matrix of the same type and size of *src* (to be *dst*)
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
dst.create( src.size(), src.type() );
|
||||
|
||||
#. Convert the image to grayscale (using the function :cvt_color:`cvtColor <>`:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
cvtColor( src, src_gray, COLOR_BGR2GRAY );
|
||||
|
||||
#. Create a window to display the results
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
#. Create a Trackbar for the user to enter the lower threshold for our Canny detector:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
|
||||
|
||||
Observe the following:
|
||||
|
||||
a. The variable to be controlled by the Trackbar is *lowThreshold* with a limit of *max_lowThreshold* (which we set to 100 previously)
|
||||
b. Each time the Trackbar registers an action, the callback function *CannyThreshold* will be invoked.
|
||||
|
||||
#. Let's check the *CannyThreshold* function, step by step:
|
||||
|
||||
a. First, we blur the image with a filter of kernel size 3:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
blur( src_gray, detected_edges, Size(3,3) );
|
||||
|
||||
b. Second, we apply the OpenCV function :canny:`Canny <>`:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
|
||||
|
||||
where the arguments are:
|
||||
|
||||
* *detected_edges*: Source image, grayscale
|
||||
* *detected_edges*: Output of the detector (can be the same as the input)
|
||||
* *lowThreshold*: The value entered by the user moving the Trackbar
|
||||
* *highThreshold*: Set in the program as three times the lower threshold (following Canny's recommendation)
|
||||
* *kernel_size*: We defined it to be 3 (the size of the Sobel kernel to be used internally)
|
||||
|
||||
#. We fill a *dst* image with zeros (meaning the image is completely black).
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
dst = Scalar::all(0);
|
||||
|
||||
#. Finally, we will use the function :copy_to:`copyTo <>` to map only the areas of the image that are identified as edges (on a black background).
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src.copyTo( dst, detected_edges);
|
||||
|
||||
:copy_to:`copyTo <>` copy the *src* image onto *dst*. However, it will only copy the pixels in the locations where they have non-zero values. Since the output of the Canny detector is the edge contours on a black background, the resulting *dst* will be black in all the area but the detected edges.
|
||||
|
||||
#. We display our result:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
imshow( window_name, dst );
|
||||
|
||||
Result
|
||||
=======
|
||||
|
||||
* After compiling the code above, we can run it giving as argument the path to an image. For example, using as an input the following image:
|
||||
|
||||
.. image:: images/Canny_Detector_Tutorial_Original_Image.jpg
|
||||
:alt: Original test image
|
||||
:width: 200pt
|
||||
:align: center
|
||||
|
||||
* Moving the slider, trying different threshold, we obtain the following result:
|
||||
|
||||
.. image:: images/Canny_Detector_Tutorial_Result.jpg
|
||||
:alt: Result after running Canny
|
||||
:width: 200pt
|
||||
:align: center
|
||||
|
||||
* Notice how the image is superposed to the black background on the edge regions.
|
@@ -1,226 +0,0 @@
|
||||
.. _copyMakeBorderTutorial:
|
||||
|
||||
Adding borders to your images
|
||||
******************************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :copy_make_border:`copyMakeBorder <>` to set the borders (extra padding to your image).
|
||||
|
||||
Theory
|
||||
========
|
||||
|
||||
.. note::
|
||||
The explanation below belongs to the book **Learning OpenCV** by Bradski and Kaehler.
|
||||
|
||||
|
||||
#. In our previous tutorial we learned to use convolution to operate on images. One problem that naturally arises is how to handle the boundaries. How can we convolve them if the evaluated points are at the edge of the image?
|
||||
|
||||
#. What most of OpenCV functions do is to copy a given image onto another slightly larger image and then automatically pads the boundary (by any of the methods explained in the sample code just below). This way, the convolution can be performed over the needed pixels without problems (the extra padding is cut after the operation is done).
|
||||
|
||||
#. In this tutorial, we will briefly explore two ways of defining the extra padding (border) for an image:
|
||||
|
||||
a. **BORDER_CONSTANT**: Pad the image with a constant value (i.e. black or :math:`0`
|
||||
|
||||
b. **BORDER_REPLICATE**: The row or column at the very edge of the original is replicated to the extra border.
|
||||
|
||||
This will be seen more clearly in the Code section.
|
||||
|
||||
|
||||
|
||||
Code
|
||||
======
|
||||
|
||||
#. **What does this program do?**
|
||||
|
||||
* Load an image
|
||||
* Let the user choose what kind of padding use in the input image. There are two options:
|
||||
|
||||
#. *Constant value border*: Applies a padding of a constant value for the whole border. This value will be updated randomly each 0.5 seconds.
|
||||
#. *Replicated border*: The border will be replicated from the pixel values at the edges of the original image.
|
||||
|
||||
The user chooses either option by pressing 'c' (constant) or 'r' (replicate)
|
||||
* The program finishes when the user presses 'ESC'
|
||||
|
||||
#. The tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/copyMakeBorder_demo.cpp>`_
|
||||
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/// Global Variables
|
||||
Mat src, dst;
|
||||
int top, bottom, left, right;
|
||||
int borderType;
|
||||
Scalar value;
|
||||
char* window_name = "copyMakeBorder Demo";
|
||||
RNG rng(12345);
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
|
||||
int c;
|
||||
|
||||
/// Load an image
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1;
|
||||
printf(" No data entered, please enter the path to an image file \n");
|
||||
}
|
||||
|
||||
/// Brief how-to for this program
|
||||
printf( "\n \t copyMakeBorder Demo: \n" );
|
||||
printf( "\t -------------------- \n" );
|
||||
printf( " ** Press 'c' to set the border to a random constant value \n");
|
||||
printf( " ** Press 'r' to set the border to be replicated \n");
|
||||
printf( " ** Press 'ESC' to exit the program \n");
|
||||
|
||||
/// Create window
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Initialize arguments for the filter
|
||||
top = (int) (0.05*src.rows); bottom = (int) (0.05*src.rows);
|
||||
left = (int) (0.05*src.cols); right = (int) (0.05*src.cols);
|
||||
dst = src;
|
||||
|
||||
imshow( window_name, dst );
|
||||
|
||||
while( true )
|
||||
{
|
||||
c = waitKey(500);
|
||||
|
||||
if( (char)c == 27 )
|
||||
{ break; }
|
||||
else if( (char)c == 'c' )
|
||||
{ borderType = BORDER_CONSTANT; }
|
||||
else if( (char)c == 'r' )
|
||||
{ borderType = BORDER_REPLICATE; }
|
||||
|
||||
value = Scalar( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );
|
||||
copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );
|
||||
|
||||
imshow( window_name, dst );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Explanation
|
||||
=============
|
||||
|
||||
#. First we declare the variables we are going to use:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src, dst;
|
||||
int top, bottom, left, right;
|
||||
int borderType;
|
||||
Scalar value;
|
||||
char* window_name = "copyMakeBorder Demo";
|
||||
RNG rng(12345);
|
||||
|
||||
Especial attention deserves the variable *rng* which is a random number generator. We use it to generate the random border color, as we will see soon.
|
||||
|
||||
#. As usual we load our source image *src*:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1;
|
||||
printf(" No data entered, please enter the path to an image file \n");
|
||||
}
|
||||
|
||||
#. After giving a short intro of how to use the program, we create a window:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
#. Now we initialize the argument that defines the size of the borders (*top*, *bottom*, *left* and *right*). We give them a value of 5% the size of *src*.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
top = (int) (0.05*src.rows); bottom = (int) (0.05*src.rows);
|
||||
left = (int) (0.05*src.cols); right = (int) (0.05*src.cols);
|
||||
|
||||
#. The program begins a *while* loop. If the user presses 'c' or 'r', the *borderType* variable takes the value of *BORDER_CONSTANT* or *BORDER_REPLICATE* respectively:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
while( true )
|
||||
{
|
||||
c = waitKey(500);
|
||||
|
||||
if( (char)c == 27 )
|
||||
{ break; }
|
||||
else if( (char)c == 'c' )
|
||||
{ borderType = BORDER_CONSTANT; }
|
||||
else if( (char)c == 'r' )
|
||||
{ borderType = BORDER_REPLICATE; }
|
||||
|
||||
#. In each iteration (after 0.5 seconds), the variable *value* is updated...
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
value = Scalar( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );
|
||||
|
||||
with a random value generated by the **RNG** variable *rng*. This value is a number picked randomly in the range :math:`[0,255]`
|
||||
|
||||
#. Finally, we call the function :copy_make_border:`copyMakeBorder <>` to apply the respective padding:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );
|
||||
|
||||
The arguments are:
|
||||
|
||||
a. *src*: Source image
|
||||
#. *dst*: Destination image
|
||||
#. *top*, *bottom*, *left*, *right*: Length in pixels of the borders at each side of the image. We define them as being 5% of the original size of the image.
|
||||
#. *borderType*: Define what type of border is applied. It can be constant or replicate for this example.
|
||||
#. *value*: If *borderType* is *BORDER_CONSTANT*, this is the value used to fill the border pixels.
|
||||
|
||||
#. We display our output image in the image created previously
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
imshow( window_name, dst );
|
||||
|
||||
|
||||
|
||||
|
||||
Results
|
||||
========
|
||||
|
||||
#. After compiling the code above, you can execute it giving as argument the path of an image. The result should be:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* By default, it begins with the border set to BORDER_CONSTANT. Hence, a succession of random colored borders will be shown.
|
||||
* If you press 'r', the border will become a replica of the edge pixels.
|
||||
* If you press 'c', the random colored borders will appear again
|
||||
* If you press 'ESC' the program will exit.
|
||||
|
||||
Below some screenshot showing how the border changes color and how the *BORDER_REPLICATE* option looks:
|
||||
|
||||
|
||||
.. image:: images/CopyMakeBorder_Tutorial_Results.jpg
|
||||
:alt: Final result after copyMakeBorder application
|
||||
:width: 750pt
|
||||
:align: center
|
@@ -1,201 +0,0 @@
|
||||
.. _filter_2d:
|
||||
|
||||
Making your own linear filters!
|
||||
********************************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :filter2d:`filter2D <>` to create your own linear filters.
|
||||
|
||||
Theory
|
||||
=======
|
||||
|
||||
.. note::
|
||||
The explanation below belongs to the book **Learning OpenCV** by Bradski and Kaehler.
|
||||
|
||||
|
||||
Convolution
|
||||
------------
|
||||
In a very general sense, convolution is an operation between every part of an image and an operator (kernel).
|
||||
|
||||
What is a kernel?
|
||||
------------------
|
||||
A kernel is essentially a fixed size array of numerical coefficeints along with an *anchor point* in that array, which is tipically located at the center.
|
||||
|
||||
.. image:: images/filter_2d_tutorial_kernel_theory.png
|
||||
:alt: kernel example
|
||||
:align: center
|
||||
|
||||
How does convolution with a kernel work?
|
||||
-----------------------------------------
|
||||
|
||||
Assume you want to know the resulting value of a particular location in the image. The value of the convolution is calculated in the following way:
|
||||
|
||||
#. Place the kernel anchor on top of a determined pixel, with the rest of the kernel overlaying the corresponding local pixels in the image.
|
||||
|
||||
#. Multiply the kernel coefficients by the corresponding image pixel values and sum the result.
|
||||
|
||||
#. Place the result to the location of the *anchor* in the input image.
|
||||
|
||||
#. Repeat the process for all pixels by scanning the kernel over the entire image.
|
||||
|
||||
Expressing the procedure above in the form of an equation we would have:
|
||||
|
||||
.. math::
|
||||
|
||||
H(x,y) = \sum_{i=0}^{M_{i} - 1} \sum_{j=0}^{M_{j}-1} I(x+i - a_{i}, y + j - a_{j})K(i,j)
|
||||
|
||||
Fortunately, OpenCV provides you with the function :filter2d:`filter2D <>` so you do not have to code all these operations.
|
||||
|
||||
Code
|
||||
======
|
||||
|
||||
#. **What does this program do?**
|
||||
|
||||
* Loads an image
|
||||
* Performs a *normalized box filter*. For instance, for a kernel of size :math:`size = 3`, the kernel would be:
|
||||
|
||||
.. math::
|
||||
|
||||
K = \dfrac{1}{3 \cdot 3} \begin{bmatrix}
|
||||
1 & 1 & 1 \\
|
||||
1 & 1 & 1 \\
|
||||
1 & 1 & 1
|
||||
\end{bmatrix}
|
||||
|
||||
The program will perform the filter operation with kernels of sizes 3, 5, 7, 9 and 11.
|
||||
|
||||
* The filter output (with each kernel) will be shown during 500 milliseconds
|
||||
|
||||
#. The tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/filter2D_demo.cpp>`_
|
||||
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/* @function main */
|
||||
int main ( int argc, char** argv )
|
||||
{
|
||||
/// Declare variables
|
||||
Mat src, dst;
|
||||
|
||||
Mat kernel;
|
||||
Point anchor;
|
||||
double delta;
|
||||
int ddepth;
|
||||
int kernel_size;
|
||||
char* window_name = "filter2D Demo";
|
||||
|
||||
int c;
|
||||
|
||||
/// Load an image
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
/// Create window
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Initialize arguments for the filter
|
||||
anchor = Point( -1, -1 );
|
||||
delta = 0;
|
||||
ddepth = -1;
|
||||
|
||||
/// Loop - Will filter the image with different kernel sizes each 0.5 seconds
|
||||
int ind = 0;
|
||||
while( true )
|
||||
{
|
||||
c = waitKey(500);
|
||||
/// Press 'ESC' to exit the program
|
||||
if( (char)c == 27 )
|
||||
{ break; }
|
||||
|
||||
/// Update kernel size for a normalized box filter
|
||||
kernel_size = 3 + 2*( ind%5 );
|
||||
kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
|
||||
|
||||
/// Apply filter
|
||||
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
|
||||
imshow( window_name, dst );
|
||||
ind++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Explanation
|
||||
=============
|
||||
|
||||
#. Load an image
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
#. Create a window to display the result
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
#. Initialize the arguments for the linear filter
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
anchor = Point( -1, -1 );
|
||||
delta = 0;
|
||||
ddepth = -1;
|
||||
|
||||
|
||||
#. Perform an infinite loop updating the kernel size and applying our linear filter to the input image. Let's analyze that more in detail:
|
||||
|
||||
#. First we define the kernel our filter is going to use. Here it is:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
kernel_size = 3 + 2*( ind%5 );
|
||||
kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
|
||||
|
||||
The first line is to update the *kernel_size* to odd values in the range: :math:`[3,11]`. The second line actually builds the kernel by setting its value to a matrix filled with :math:`1's` and normalizing it by dividing it between the number of elements.
|
||||
|
||||
#. After setting the kernel, we can generate the filter by using the function :filter2d:`filter2D <>`:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
|
||||
|
||||
The arguments denote:
|
||||
|
||||
a. *src*: Source image
|
||||
#. *dst*: Destination image
|
||||
#. *ddepth*: The depth of *dst*. A negative value (such as :math:`-1`) indicates that the depth is the same as the source.
|
||||
#. *kernel*: The kernel to be scanned through the image
|
||||
#. *anchor*: The position of the anchor relative to its kernel. The location *Point(-1, -1)* indicates the center by default.
|
||||
#. *delta*: A value to be added to each pixel during the convolution. By default it is :math:`0`
|
||||
#. *BORDER_DEFAULT*: We let this value by default (more details in the following tutorial)
|
||||
|
||||
#. Our program will effectuate a *while* loop, each 500 ms the kernel size of our filter will be updated in the range indicated.
|
||||
|
||||
Results
|
||||
========
|
||||
|
||||
#. After compiling the code above, you can execute it giving as argument the path of an image. The result should be a window that shows an image blurred by a normalized filter. Each 0.5 seconds the kernel size should change, as can be seen in the series of snapshots below:
|
||||
|
||||
.. image:: images/filter_2d_tutorial_result.jpg
|
||||
:alt: kernel example
|
||||
:align: center
|
@@ -1,182 +0,0 @@
|
||||
.. _hough_circle:
|
||||
|
||||
Hough Circle Transform
|
||||
***********************
|
||||
|
||||
Goal
|
||||
=====
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
* Use the OpenCV function :hough_circles:`HoughCircles <>` to detect circles in an image.
|
||||
|
||||
Theory
|
||||
=======
|
||||
|
||||
Hough Circle Transform
|
||||
------------------------
|
||||
|
||||
* The Hough Circle Transform works in a *roughly* analogous way to the Hough Line Transform explained in the previous tutorial.
|
||||
* In the line detection case, a line was defined by two parameters :math:`(r, \theta)`. In the circle case, we need three parameters to define a circle:
|
||||
|
||||
.. math::
|
||||
|
||||
C : ( x_{center}, y_{center}, r )
|
||||
|
||||
where :math:`(x_{center}, y_{center})` define the center position (green point) and :math:`r` is the radius, which allows us to completely define a circle, as it can be seen below:
|
||||
|
||||
.. image:: images/Hough_Circle_Tutorial_Theory_0.jpg
|
||||
:alt: Result of detecting circles with Hough Transform
|
||||
:align: center
|
||||
|
||||
* For sake of efficiency, OpenCV implements a detection method slightly trickier than the standard Hough Transform: *The Hough gradient method*, which is made up of two main stages. The first stage involves edge detection and finding the possible circle centers and the second stage finds the best radius for each candidate center. For more details, please check the book *Learning OpenCV* or your favorite Computer Vision bibliography
|
||||
|
||||
Code
|
||||
======
|
||||
|
||||
#. **What does this program do?**
|
||||
|
||||
* Loads an image and blur it to reduce the noise
|
||||
* Applies the *Hough Circle Transform* to the blurred image .
|
||||
* Display the detected circle in a window.
|
||||
|
||||
.. |TutorialHoughCirclesSimpleDownload| replace:: here
|
||||
.. _TutorialHoughCirclesSimpleDownload: https://github.com/Itseez/opencv/tree/master/samples/cpp/houghcircles.cpp
|
||||
.. |TutorialHoughCirclesFancyDownload| replace:: here
|
||||
.. _TutorialHoughCirclesFancyDownload: https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/HoughCircle_Demo.cpp
|
||||
|
||||
#. The sample code that we will explain can be downloaded from |TutorialHoughCirclesSimpleDownload|_. A slightly fancier version (which shows trackbars for changing the threshold values) can be found |TutorialHoughCirclesFancyDownload|_.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/* @function main */
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Mat src, src_gray;
|
||||
|
||||
/// Read the image
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
/// Convert it to gray
|
||||
cvtColor( src, src_gray, COLOR_BGR2GRAY );
|
||||
|
||||
/// Reduce the noise so we avoid false circle detection
|
||||
GaussianBlur( src_gray, src_gray, Size(9, 9), 2, 2 );
|
||||
|
||||
vector<Vec3f> circles;
|
||||
|
||||
/// Apply the Hough Transform to find the circles
|
||||
HoughCircles( src_gray, circles, HOUGH_GRADIENT, 1, src_gray.rows/8, 200, 100, 0, 0 );
|
||||
|
||||
/// Draw the circles detected
|
||||
for( size_t i = 0; i < circles.size(); i++ )
|
||||
{
|
||||
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
|
||||
int radius = cvRound(circles[i][2]);
|
||||
// circle center
|
||||
circle( src, center, 3, Scalar(0,255,0), -1, 8, 0 );
|
||||
// circle outline
|
||||
circle( src, center, radius, Scalar(0,0,255), 3, 8, 0 );
|
||||
}
|
||||
|
||||
/// Show your results
|
||||
namedWindow( "Hough Circle Transform Demo", WINDOW_AUTOSIZE );
|
||||
imshow( "Hough Circle Transform Demo", src );
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Explanation
|
||||
============
|
||||
|
||||
|
||||
#. Load an image
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
#. Convert it to grayscale:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
cvtColor( src, src_gray, COLOR_BGR2GRAY );
|
||||
|
||||
#. Apply a Gaussian blur to reduce noise and avoid false circle detection:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
GaussianBlur( src_gray, src_gray, Size(9, 9), 2, 2 );
|
||||
|
||||
#. Proceed to apply Hough Circle Transform:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
vector<Vec3f> circles;
|
||||
|
||||
HoughCircles( src_gray, circles, HOUGH_GRADIENT, 1, src_gray.rows/8, 200, 100, 0, 0 );
|
||||
|
||||
with the arguments:
|
||||
|
||||
* *src_gray*: Input image (grayscale).
|
||||
* *circles*: A vector that stores sets of 3 values: :math:`x_{c}, y_{c}, r` for each detected circle.
|
||||
* *HOUGH_GRADIENT*: Define the detection method. Currently this is the only one available in OpenCV.
|
||||
* *dp = 1*: The inverse ratio of resolution.
|
||||
* *min_dist = src_gray.rows/8*: Minimum distance between detected centers.
|
||||
* *param_1 = 200*: Upper threshold for the internal Canny edge detector.
|
||||
* *param_2* = 100*: Threshold for center detection.
|
||||
* *min_radius = 0*: Minimum radio to be detected. If unknown, put zero as default.
|
||||
* *max_radius = 0*: Maximum radius to be detected. If unknown, put zero as default.
|
||||
|
||||
#. Draw the detected circles:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
for( size_t i = 0; i < circles.size(); i++ )
|
||||
{
|
||||
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
|
||||
int radius = cvRound(circles[i][2]);
|
||||
// circle center
|
||||
circle( src, center, 3, Scalar(0,255,0), -1, 8, 0 );
|
||||
// circle outline
|
||||
circle( src, center, radius, Scalar(0,0,255), 3, 8, 0 );
|
||||
}
|
||||
|
||||
You can see that we will draw the circle(s) on red and the center(s) with a small green dot
|
||||
|
||||
#. Display the detected circle(s):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow( "Hough Circle Transform Demo", WINDOW_AUTOSIZE );
|
||||
imshow( "Hough Circle Transform Demo", src );
|
||||
|
||||
#. Wait for the user to exit the program
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
waitKey(0);
|
||||
|
||||
|
||||
Result
|
||||
=======
|
||||
|
||||
The result of running the code above with a test image is shown below:
|
||||
|
||||
.. image:: images/Hough_Circle_Tutorial_Result.jpg
|
||||
:alt: Result of detecting circles with Hough Transform
|
||||
:align: center
|
@@ -1,292 +0,0 @@
|
||||
.. _hough_lines:
|
||||
|
||||
Hough Line Transform
|
||||
*********************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
* Use the OpenCV functions :hough_lines:`HoughLines <>` and :hough_lines_p:`HoughLinesP <>` to detect lines in an image.
|
||||
|
||||
Theory
|
||||
=======
|
||||
|
||||
.. note::
|
||||
The explanation below belongs to the book **Learning OpenCV** by Bradski and Kaehler.
|
||||
|
||||
Hough Line Transform
|
||||
---------------------
|
||||
#. The Hough Line Transform is a transform used to detect straight lines.
|
||||
#. To apply the Transform, first an edge detection pre-processing is desirable.
|
||||
|
||||
How does it work?
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
#. As you know, a line in the image space can be expressed with two variables. For example:
|
||||
|
||||
a. In the **Cartesian coordinate system:** Parameters: :math:`(m,b)`.
|
||||
b. In the **Polar coordinate system:** Parameters: :math:`(r,\theta)`
|
||||
|
||||
.. image:: images/Hough_Lines_Tutorial_Theory_0.jpg
|
||||
:alt: Line variables
|
||||
:align: center
|
||||
|
||||
For Hough Transforms, we will express lines in the *Polar system*. Hence, a line equation can be written as:
|
||||
|
||||
.. math::
|
||||
|
||||
y = \left ( -\dfrac{\cos \theta}{\sin \theta} \right ) x + \left ( \dfrac{r}{\sin \theta} \right )
|
||||
|
||||
Arranging the terms: :math:`r = x \cos \theta + y \sin \theta`
|
||||
|
||||
#. In general for each point :math:`(x_{0}, y_{0})`, we can define the family of lines that goes through that point as:
|
||||
|
||||
.. math::
|
||||
|
||||
r_{\theta} = x_{0} \cdot \cos \theta + y_{0} \cdot \sin \theta
|
||||
|
||||
Meaning that each pair :math:`(r_{\theta},\theta)` represents each line that passes by :math:`(x_{0}, y_{0})`.
|
||||
|
||||
#. If for a given :math:`(x_{0}, y_{0})` we plot the family of lines that goes through it, we get a sinusoid. For instance, for :math:`x_{0} = 8` and :math:`y_{0} = 6` we get the following plot (in a plane :math:`\theta` - :math:`r`):
|
||||
|
||||
.. image:: images/Hough_Lines_Tutorial_Theory_1.jpg
|
||||
:alt: Polar plot of a the family of lines of a point
|
||||
:align: center
|
||||
|
||||
We consider only points such that :math:`r > 0` and :math:`0< \theta < 2 \pi`.
|
||||
|
||||
#. We can do the same operation above for all the points in an image. If the curves of two different points intersect in the plane :math:`\theta` - :math:`r`, that means that both points belong to a same line. For instance, following with the example above and drawing the plot for two more points: :math:`x_{1} = 9`, :math:`y_{1} = 4` and :math:`x_{2} = 12`, :math:`y_{2} = 3`, we get:
|
||||
|
||||
.. image:: images/Hough_Lines_Tutorial_Theory_2.jpg
|
||||
:alt: Polar plot of the family of lines for three points
|
||||
:align: center
|
||||
|
||||
The three plots intersect in one single point :math:`(0.925, 9.6)`, these coordinates are the parameters (:math:`\theta, r`) or the line in which :math:`(x_{0}, y_{0})`, :math:`(x_{1}, y_{1})` and :math:`(x_{2}, y_{2})` lay.
|
||||
|
||||
#. What does all the stuff above mean? It means that in general, a line can be *detected* by finding the number of intersections between curves.The more curves intersecting means that the line represented by that intersection have more points. In general, we can define a *threshold* of the minimum number of intersections needed to *detect* a line.
|
||||
|
||||
#. This is what the Hough Line Transform does. It keeps track of the intersection between curves of every point in the image. If the number of intersections is above some *threshold*, then it declares it as a line with the parameters :math:`(\theta, r_{\theta})` of the intersection point.
|
||||
|
||||
Standard and Probabilistic Hough Line Transform
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
OpenCV implements two kind of Hough Line Transforms:
|
||||
|
||||
a. **The Standard Hough Transform**
|
||||
|
||||
* It consists in pretty much what we just explained in the previous section. It gives you as result a vector of couples :math:`(\theta, r_{\theta})`
|
||||
|
||||
* In OpenCV it is implemented with the function :hough_lines:`HoughLines <>`
|
||||
|
||||
b. **The Probabilistic Hough Line Transform**
|
||||
|
||||
* A more efficient implementation of the Hough Line Transform. It gives as output the extremes of the detected lines :math:`(x_{0}, y_{0}, x_{1}, y_{1})`
|
||||
|
||||
* In OpenCV it is implemented with the function :hough_lines_p:`HoughLinesP <>`
|
||||
|
||||
Code
|
||||
======
|
||||
|
||||
.. |TutorialHoughLinesSimpleDownload| replace:: here
|
||||
.. _TutorialHoughLinesSimpleDownload: https://github.com/Itseez/opencv/tree/master/samples/cpp/houghlines.cpp
|
||||
.. |TutorialHoughLinesFancyDownload| replace:: here
|
||||
.. _TutorialHoughLinesFancyDownload: https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp
|
||||
|
||||
|
||||
#. **What does this program do?**
|
||||
|
||||
* Loads an image
|
||||
* Applies either a *Standard Hough Line Transform* or a *Probabilistic Line Transform*.
|
||||
* Display the original image and the detected line in two windows.
|
||||
|
||||
#. The sample code that we will explain can be downloaded from |TutorialHoughLinesSimpleDownload|_. A slightly fancier version (which shows both Hough standard and probabilistic with trackbars for changing the threshold values) can be found |TutorialHoughLinesFancyDownload|_.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
void help()
|
||||
{
|
||||
cout << "\nThis program demonstrates line finding with the Hough transform.\n"
|
||||
"Usage:\n"
|
||||
"./houghlines <image_name>, Default is pic1.jpg\n" << endl;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
const char* filename = argc >= 2 ? argv[1] : "pic1.jpg";
|
||||
|
||||
Mat src = imread(filename, 0);
|
||||
if(src.empty())
|
||||
{
|
||||
help();
|
||||
cout << "can not open " << filename << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Mat dst, cdst;
|
||||
Canny(src, dst, 50, 200, 3);
|
||||
cvtColor(dst, cdst, COLOR_GRAY2BGR);
|
||||
|
||||
#if 0
|
||||
vector<Vec2f> lines;
|
||||
HoughLines(dst, lines, 1, CV_PI/180, 100, 0, 0 );
|
||||
|
||||
for( size_t i = 0; i < lines.size(); i++ )
|
||||
{
|
||||
float rho = lines[i][0], theta = lines[i][1];
|
||||
Point pt1, pt2;
|
||||
double a = cos(theta), b = sin(theta);
|
||||
double x0 = a*rho, y0 = b*rho;
|
||||
pt1.x = cvRound(x0 + 1000*(-b));
|
||||
pt1.y = cvRound(y0 + 1000*(a));
|
||||
pt2.x = cvRound(x0 - 1000*(-b));
|
||||
pt2.y = cvRound(y0 - 1000*(a));
|
||||
line( cdst, pt1, pt2, Scalar(0,0,255), 3, LINE_AA);
|
||||
}
|
||||
#else
|
||||
vector<Vec4i> lines;
|
||||
HoughLinesP(dst, lines, 1, CV_PI/180, 50, 50, 10 );
|
||||
for( size_t i = 0; i < lines.size(); i++ )
|
||||
{
|
||||
Vec4i l = lines[i];
|
||||
line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, CV_AA);
|
||||
}
|
||||
#endif
|
||||
imshow("source", src);
|
||||
imshow("detected lines", cdst);
|
||||
|
||||
waitKey();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Explanation
|
||||
=============
|
||||
|
||||
#. Load an image
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src = imread(filename, 0);
|
||||
if(src.empty())
|
||||
{
|
||||
help();
|
||||
cout << "can not open " << filename << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#. Detect the edges of the image by using a Canny detector
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Canny(src, dst, 50, 200, 3);
|
||||
|
||||
Now we will apply the Hough Line Transform. We will explain how to use both OpenCV functions available for this purpose:
|
||||
|
||||
#. **Standard Hough Line Transform**
|
||||
|
||||
a. First, you apply the Transform:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
vector<Vec2f> lines;
|
||||
HoughLines(dst, lines, 1, CV_PI/180, 100, 0, 0 );
|
||||
|
||||
with the following arguments:
|
||||
|
||||
* *dst*: Output of the edge detector. It should be a grayscale image (although in fact it is a binary one)
|
||||
* *lines*: A vector that will store the parameters :math:`(r,\theta)` of the detected lines
|
||||
* *rho* : The resolution of the parameter :math:`r` in pixels. We use **1** pixel.
|
||||
* *theta*: The resolution of the parameter :math:`\theta` in radians. We use **1 degree** (CV_PI/180)
|
||||
* *threshold*: The minimum number of intersections to "*detect*" a line
|
||||
* *srn* and *stn*: Default parameters to zero. Check OpenCV reference for more info.
|
||||
|
||||
b. And then you display the result by drawing the lines.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
for( size_t i = 0; i < lines.size(); i++ )
|
||||
{
|
||||
float rho = lines[i][0], theta = lines[i][1];
|
||||
Point pt1, pt2;
|
||||
double a = cos(theta), b = sin(theta);
|
||||
double x0 = a*rho, y0 = b*rho;
|
||||
pt1.x = cvRound(x0 + 1000*(-b));
|
||||
pt1.y = cvRound(y0 + 1000*(a));
|
||||
pt2.x = cvRound(x0 - 1000*(-b));
|
||||
pt2.y = cvRound(y0 - 1000*(a));
|
||||
line( cdst, pt1, pt2, Scalar(0,0,255), 3, LINE_AA);
|
||||
}
|
||||
|
||||
#. **Probabilistic Hough Line Transform**
|
||||
|
||||
a. First you apply the transform:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
vector<Vec4i> lines;
|
||||
HoughLinesP(dst, lines, 1, CV_PI/180, 50, 50, 10 );
|
||||
|
||||
with the arguments:
|
||||
|
||||
* *dst*: Output of the edge detector. It should be a grayscale image (although in fact it is a binary one)
|
||||
* *lines*: A vector that will store the parameters :math:`(x_{start}, y_{start}, x_{end}, y_{end})` of the detected lines
|
||||
* *rho* : The resolution of the parameter :math:`r` in pixels. We use **1** pixel.
|
||||
* *theta*: The resolution of the parameter :math:`\theta` in radians. We use **1 degree** (CV_PI/180)
|
||||
* *threshold*: The minimum number of intersections to "*detect*" a line
|
||||
* *minLinLength*: The minimum number of points that can form a line. Lines with less than this number of points are disregarded.
|
||||
* *maxLineGap*: The maximum gap between two points to be considered in the same line.
|
||||
|
||||
b. And then you display the result by drawing the lines.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
for( size_t i = 0; i < lines.size(); i++ )
|
||||
{
|
||||
Vec4i l = lines[i];
|
||||
line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, LINE_AA);
|
||||
}
|
||||
|
||||
|
||||
#. Display the original image and the detected lines:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
imshow("source", src);
|
||||
imshow("detected lines", cdst);
|
||||
|
||||
#. Wait until the user exits the program
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
waitKey();
|
||||
|
||||
|
||||
Result
|
||||
=======
|
||||
|
||||
.. note::
|
||||
|
||||
The results below are obtained using the slightly fancier version we mentioned in the *Code* section. It still implements the same stuff as above, only adding the Trackbar for the Threshold.
|
||||
|
||||
Using an input image such as:
|
||||
|
||||
.. image:: images/Hough_Lines_Tutorial_Original_Image.jpg
|
||||
:alt: Result of detecting lines with Hough Transform
|
||||
:align: center
|
||||
|
||||
We get the following result by using the Probabilistic Hough Line Transform:
|
||||
|
||||
.. image:: images/Hough_Lines_Tutorial_Result.jpg
|
||||
:alt: Result of detecting lines with Hough Transform
|
||||
:align: center
|
||||
|
||||
You may observe that the number of lines detected vary while you change the *threshold*. The explanation is sort of evident: If you establish a higher threshold, fewer lines will be detected (since you will need more points to declare a line detected).
|
@@ -1,188 +0,0 @@
|
||||
.. _laplace_operator:
|
||||
|
||||
Laplace Operator
|
||||
*****************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :laplacian:`Laplacian <>` to implement a discrete analog of the *Laplacian operator*.
|
||||
|
||||
|
||||
Theory
|
||||
=======
|
||||
|
||||
#. In the previous tutorial we learned how to use the *Sobel Operator*. It was based on the fact that in the edge area, the pixel intensity shows a "jump" or a high variation of intensity. Getting the first derivative of the intensity, we observed that an edge is characterized by a maximum, as it can be seen in the figure:
|
||||
|
||||
.. image:: images/Laplace_Operator_Tutorial_Theory_Previous.jpg
|
||||
:alt: Previous theory
|
||||
:align: center
|
||||
|
||||
#. And...what happens if we take the second derivative?
|
||||
|
||||
.. image:: images/Laplace_Operator_Tutorial_Theory_ddIntensity.jpg
|
||||
:alt: Second derivative
|
||||
:align: center
|
||||
|
||||
You can observe that the second derivative is zero! So, we can also use this criterion to attempt to detect edges in an image. However, note that zeros will not only appear in edges (they can actually appear in other meaningless locations); this can be solved by applying filtering where needed.
|
||||
|
||||
|
||||
Laplacian Operator
|
||||
-------------------
|
||||
|
||||
#. From the explanation above, we deduce that the second derivative can be used to *detect edges*. Since images are "*2D*", we would need to take the derivative in both dimensions. Here, the Laplacian operator comes handy.
|
||||
|
||||
#. The *Laplacian operator* is defined by:
|
||||
|
||||
.. math::
|
||||
|
||||
Laplace(f) = \dfrac{\partial^{2} f}{\partial x^{2}} + \dfrac{\partial^{2} f}{\partial y^{2}}
|
||||
|
||||
#. The Laplacian operator is implemented in OpenCV by the function :laplacian:`Laplacian <>`. In fact, since the Laplacian uses the gradient of images, it calls internally the *Sobel* operator to perform its computation.
|
||||
|
||||
Code
|
||||
======
|
||||
|
||||
#. **What does this program do?**
|
||||
|
||||
* Loads an image
|
||||
* Remove noise by applying a Gaussian blur and then convert the original image to grayscale
|
||||
* Applies a Laplacian operator to the grayscale image and stores the output image
|
||||
* Display the result in a window
|
||||
|
||||
#. The tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/Laplace_Demo.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
Mat src, src_gray, dst;
|
||||
int kernel_size = 3;
|
||||
int scale = 1;
|
||||
int delta = 0;
|
||||
int ddepth = CV_16S;
|
||||
char* window_name = "Laplace Demo";
|
||||
|
||||
int c;
|
||||
|
||||
/// Load an image
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
/// Remove noise by blurring with a Gaussian filter
|
||||
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
|
||||
|
||||
/// Convert the image to grayscale
|
||||
cvtColor( src, src_gray, COLOR_RGB2GRAY );
|
||||
|
||||
/// Create window
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Apply Laplace function
|
||||
Mat abs_dst;
|
||||
|
||||
Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
|
||||
convertScaleAbs( dst, abs_dst );
|
||||
|
||||
/// Show what you got
|
||||
imshow( window_name, abs_dst );
|
||||
|
||||
waitKey(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Explanation
|
||||
============
|
||||
|
||||
#. Create some needed variables:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src, src_gray, dst;
|
||||
int kernel_size = 3;
|
||||
int scale = 1;
|
||||
int delta = 0;
|
||||
int ddepth = CV_16S;
|
||||
char* window_name = "Laplace Demo";
|
||||
|
||||
#. Loads the source image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
#. Apply a Gaussian blur to reduce noise:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
|
||||
|
||||
#. Convert the image to grayscale using :cvt_color:`cvtColor <>`
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
cvtColor( src, src_gray, COLOR_RGB2GRAY );
|
||||
|
||||
#. Apply the Laplacian operator to the grayscale image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
|
||||
|
||||
where the arguments are:
|
||||
|
||||
* *src_gray*: The input image.
|
||||
* *dst*: Destination (output) image
|
||||
* *ddepth*: Depth of the destination image. Since our input is *CV_8U* we define *ddepth* = *CV_16S* to avoid overflow
|
||||
* *kernel_size*: The kernel size of the Sobel operator to be applied internally. We use 3 in this example.
|
||||
* *scale*, *delta* and *BORDER_DEFAULT*: We leave them as default values.
|
||||
|
||||
#. Convert the output from the Laplacian operator to a *CV_8U* image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
convertScaleAbs( dst, abs_dst );
|
||||
|
||||
#. Display the result in a window:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
imshow( window_name, abs_dst );
|
||||
|
||||
|
||||
Results
|
||||
========
|
||||
|
||||
#. After compiling the code above, we can run it giving as argument the path to an image. For example, using as an input:
|
||||
|
||||
.. image:: images/Laplace_Operator_Tutorial_Original_Image.jpg
|
||||
:alt: Original test image
|
||||
:width: 250pt
|
||||
:align: center
|
||||
|
||||
#. We obtain the following result. Notice how the trees and the silhouette of the cow are approximately well defined (except in areas in which the intensity are very similar, i.e. around the cow's head). Also, note that the roof of the house behind the trees (right side) is notoriously marked. This is due to the fact that the contrast is higher in that region.
|
||||
|
||||
.. image:: images/Laplace_Operator_Tutorial_Result.jpg
|
||||
:alt: Original test image
|
||||
:width: 250pt
|
||||
:align: center
|
@@ -1,313 +0,0 @@
|
||||
.. _remap:
|
||||
|
||||
Remapping
|
||||
*********
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
a. Use the OpenCV function :remap:`remap <>` to implement simple remapping routines.
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
What is remapping?
|
||||
------------------
|
||||
|
||||
* It is the process of taking pixels from one place in the image and locating them in another position in a new image.
|
||||
|
||||
* To accomplish the mapping process, it might be necessary to do some interpolation for non-integer pixel locations, since there will not always be a one-to-one-pixel correspondence between source and destination images.
|
||||
|
||||
* We can express the remap for every pixel location :math:`(x,y)` as:
|
||||
|
||||
.. math::
|
||||
|
||||
g(x,y) = f ( h(x,y) )
|
||||
|
||||
where :math:`g()` is the remapped image, :math:`f()` the source image and :math:`h(x,y)` is the mapping function that operates on :math:`(x,y)`.
|
||||
|
||||
* Let's think in a quick example. Imagine that we have an image :math:`I` and, say, we want to do a remap such that:
|
||||
|
||||
.. math::
|
||||
|
||||
h(x,y) = (I.cols - x, y )
|
||||
|
||||
What would happen? It is easily seen that the image would flip in the :math:`x` direction. For instance, consider the input image:
|
||||
|
||||
.. image:: images/Remap_Tutorial_Theory_0.jpg
|
||||
:alt: Original test image
|
||||
:width: 120pt
|
||||
:align: center
|
||||
|
||||
observe how the red circle changes positions with respect to x (considering :math:`x` the horizontal direction):
|
||||
|
||||
.. image:: images/Remap_Tutorial_Theory_1.jpg
|
||||
:alt: Original test image
|
||||
:width: 120pt
|
||||
:align: center
|
||||
|
||||
* In OpenCV, the function :remap:`remap <>` offers a simple remapping implementation.
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
#. **What does this program do?**
|
||||
|
||||
* Loads an image
|
||||
* Each second, apply 1 of 4 different remapping processes to the image and display them indefinitely in a window.
|
||||
* Wait for the user to exit the program
|
||||
|
||||
#. The tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/Remap_Demo.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/// Global variables
|
||||
Mat src, dst;
|
||||
Mat map_x, map_y;
|
||||
char* remap_window = "Remap demo";
|
||||
int ind = 0;
|
||||
|
||||
/// Function Headers
|
||||
void update_map( void );
|
||||
|
||||
/*
|
||||
* @function main
|
||||
*/
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load the image
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
/// Create dst, map_x and map_y with the same size as src:
|
||||
dst.create( src.size(), src.type() );
|
||||
map_x.create( src.size(), CV_32FC1 );
|
||||
map_y.create( src.size(), CV_32FC1 );
|
||||
|
||||
/// Create window
|
||||
namedWindow( remap_window, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Loop
|
||||
while( true )
|
||||
{
|
||||
/// Each 1 sec. Press ESC to exit the program
|
||||
int c = waitKey( 1000 );
|
||||
|
||||
if( (char)c == 27 )
|
||||
{ break; }
|
||||
|
||||
/// Update map_x & map_y. Then apply remap
|
||||
update_map();
|
||||
remap( src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0, 0) );
|
||||
|
||||
/// Display results
|
||||
imshow( remap_window, dst );
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @function update_map
|
||||
* @brief Fill the map_x and map_y matrices with 4 types of mappings
|
||||
*/
|
||||
void update_map( void )
|
||||
{
|
||||
ind = ind%4;
|
||||
|
||||
for( int j = 0; j < src.rows; j++ )
|
||||
{ for( int i = 0; i < src.cols; i++ )
|
||||
{
|
||||
switch( ind )
|
||||
{
|
||||
case 0:
|
||||
if( i > src.cols*0.25 && i < src.cols*0.75 && j > src.rows*0.25 && j < src.rows*0.75 )
|
||||
{
|
||||
map_x.at<float>(j,i) = 2*( i - src.cols*0.25 ) + 0.5 ;
|
||||
map_y.at<float>(j,i) = 2*( j - src.rows*0.25 ) + 0.5 ;
|
||||
}
|
||||
else
|
||||
{ map_x.at<float>(j,i) = 0 ;
|
||||
map_y.at<float>(j,i) = 0 ;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
map_x.at<float>(j,i) = i ;
|
||||
map_y.at<float>(j,i) = src.rows - j ;
|
||||
break;
|
||||
case 2:
|
||||
map_x.at<float>(j,i) = src.cols - i ;
|
||||
map_y.at<float>(j,i) = j ;
|
||||
break;
|
||||
case 3:
|
||||
map_x.at<float>(j,i) = src.cols - i ;
|
||||
map_y.at<float>(j,i) = src.rows - j ;
|
||||
break;
|
||||
} // end of switch
|
||||
}
|
||||
}
|
||||
ind++;
|
||||
}
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
#. Create some variables we will use:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src, dst;
|
||||
Mat map_x, map_y;
|
||||
char* remap_window = "Remap demo";
|
||||
int ind = 0;
|
||||
|
||||
#. Load an image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
#. Create the destination image and the two mapping matrices (for x and y )
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
dst.create( src.size(), src.type() );
|
||||
map_x.create( src.size(), CV_32FC1 );
|
||||
map_y.create( src.size(), CV_32FC1 );
|
||||
|
||||
#. Create a window to display results
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow( remap_window, WINDOW_AUTOSIZE );
|
||||
|
||||
#. Establish a loop. Each 1000 ms we update our mapping matrices (*mat_x* and *mat_y*) and apply them to our source image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
while( true )
|
||||
{
|
||||
/// Each 1 sec. Press ESC to exit the program
|
||||
int c = waitKey( 1000 );
|
||||
|
||||
if( (char)c == 27 )
|
||||
{ break; }
|
||||
|
||||
/// Update map_x & map_y. Then apply remap
|
||||
update_map();
|
||||
remap( src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0, 0) );
|
||||
|
||||
/// Display results
|
||||
imshow( remap_window, dst );
|
||||
}
|
||||
|
||||
The function that applies the remapping is :remap:`remap <>`. We give the following arguments:
|
||||
|
||||
* **src**: Source image
|
||||
* **dst**: Destination image of same size as *src*
|
||||
* **map_x**: The mapping function in the x direction. It is equivalent to the first component of :math:`h(i,j)`
|
||||
* **map_y**: Same as above, but in y direction. Note that *map_y* and *map_x* are both of the same size as *src*
|
||||
* **INTER_LINEAR**: The type of interpolation to use for non-integer pixels. This is by default.
|
||||
* **BORDER_CONSTANT**: Default
|
||||
|
||||
How do we update our mapping matrices *mat_x* and *mat_y*? Go on reading:
|
||||
|
||||
#. **Updating the mapping matrices:** We are going to perform 4 different mappings:
|
||||
|
||||
a. Reduce the picture to half its size and will display it in the middle:
|
||||
|
||||
.. math::
|
||||
|
||||
h(i,j) = ( 2*i - src.cols/2 + 0.5, 2*j - src.rows/2 + 0.5)
|
||||
|
||||
for all pairs :math:`(i,j)` such that: :math:`\dfrac{src.cols}{4}<i<\dfrac{3 \cdot src.cols}{4}` and :math:`\dfrac{src.rows}{4}<j<\dfrac{3 \cdot src.rows}{4}`
|
||||
|
||||
b. Turn the image upside down: :math:`h( i, j ) = (i, src.rows - j)`
|
||||
|
||||
c. Reflect the image from left to right: :math:`h(i,j) = ( src.cols - i, j )`
|
||||
|
||||
d. Combination of b and c: :math:`h(i,j) = ( src.cols - i, src.rows - j )`
|
||||
|
||||
This is expressed in the following snippet. Here, *map_x* represents the first coordinate of *h(i,j)* and *map_y* the second coordinate.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
for( int j = 0; j < src.rows; j++ )
|
||||
{ for( int i = 0; i < src.cols; i++ )
|
||||
{
|
||||
switch( ind )
|
||||
{
|
||||
case 0:
|
||||
if( i > src.cols*0.25 && i < src.cols*0.75 && j > src.rows*0.25 && j < src.rows*0.75 )
|
||||
{
|
||||
map_x.at<float>(j,i) = 2*( i - src.cols*0.25 ) + 0.5 ;
|
||||
map_y.at<float>(j,i) = 2*( j - src.rows*0.25 ) + 0.5 ;
|
||||
}
|
||||
else
|
||||
{ map_x.at<float>(j,i) = 0 ;
|
||||
map_y.at<float>(j,i) = 0 ;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
map_x.at<float>(j,i) = i ;
|
||||
map_y.at<float>(j,i) = src.rows - j ;
|
||||
break;
|
||||
case 2:
|
||||
map_x.at<float>(j,i) = src.cols - i ;
|
||||
map_y.at<float>(j,i) = j ;
|
||||
break;
|
||||
case 3:
|
||||
map_x.at<float>(j,i) = src.cols - i ;
|
||||
map_y.at<float>(j,i) = src.rows - j ;
|
||||
break;
|
||||
} // end of switch
|
||||
}
|
||||
}
|
||||
ind++;
|
||||
}
|
||||
|
||||
|
||||
Result
|
||||
======
|
||||
|
||||
#. After compiling the code above, you can execute it giving as argument an image path. For instance, by using the following image:
|
||||
|
||||
.. image:: images/Remap_Tutorial_Original_Image.jpg
|
||||
:alt: Original test image
|
||||
:width: 250pt
|
||||
:align: center
|
||||
|
||||
#. This is the result of reducing it to half the size and centering it:
|
||||
|
||||
.. image:: images/Remap_Tutorial_Result_0.jpg
|
||||
:alt: Result 0 for remapping
|
||||
:width: 250pt
|
||||
:align: center
|
||||
|
||||
#. Turning it upside down:
|
||||
|
||||
.. image:: images/Remap_Tutorial_Result_1.jpg
|
||||
:alt: Result 0 for remapping
|
||||
:width: 250pt
|
||||
:align: center
|
||||
|
||||
#. Reflecting it in the x direction:
|
||||
|
||||
.. image:: images/Remap_Tutorial_Result_2.jpg
|
||||
:alt: Result 0 for remapping
|
||||
:width: 250pt
|
||||
:align: center
|
||||
|
||||
#. Reflecting it in both directions:
|
||||
|
||||
.. image:: images/Remap_Tutorial_Result_3.jpg
|
||||
:alt: Result 0 for remapping
|
||||
:width: 250pt
|
||||
:align: center
|
@@ -1,276 +0,0 @@
|
||||
.. _sobel_derivatives:
|
||||
|
||||
Sobel Derivatives
|
||||
******************
|
||||
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :sobel:`Sobel <>` to calculate the derivatives from an image.
|
||||
* Use the OpenCV function :scharr:`Scharr <>` to calculate a more accurate derivative for a kernel of size :math:`3 \cdot 3`
|
||||
|
||||
Theory
|
||||
========
|
||||
|
||||
.. note::
|
||||
The explanation below belongs to the book **Learning OpenCV** by Bradski and Kaehler.
|
||||
|
||||
|
||||
#. In the last two tutorials we have seen applicative examples of convolutions. One of the most important convolutions is the computation of derivatives in an image (or an approximation to them).
|
||||
|
||||
#. Why may be important the calculus of the derivatives in an image? Let's imagine we want to detect the *edges* present in the image. For instance:
|
||||
|
||||
|
||||
.. image:: images/Sobel_Derivatives_Tutorial_Theory_0.jpg
|
||||
:alt: How intensity changes in an edge
|
||||
:align: center
|
||||
|
||||
You can easily notice that in an *edge*, the pixel intensity *changes* in a notorious way. A good way to express *changes* is by using *derivatives*. A high change in gradient indicates a major change in the image.
|
||||
|
||||
#. To be more graphical, let's assume we have a 1D-image. An edge is shown by the "jump" in intensity in the plot below:
|
||||
|
||||
.. image:: images/Sobel_Derivatives_Tutorial_Theory_Intensity_Function.jpg
|
||||
:alt: Intensity Plot for an edge
|
||||
:align: center
|
||||
|
||||
#. The edge "jump" can be seen more easily if we take the first derivative (actually, here appears as a maximum)
|
||||
|
||||
.. image:: images/Sobel_Derivatives_Tutorial_Theory_dIntensity_Function.jpg
|
||||
:alt: First derivative of Intensity - Plot for an edge
|
||||
:align: center
|
||||
|
||||
#. So, from the explanation above, we can deduce that a method to detect edges in an image can be performed by locating pixel locations where the gradient is higher than its neighbors (or to generalize, higher than a threshold).
|
||||
|
||||
#. More detailed explanation, please refer to **Learning OpenCV** by Bradski and Kaehler
|
||||
|
||||
Sobel Operator
|
||||
---------------
|
||||
|
||||
#. The Sobel Operator is a discrete differentiation operator. It computes an approximation of the gradient of an image intensity function.
|
||||
|
||||
#. The Sobel Operator combines Gaussian smoothing and differentiation.
|
||||
|
||||
Formulation
|
||||
^^^^^^^^^^^^
|
||||
Assuming that the image to be operated is :math:`I`:
|
||||
|
||||
#. We calculate two derivatives:
|
||||
|
||||
a. **Horizontal changes**: This is computed by convolving :math:`I` with a kernel :math:`G_{x}` with odd size. For example for a kernel size of 3, :math:`G_{x}` would be computed as:
|
||||
|
||||
.. math::
|
||||
|
||||
G_{x} = \begin{bmatrix}
|
||||
-1 & 0 & +1 \\
|
||||
-2 & 0 & +2 \\
|
||||
-1 & 0 & +1
|
||||
\end{bmatrix} * I
|
||||
|
||||
b. **Vertical changes**: This is computed by convolving :math:`I` with a kernel :math:`G_{y}` with odd size. For example for a kernel size of 3, :math:`G_{y}` would be computed as:
|
||||
|
||||
.. math::
|
||||
|
||||
G_{y} = \begin{bmatrix}
|
||||
-1 & -2 & -1 \\
|
||||
0 & 0 & 0 \\
|
||||
+1 & +2 & +1
|
||||
\end{bmatrix} * I
|
||||
|
||||
#. At each point of the image we calculate an approximation of the *gradient* in that point by combining both results above:
|
||||
|
||||
.. math::
|
||||
|
||||
G = \sqrt{ G_{x}^{2} + G_{y}^{2} }
|
||||
|
||||
Although sometimes the following simpler equation is used:
|
||||
|
||||
.. math::
|
||||
|
||||
G = |G_{x}| + |G_{y}|
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
When the size of the kernel is :math:`3`, the Sobel kernel shown above may produce noticeable inaccuracies (after all, Sobel is only an approximation of the derivative). OpenCV addresses this inaccuracy for kernels of size 3 by using the :scharr:`Scharr <>` function. This is as fast but more accurate than the standar Sobel function. It implements the following kernels:
|
||||
|
||||
.. math::
|
||||
|
||||
G_{x} = \begin{bmatrix}
|
||||
-3 & 0 & +3 \\
|
||||
-10 & 0 & +10 \\
|
||||
-3 & 0 & +3
|
||||
\end{bmatrix}
|
||||
|
||||
G_{y} = \begin{bmatrix}
|
||||
-3 & -10 & -3 \\
|
||||
0 & 0 & 0 \\
|
||||
+3 & +10 & +3
|
||||
\end{bmatrix}
|
||||
|
||||
You can check out more information of this function in the OpenCV reference (:scharr:`Scharr <>`). Also, in the sample code below, you will notice that above the code for :sobel:`Sobel <>` function there is also code for the :scharr:`Scharr <>` function commented. Uncommenting it (and obviously commenting the Sobel stuff) should give you an idea of how this function works.
|
||||
|
||||
Code
|
||||
=====
|
||||
|
||||
#. **What does this program do?**
|
||||
|
||||
* Applies the *Sobel Operator* and generates as output an image with the detected *edges* bright on a darker background.
|
||||
|
||||
#. The tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/Sobel_Demo.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
|
||||
Mat src, src_gray;
|
||||
Mat grad;
|
||||
char* window_name = "Sobel Demo - Simple Edge Detector";
|
||||
int scale = 1;
|
||||
int delta = 0;
|
||||
int ddepth = CV_16S;
|
||||
|
||||
int c;
|
||||
|
||||
/// Load an image
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
|
||||
|
||||
/// Convert it to gray
|
||||
cvtColor( src, src_gray, COLOR_RGB2GRAY );
|
||||
|
||||
/// Create window
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Generate grad_x and grad_y
|
||||
Mat grad_x, grad_y;
|
||||
Mat abs_grad_x, abs_grad_y;
|
||||
|
||||
/// Gradient X
|
||||
//Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
|
||||
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
|
||||
convertScaleAbs( grad_x, abs_grad_x );
|
||||
|
||||
/// Gradient Y
|
||||
//Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
|
||||
Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
|
||||
convertScaleAbs( grad_y, abs_grad_y );
|
||||
|
||||
/// Total Gradient (approximate)
|
||||
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
|
||||
|
||||
imshow( window_name, grad );
|
||||
|
||||
waitKey(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Explanation
|
||||
=============
|
||||
|
||||
#. First we declare the variables we are going to use:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src, src_gray;
|
||||
Mat grad;
|
||||
char* window_name = "Sobel Demo - Simple Edge Detector";
|
||||
int scale = 1;
|
||||
int delta = 0;
|
||||
int ddepth = CV_16S;
|
||||
|
||||
#. As usual we load our source image *src*:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
#. First, we apply a :gaussian_blur:`GaussianBlur <>` to our image to reduce the noise ( kernel size = 3 )
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
|
||||
|
||||
#. Now we convert our filtered image to grayscale:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
cvtColor( src, src_gray, COLOR_RGB2GRAY );
|
||||
|
||||
#. Second, we calculate the "*derivatives*" in *x* and *y* directions. For this, we use the function :sobel:`Sobel <>` as shown below:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat grad_x, grad_y;
|
||||
Mat abs_grad_x, abs_grad_y;
|
||||
|
||||
/// Gradient X
|
||||
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
|
||||
/// Gradient Y
|
||||
Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
|
||||
|
||||
The function takes the following arguments:
|
||||
|
||||
* *src_gray*: In our example, the input image. Here it is *CV_8U*
|
||||
* *grad_x*/*grad_y*: The output image.
|
||||
* *ddepth*: The depth of the output image. We set it to *CV_16S* to avoid overflow.
|
||||
* *x_order*: The order of the derivative in **x** direction.
|
||||
* *y_order*: The order of the derivative in **y** direction.
|
||||
* *scale*, *delta* and *BORDER_DEFAULT*: We use default values.
|
||||
|
||||
Notice that to calculate the gradient in *x* direction we use: :math:`x_{order}= 1` and :math:`y_{order} = 0`. We do analogously for the *y* direction.
|
||||
|
||||
#. We convert our partial results back to *CV_8U*:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
convertScaleAbs( grad_x, abs_grad_x );
|
||||
convertScaleAbs( grad_y, abs_grad_y );
|
||||
|
||||
|
||||
#. Finally, we try to approximate the *gradient* by adding both directional gradients (note that this is not an exact calculation at all! but it is good for our purposes).
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
|
||||
|
||||
#. Finally, we show our result:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
imshow( window_name, grad );
|
||||
|
||||
|
||||
|
||||
Results
|
||||
========
|
||||
|
||||
#. Here is the output of applying our basic detector to *lena.jpg*:
|
||||
|
||||
|
||||
.. image:: images/Sobel_Derivatives_Tutorial_Result.jpg
|
||||
:alt: Result of applying Sobel operator to lena.jpg
|
||||
:width: 300pt
|
||||
:align: center
|
@@ -1,308 +0,0 @@
|
||||
.. _warp_affine:
|
||||
|
||||
Affine Transformations
|
||||
**********************
|
||||
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
a. Use the OpenCV function :warp_affine:`warpAffine <>` to implement simple remapping routines.
|
||||
b. Use the OpenCV function :get_rotation_matrix_2d:`getRotationMatrix2D <>` to obtain a :math:`2 \times 3` rotation matrix
|
||||
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
What is an Affine Transformation?
|
||||
----------------------------------
|
||||
|
||||
#. It is any transformation that can be expressed in the form of a *matrix multiplication* (linear transformation) followed by a *vector addition* (translation).
|
||||
|
||||
#. From the above, We can use an Affine Transformation to express:
|
||||
|
||||
a. Rotations (linear transformation)
|
||||
b. Translations (vector addition)
|
||||
c. Scale operations (linear transformation)
|
||||
|
||||
you can see that, in essence, an Affine Transformation represents a **relation** between two images.
|
||||
|
||||
#. The usual way to represent an Affine Transform is by using a :math:`2 \times 3` matrix.
|
||||
|
||||
.. math::
|
||||
|
||||
A = \begin{bmatrix}
|
||||
a_{00} & a_{01} \\
|
||||
a_{10} & a_{11}
|
||||
\end{bmatrix}_{2 \times 2}
|
||||
B = \begin{bmatrix}
|
||||
b_{00} \\
|
||||
b_{10}
|
||||
\end{bmatrix}_{2 \times 1}
|
||||
|
||||
M = \begin{bmatrix}
|
||||
A & B
|
||||
\end{bmatrix}
|
||||
=
|
||||
\begin{bmatrix}
|
||||
a_{00} & a_{01} & b_{00} \\
|
||||
a_{10} & a_{11} & b_{10}
|
||||
\end{bmatrix}_{2 \times 3}
|
||||
|
||||
Considering that we want to transform a 2D vector :math:`X = \begin{bmatrix}x \\ y\end{bmatrix}` by using :math:`A` and :math:`B`, we can do it equivalently with:
|
||||
|
||||
|
||||
:math:`T = A \cdot \begin{bmatrix}x \\ y\end{bmatrix} + B` or :math:`T = M \cdot [x, y, 1]^{T}`
|
||||
|
||||
.. math::
|
||||
|
||||
T = \begin{bmatrix}
|
||||
a_{00}x + a_{01}y + b_{00} \\
|
||||
a_{10}x + a_{11}y + b_{10}
|
||||
\end{bmatrix}
|
||||
|
||||
|
||||
How do we get an Affine Transformation?
|
||||
---------------------------------------
|
||||
|
||||
1. Excellent question. We mentioned that an Affine Transformation is basically a **relation** between two images. The information about this relation can come, roughly, in two ways:
|
||||
|
||||
a. We know both :math:`X` and `T` and we also know that they are related. Then our job is to find :math:`M`
|
||||
|
||||
b. We know :math:`M` and :math:`X`. To obtain :math:`T` we only need to apply :math:`T = M \cdot X`. Our information for :math:`M` may be explicit (i.e. have the 2-by-3 matrix) or it can come as a geometric relation between points.
|
||||
|
||||
2. Let's explain a little bit better (b). Since :math:`M` relates 02 images, we can analyze the simplest case in which it relates three points in both images. Look at the figure below:
|
||||
|
||||
.. image:: images/Warp_Affine_Tutorial_Theory_0.jpg
|
||||
:alt: Theory of Warp Affine
|
||||
:width: 350pt
|
||||
:align: center
|
||||
|
||||
the points 1, 2 and 3 (forming a triangle in image 1) are mapped into image 2, still forming a triangle, but now they have changed notoriously. If we find the Affine Transformation with these 3 points (you can choose them as you like), then we can apply this found relation to the whole pixels in the image.
|
||||
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
#. **What does this program do?**
|
||||
|
||||
* Loads an image
|
||||
* Applies an Affine Transform to the image. This Transform is obtained from the relation between three points. We use the function :warp_affine:`warpAffine <>` for that purpose.
|
||||
* Applies a Rotation to the image after being transformed. This rotation is with respect to the image center
|
||||
* Waits until the user exits the program
|
||||
|
||||
#. The tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgTrans/Geometric_Transforms_Demo.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
/// Global variables
|
||||
char* source_window = "Source image";
|
||||
char* warp_window = "Warp";
|
||||
char* warp_rotate_window = "Warp + Rotate";
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
Point2f srcTri[3];
|
||||
Point2f dstTri[3];
|
||||
|
||||
Mat rot_mat( 2, 3, CV_32FC1 );
|
||||
Mat warp_mat( 2, 3, CV_32FC1 );
|
||||
Mat src, warp_dst, warp_rotate_dst;
|
||||
|
||||
/// Load the image
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
/// Set the dst image the same type and size as src
|
||||
warp_dst = Mat::zeros( src.rows, src.cols, src.type() );
|
||||
|
||||
/// Set your 3 points to calculate the Affine Transform
|
||||
srcTri[0] = Point2f( 0,0 );
|
||||
srcTri[1] = Point2f( src.cols - 1, 0 );
|
||||
srcTri[2] = Point2f( 0, src.rows - 1 );
|
||||
|
||||
dstTri[0] = Point2f( src.cols*0.0, src.rows*0.33 );
|
||||
dstTri[1] = Point2f( src.cols*0.85, src.rows*0.25 );
|
||||
dstTri[2] = Point2f( src.cols*0.15, src.rows*0.7 );
|
||||
|
||||
/// Get the Affine Transform
|
||||
warp_mat = getAffineTransform( srcTri, dstTri );
|
||||
|
||||
/// Apply the Affine Transform just found to the src image
|
||||
warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
|
||||
|
||||
/* Rotating the image after Warp */
|
||||
|
||||
/// Compute a rotation matrix with respect to the center of the image
|
||||
Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
|
||||
double angle = -50.0;
|
||||
double scale = 0.6;
|
||||
|
||||
/// Get the rotation matrix with the specifications above
|
||||
rot_mat = getRotationMatrix2D( center, angle, scale );
|
||||
|
||||
/// Rotate the warped image
|
||||
warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );
|
||||
|
||||
/// Show what you got
|
||||
namedWindow( source_window, WINDOW_AUTOSIZE );
|
||||
imshow( source_window, src );
|
||||
|
||||
namedWindow( warp_window, WINDOW_AUTOSIZE );
|
||||
imshow( warp_window, warp_dst );
|
||||
|
||||
namedWindow( warp_rotate_window, WINDOW_AUTOSIZE );
|
||||
imshow( warp_rotate_window, warp_rotate_dst );
|
||||
|
||||
/// Wait until user exits the program
|
||||
waitKey(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
#. Declare some variables we will use, such as the matrices to store our results and 2 arrays of points to store the 2D points that define our Affine Transform.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Point2f srcTri[3];
|
||||
Point2f dstTri[3];
|
||||
|
||||
Mat rot_mat( 2, 3, CV_32FC1 );
|
||||
Mat warp_mat( 2, 3, CV_32FC1 );
|
||||
Mat src, warp_dst, warp_rotate_dst;
|
||||
|
||||
#. Load an image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
#. Initialize the destination image as having the same size and type as the source:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
warp_dst = Mat::zeros( src.rows, src.cols, src.type() );
|
||||
|
||||
#. **Affine Transform:** As we explained lines above, we need two sets of 3 points to derive the affine transform relation. Take a look:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
srcTri[0] = Point2f( 0,0 );
|
||||
srcTri[1] = Point2f( src.cols - 1, 0 );
|
||||
srcTri[2] = Point2f( 0, src.rows - 1 );
|
||||
|
||||
dstTri[0] = Point2f( src.cols*0.0, src.rows*0.33 );
|
||||
dstTri[1] = Point2f( src.cols*0.85, src.rows*0.25 );
|
||||
dstTri[2] = Point2f( src.cols*0.15, src.rows*0.7 );
|
||||
|
||||
You may want to draw the points to make a better idea of how they change. Their locations are approximately the same as the ones depicted in the example figure (in the Theory section). You may note that the size and orientation of the triangle defined by the 3 points change.
|
||||
|
||||
#. Armed with both sets of points, we calculate the Affine Transform by using OpenCV function :get_affine_transform:`getAffineTransform <>`:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
warp_mat = getAffineTransform( srcTri, dstTri );
|
||||
|
||||
|
||||
We get as an output a :math:`2 \times 3` matrix (in this case **warp_mat**)
|
||||
|
||||
#. We apply the Affine Transform just found to the src image
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
|
||||
|
||||
with the following arguments:
|
||||
|
||||
* **src**: Input image
|
||||
* **warp_dst**: Output image
|
||||
* **warp_mat**: Affine transform
|
||||
* **warp_dst.size()**: The desired size of the output image
|
||||
|
||||
We just got our first transformed image! We will display it in one bit. Before that, we also want to rotate it...
|
||||
|
||||
#. **Rotate:**
|
||||
To rotate an image, we need to know two things:
|
||||
|
||||
a. The center with respect to which the image will rotate
|
||||
b. The angle to be rotated. In OpenCV a positive angle is counter-clockwise
|
||||
c. *Optional:* A scale factor
|
||||
|
||||
We define these parameters with the following snippet:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
|
||||
double angle = -50.0;
|
||||
double scale = 0.6;
|
||||
|
||||
#. We generate the rotation matrix with the OpenCV function :get_rotation_matrix_2d:`getRotationMatrix2D <>`, which returns a :math:`2 \times 3` matrix (in this case *rot_mat*)
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
rot_mat = getRotationMatrix2D( center, angle, scale );
|
||||
|
||||
#. We now apply the found rotation to the output of our previous Transformation.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );
|
||||
|
||||
#. Finally, we display our results in two windows plus the original image for good measure:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow( source_window, WINDOW_AUTOSIZE );
|
||||
imshow( source_window, src );
|
||||
|
||||
namedWindow( warp_window, WINDOW_AUTOSIZE );
|
||||
imshow( warp_window, warp_dst );
|
||||
|
||||
namedWindow( warp_rotate_window, WINDOW_AUTOSIZE );
|
||||
imshow( warp_rotate_window, warp_rotate_dst );
|
||||
|
||||
|
||||
#. We just have to wait until the user exits the program
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
waitKey(0);
|
||||
|
||||
|
||||
|
||||
Result
|
||||
======
|
||||
|
||||
#. After compiling the code above, we can give it the path of an image as argument. For instance, for a picture like:
|
||||
|
||||
.. image:: images/Warp_Affine_Tutorial_Original_Image.jpg
|
||||
:alt: Original image
|
||||
:width: 250pt
|
||||
:align: center
|
||||
|
||||
after applying the first Affine Transform we obtain:
|
||||
|
||||
.. image:: images/Warp_Affine_Tutorial_Result_Warp.jpg
|
||||
:alt: Original image
|
||||
:width: 250pt
|
||||
:align: center
|
||||
|
||||
and finally, after applying a negative rotation (remember negative means clockwise) and a scale factor, we get:
|
||||
|
||||
.. image:: images/Warp_Affine_Tutorial_Result_Warp_Rotate.jpg
|
||||
:alt: Original image
|
||||
:width: 250pt
|
||||
:align: center
|
@@ -1,281 +0,0 @@
|
||||
.. _Morphology_2:
|
||||
|
||||
More Morphology Transformations
|
||||
*********************************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :morphology_ex:`morphologyEx <>` to apply Morphological Transformation such as:
|
||||
|
||||
+ Opening
|
||||
+ Closing
|
||||
+ Morphological Gradient
|
||||
+ Top Hat
|
||||
+ Black Hat
|
||||
|
||||
Theory
|
||||
=======
|
||||
|
||||
.. note::
|
||||
The explanation below belongs to the book **Learning OpenCV** by Bradski and Kaehler.
|
||||
|
||||
In the previous tutorial we covered two basic Morphology operations:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Erosion
|
||||
* Dilation.
|
||||
|
||||
Based on these two we can effectuate more sophisticated transformations to our images. Here we discuss briefly 05 operations offered by OpenCV:
|
||||
|
||||
Opening
|
||||
---------
|
||||
|
||||
* It is obtained by the erosion of an image followed by a dilation.
|
||||
|
||||
.. math::
|
||||
|
||||
dst = open( src, element) = dilate( erode( src, element ) )
|
||||
|
||||
* Useful for removing small objects (it is assumed that the objects are bright on a dark foreground)
|
||||
|
||||
* For instance, check out the example below. The image at the left is the original and the image at the right is the result after applying the opening transformation. We can observe that the small spaces in the corners of the letter tend to dissapear.
|
||||
|
||||
.. image:: images/Morphology_2_Tutorial_Theory_Opening.png
|
||||
:alt: Opening
|
||||
:align: center
|
||||
|
||||
Closing
|
||||
---------
|
||||
|
||||
* It is obtained by the dilation of an image followed by an erosion.
|
||||
|
||||
.. math::
|
||||
|
||||
dst = close( src, element ) = erode( dilate( src, element ) )
|
||||
|
||||
* Useful to remove small holes (dark regions).
|
||||
|
||||
.. image:: images/Morphology_2_Tutorial_Theory_Closing.png
|
||||
:alt: Closing example
|
||||
:align: center
|
||||
|
||||
|
||||
Morphological Gradient
|
||||
------------------------
|
||||
|
||||
* It is the difference between the dilation and the erosion of an image.
|
||||
|
||||
.. math::
|
||||
|
||||
dst = morph_{grad}( src, element ) = dilate( src, element ) - erode( src, element )
|
||||
|
||||
* It is useful for finding the outline of an object as can be seen below:
|
||||
|
||||
.. image:: images/Morphology_2_Tutorial_Theory_Gradient.png
|
||||
:alt: Gradient
|
||||
:align: center
|
||||
|
||||
|
||||
Top Hat
|
||||
---------
|
||||
|
||||
* It is the difference between an input image and its opening.
|
||||
|
||||
.. math::
|
||||
|
||||
dst = tophat( src, element ) = src - open( src, element )
|
||||
|
||||
.. image:: images/Morphology_2_Tutorial_Theory_TopHat.png
|
||||
:alt: Top Hat
|
||||
:align: center
|
||||
|
||||
Black Hat
|
||||
----------
|
||||
|
||||
* It is the difference between the closing and its input image
|
||||
|
||||
.. math::
|
||||
|
||||
dst = blackhat( src, element ) = close( src, element ) - src
|
||||
|
||||
.. image:: images/Morphology_2_Tutorial_Theory_BlackHat.png
|
||||
:alt: Black Hat
|
||||
:align: center
|
||||
|
||||
Code
|
||||
======
|
||||
|
||||
This tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgProc/Morphology_2.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/// Global variables
|
||||
Mat src, dst;
|
||||
|
||||
int morph_elem = 0;
|
||||
int morph_size = 0;
|
||||
int morph_operator = 0;
|
||||
int const max_operator = 4;
|
||||
int const max_elem = 2;
|
||||
int const max_kernel_size = 21;
|
||||
|
||||
char* window_name = "Morphology Transformations Demo";
|
||||
|
||||
/* Function Headers */
|
||||
void Morphology_Operations( int, void* );
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load an image
|
||||
src = imread( argv[1] );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
/// Create window
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Create Trackbar to select Morphology operation
|
||||
createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );
|
||||
|
||||
/// Create Trackbar to select kernel type
|
||||
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
|
||||
&morph_elem, max_elem,
|
||||
Morphology_Operations );
|
||||
|
||||
/// Create Trackbar to choose kernel size
|
||||
createTrackbar( "Kernel size:\n 2n +1", window_name,
|
||||
&morph_size, max_kernel_size,
|
||||
Morphology_Operations );
|
||||
|
||||
/// Default start
|
||||
Morphology_Operations( 0, 0 );
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @function Morphology_Operations
|
||||
*/
|
||||
void Morphology_Operations( int, void* )
|
||||
{
|
||||
// Since MORPH_X : 2,3,4,5 and 6
|
||||
int operation = morph_operator + 2;
|
||||
|
||||
Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
|
||||
|
||||
/// Apply the specified morphology operation
|
||||
morphologyEx( src, dst, operation, element );
|
||||
imshow( window_name, dst );
|
||||
}
|
||||
|
||||
|
||||
Explanation
|
||||
=============
|
||||
|
||||
#. Let's check the general structure of the program:
|
||||
|
||||
* Load an image
|
||||
|
||||
* Create a window to display results of the Morphological operations
|
||||
|
||||
* Create 03 Trackbars for the user to enter parameters:
|
||||
|
||||
* The first trackbar **"Operator"** returns the kind of morphology operation to use (**morph_operator**).
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat",
|
||||
window_name, &morph_operator, max_operator,
|
||||
Morphology_Operations );
|
||||
|
||||
|
||||
|
||||
* The second trackbar **"Element"** returns **morph_elem**, which indicates what kind of structure our kernel is:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
|
||||
&morph_elem, max_elem,
|
||||
Morphology_Operations );
|
||||
|
||||
* The final trackbar **"Kernel Size"** returns the size of the kernel to be used (**morph_size**)
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
createTrackbar( "Kernel size:\n 2n +1", window_name,
|
||||
&morph_size, max_kernel_size,
|
||||
Morphology_Operations );
|
||||
|
||||
|
||||
* Every time we move any slider, the user's function **Morphology_Operations** will be called to effectuate a new morphology operation and it will update the output image based on the current trackbar values.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/*
|
||||
* @function Morphology_Operations
|
||||
*/
|
||||
void Morphology_Operations( int, void* )
|
||||
{
|
||||
// Since MORPH_X : 2,3,4,5 and 6
|
||||
int operation = morph_operator + 2;
|
||||
|
||||
Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
|
||||
|
||||
/// Apply the specified morphology operation
|
||||
morphologyEx( src, dst, operation, element );
|
||||
imshow( window_name, dst );
|
||||
}
|
||||
|
||||
|
||||
We can observe that the key function to perform the morphology transformations is :morphology_ex:`morphologyEx <>`. In this example we use four arguments (leaving the rest as defaults):
|
||||
|
||||
* **src** : Source (input) image
|
||||
* **dst**: Output image
|
||||
* **operation**: The kind of morphology transformation to be performed. Note that we have 5 alternatives:
|
||||
|
||||
+ *Opening*: MORPH_OPEN : 2
|
||||
+ *Closing*: MORPH_CLOSE: 3
|
||||
+ *Gradient*: MORPH_GRADIENT: 4
|
||||
+ *Top Hat*: MORPH_TOPHAT: 5
|
||||
+ *Black Hat*: MORPH_BLACKHAT: 6
|
||||
|
||||
As you can see the values range from <2-6>, that is why we add (+2) to the values entered by the Trackbar:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
int operation = morph_operator + 2;
|
||||
|
||||
* **element**: The kernel to be used. We use the function :get_structuring_element:`getStructuringElement <>` to define our own structure.
|
||||
|
||||
|
||||
|
||||
Results
|
||||
========
|
||||
|
||||
* After compiling the code above we can execute it giving an image path as an argument. For this tutorial we use as input the image: **baboon.png**:
|
||||
|
||||
.. image:: images/Morphology_2_Tutorial_Original_Image.jpg
|
||||
:alt: Morphology 2: Original image
|
||||
:align: center
|
||||
|
||||
* And here are two snapshots of the display window. The first picture shows the output after using the operator **Opening** with a cross kernel. The second picture (right side, shows the result of using a **Blackhat** operator with an ellipse kernel.
|
||||
|
||||
.. image:: images/Morphology_2_Tutorial_Result.jpg
|
||||
:alt: Morphology 2: Result sample
|
||||
:align: center
|
@@ -1,261 +0,0 @@
|
||||
.. _Pyramids:
|
||||
|
||||
Image Pyramids
|
||||
***************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV functions :pyr_up:`pyrUp <>` and :pyr_down:`pyrDown <>` to downsample or upsample a given image.
|
||||
|
||||
Theory
|
||||
=======
|
||||
|
||||
.. note::
|
||||
The explanation below belongs to the book **Learning OpenCV** by Bradski and Kaehler.
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Usually we need to convert an image to a size different than its original. For this, there are two possible options:
|
||||
|
||||
#. *Upsize* the image (zoom in) or
|
||||
#. *Downsize* it (zoom out).
|
||||
|
||||
* Although there is a *geometric transformation* function in OpenCV that -literally- resize an image (:resize:`resize <>`, which we will show in a future tutorial), in this section we analyze first the use of **Image Pyramids**, which are widely applied in a huge range of vision applications.
|
||||
|
||||
|
||||
Image Pyramid
|
||||
--------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* An image pyramid is a collection of images - all arising from a single original image - that are successively downsampled until some desired stopping point is reached.
|
||||
|
||||
* There are two common kinds of image pyramids:
|
||||
|
||||
* **Gaussian pyramid:** Used to downsample images
|
||||
|
||||
* **Laplacian pyramid:** Used to reconstruct an upsampled image from an image lower in the pyramid (with less resolution)
|
||||
|
||||
* In this tutorial we'll use the *Gaussian pyramid*.
|
||||
|
||||
Gaussian Pyramid
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
* Imagine the pyramid as a set of layers in which the higher the layer, the smaller the size.
|
||||
|
||||
.. image:: images/Pyramids_Tutorial_Pyramid_Theory.png
|
||||
:alt: Pyramid figure
|
||||
:align: center
|
||||
|
||||
* Every layer is numbered from bottom to top, so layer :math:`(i+1)` (denoted as :math:`G_{i+1}` is smaller than layer :math:`i` (:math:`G_{i}`).
|
||||
|
||||
* To produce layer :math:`(i+1)` in the Gaussian pyramid, we do the following:
|
||||
|
||||
* Convolve :math:`G_{i}` with a Gaussian kernel:
|
||||
|
||||
.. math::
|
||||
|
||||
\frac{1}{16} \begin{bmatrix} 1 & 4 & 6 & 4 & 1 \\ 4 & 16 & 24 & 16 & 4 \\ 6 & 24 & 36 & 24 & 6 \\ 4 & 16 & 24 & 16 & 4 \\ 1 & 4 & 6 & 4 & 1 \end{bmatrix}
|
||||
|
||||
* Remove every even-numbered row and column.
|
||||
|
||||
* You can easily notice that the resulting image will be exactly one-quarter the area of its predecessor. Iterating this process on the input image :math:`G_{0}` (original image) produces the entire pyramid.
|
||||
|
||||
* The procedure above was useful to downsample an image. What if we want to make it bigger?:
|
||||
|
||||
* First, upsize the image to twice the original in each dimension, wit the new even rows and columns filled with zeros (:math:`0`)
|
||||
|
||||
* Perform a convolution with the same kernel shown above (multiplied by 4) to approximate the values of the "missing pixels"
|
||||
|
||||
* These two procedures (downsampling and upsampling as explained above) are implemented by the OpenCV functions :pyr_up:`pyrUp <>` and :pyr_down:`pyrDown <>`, as we will see in an example with the code below:
|
||||
|
||||
.. note::
|
||||
When we reduce the size of an image, we are actually *losing* information of the image.
|
||||
|
||||
Code
|
||||
======
|
||||
|
||||
This tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgProc/Pyramids.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/// Global variables
|
||||
Mat src, dst, tmp;
|
||||
char* window_name = "Pyramids Demo";
|
||||
|
||||
|
||||
/*
|
||||
* @function main
|
||||
*/
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// General instructions
|
||||
printf( "\n Zoom In-Out demo \n " );
|
||||
printf( "------------------ \n" );
|
||||
printf( " * [u] -> Zoom in \n" );
|
||||
printf( " * [d] -> Zoom out \n" );
|
||||
printf( " * [ESC] -> Close program \n \n" );
|
||||
|
||||
/// Test image - Make sure it s divisible by 2^{n}
|
||||
src = imread( "../images/chicky_512.jpg" );
|
||||
if( !src.data )
|
||||
{ printf(" No data! -- Exiting the program \n");
|
||||
return -1; }
|
||||
|
||||
tmp = src;
|
||||
dst = tmp;
|
||||
|
||||
/// Create window
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
imshow( window_name, dst );
|
||||
|
||||
/// Loop
|
||||
while( true )
|
||||
{
|
||||
int c;
|
||||
c = waitKey(10);
|
||||
|
||||
if( (char)c == 27 )
|
||||
{ break; }
|
||||
if( (char)c == 'u' )
|
||||
{ pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );
|
||||
printf( "** Zoom In: Image x 2 \n" );
|
||||
}
|
||||
else if( (char)c == 'd' )
|
||||
{ pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );
|
||||
printf( "** Zoom Out: Image / 2 \n" );
|
||||
}
|
||||
|
||||
imshow( window_name, dst );
|
||||
tmp = dst;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Explanation
|
||||
=============
|
||||
|
||||
#. Let's check the general structure of the program:
|
||||
|
||||
* Load an image (in this case it is defined in the program, the user does not have to enter it as an argument)
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/// Test image - Make sure it s divisible by 2^{n}
|
||||
src = imread( "../images/chicky_512.jpg" );
|
||||
if( !src.data )
|
||||
{ printf(" No data! -- Exiting the program \n");
|
||||
return -1; }
|
||||
|
||||
* Create a Mat object to store the result of the operations (*dst*) and one to save temporal results (*tmp*).
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src, dst, tmp;
|
||||
/* ... */
|
||||
tmp = src;
|
||||
dst = tmp;
|
||||
|
||||
|
||||
|
||||
* Create a window to display the result
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
imshow( window_name, dst );
|
||||
|
||||
* Perform an infinite loop waiting for user input.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
while( true )
|
||||
{
|
||||
int c;
|
||||
c = waitKey(10);
|
||||
|
||||
if( (char)c == 27 )
|
||||
{ break; }
|
||||
if( (char)c == 'u' )
|
||||
{ pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );
|
||||
printf( "** Zoom In: Image x 2 \n" );
|
||||
}
|
||||
else if( (char)c == 'd' )
|
||||
{ pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );
|
||||
printf( "** Zoom Out: Image / 2 \n" );
|
||||
}
|
||||
|
||||
imshow( window_name, dst );
|
||||
tmp = dst;
|
||||
}
|
||||
|
||||
|
||||
Our program exits if the user presses *ESC*. Besides, it has two options:
|
||||
|
||||
* **Perform upsampling (after pressing 'u')**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 )
|
||||
|
||||
We use the function :pyr_up:`pyrUp <>` with 03 arguments:
|
||||
|
||||
* *tmp*: The current image, it is initialized with the *src* original image.
|
||||
* *dst*: The destination image (to be shown on screen, supposedly the double of the input image)
|
||||
* *Size( tmp.cols*2, tmp.rows*2 )* : The destination size. Since we are upsampling, :pyr_up:`pyrUp <>` expects a size double than the input image (in this case *tmp*).
|
||||
|
||||
* **Perform downsampling (after pressing 'd')**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 )
|
||||
|
||||
Similarly as with :pyr_up:`pyrUp <>`, we use the function :pyr_down:`pyrDown <>` with 03 arguments:
|
||||
|
||||
* *tmp*: The current image, it is initialized with the *src* original image.
|
||||
* *dst*: The destination image (to be shown on screen, supposedly half the input image)
|
||||
* *Size( tmp.cols/2, tmp.rows/2 )* : The destination size. Since we are upsampling, :pyr_down:`pyrDown <>` expects half the size the input image (in this case *tmp*).
|
||||
|
||||
* Notice that it is important that the input image can be divided by a factor of two (in both dimensions). Otherwise, an error will be shown.
|
||||
|
||||
* Finally, we update the input image **tmp** with the current image displayed, so the subsequent operations are performed on it.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
tmp = dst;
|
||||
|
||||
|
||||
|
||||
Results
|
||||
========
|
||||
|
||||
* After compiling the code above we can test it. The program calls an image **chicky_512.jpg** that comes in the *tutorial_code/image* folder. Notice that this image is :math:`512 \times 512`, hence a downsample won't generate any error (:math:`512 = 2^{9}`). The original image is shown below:
|
||||
|
||||
.. image:: images/Pyramids_Tutorial_Original_Image.jpg
|
||||
:alt: Pyramids: Original image
|
||||
:align: center
|
||||
|
||||
* First we apply two successive :pyr_down:`pyrDown <>` operations by pressing 'd'. Our output is:
|
||||
|
||||
.. image:: images/Pyramids_Tutorial_PyrDown_Result.jpg
|
||||
:alt: Pyramids: PyrDown Result
|
||||
:align: center
|
||||
|
||||
* Note that we should have lost some resolution due to the fact that we are diminishing the size of the image. This is evident after we apply :pyr_up:`pyrUp <>` twice (by pressing 'u'). Our output is now:
|
||||
|
||||
.. image:: images/Pyramids_Tutorial_PyrUp_Result.jpg
|
||||
:alt: Pyramids: PyrUp Result
|
||||
:align: center
|
@@ -1,123 +0,0 @@
|
||||
.. _bounding_rects_circles:
|
||||
|
||||
|
||||
Creating Bounding boxes and circles for contours
|
||||
*************************************************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :bounding_rect:`boundingRect <>`
|
||||
* Use the OpenCV function :min_enclosing_circle:`minEnclosingCircle <>`
|
||||
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
This tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ShapeDescriptors/generalContours_demo1.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
Mat src; Mat src_gray;
|
||||
int thresh = 100;
|
||||
int max_thresh = 255;
|
||||
RNG rng(12345);
|
||||
|
||||
/// Function header
|
||||
void thresh_callback(int, void* );
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load source image and convert it to gray
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
/// Convert image to gray and blur it
|
||||
cvtColor( src, src_gray, COLOR_BGR2GRAY );
|
||||
blur( src_gray, src_gray, Size(3,3) );
|
||||
|
||||
/// Create Window
|
||||
char* source_window = "Source";
|
||||
namedWindow( source_window, WINDOW_AUTOSIZE );
|
||||
imshow( source_window, src );
|
||||
|
||||
createTrackbar( " Threshold:", "Source", &thresh, max_thresh, thresh_callback );
|
||||
thresh_callback( 0, 0 );
|
||||
|
||||
waitKey(0);
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* @function thresh_callback */
|
||||
void thresh_callback(int, void* )
|
||||
{
|
||||
Mat threshold_output;
|
||||
vector<vector<Point> > contours;
|
||||
vector<Vec4i> hierarchy;
|
||||
|
||||
/// Detect edges using Threshold
|
||||
threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY );
|
||||
/// Find contours
|
||||
findContours( threshold_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0) );
|
||||
|
||||
/// Approximate contours to polygons + get bounding rects and circles
|
||||
vector<vector<Point> > contours_poly( contours.size() );
|
||||
vector<Rect> boundRect( contours.size() );
|
||||
vector<Point2f>center( contours.size() );
|
||||
vector<float>radius( contours.size() );
|
||||
|
||||
for( int i = 0; i < contours.size(); i++ )
|
||||
{ approxPolyDP( Mat(contours[i]), contours_poly[i], 3, true );
|
||||
boundRect[i] = boundingRect( Mat(contours_poly[i]) );
|
||||
minEnclosingCircle( (Mat)contours_poly[i], center[i], radius[i] );
|
||||
}
|
||||
|
||||
|
||||
/// Draw polygonal contour + bonding rects + circles
|
||||
Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 );
|
||||
for( int i = 0; i< contours.size(); i++ )
|
||||
{
|
||||
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
|
||||
drawContours( drawing, contours_poly, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
|
||||
rectangle( drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0 );
|
||||
circle( drawing, center[i], (int)radius[i], color, 2, 8, 0 );
|
||||
}
|
||||
|
||||
/// Show in a window
|
||||
namedWindow( "Contours", WINDOW_AUTOSIZE );
|
||||
imshow( "Contours", drawing );
|
||||
}
|
||||
|
||||
Explanation
|
||||
============
|
||||
|
||||
Result
|
||||
======
|
||||
|
||||
#. Here it is:
|
||||
|
||||
========== ==========
|
||||
|BRC_0| |BRC_1|
|
||||
========== ==========
|
||||
|
||||
.. |BRC_0| image:: images/Bounding_Rects_Circles_Source_Image.jpg
|
||||
:align: middle
|
||||
|
||||
.. |BRC_1| image:: images/Bounding_Rects_Circles_Result.jpg
|
||||
:align: middle
|
@@ -1,125 +0,0 @@
|
||||
.. _bounding_rotated_ellipses:
|
||||
|
||||
|
||||
Creating Bounding rotated boxes and ellipses for contours
|
||||
**********************************************************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :min_area_rect:`minAreaRect <>`
|
||||
* Use the OpenCV function :fit_ellipse:`fitEllipse <>`
|
||||
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
This tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ShapeDescriptors/generalContours_demo2.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
Mat src; Mat src_gray;
|
||||
int thresh = 100;
|
||||
int max_thresh = 255;
|
||||
RNG rng(12345);
|
||||
|
||||
/// Function header
|
||||
void thresh_callback(int, void* );
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load source image and convert it to gray
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
/// Convert image to gray and blur it
|
||||
cvtColor( src, src_gray, COLOR_BGR2GRAY );
|
||||
blur( src_gray, src_gray, Size(3,3) );
|
||||
|
||||
/// Create Window
|
||||
char* source_window = "Source";
|
||||
namedWindow( source_window, WINDOW_AUTOSIZE );
|
||||
imshow( source_window, src );
|
||||
|
||||
createTrackbar( " Threshold:", "Source", &thresh, max_thresh, thresh_callback );
|
||||
thresh_callback( 0, 0 );
|
||||
|
||||
waitKey(0);
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* @function thresh_callback */
|
||||
void thresh_callback(int, void* )
|
||||
{
|
||||
Mat threshold_output;
|
||||
vector<vector<Point> > contours;
|
||||
vector<Vec4i> hierarchy;
|
||||
|
||||
/// Detect edges using Threshold
|
||||
threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY );
|
||||
/// Find contours
|
||||
findContours( threshold_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0) );
|
||||
|
||||
/// Find the rotated rectangles and ellipses for each contour
|
||||
vector<RotatedRect> minRect( contours.size() );
|
||||
vector<RotatedRect> minEllipse( contours.size() );
|
||||
|
||||
for( int i = 0; i < contours.size(); i++ )
|
||||
{ minRect[i] = minAreaRect( Mat(contours[i]) );
|
||||
if( contours[i].size() > 5 )
|
||||
{ minEllipse[i] = fitEllipse( Mat(contours[i]) ); }
|
||||
}
|
||||
|
||||
/// Draw contours + rotated rects + ellipses
|
||||
Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 );
|
||||
for( int i = 0; i< contours.size(); i++ )
|
||||
{
|
||||
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
|
||||
// contour
|
||||
drawContours( drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
|
||||
// ellipse
|
||||
ellipse( drawing, minEllipse[i], color, 2, 8 );
|
||||
// rotated rectangle
|
||||
Point2f rect_points[4]; minRect[i].points( rect_points );
|
||||
for( int j = 0; j < 4; j++ )
|
||||
line( drawing, rect_points[j], rect_points[(j+1)%4], color, 1, 8 );
|
||||
}
|
||||
|
||||
/// Show in a window
|
||||
namedWindow( "Contours", WINDOW_AUTOSIZE );
|
||||
imshow( "Contours", drawing );
|
||||
}
|
||||
|
||||
Explanation
|
||||
============
|
||||
|
||||
Result
|
||||
======
|
||||
|
||||
#. Here it is:
|
||||
|
||||
========== ==========
|
||||
|BRE_0| |BRE_1|
|
||||
========== ==========
|
||||
|
||||
.. |BRE_0| image:: images/Bounding_Rotated_Ellipses_Source_Image.jpg
|
||||
:align: middle
|
||||
|
||||
.. |BRE_1| image:: images/Bounding_Rotated_Ellipses_Result.jpg
|
||||
:align: middle
|
@@ -1,106 +0,0 @@
|
||||
.. _find_contours:
|
||||
|
||||
Finding contours in your image
|
||||
******************************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :find_contours:`findContours <>`
|
||||
* Use the OpenCV function :draw_contours:`drawContours <>`
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
This tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ShapeDescriptors/findContours_demo.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
Mat src; Mat src_gray;
|
||||
int thresh = 100;
|
||||
int max_thresh = 255;
|
||||
RNG rng(12345);
|
||||
|
||||
/// Function header
|
||||
void thresh_callback(int, void* );
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load source image and convert it to gray
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
/// Convert image to gray and blur it
|
||||
cvtColor( src, src_gray, COLOR_BGR2GRAY );
|
||||
blur( src_gray, src_gray, Size(3,3) );
|
||||
|
||||
/// Create Window
|
||||
char* source_window = "Source";
|
||||
namedWindow( source_window, WINDOW_AUTOSIZE );
|
||||
imshow( source_window, src );
|
||||
|
||||
createTrackbar( " Canny thresh:", "Source", &thresh, max_thresh, thresh_callback );
|
||||
thresh_callback( 0, 0 );
|
||||
|
||||
waitKey(0);
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* @function thresh_callback */
|
||||
void thresh_callback(int, void* )
|
||||
{
|
||||
Mat canny_output;
|
||||
vector<vector<Point> > contours;
|
||||
vector<Vec4i> hierarchy;
|
||||
|
||||
/// Detect edges using canny
|
||||
Canny( src_gray, canny_output, thresh, thresh*2, 3 );
|
||||
/// Find contours
|
||||
findContours( canny_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0) );
|
||||
|
||||
/// Draw contours
|
||||
Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 );
|
||||
for( int i = 0; i< contours.size(); i++ )
|
||||
{
|
||||
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
|
||||
drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, Point() );
|
||||
}
|
||||
|
||||
/// Show in a window
|
||||
namedWindow( "Contours", WINDOW_AUTOSIZE );
|
||||
imshow( "Contours", drawing );
|
||||
}
|
||||
|
||||
Explanation
|
||||
============
|
||||
|
||||
Result
|
||||
======
|
||||
|
||||
#. Here it is:
|
||||
|
||||
============= =============
|
||||
|contour_0| |contour_1|
|
||||
============= =============
|
||||
|
||||
.. |contour_0| image:: images/Find_Contours_Original_Image.jpg
|
||||
:align: middle
|
||||
|
||||
.. |contour_1| image:: images/Find_Contours_Result.jpg
|
||||
:align: middle
|
@@ -1,115 +0,0 @@
|
||||
.. _hull:
|
||||
|
||||
Convex Hull
|
||||
***********
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :convex_hull:`convexHull <>`
|
||||
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
This tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ShapeDescriptors/hull_demo.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
Mat src; Mat src_gray;
|
||||
int thresh = 100;
|
||||
int max_thresh = 255;
|
||||
RNG rng(12345);
|
||||
|
||||
/// Function header
|
||||
void thresh_callback(int, void* );
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load source image and convert it to gray
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
/// Convert image to gray and blur it
|
||||
cvtColor( src, src_gray, COLOR_BGR2GRAY );
|
||||
blur( src_gray, src_gray, Size(3,3) );
|
||||
|
||||
/// Create Window
|
||||
char* source_window = "Source";
|
||||
namedWindow( source_window, WINDOW_AUTOSIZE );
|
||||
imshow( source_window, src );
|
||||
|
||||
createTrackbar( " Threshold:", "Source", &thresh, max_thresh, thresh_callback );
|
||||
thresh_callback( 0, 0 );
|
||||
|
||||
waitKey(0);
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* @function thresh_callback */
|
||||
void thresh_callback(int, void* )
|
||||
{
|
||||
Mat src_copy = src.clone();
|
||||
Mat threshold_output;
|
||||
vector<vector<Point> > contours;
|
||||
vector<Vec4i> hierarchy;
|
||||
|
||||
/// Detect edges using Threshold
|
||||
threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY );
|
||||
|
||||
/// Find contours
|
||||
findContours( threshold_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0) );
|
||||
|
||||
/// Find the convex hull object for each contour
|
||||
vector<vector<Point> >hull( contours.size() );
|
||||
for( int i = 0; i < contours.size(); i++ )
|
||||
{ convexHull( Mat(contours[i]), hull[i], false ); }
|
||||
|
||||
/// Draw contours + hull results
|
||||
Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 );
|
||||
for( int i = 0; i< contours.size(); i++ )
|
||||
{
|
||||
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
|
||||
drawContours( drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
|
||||
drawContours( drawing, hull, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
|
||||
}
|
||||
|
||||
/// Show in a window
|
||||
namedWindow( "Hull demo", WINDOW_AUTOSIZE );
|
||||
imshow( "Hull demo", drawing );
|
||||
}
|
||||
|
||||
|
||||
Explanation
|
||||
============
|
||||
|
||||
Result
|
||||
======
|
||||
|
||||
#. Here it is:
|
||||
|
||||
========== ==========
|
||||
|Hull_0| |Hull_1|
|
||||
========== ==========
|
||||
|
||||
.. |Hull_0| image:: images/Hull_Original_Image.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Hull_1| image:: images/Hull_Result.jpg
|
||||
:align: middle
|
@@ -1,135 +0,0 @@
|
||||
.. _moments:
|
||||
|
||||
|
||||
Image Moments
|
||||
**************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :moments:`moments <>`
|
||||
* Use the OpenCV function :contour_area:`contourArea <>`
|
||||
* Use the OpenCV function :arc_length:`arcLength <>`
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
This tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ShapeDescriptors/moments_demo.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
Mat src; Mat src_gray;
|
||||
int thresh = 100;
|
||||
int max_thresh = 255;
|
||||
RNG rng(12345);
|
||||
|
||||
/// Function header
|
||||
void thresh_callback(int, void* );
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load source image and convert it to gray
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
/// Convert image to gray and blur it
|
||||
cvtColor( src, src_gray, COLOR_BGR2GRAY );
|
||||
blur( src_gray, src_gray, Size(3,3) );
|
||||
|
||||
/// Create Window
|
||||
char* source_window = "Source";
|
||||
namedWindow( source_window, WINDOW_AUTOSIZE );
|
||||
imshow( source_window, src );
|
||||
|
||||
createTrackbar( " Canny thresh:", "Source", &thresh, max_thresh, thresh_callback );
|
||||
thresh_callback( 0, 0 );
|
||||
|
||||
waitKey(0);
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* @function thresh_callback */
|
||||
void thresh_callback(int, void* )
|
||||
{
|
||||
Mat canny_output;
|
||||
vector<vector<Point> > contours;
|
||||
vector<Vec4i> hierarchy;
|
||||
|
||||
/// Detect edges using canny
|
||||
Canny( src_gray, canny_output, thresh, thresh*2, 3 );
|
||||
/// Find contours
|
||||
findContours( canny_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0) );
|
||||
|
||||
/// Get the moments
|
||||
vector<Moments> mu(contours.size() );
|
||||
for( int i = 0; i < contours.size(); i++ )
|
||||
{ mu[i] = moments( contours[i], false ); }
|
||||
|
||||
/// Get the mass centers:
|
||||
vector<Point2f> mc( contours.size() );
|
||||
for( int i = 0; i < contours.size(); i++ )
|
||||
{ mc[i] = Point2f( mu[i].m10/mu[i].m00 , mu[i].m01/mu[i].m00 ); }
|
||||
|
||||
/// Draw contours
|
||||
Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 );
|
||||
for( int i = 0; i< contours.size(); i++ )
|
||||
{
|
||||
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
|
||||
drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, Point() );
|
||||
circle( drawing, mc[i], 4, color, -1, 8, 0 );
|
||||
}
|
||||
|
||||
/// Show in a window
|
||||
namedWindow( "Contours", WINDOW_AUTOSIZE );
|
||||
imshow( "Contours", drawing );
|
||||
|
||||
/// Calculate the area with the moments 00 and compare with the result of the OpenCV function
|
||||
printf("\t Info: Area and Contour Length \n");
|
||||
for( int i = 0; i< contours.size(); i++ )
|
||||
{
|
||||
printf(" * Contour[%d] - Area (M_00) = %.2f - Area OpenCV: %.2f - Length: %.2f \n", i, mu[i].m00, contourArea(contours[i]), arcLength( contours[i], true ) );
|
||||
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
|
||||
drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, Point() );
|
||||
circle( drawing, mc[i], 4, color, -1, 8, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
Explanation
|
||||
============
|
||||
|
||||
Result
|
||||
======
|
||||
|
||||
#. Here it is:
|
||||
|
||||
========== ========== ==========
|
||||
|MU_0| |MU_1| |MU_2|
|
||||
========== ========== ==========
|
||||
|
||||
.. |MU_0| image:: images/Moments_Source_Image.jpg
|
||||
:width: 250pt
|
||||
:align: middle
|
||||
|
||||
.. |MU_1| image:: images/Moments_Result1.jpg
|
||||
:width: 250pt
|
||||
:align: middle
|
||||
|
||||
.. |MU_2| image:: images/Moments_Result2.jpg
|
||||
:width: 250pt
|
||||
:align: middle
|
@@ -1,116 +0,0 @@
|
||||
.. _point_polygon_test:
|
||||
|
||||
Point Polygon Test
|
||||
*******************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :point_polygon_test:`pointPolygonTest <>`
|
||||
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
This tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ShapeDescriptors/pointPolygonTest_demo.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Create an image
|
||||
const int r = 100;
|
||||
Mat src = Mat::zeros( Size( 4*r, 4*r ), CV_8UC1 );
|
||||
|
||||
/// Create a sequence of points to make a contour:
|
||||
vector<Point2f> vert(6);
|
||||
|
||||
vert[0] = Point( 1.5*r, 1.34*r );
|
||||
vert[1] = Point( 1*r, 2*r );
|
||||
vert[2] = Point( 1.5*r, 2.866*r );
|
||||
vert[3] = Point( 2.5*r, 2.866*r );
|
||||
vert[4] = Point( 3*r, 2*r );
|
||||
vert[5] = Point( 2.5*r, 1.34*r );
|
||||
|
||||
/// Draw it in src
|
||||
for( int j = 0; j < 6; j++ )
|
||||
{ line( src, vert[j], vert[(j+1)%6], Scalar( 255 ), 3, 8 ); }
|
||||
|
||||
/// Get the contours
|
||||
vector<vector<Point> > contours; vector<Vec4i> hierarchy;
|
||||
Mat src_copy = src.clone();
|
||||
|
||||
findContours( src_copy, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
|
||||
|
||||
/// Calculate the distances to the contour
|
||||
Mat raw_dist( src.size(), CV_32FC1 );
|
||||
|
||||
for( int j = 0; j < src.rows; j++ )
|
||||
{ for( int i = 0; i < src.cols; i++ )
|
||||
{ raw_dist.at<float>(j,i) = pointPolygonTest( contours[0], Point2f(i,j), true ); }
|
||||
}
|
||||
|
||||
double minVal; double maxVal;
|
||||
minMaxLoc( raw_dist, &minVal, &maxVal, 0, 0, Mat() );
|
||||
minVal = abs(minVal); maxVal = abs(maxVal);
|
||||
|
||||
/// Depicting the distances graphically
|
||||
Mat drawing = Mat::zeros( src.size(), CV_8UC3 );
|
||||
|
||||
for( int j = 0; j < src.rows; j++ )
|
||||
{ for( int i = 0; i < src.cols; i++ )
|
||||
{
|
||||
if( raw_dist.at<float>(j,i) < 0 )
|
||||
{ drawing.at<Vec3b>(j,i)[0] = 255 - (int) abs(raw_dist.at<float>(j,i))*255/minVal; }
|
||||
else if( raw_dist.at<float>(j,i) > 0 )
|
||||
{ drawing.at<Vec3b>(j,i)[2] = 255 - (int) raw_dist.at<float>(j,i)*255/maxVal; }
|
||||
else
|
||||
{ drawing.at<Vec3b>(j,i)[0] = 255; drawing.at<Vec3b>(j,i)[1] = 255; drawing.at<Vec3b>(j,i)[2] = 255; }
|
||||
}
|
||||
}
|
||||
|
||||
/// Create Window and show your results
|
||||
char* source_window = "Source";
|
||||
namedWindow( source_window, WINDOW_AUTOSIZE );
|
||||
imshow( source_window, src );
|
||||
namedWindow( "Distance", WINDOW_AUTOSIZE );
|
||||
imshow( "Distance", drawing );
|
||||
|
||||
waitKey(0);
|
||||
return(0);
|
||||
}
|
||||
|
||||
Explanation
|
||||
============
|
||||
|
||||
Result
|
||||
======
|
||||
|
||||
#. Here it is:
|
||||
|
||||
========== ==========
|
||||
|PPT_0| |PPT_1|
|
||||
========== ==========
|
||||
|
||||
.. |PPT_0| image:: images/Point_Polygon_Test_Source_Image.png
|
||||
:align: middle
|
||||
|
||||
.. |PPT_1| image:: images/Point_Polygon_Test_Result.jpg
|
||||
:align: middle
|
@@ -1,541 +0,0 @@
|
||||
.. _Table-Of-Content-ImgProc:
|
||||
|
||||
*imgproc* module. Image Processing
|
||||
-----------------------------------------------------------
|
||||
|
||||
In this section you will learn about the image processing (manipulation) functions inside OpenCV.
|
||||
|
||||
.. include:: ../../definitions/tocDefinitions.rst
|
||||
|
||||
|
||||
+
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|ImageProcessing_1| **Title:** :ref:`Smoothing`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Let's take a look at some basic linear filters!
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |ImageProcessing_1| image:: images/Smoothing_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|ImageProcessing_2| **Title:** :ref:`Morphology_1`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
Author: |Author_AnaH|
|
||||
|
||||
Let's *change* the shape of objects!
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |ImageProcessing_2| image:: images/Morphology_1_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
================= ==================================================
|
||||
|Morphology_2| **Title:** :ref:`Morphology_2`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Here we investigate different morphology operators
|
||||
|
||||
================= ==================================================
|
||||
|
||||
.. |Morphology_2| image:: images/Morphology_2_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|Pyramids| **Title:** :ref:`Pyramids`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
What if I need a bigger/smaller image?
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |Pyramids| image:: images/Pyramids_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|Threshold| **Title:** :ref:`Basic_Threshold`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
After so much processing, it is time to decide which pixels stay!
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |Threshold| image:: images/Threshold_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
+
|
||||
===================== ==============================================
|
||||
|Filter_2D| **Title:** :ref:`filter_2d`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn to design our own filters by using OpenCV functions
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |Filter_2D| image:: images/imgtrans/Filter_2D_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
+
|
||||
===================== ==============================================
|
||||
|CopyMakeBorder| **Title:** :ref:`copyMakeBorderTutorial`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to pad our images!
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |CopyMakeBorder| image:: images/imgtrans/CopyMakeBorder_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|SobelDerivatives| **Title:** :ref:`sobel_derivatives`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to calculate gradients and use them to detect edges!
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |SobelDerivatives| image:: images/imgtrans/Sobel_Derivatives_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|LaplaceOperator| **Title:** :ref:`laplace_operator`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn about the *Laplace* operator and how to detect edges with it.
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |LaplaceOperator| image:: images/imgtrans/Laplace_Operator_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|CannyDetector| **Title:** :ref:`canny_detector`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn a sophisticated alternative to detect edges.
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |CannyDetector| image:: images/imgtrans/Canny_Detector_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|HoughLines| **Title:** :ref:`hough_lines`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to detect lines
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |HoughLines| image:: images/imgtrans/Hough_Lines_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|HoughCircle| **Title:** :ref:`hough_circle`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to detect circles
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |HoughCircle| image:: images/imgtrans/Hough_Circle_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|Remap| **Title:** :ref:`remap`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to manipulate pixels locations
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |Remap| image:: images/imgtrans/Remap_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|WarpAffine| **Title:** :ref:`warp_affine`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to rotate, translate and scale our images
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |WarpAffine| image:: images/imgtrans/Warp_Affine_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|HistEqualization| **Title:** :ref:`histogram_equalization`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to improve the contrast in our images
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |HistEqualization| image:: images/histograms/Histogram_Equalization_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|HistCalculation| **Title:** :ref:`histogram_calculation`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to create and generate histograms
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |HistCalculation| image:: images/histograms/Histogram_Calculation_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|HistComparison| **Title:** :ref:`histogram_comparison`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn to calculate metrics between histograms
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |HistComparison| image:: images/histograms/Histogram_Comparison_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|BackProjection| **Title:** :ref:`back_projection`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to use histograms to find similar objects in images
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |BackProjection| image:: images/histograms/Back_Projection_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|TemplateMatching| **Title:** :ref:`template_matching`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to match templates in an image
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |TemplateMatching| image:: images/histograms/Template_Matching_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|FindContours| **Title:** :ref:`find_contours`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to find contours of objects in our image
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |FindContours| image:: images/shapedescriptors/Find_Contours_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|Hull| **Title:** :ref:`hull`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to get hull contours and draw them!
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |Hull| image:: images/shapedescriptors/Hull_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
===================== ==============================================
|
||||
|BRC| **Title:** :ref:`bounding_rects_circles`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to obtain bounding boxes and circles for our contours.
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |BRC| image:: images/shapedescriptors/Bounding_Rects_Circles_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
|
||||
===================== ==============================================
|
||||
|BRE| **Title:** :ref:`bounding_rotated_ellipses`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to obtain rotated bounding boxes and ellipses for our contours.
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |BRE| image:: images/shapedescriptors/Bounding_Rotated_Ellipses_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
|
||||
===================== ==============================================
|
||||
|MU| **Title:** :ref:`moments`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn to calculate the moments of an image
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |MU| image:: images/shapedescriptors/Moments_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
|
||||
+
|
||||
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
|
||||
===================== ==============================================
|
||||
|PPT| **Title:** :ref:`point_polygon_test`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
Where we learn how to calculate distances from the image to contours
|
||||
|
||||
===================== ==============================================
|
||||
|
||||
.. |PPT| image:: images/shapedescriptors/Point_Polygon_Test_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\pagebreak
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
../gausian_median_blur_bilateral_filter/gausian_median_blur_bilateral_filter
|
||||
../erosion_dilatation/erosion_dilatation
|
||||
../opening_closing_hats/opening_closing_hats
|
||||
../pyramids/pyramids
|
||||
../threshold/threshold
|
||||
../imgtrans/filter_2d/filter_2d
|
||||
../imgtrans/copyMakeBorder/copyMakeBorder
|
||||
../imgtrans/sobel_derivatives/sobel_derivatives
|
||||
../imgtrans/laplace_operator/laplace_operator
|
||||
../imgtrans/canny_detector/canny_detector
|
||||
../imgtrans/hough_lines/hough_lines
|
||||
../imgtrans/hough_circle/hough_circle
|
||||
../imgtrans/remap/remap
|
||||
../imgtrans/warp_affine/warp_affine
|
||||
../histograms/histogram_equalization/histogram_equalization
|
||||
../histograms/histogram_calculation/histogram_calculation
|
||||
../histograms/histogram_comparison/histogram_comparison
|
||||
../histograms/back_projection/back_projection
|
||||
../histograms/template_matching/template_matching
|
||||
../shapedescriptors/find_contours/find_contours
|
||||
../shapedescriptors/hull/hull
|
||||
../shapedescriptors/bounding_rects_circles/bounding_rects_circles
|
||||
../shapedescriptors/bounding_rotated_ellipses/bounding_rotated_ellipses
|
||||
../shapedescriptors/moments/moments
|
||||
../shapedescriptors/point_polygon_test/point_polygon_test
|
@@ -1,310 +0,0 @@
|
||||
.. _Basic_Threshold:
|
||||
|
||||
Basic Thresholding Operations
|
||||
*******************************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Perform basic thresholding operations using OpenCV function :threshold:`threshold <>`
|
||||
|
||||
|
||||
Cool Theory
|
||||
============
|
||||
|
||||
.. note::
|
||||
The explanation below belongs to the book **Learning OpenCV** by Bradski and Kaehler.
|
||||
|
||||
What is Thresholding?
|
||||
-----------------------
|
||||
|
||||
* The simplest segmentation method
|
||||
|
||||
* Application example: Separate out regions of an image corresponding to objects which we want to analyze. This separation is based on the variation of intensity between the object pixels and the background pixels.
|
||||
|
||||
* To differentiate the pixels we are interested in from the rest (which will eventually be rejected), we perform a comparison of each pixel intensity value with respect to a *threshold* (determined according to the problem to solve).
|
||||
|
||||
* Once we have separated properly the important pixels, we can set them with a determined value to identify them (i.e. we can assign them a value of :math:`0` (black), :math:`255` (white) or any value that suits your needs).
|
||||
|
||||
.. image:: images/Threshold_Tutorial_Theory_Example.jpg
|
||||
:alt: Threshold simple example
|
||||
:align: center
|
||||
|
||||
Types of Thresholding
|
||||
-----------------------
|
||||
|
||||
* OpenCV offers the function :threshold:`threshold <>` to perform thresholding operations.
|
||||
|
||||
* We can effectuate :math:`5` types of Thresholding operations with this function. We will explain them in the following subsections.
|
||||
|
||||
* To illustrate how these thresholding processes work, let's consider that we have a source image with pixels with intensity values :math:`src(x,y)`. The plot below depicts this. The horizontal blue line represents the threshold :math:`thresh` (fixed).
|
||||
|
||||
.. image:: images/Threshold_Tutorial_Theory_Base_Figure.png
|
||||
:alt: Threshold Binary
|
||||
:align: center
|
||||
|
||||
Threshold Binary
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
* This thresholding operation can be expressed as:
|
||||
|
||||
.. math::
|
||||
|
||||
\texttt{dst} (x,y) = \fork{\texttt{maxVal}}{if $\texttt{src}(x,y) > \texttt{thresh}$}{0}{otherwise}
|
||||
|
||||
* So, if the intensity of the pixel :math:`src(x,y)` is higher than :math:`thresh`, then the new pixel intensity is set to a :math:`MaxVal`. Otherwise, the pixels are set to :math:`0`.
|
||||
|
||||
.. image:: images/Threshold_Tutorial_Theory_Binary.png
|
||||
:alt: Threshold Binary
|
||||
:align: center
|
||||
|
||||
|
||||
Threshold Binary, Inverted
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* This thresholding operation can be expressed as:
|
||||
|
||||
.. math::
|
||||
|
||||
\texttt{dst} (x,y) = \fork{0}{if $\texttt{src}(x,y) > \texttt{thresh}$}{\texttt{maxVal}}{otherwise}
|
||||
|
||||
* If the intensity of the pixel :math:`src(x,y)` is higher than :math:`thresh`, then the new pixel intensity is set to a :math:`0`. Otherwise, it is set to :math:`MaxVal`.
|
||||
|
||||
.. image:: images/Threshold_Tutorial_Theory_Binary_Inverted.png
|
||||
:alt: Threshold Binary Inverted
|
||||
:align: center
|
||||
|
||||
Truncate
|
||||
^^^^^^^^^
|
||||
|
||||
* This thresholding operation can be expressed as:
|
||||
|
||||
.. math::
|
||||
|
||||
\texttt{dst} (x,y) = \fork{\texttt{threshold}}{if $\texttt{src}(x,y) > \texttt{thresh}$}{\texttt{src}(x,y)}{otherwise}
|
||||
|
||||
* The maximum intensity value for the pixels is :math:`thresh`, if :math:`src(x,y)` is greater, then its value is *truncated*. See figure below:
|
||||
|
||||
.. image:: images/Threshold_Tutorial_Theory_Truncate.png
|
||||
:alt: Threshold Truncate
|
||||
:align: center
|
||||
|
||||
|
||||
|
||||
Threshold to Zero
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* This operation can be expressed as:
|
||||
|
||||
.. math::
|
||||
|
||||
\texttt{dst} (x,y) = \fork{\texttt{src}(x,y)}{if $\texttt{src}(x,y) > \texttt{thresh}$}{0}{otherwise}
|
||||
|
||||
* If :math:`src(x,y)` is lower than :math:`thresh`, the new pixel value will be set to :math:`0`.
|
||||
|
||||
.. image:: images/Threshold_Tutorial_Theory_Zero.png
|
||||
:alt: Threshold Zero
|
||||
:align: center
|
||||
|
||||
|
||||
Threshold to Zero, Inverted
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* This operation can be expressed as:
|
||||
|
||||
.. math::
|
||||
|
||||
\texttt{dst} (x,y) = \fork{0}{if $\texttt{src}(x,y) > \texttt{thresh}$}{\texttt{src}(x,y)}{otherwise}
|
||||
|
||||
* If :math:`src(x,y)` is greater than :math:`thresh`, the new pixel value will be set to :math:`0`.
|
||||
|
||||
.. image:: images/Threshold_Tutorial_Theory_Zero_Inverted.png
|
||||
:alt: Threshold Zero Inverted
|
||||
:align: center
|
||||
|
||||
|
||||
Code
|
||||
======
|
||||
|
||||
The tutorial code's is shown lines below. You can also download it from `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/ImgProc/Threshold.cpp>`_
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
/// Global variables
|
||||
|
||||
int threshold_value = 0;
|
||||
int threshold_type = 3;;
|
||||
int const max_value = 255;
|
||||
int const max_type = 4;
|
||||
int const max_BINARY_value = 255;
|
||||
|
||||
Mat src, src_gray, dst;
|
||||
char* window_name = "Threshold Demo";
|
||||
|
||||
char* trackbar_type = "Type: \n 0: Binary \n 1: Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted";
|
||||
char* trackbar_value = "Value";
|
||||
|
||||
/// Function headers
|
||||
void Threshold_Demo( int, void* );
|
||||
|
||||
/*
|
||||
* @function main
|
||||
*/
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load an image
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
/// Convert the image to Gray
|
||||
cvtColor( src, src_gray, COLOR_RGB2GRAY );
|
||||
|
||||
/// Create a window to display results
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Create Trackbar to choose type of Threshold
|
||||
createTrackbar( trackbar_type,
|
||||
window_name, &threshold_type,
|
||||
max_type, Threshold_Demo );
|
||||
|
||||
createTrackbar( trackbar_value,
|
||||
window_name, &threshold_value,
|
||||
max_value, Threshold_Demo );
|
||||
|
||||
/// Call the function to initialize
|
||||
Threshold_Demo( 0, 0 );
|
||||
|
||||
/// Wait until user finishes program
|
||||
while(true)
|
||||
{
|
||||
int c;
|
||||
c = waitKey( 20 );
|
||||
if( (char)c == 27 )
|
||||
{ break; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @function Threshold_Demo
|
||||
*/
|
||||
void Threshold_Demo( int, void* )
|
||||
{
|
||||
/* 0: Binary
|
||||
1: Binary Inverted
|
||||
2: Threshold Truncated
|
||||
3: Threshold to Zero
|
||||
4: Threshold to Zero Inverted
|
||||
*/
|
||||
|
||||
threshold( src_gray, dst, threshold_value, max_BINARY_value,threshold_type );
|
||||
|
||||
imshow( window_name, dst );
|
||||
}
|
||||
|
||||
|
||||
|
||||
Explanation
|
||||
=============
|
||||
|
||||
|
||||
#. Let's check the general structure of the program:
|
||||
|
||||
* Load an image. If it is RGB we convert it to Grayscale. For this, remember that we can use the function :cvt_color:`cvtColor <>`:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
/// Convert the image to Gray
|
||||
cvtColor( src, src_gray, COLOR_RGB2GRAY );
|
||||
|
||||
|
||||
* Create a window to display the result
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow( window_name, WINDOW_AUTOSIZE );
|
||||
|
||||
* Create :math:`2` trackbars for the user to enter user input:
|
||||
|
||||
* **Type of thresholding**: Binary, To Zero, etc...
|
||||
* **Threshold value**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
createTrackbar( trackbar_type,
|
||||
window_name, &threshold_type,
|
||||
max_type, Threshold_Demo );
|
||||
|
||||
createTrackbar( trackbar_value,
|
||||
window_name, &threshold_value,
|
||||
max_value, Threshold_Demo );
|
||||
|
||||
* Wait until the user enters the threshold value, the type of thresholding (or until the program exits)
|
||||
|
||||
* Whenever the user changes the value of any of the Trackbars, the function *Threshold_Demo* is called:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/*
|
||||
* @function Threshold_Demo
|
||||
*/
|
||||
void Threshold_Demo( int, void* )
|
||||
{
|
||||
/* 0: Binary
|
||||
1: Binary Inverted
|
||||
2: Threshold Truncated
|
||||
3: Threshold to Zero
|
||||
4: Threshold to Zero Inverted
|
||||
*/
|
||||
|
||||
threshold( src_gray, dst, threshold_value, max_BINARY_value,threshold_type );
|
||||
|
||||
imshow( window_name, dst );
|
||||
}
|
||||
|
||||
As you can see, the function :threshold:`threshold <>` is invoked. We give :math:`5` parameters:
|
||||
|
||||
* *src_gray*: Our input image
|
||||
* *dst*: Destination (output) image
|
||||
* *threshold_value*: The :math:`thresh` value with respect to which the thresholding operation is made
|
||||
* *max_BINARY_value*: The value used with the Binary thresholding operations (to set the chosen pixels)
|
||||
* *threshold_type*: One of the :math:`5` thresholding operations. They are listed in the comment section of the function above.
|
||||
|
||||
|
||||
|
||||
Results
|
||||
========
|
||||
|
||||
#. After compiling this program, run it giving a path to an image as argument. For instance, for an input image as:
|
||||
|
||||
|
||||
.. image:: images/Threshold_Tutorial_Original_Image.jpg
|
||||
:alt: Threshold Original Image
|
||||
:align: center
|
||||
|
||||
#. First, we try to threshold our image with a *binary threhold inverted*. We expect that the pixels brighter than the :math:`thresh` will turn dark, which is what actually happens, as we can see in the snapshot below (notice from the original image, that the doggie's tongue and eyes are particularly bright in comparison with the image, this is reflected in the output image).
|
||||
|
||||
|
||||
.. image:: images/Threshold_Tutorial_Result_Binary_Inverted.jpg
|
||||
:alt: Threshold Result Binary Inverted
|
||||
:align: center
|
||||
|
||||
|
||||
#. Now we try with the *threshold to zero*. With this, we expect that the darkest pixels (below the threshold) will become completely black, whereas the pixels with value greater than the threshold will keep its original value. This is verified by the following snapshot of the output image:
|
||||
|
||||
.. image:: images/Threshold_Tutorial_Result_Zero.jpg
|
||||
:alt: Threshold Result Zero
|
||||
:align: center
|
Reference in New Issue
Block a user