Doxygen tutorials: basic structure

This commit is contained in:
Maksim Shabunin
2014-11-27 15:39:05 +03:00
parent 220f671655
commit 8375182e34
99 changed files with 17805 additions and 0 deletions

View File

@@ -0,0 +1,256 @@
Eroding and Dilating {#tutorial_erosion_dilatation}
====================
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:
- @ref cv::erode
- @ref cv::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)
### Dilation
- This operations consists of convoluting an image \f$A\f$ with some kernel (\f$B\f$), which can have any
shape or size, usually a square or circle.
- The kernel \f$B\f$ has a defined *anchor point*, usually being the center of the kernel.
- As the kernel \f$B\f$ is scanned over the image, we compute the maximal pixel value overlapped by
\f$B\f$ 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)
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 \f$B\f$ is scanned over the image, we compute the minimal pixel value overlapped by
\f$B\f$ 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)
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{.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 );
}
@endcode
Explanation
-----------
1. 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:
- 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:
2. **erosion:**
@code{.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 );
}
@endcode
- The function that performs the *erosion* operation is @ref cv::erode . As we can see, it
receives three arguments:
- *src*: The source image
- *erosion_dst*: The output image
- *element*: This is the kernel we will use to perform the operation. If we do not
specify, the default is a simple @ref cv::3x3\` matrix. Otherwise, we can specify its
shape. For this, we need to use the function
get_structuring_element:\`getStructuringElement :
@code{.cpp}
Mat element = getStructuringElement( erosion_type,
Size( 2*erosion_size + 1, 2*erosion_size+1 ),
Point( erosion_size, erosion_size ) );
@endcode
We can choose any of three shapes for our kernel:
- Rectangular box: MORPH_RECT
- Cross: MORPH_CROSS
- Ellipse: MORPH_ELLIPSE
Then, we just have to specify the size of our kernel and the *anchor point*. If not
specified, it is assumed to be in the center.
- That is all. We are ready to perform the erosion of our image.
@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.
1. **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{.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 );
}
@endcode
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)
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_Tutorial_Cover.jpg)

View File

@@ -0,0 +1,273 @@
Smoothing Images {#tutorial_gausian_median_blur_bilateral_filter}
================
Goal
----
In this tutorial you will learn how to apply diverse linear filters to smooth images using OpenCV
functions such as:
- @ref cv::blur
- @ref cv::GaussianBlur
- @ref cv::medianBlur
- @ref cv::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. \f$g(i,j)\f$) is determined as a
weighted sum of input pixel values (i.e. \f$f(i+k,j+l)\f$) :
\f[g(i,j) = \sum_{k,l} f(i+k, j+l) h(k,l)\f]
\f$h(k,l)\f$ 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
- 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:
\f[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}\f]
### Gaussian Filter
- 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)
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 :
\f[G_{0}(x, y) = A e^{ \dfrac{ -(x - \mu_{x})^{2} }{ 2\sigma^{2}_{x} } + \dfrac{ -(y - \mu_{y})^{2} }{ 2\sigma^{2}_{y} } }\f]
where \f$\mu\f$ is the mean (the peak) and \f$\sigma\f$ represents the variance (per each of the
variables \f$x\f$ and \f$y\f$)
### 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
- 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
----
- **What does this program do?**
- 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{.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;
}
@endcode
Explanation
-----------
1. Let's check the OpenCV functions that involve only the smoothing procedure, since the rest is
already known by now.
2. **Normalized Block Filter:**
OpenCV offers the function @ref cv::blur to perform smoothing with this filter.
@code{.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; } }
@endcode
We specify 4 arguments (more details, check the Reference):
- *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.
3. **Gaussian Filter:**
It is performed by the function @ref cv::GaussianBlur :
@code{.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; } }
@endcode
Here we use 4 arguments (more details, check the OpenCV reference):
- *src*: Source image
- *dst*: Destination image
- *Size(w, h)*: The size of the kernel to be used (the neighbors to be considered). \f$w\f$ and
\f$h\f$ have to be odd and positive numbers otherwise thi size will be calculated using the
\f$\sigma_{x}\f$ and \f$\sigma_{y}\f$ arguments.
- \f$\sigma_{x}\f$: The standard deviation in x. Writing \f$0\f$ implies that \f$\sigma_{x}\f$ is
calculated using kernel size.
- \f$\sigma_{y}\f$: The standard deviation in y. Writing \f$0\f$ implies that \f$\sigma_{y}\f$ is
calculated using kernel size.
4. **Median Filter:**
This filter is provided by the @ref cv::medianBlur function:
@code{.cpp}
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
{ medianBlur ( src, dst, i );
if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
@endcode
We use three arguments:
- *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.
5. **Bilateral Filter**
Provided by OpenCV function @ref cv::bilateralFilter
@code{.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; } }
@endcode
We use 5 arguments:
- *src*: Source image
- *dst*: Destination image
- *d*: The diameter of each pixel neighborhood.
- \f$\sigma_{Color}\f$: Standard deviation in the color space.
- \f$\sigma_{Space}\f$: Standard deviation in the coordinate space (in pixel terms)
Results
-------
- 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)

View File

@@ -0,0 +1,259 @@
Back Projection {#tutorial_back_projection}
===============
Goal
----
In this tutorial you will learn:
- What is Back Projection and why it is useful
- How to use the OpenCV function @ref cv::calcBackProject to calculate Back Projection
- How to mix different channels of an image by using the OpenCV function @ref cv::mixChannels
Theory
------
### What is Back Projection?
- 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?
- 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|
------ ------
- Now, let's imagine that you get another hand image (Test Image) like the one below: (with its
respective histogram):
------ ------
|T2| |T3|
------ ------
- 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. \f$p(i,j)\f$ ), collect the data and find the
correspondent bin location for that pixel (i.e. \f$( h_{i,j}, s_{i,j} )\f$ ).
b. Lookup the *model histogram* in the correspondent bin - \f$( h_{i,j}, s_{i,j} )\f$ - 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)
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
----
- **What does this program do?**
- Loads an image
- Convert the original to HSV format and separate only *Hue* channel to be used for the
Histogram (using the OpenCV function @ref cv::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{.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 );
}
@endcode
Explanation
-----------
1. Declare the matrices to store our images and initialize the number of bins to be used by our
histogram:
@code{.cpp}
Mat src; Mat hsv; Mat hue;
int bins = 25;
@endcode
2. Read the input image and transform it to HSV format:
@code{.cpp}
src = imread( argv[1], 1 );
cvtColor( src, hsv, COLOR_BGR2HSV );
@endcode
3. 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{.cpp}
hue.create( hsv.size(), hsv.depth() );
int ch[] = { 0, 0 };
mixChannels( &hsv, 1, &hue, 1, ch, 1 );
@endcode
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:
- **&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
4. 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{.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);
@endcode
5. Show the image and wait for the user to exit the program:
@code{.cpp}
imshow( window_image, src );
waitKey(0);
return 0;
@endcode
6. **Hist_and_Backproj function:** Initialize the arguments needed for @ref cv::calcHist . The
number of bins comes from the Trackbar:
@code{.cpp}
void Hist_and_Backproj(int, void* )
{
MatND hist;
int histSize = MAX( bins, 2 );
float hue_range[] = { 0, 180 };
const float* ranges = { hue_range };
@endcode
7. Calculate the Histogram and normalize it to the range \f$[0,255]\f$
@code{.cpp}
calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
@endcode
8. Get the Backprojection of the same image by calling the function @ref cv::calcBackProject
@code{.cpp}
MatND backproj;
calcBackProject( &hue, 1, 0, hist, backproj, &ranges, 1, true );
@endcode
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)
9. Display backproj:
@code{.cpp}
imshow( "BackProj", backproj );
@endcode
10. Draw the 1-D Hue histogram of the image:
@code{.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 );
@endcode
Results
-------
1. 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|
------ ------ ------

View File

@@ -0,0 +1,289 @@
Histogram Calculation {#tutorial_histogram_calculation}
=====================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::split to divide an image into its correspondent planes.
- To calculate histograms of arrays of images by using the OpenCV function @ref cv::calcHist
- To normalize an array by using the function @ref cv::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?
- 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 \f$0-255\f$):
![image](images/Histogram_Calculation_Theory_Hist0.jpg)
- 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:
\f[\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}\f]
and we can keep count of the number of pixels that fall in the range of each \f$bin_{i}\f$. 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)
- 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 \f$bin_{x}\f$ and \f$bin_{y}\f$ for each feature and z would be the number of
counts for each combination of \f$(bin_{x}, bin_{y})\f$. The same would apply for more features (of
course it gets trickier).
### What OpenCV offers you
For simple purposes, OpenCV implements the function @ref cv::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
----
- **What does this program do?**
- Loads an image
- Splits the image into its R, G and B planes using the function @ref cv::split
- Calculate the Histogram of each 1-channel plane by calling the function @ref cv::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{.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;
}
@endcode
Explanation
-----------
1. Create the necessary matrices:
@code{.cpp}
Mat src, dst;
@endcode
2. Load the source image
@code{.cpp}
src = imread( argv[1], 1 );
if( !src.data )
{ return -1; }
@endcode
3. Separate the source image in its three R,G and B planes. For this we use the OpenCV function
@ref cv::split :
@code{.cpp}
vector<Mat> bgr_planes;
split( src, bgr_planes );
@endcode
our input is the image to be divided (this case with three channels) and the output is a vector
of Mat )
4. 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 \f$[0,255]\f$
a. Establish number of bins (5, 10...):
@code{.cpp}
int histSize = 256; //from 0 to 255
@endcode
b. Set the range of values (as we said, between 0 and 255 )
@code{.cpp}
/// Set the ranges ( for B,G,R) )
float range[] = { 0, 256 } ; //the upper boundary is exclusive
const float* histRange = { range };
@endcode
c. We want our bins to have the same size (uniform) and to clear the histograms in the
beginning, so:
@code{.cpp}
bool uniform = true; bool accumulate = false;
@endcode
d. Finally, we create the Mat objects to save our histograms. Creating 3 (one for each plane):
@code{.cpp}
Mat b_hist, g_hist, r_hist;
@endcode
e. We proceed to calculate the histograms by using the OpenCV function @ref cv::calcHist :
@code{.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 );
@endcode
where the arguments are:
- **&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.
5. Create an image to display the histograms:
@code{.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) );
@endcode
6. Notice that before drawing, we first @ref cv::normalize the histogram so its values fall in the
range indicated by the parameters entered:
@code{.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() );
@endcode
this function receives these arguments:
- **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 ofr_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
7. Finally, observe that to access the bin (in this case in this 1D-Histogram):
@code{.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 );
}
@endcode
we use the expression:
@code{.cpp}
b_hist.at<float>(i)
@endcode
where \f$i\f$ indicates the dimension. If it were a 2D-histogram we would use something like:
@code{.cpp}
b_hist.at<float>( i, j )
@endcode
8. Finally we display our histograms and wait for the user to exit:
@code{.cpp}
namedWindow("calcHist Demo", WINDOW_AUTOSIZE );
imshow("calcHist Demo", histImage );
waitKey(0);
return 0;
@endcode
Result
------
1. Using as input argument an image like the shown below:
![image](images/Histogram_Calculation_Original_Image.jpg)
2. Produces the following histogram:
![image](images/Histogram_Calculation_Result.jpg)

View File

@@ -0,0 +1,168 @@
Histogram Comparison {#tutorial_histogram_comparison}
====================
Goal
----
In this tutorial you will learn how to:
- Use the function @ref cv::compareHist to get a numerical parameter that express how well two
histograms match with each other.
- Use different metrics to compare histograms
Theory
------
- To compare two histograms ( \f$H_{1}\f$ and \f$H_{2}\f$ ), first we have to choose a *metric*
(\f$d(H_{1}, H_{2})\f$) to express how well both histograms match.
- OpenCV implements the function @ref cv::compareHist to perform a comparison. It also offers 4
different metrics to compute the matching:
a. **Correlation ( CV_COMP_CORREL )**
\f[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}}\f]
where
\f[\bar{H_k} = \frac{1}{N} \sum _J H_k(J)\f]
and \f$N\f$ is the total number of histogram bins.
b. **Chi-Square ( CV_COMP_CHISQR )**
\f[d(H_1,H_2) = \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)}\f]
c. **Intersection ( method=CV_COMP_INTERSECT )**
\f[d(H_1,H_2) = \sum _I \min (H_1(I), H_2(I))\f]
d. **Bhattacharyya distance ( CV_COMP_BHATTACHARYYA )**
\f[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)}}\f]
Code
----
- **What does this program do?**
- 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:**
@includelineno cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp
Explanation
-----------
1. Declare variables such as the matrices to store the base image and the two other images to
compare ( RGB and HSV )
@code{.cpp}
Mat src_base, hsv_base;
Mat src_test1, hsv_test1;
Mat src_test2, hsv_test2;
Mat hsv_half_down;
@endcode
2. Load the base image (src_base) and the other two test images:
@code{.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 );
@endcode
3. Convert them to HSV format:
@code{.cpp}
cvtColor( src_base, hsv_base, COLOR_BGR2HSV );
cvtColor( src_test1, hsv_test1, COLOR_BGR2HSV );
cvtColor( src_test2, hsv_test2, COLOR_BGR2HSV );
@endcode
4. Also, create an image of half the base image (in HSV format):
@code{.cpp}
hsv_half_down = hsv_base( Range( hsv_base.rows/2, hsv_base.rows - 1 ), Range( 0, hsv_base.cols - 1 ) );
@endcode
5. Initialize the arguments to calculate the histograms (bins, ranges and channels H and S ).
@code{.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 };
@endcode
6. Create the MatND objects to store the histograms:
@code{.cpp}
MatND hist_base;
MatND hist_half_down;
MatND hist_test1;
MatND hist_test2;
@endcode
7. Calculate the Histograms for the base image, the 2 test images and the half-down base image:
@code{.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() );
@endcode
8. Apply sequentially the 4 comparison methods between the histogram of the base image (hist_base)
and the other histograms:
@code{.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 );
}
@endcode
Results
-------
1. We use as input the following images:
----------- ----------- -----------
|Base_0| |Test_1| |Test_2|
----------- ----------- -----------
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.
2. 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:
3. 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.

View File

@@ -0,0 +1,180 @@
Histogram Equalization {#tutorial_histogram_equalization}
======================
Goal
----
In this tutorial you will learn:
- What an image histogram is and why it is useful
- To equalize histograms of images by using the OpenCV <function@ref> cv::equalizeHist
Theory
------
### What is an Image Histogram?
- 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)
### What is Histogram Equalization?
- 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)
### How does it work?
- 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 \f$H(i)\f$, its
*cumulative distribution* \f$H^{'}(i)\f$ is:
\f[H^{'}(i) = \sum_{0 \le j < i} H(j)\f]
To use this as a remapping function, we have to normalize \f$H^{'}(i)\f$ 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)
- Finally, we use a simple remapping procedure to obtain the intensity values of the equalized
image:
\f[equalized( x, y ) = H^{'}( src(x,y) )\f]
Code
----
- **What does this program do?**
- Loads an image
- Convert the original image to grayscale
- Equalize the Histogram by using the OpenCV function @ref cv::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{.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;
}
@endcode
Explanation
-----------
1. Declare the source and destination images as well as the windows names:
@code{.cpp}
Mat src, dst;
char* source_window = "Source image";
char* equalized_window = "Equalized Image";
@endcode
2. Load the source image:
@code{.cpp}
src = imread( argv[1], 1 );
if( !src.data )
{ cout<<"Usage: ./Histogram_Demo <path_to_image>"<<endl;
return -1;}
@endcode
3. Convert it to grayscale:
@code{.cpp}
cvtColor( src, src, COLOR_BGR2GRAY );
@endcode
4. Apply histogram equalization with the function @ref cv::equalizeHist :
@code{.cpp}
equalizeHist( src, dst );
@endcode
As it can be easily seen, the only arguments are the original image and the output (equalized)
image.
5. Display both images (original and equalized) :
@code{.cpp}
namedWindow( source_window, WINDOW_AUTOSIZE );
namedWindow( equalized_window, WINDOW_AUTOSIZE );
imshow( source_window, src );
imshow( equalized_window, dst );
@endcode
6. Wait until user exists the program
@code{.cpp}
waitKey(0);
return 0;
@endcode
Results
-------
1. 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)
which, by the way, has this histogram:
![image](images/Histogram_Equalization_Original_Histogram.jpg)
notice that the pixels are clustered around the center of the histogram.
2. After applying the equalization with our program, we get this result:
![image](images/Histogram_Equalization_Equalized_Image.jpg)
this image has certainly more contrast. Check out its new histogram like this:
![image](images/Histogram_Equalization_Equalized_Histogram.jpg)
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!

View File

@@ -0,0 +1,304 @@
Template Matching {#tutorial_template_matching}
=================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::matchTemplate to search for matches between an image patch and
an input image
- Use the OpenCV function @ref cv::minMaxLoc to find the maximum and minimum values (as well as
their positions) in a given array.
Theory
------
### What is template matching?
Template matching is a technique for finding areas of an image that match (are similar) to a
template image (patch).
### How does it work?
- 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)
- 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)
- 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 \f$(x,y)\f$ in **R** contains the match metric:
![image](images/Template_Matching_Template_Theory_Result.jpg)
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 @ref cv::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 @ref cv::matchTemplate . The
available methods are 6:
a. **method=CV_TM_SQDIFF**
\f[R(x,y)= \sum _{x',y'} (T(x',y')-I(x+x',y+y'))^2\f]
b. **method=CV_TM_SQDIFF_NORMED**
\f[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}}\f]
c. **method=CV_TM_CCORR**
\f[R(x,y)= \sum _{x',y'} (T(x',y') \cdot I(x+x',y+y'))\f]
d. **method=CV_TM_CCORR_NORMED**
\f[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}}\f]
e. **method=CV_TM_CCOEFF**
\f[R(x,y)= \sum _{x',y'} (T'(x',y') \cdot I(x+x',y+y'))\f]
where
\f[\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]
f. **method=CV_TM_CCOEFF_NORMED**
\f[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} }\f]
Code
----
- **What does this program do?**
- Loads an input image and a image patch (*template*)
- Perform a template matching procedure by using the OpenCV function @ref cv::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{.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;
}
@endcode
Explanation
-----------
1. Declare some global variables, such as the image, template and result matrices, as well as the
match method and the window names:
@code{.cpp}
Mat img; Mat templ; Mat result;
char* image_window = "Source Image";
char* result_window = "Result window";
int match_method;
int max_Trackbar = 5;
@endcode
2. Load the source image and template:
@code{.cpp}
img = imread( argv[1], 1 );
templ = imread( argv[2], 1 );
@endcode
3. Create the windows to show the results:
@code{.cpp}
namedWindow( image_window, WINDOW_AUTOSIZE );
namedWindow( result_window, WINDOW_AUTOSIZE );
@endcode
4. 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{.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 );
@endcode
5. Wait until user exits the program.
@code{.cpp}
waitKey(0);
return 0;
@endcode
6. Let's check out the callback function. First, it makes a copy of the source image:
@code{.cpp}
Mat img_display;
img.copyTo( img_display );
@endcode
7. 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{.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 );
@endcode
8. Perform the template matching operation:
@code{.cpp}
matchTemplate( img, templ, result, match_method );
@endcode
the arguments are naturally the input image **I**, the template **T**, the result **R** and the
match_method (given by the Trackbar)
9. We normalize the results:
@code{.cpp}
normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );
@endcode
10. We localize the minimum and maximum values in the result matrix **R** by using @ref
cv::minMaxLoc .
@code{.cpp}
double minVal; double maxVal; Point minLoc; Point maxLoc;
Point matchLoc;
minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );
@endcode
the function calls as arguments:
- **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
11. 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{.cpp}
if( match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED )
{ matchLoc = minLoc; }
else
{ matchLoc = maxLoc; }
@endcode
12. Display the source image and the result matrix. Draw a rectangle around the highest possible
matching area:
@code{.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 );
@endcode
Results
-------
1. Testing our program with an input image such as:
![image](images/Template_Matching_Original_Image.jpg)
and a template image:
![image](images/Template_Matching_Template_Image.jpg)
2. 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|
3. 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)

View File

@@ -0,0 +1,254 @@
Canny Edge Detector {#tutorial_canny_detector}
===================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::Canny to implement the Canny Edge Detector.
Theory
------
1. 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
1. Filter out any noise. The Gaussian filter is used for this purpose. An example of a Gaussian
kernel of \f$size = 5\f$ that might be used is shown below:
\f[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}\f]
2. Find the intensity gradient of the image. For this, we follow a procedure analogous to Sobel:
a. Apply a pair of convolution masks (in \f$x\f$ and \f$y\f$ directions:
\f[G_{x} = \begin{bmatrix}
-1 & 0 & +1 \\
-2 & 0 & +2 \\
-1 & 0 & +1
\end{bmatrix}\f]\f[G_{y} = \begin{bmatrix}
-1 & -2 & -1 \\
0 & 0 & 0 \\
+1 & +2 & +1
\end{bmatrix}\f]
b. Find the gradient strength and direction with:
\f[\begin{array}{l}
G = \sqrt{ G_{x}^{2} + G_{y}^{2} } \\
\theta = \arctan(\dfrac{ G_{y} }{ G_{x} })
\end{array}\f]
The direction is rounded to one of four possible angles (namely 0, 45, 90 or 135)
3. *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.
4. *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.
5. For more details, you can always consult your favorite Computer Vision book.
Code
----
1. **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.
2. 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{.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;
}
@endcode
Explanation
-----------
1. Create some needed variables:
@code{.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";
@endcode
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 \f$3\f$ (for the Sobel operations to be performed internally by the
Canny function)
c. We set a maximum value for the lower Threshold of \f$100\f$.
2. Loads the source image:
@code{.cpp}
/// Load an image
src = imread( argv[1] );
if( !src.data )
{ return -1; }
@endcode
3. Create a matrix of the same type and size of *src* (to be *dst*)
@code{.cpp}
dst.create( src.size(), src.type() );
@endcode
4. Convert the image to grayscale (using the function @ref cv::cvtColor :
@code{.cpp}
cvtColor( src, src_gray, COLOR_BGR2GRAY );
@endcode
5. Create a window to display the results
@code{.cpp}
namedWindow( window_name, WINDOW_AUTOSIZE );
@endcode
6. Create a Trackbar for the user to enter the lower threshold for our Canny detector:
@code{.cpp}
createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
@endcode
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.
7. Let's check the *CannyThreshold* function, step by step:
a. First, we blur the image with a filter of kernel size 3:
@code{.cpp}
blur( src_gray, detected_edges, Size(3,3) );
@endcode
b. Second, we apply the OpenCV function @ref cv::Canny :
@code{.cpp}
Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
@endcode
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)
8. We fill a *dst* image with zeros (meaning the image is completely black).
@code{.cpp}
dst = Scalar::all(0);
@endcode
9. Finally, we will use the function @ref cv::copyTo to map only the areas of the image that are
identified as edges (on a black background).
@code{.cpp}
src.copyTo( dst, detected_edges);
@endcode
@ref cv::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.
10. We display our result:
@code{.cpp}
imshow( window_name, dst );
@endcode
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)
- Moving the slider, trying different threshold, we obtain the following result:
![image](images/Canny_Detector_Tutorial_Result.jpg)
- Notice how the image is superposed to the black background on the edge regions.

View File

@@ -0,0 +1,208 @@
Adding borders to your images {#tutorial_copyMakeBorder}
=============================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::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.
1. 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?
2. 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).
3. 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 \f$0\f$
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
----
1. **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:
1. *Constant value border*: Applies a padding of a constant value for the whole border.
This value will be updated randomly each 0.5 seconds.
2. *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'
2. 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{.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;
}
@endcode
Explanation
-----------
1. First we declare the variables we are going to use:
@code{.cpp}
Mat src, dst;
int top, bottom, left, right;
int borderType;
Scalar value;
char* window_name = "copyMakeBorder Demo";
RNG rng(12345);
@endcode
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.
2. As usual we load our source image *src*:
@code{.cpp}
src = imread( argv[1] );
if( !src.data )
{ return -1;
printf(" No data entered, please enter the path to an image file \n");
}
@endcode
3. After giving a short intro of how to use the program, we create a window:
@code{.cpp}
namedWindow( window_name, WINDOW_AUTOSIZE );
@endcode
4. 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{.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);
@endcode
5. 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{.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; }
@endcode
6. In each iteration (after 0.5 seconds), the variable *value* is updated...
@code{.cpp}
value = Scalar( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );
@endcode
with a random value generated by the **RNG** variable *rng*. This value is a number picked
randomly in the range \f$[0,255]\f$
7. Finally, we call the function @ref cv::copyMakeBorder to apply the respective padding:
@code{.cpp}
copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );
@endcode
The arguments are:
a. *src*: Source image
b. *dst*: Destination image
c. *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.
d. *borderType*: Define what type of border is applied. It can be constant or replicate for
this example.
e. *value*: If *borderType* is *BORDER_CONSTANT*, this is the value used to fill the border
pixels.
8. We display our output image in the image created previously
@code{.cpp}
imshow( window_name, dst );
@endcode
Results
-------
1. After compiling the code above, you can execute it giving as argument the path of an image. The
result should be:
- 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)

View File

@@ -0,0 +1,184 @@
Making your own linear filters! {#tutorial_filter_2d}
===============================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::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)
### 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:
1. Place the kernel anchor on top of a determined pixel, with the rest of the kernel overlaying the
corresponding local pixels in the image.
2. Multiply the kernel coefficients by the corresponding image pixel values and sum the result.
3. Place the result to the location of the *anchor* in the input image.
4. 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:
\f[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)\f]
Fortunately, OpenCV provides you with the function @ref cv::filter2D so you do not have to code all
these operations.
Code
----
1. **What does this program do?**
- Loads an image
- Performs a *normalized box filter*. For instance, for a kernel of size \f$size = 3\f$, the
kernel would be:
\f[K = \dfrac{1}{3 \cdot 3} \begin{bmatrix}
1 & 1 & 1 \\
1 & 1 & 1 \\
1 & 1 & 1
\end{bmatrix}\f]
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
2. 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{.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;
}
@endcode
Explanation
-----------
1. Load an image
@code{.cpp}
src = imread( argv[1] );
if( !src.data )
{ return -1; }
@endcode
2. Create a window to display the result
@code{.cpp}
namedWindow( window_name, WINDOW_AUTOSIZE );
@endcode
3. Initialize the arguments for the linear filter
@code{.cpp}
anchor = Point( -1, -1 );
delta = 0;
ddepth = -1;
@endcode
4. Perform an infinite loop updating the kernel size and applying our linear filter to the input
image. Let's analyze that more in detail:
5. First we define the kernel our filter is going to use. Here it is:
@code{.cpp}
kernel_size = 3 + 2*( ind%5 );
kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
@endcode
The first line is to update the *kernel_size* to odd values in the range: \f$[3,11]\f$. The second
line actually builds the kernel by setting its value to a matrix filled with \f$1's\f$ and
normalizing it by dividing it between the number of elements.
6. After setting the kernel, we can generate the filter by using the function @ref cv::filter2D :
@code{.cpp}
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
@endcode
The arguments denote:
a. *src*: Source image
b. *dst*: Destination image
c. *ddepth*: The depth of *dst*. A negative value (such as \f$-1\f$) indicates that the depth is
the same as the source.
d. *kernel*: The kernel to be scanned through the image
e. *anchor*: The position of the anchor relative to its kernel. The location *Point(-1, -1)*
indicates the center by default.
f. *delta*: A value to be added to each pixel during the convolution. By default it is \f$0\f$
g. *BORDER_DEFAULT*: We let this value by default (more details in the following tutorial)
7. Our program will effectuate a *while* loop, each 500 ms the kernel size of our filter will be
updated in the range indicated.
Results
-------
1. 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)

View File

@@ -0,0 +1,161 @@
Hough Circle Transform {#tutorial_hough_circle}
======================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::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 \f$(r, \theta)\f$. In the circle
case, we need three parameters to define a circle:
\f[C : ( x_{center}, y_{center}, r )\f]
where \f$(x_{center}, y_{center})\f$ define the center position (green point) and \f$r\f$ 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)
- 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
----
1. **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.
2. 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{.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;
}
@endcode
Explanation
-----------
1. Load an image
@code{.cpp}
src = imread( argv[1], 1 );
if( !src.data )
{ return -1; }
@endcode
2. Convert it to grayscale:
@code{.cpp}
cvtColor( src, src_gray, COLOR_BGR2GRAY );
@endcode
3. Apply a Gaussian blur to reduce noise and avoid false circle detection:
@code{.cpp}
GaussianBlur( src_gray, src_gray, Size(9, 9), 2, 2 );
@endcode
4. Proceed to apply Hough Circle Transform:
@code{.cpp}
vector<Vec3f> circles;
HoughCircles( src_gray, circles, HOUGH_GRADIENT, 1, src_gray.rows/8, 200, 100, 0, 0 );
@endcode
with the arguments:
- *src_gray*: Input image (grayscale).
- *circles*: A vector that stores sets of 3 values: \f$x_{c}, y_{c}, r\f$ 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.
5. Draw the detected circles:
@code{.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 );
}
@endcode
You can see that we will draw the circle(s) on red and the center(s) with a small green dot
6. Display the detected circle(s):
@code{.cpp}
namedWindow( "Hough Circle Transform Demo", WINDOW_AUTOSIZE );
imshow( "Hough Circle Transform Demo", src );
@endcode
7. Wait for the user to exit the program
@code{.cpp}
waitKey(0);
@endcode
Result
------
The result of running the code above with a test image is shown below:
![image](images/Hough_Circle_Tutorial_Result.jpg)

View File

@@ -0,0 +1,270 @@
Hough Line Transform {#tutorial_hough_lines}
====================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV functions @ref cv::HoughLines and @ref cv::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?
1. As you know, a line in the image space can be expressed with two variables. For example:
a. In the **Cartesian coordinate system:** Parameters: \f$(m,b)\f$.
b. In the **Polar coordinate system:** Parameters: \f$(r,\theta)\f$
![image](images/Hough_Lines_Tutorial_Theory_0.jpg)
For Hough Transforms, we will express lines in the *Polar system*. Hence, a line equation can be
written as:
\f[y = \left ( -\dfrac{\cos \theta}{\sin \theta} \right ) x + \left ( \dfrac{r}{\sin \theta} \right )\f]
Arranging the terms: \f$r = x \cos \theta + y \sin \theta\f$
1. In general for each point \f$(x_{0}, y_{0})\f$, we can define the family of lines that goes through
that point as:
\f[r_{\theta} = x_{0} \cdot \cos \theta + y_{0} \cdot \sin \theta\f]
Meaning that each pair \f$(r_{\theta},\theta)\f$ represents each line that passes by
\f$(x_{0}, y_{0})\f$.
2. If for a given \f$(x_{0}, y_{0})\f$ we plot the family of lines that goes through it, we get a
sinusoid. For instance, for \f$x_{0} = 8\f$ and \f$y_{0} = 6\f$ we get the following plot (in a plane
\f$\theta\f$ - \f$r\f$):
![image](images/Hough_Lines_Tutorial_Theory_1.jpg)
We consider only points such that \f$r > 0\f$ and \f$0< \theta < 2 \pi\f$.
3. 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 \f$\theta\f$ - \f$r\f$, 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: \f$x_{1} = 9\f$, \f$y_{1} = 4\f$ and \f$x_{2} = 12\f$, \f$y_{2} = 3\f$, we get:
![image](images/Hough_Lines_Tutorial_Theory_2.jpg)
The three plots intersect in one single point \f$(0.925, 9.6)\f$, these coordinates are the
parameters (\f$\theta, r\f$) or the line in which \f$(x_{0}, y_{0})\f$, \f$(x_{1}, y_{1})\f$ and
\f$(x_{2}, y_{2})\f$ lay.
4. 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.
5. 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 \f$(\theta, r_{\theta})\f$ 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 \f$(\theta, r_{\theta})\f$
- In OpenCV it is implemented with the function @ref cv::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 \f$(x_{0}, y_{0}, x_{1}, y_{1})\f$
- In OpenCV it is implemented with the function @ref cv::HoughLinesP
Code
----
1. **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.
2. The sample code that we will explain can be downloaded from here_. A slightly fancier version
(which shows both Hough standard and probabilistic with trackbars for changing the threshold
values) can be found here_.
@code{.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;
}
@endcode
Explanation
-----------
1. Load an image
@code{.cpp}
Mat src = imread(filename, 0);
if(src.empty())
{
help();
cout << "can not open " << filename << endl;
return -1;
}
@endcode
2. Detect the edges of the image by using a Canny detector
@code{.cpp}
Canny(src, dst, 50, 200, 3);
@endcode
Now we will apply the Hough Line Transform. We will explain how to use both OpenCV functions
available for this purpose:
3. **Standard Hough Line Transform**
a. First, you apply the Transform:
@code{.cpp}
vector<Vec2f> lines;
HoughLines(dst, lines, 1, CV_PI/180, 100, 0, 0 );
@endcode
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 \f$(r,\theta)\f$ of the detected lines
- *rho* : The resolution of the parameter \f$r\f$ in pixels. We use **1** pixel.
- *theta*: The resolution of the parameter \f$\theta\f$ 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{.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);
}
@endcode
4. **Probabilistic Hough Line Transform**
a. First you apply the transform:
@code{.cpp}
vector<Vec4i> lines;
HoughLinesP(dst, lines, 1, CV_PI/180, 50, 50, 10 );
@endcode
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
\f$(x_{start}, y_{start}, x_{end}, y_{end})\f$ of the detected lines
- *rho* : The resolution of the parameter \f$r\f$ in pixels. We use **1** pixel.
- *theta*: The resolution of the parameter \f$\theta\f$ 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{.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);
}
@endcode
5. Display the original image and the detected lines:
@code{.cpp}
imshow("source", src);
imshow("detected lines", cdst);
@endcode
6. Wait until the user exits the program
@code{.cpp}
waitKey();
@endcode
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)
We get the following result by using the Probabilistic Hough Line Transform:
![image](images/Hough_Lines_Tutorial_Result.jpg)
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).

View File

@@ -0,0 +1,168 @@
Laplace Operator {#tutorial_laplace_operator}
================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::Laplacian to implement a discrete analog of the *Laplacian
operator*.
Theory
------
1. 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)
2. And...what happens if we take the second derivative?
![image](images/Laplace_Operator_Tutorial_Theory_ddIntensity.jpg)
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
1. 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.
2. The *Laplacian operator* is defined by:
\f[Laplace(f) = \dfrac{\partial^{2} f}{\partial x^{2}} + \dfrac{\partial^{2} f}{\partial y^{2}}\f]
1. The Laplacian operator is implemented in OpenCV by the function @ref cv::Laplacian . In fact,
since the Laplacian uses the gradient of images, it calls internally the *Sobel* operator to
perform its computation.
Code
----
1. **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
2. 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{.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;
}
@endcode
Explanation
-----------
1. Create some needed variables:
@code{.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";
@endcode
2. Loads the source image:
@code{.cpp}
src = imread( argv[1] );
if( !src.data )
{ return -1; }
@endcode
3. Apply a Gaussian blur to reduce noise:
@code{.cpp}
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
@endcode
4. Convert the image to grayscale using @ref cv::cvtColor
@code{.cpp}
cvtColor( src, src_gray, COLOR_RGB2GRAY );
@endcode
5. Apply the Laplacian operator to the grayscale image:
@code{.cpp}
Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
@endcode
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.
6. Convert the output from the Laplacian operator to a *CV_8U* image:
@code{.cpp}
convertScaleAbs( dst, abs_dst );
@endcode
7. Display the result in a window:
@code{.cpp}
imshow( window_name, abs_dst );
@endcode
Results
-------
1. 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)
2. 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)

View File

@@ -0,0 +1,280 @@
Remapping {#tutorial_remap}
=========
Goal
----
In this tutorial you will learn how to:
a. Use the OpenCV function @ref cv::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 \f$(x,y)\f$ as:
\f[g(x,y) = f ( h(x,y) )\f]
where \f$g()\f$ is the remapped image, \f$f()\f$ the source image and \f$h(x,y)\f$ is the mapping function
that operates on \f$(x,y)\f$.
- Let's think in a quick example. Imagine that we have an image \f$I\f$ and, say, we want to do a
remap such that:
\f[h(x,y) = (I.cols - x, y )\f]
What would happen? It is easily seen that the image would flip in the \f$x\f$ direction. For
instance, consider the input image:
![image](images/Remap_Tutorial_Theory_0.jpg)
observe how the red circle changes positions with respect to x (considering \f$x\f$ the horizontal
direction):
![image](images/Remap_Tutorial_Theory_1.jpg)
- In OpenCV, the function @ref cv::remap offers a simple remapping implementation.
Code
----
1. **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
2. 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{.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++;
@endcode
}
Explanation
-----------
1. Create some variables we will use:
@code{.cpp}
Mat src, dst;
Mat map_x, map_y;
char* remap_window = "Remap demo";
int ind = 0;
@endcode
2. Load an image:
@code{.cpp}
src = imread( argv[1], 1 );
@endcode
3. Create the destination image and the two mapping matrices (for x and y )
@code{.cpp}
dst.create( src.size(), src.type() );
map_x.create( src.size(), CV_32FC1 );
map_y.create( src.size(), CV_32FC1 );
@endcode
4. Create a window to display results
@code{.cpp}
namedWindow( remap_window, WINDOW_AUTOSIZE );
@endcode
5. Establish a loop. Each 1000 ms we update our mapping matrices (*mat_x* and *mat_y*) and apply
them to our source image:
@code{.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 );
}
@endcode
The function that applies the remapping is @ref cv::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 \f$h(i,j)\f$
- **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:
6. **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:
\f[h(i,j) = ( 2*i - src.cols/2 + 0.5, 2*j - src.rows/2 + 0.5)\f]
for all pairs \f$(i,j)\f$ such that: \f$\dfrac{src.cols}{4}<i<\dfrac{3 \cdot src.cols}{4}\f$ and
\f$\dfrac{src.rows}{4}<j<\dfrac{3 \cdot src.rows}{4}\f$
b. Turn the image upside down: \f$h( i, j ) = (i, src.rows - j)\f$
c. Reflect the image from left to right: \f$h(i,j) = ( src.cols - i, j )\f$
d. Combination of b and c: \f$h(i,j) = ( src.cols - i, src.rows - j )\f$
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{.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++;
}
@endcode
Result
------
1. 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)
2. This is the result of reducing it to half the size and centering it:
![image](images/Remap_Tutorial_Result_0.jpg)
3. Turning it upside down:
![image](images/Remap_Tutorial_Result_1.jpg)
4. Reflecting it in the x direction:
![image](images/Remap_Tutorial_Result_2.jpg)
5. Reflecting it in both directions:
![image](images/Remap_Tutorial_Result_3.jpg)

View File

@@ -0,0 +1,243 @@
Sobel Derivatives {#tutorial_sobel_derivatives}
=================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::Sobel to calculate the derivatives from an image.
- Use the OpenCV function @ref cv::Scharr to calculate a more accurate derivative for a kernel of
size \f$3 \cdot 3\f$
Theory
------
@note The explanation below belongs to the book **Learning OpenCV** by Bradski and Kaehler.
1. 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).
2. 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)
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.
3. 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)
4. 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)
5. 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).
6. More detailed explanation, please refer to **Learning OpenCV** by Bradski and Kaehler
### Sobel Operator
1. The Sobel Operator is a discrete differentiation operator. It computes an approximation of the
gradient of an image intensity function.
2. The Sobel Operator combines Gaussian smoothing and differentiation.
#### Formulation
Assuming that the image to be operated is \f$I\f$:
1. We calculate two derivatives:
a. **Horizontal changes**: This is computed by convolving \f$I\f$ with a kernel \f$G_{x}\f$ with odd
size. For example for a kernel size of 3, \f$G_{x}\f$ would be computed as:
\f[G_{x} = \begin{bmatrix}
-1 & 0 & +1 \\
-2 & 0 & +2 \\
-1 & 0 & +1
\end{bmatrix} * I\f]
b. **Vertical changes**: This is computed by convolving \f$I\f$ with a kernel \f$G_{y}\f$ with odd
size. For example for a kernel size of 3, \f$G_{y}\f$ would be computed as:
\f[G_{y} = \begin{bmatrix}
-1 & -2 & -1 \\
0 & 0 & 0 \\
+1 & +2 & +1
\end{bmatrix} * I\f]
2. At each point of the image we calculate an approximation of the *gradient* in that point by
combining both results above:
\f[G = \sqrt{ G_{x}^{2} + G_{y}^{2} }\f]
Although sometimes the following simpler equation is used:
\f[G = |G_{x}| + |G_{y}|\f]
@note
When the size of the kernel is @ref cv::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:
\f[G_{x} = \begin{bmatrix}
-3 & 0 & +3 \\
-10 & 0 & +10 \\
-3 & 0 & +3
\end{bmatrix}\f]\f[G_{y} = \begin{bmatrix}
-3 & -10 & -3 \\
0 & 0 & 0 \\
+3 & +10 & +3
\end{bmatrix}\f]
You can check out more information of this function in the OpenCV reference (@ref cv::Scharr ).
Also, in the sample code below, you will notice that above the code for @ref cv::Sobel function
there is also code for the @ref cv::Scharr function commented. Uncommenting it (and obviously
commenting the Sobel stuff) should give you an idea of how this function works.
Code
----
1. **What does this program do?**
- Applies the *Sobel Operator* and generates as output an image with the detected *edges*
bright on a darker background.
2. 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{.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;
}
@endcode
Explanation
-----------
1. First we declare the variables we are going to use:
@code{.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;
@endcode
2. As usual we load our source image *src*:
@code{.cpp}
src = imread( argv[1] );
if( !src.data )
{ return -1; }
@endcode
3. First, we apply a @ref cv::GaussianBlur to our image to reduce the noise ( kernel size = 3 )
@code{.cpp}
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
@endcode
4. Now we convert our filtered image to grayscale:
@code{.cpp}
cvtColor( src, src_gray, COLOR_RGB2GRAY );
@endcode
5. Second, we calculate the "*derivatives*" in *x* and *y* directions. For this, we use the
function @ref cv::Sobel as shown below:
@code{.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 );
@endcode
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: \f$x_{order}= 1\f$ and
\f$y_{order} = 0\f$. We do analogously for the *y* direction.
6. We convert our partial results back to *CV_8U*:
@code{.cpp}
convertScaleAbs( grad_x, abs_grad_x );
convertScaleAbs( grad_y, abs_grad_y );
@endcode
7. 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{.cpp}
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
@endcode
8. Finally, we show our result:
@code{.cpp}
imshow( window_name, grad );
@endcode
Results
-------
1. Here is the output of applying our basic detector to *lena.jpg*:
![image](images/Sobel_Derivatives_Tutorial_Result.jpg)

View File

@@ -0,0 +1,273 @@
Affine Transformations {#tutorial_warp_affine}
======================
Goal
----
In this tutorial you will learn how to:
a. Use the OpenCV function @ref cv::warpAffine to implement simple remapping routines.
b. Use the OpenCV function @ref cv::getRotationMatrix2D to obtain a \f$2 \times 3\f$ rotation matrix
Theory
------
### What is an Affine Transformation?
1. It is any transformation that can be expressed in the form of a *matrix multiplication* (linear
transformation) followed by a *vector addition* (translation).
2. 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.
3. The usual way to represent an Affine Transform is by using a \f$2 \times 3\f$ matrix.
\f[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}\f]\f[M = \begin{bmatrix}
A & B
\end{bmatrix}
=\f]
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 \f$X = \begin{bmatrix}x \\ y\end{bmatrix}\f$ by
using \f$A\f$ and \f$B\f$, we can do it equivalently with:
\f$T = A \cdot \begin{bmatrix}x \\ y\end{bmatrix} + B\f$ or \f$T = M \cdot [x, y, 1]^{T}\f$
\f[T = \begin{bmatrix}
a_{00}x + a_{01}y + b_{00} \\
a_{10}x + a_{11}y + b_{10}
\end{bmatrix}\f]
### 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 \f$X\f$ and T and we also know that they are related. Then our job is to find \f$M\f$
b. We know \f$M\f$ and \f$X\f$. To obtain \f$T\f$ we only need to apply \f$T = M \cdot X\f$. Our information
for \f$M\f$ 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 \f$M\f$ 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)
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
----
1. **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 @ref cv::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
2. 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{.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;
}
@endcode
Explanation
-----------
1. 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{.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;
@endcode
2. Load an image:
@code{.cpp}
src = imread( argv[1], 1 );
@endcode
3. Initialize the destination image as having the same size and type as the source:
@code{.cpp}
warp_dst = Mat::zeros( src.rows, src.cols, src.type() );
@endcode
4. **Affine Transform:** As we explained lines above, we need two sets of 3 points to derive the
affine transform relation. Take a look:
@code{.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 );
@endcode
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.
5. Armed with both sets of points, we calculate the Affine Transform by using OpenCV function @ref
cv::getAffineTransform :
@code{.cpp}
warp_mat = getAffineTransform( srcTri, dstTri );
@endcode
We get as an output a \f$2 \times 3\f$ matrix (in this case **warp_mat**)
6. We apply the Affine Transform just found to the src image
@code{.cpp}
warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
@endcode
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...
7. **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{.cpp}
Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
double angle = -50.0;
double scale = 0.6;
@endcode
8. We generate the rotation matrix with the OpenCV function @ref cv::getRotationMatrix2D , which
returns a \f$2 \times 3\f$ matrix (in this case *rot_mat*)
@code{.cpp}
rot_mat = getRotationMatrix2D( center, angle, scale );
@endcode
9. We now apply the found rotation to the output of our previous Transformation.
@code{.cpp}
warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );
@endcode
10. Finally, we display our results in two windows plus the original image for good measure:
@code{.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 );
@endcode
11. We just have to wait until the user exits the program
@code{.cpp}
waitKey(0);
@endcode
Result
------
1. 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)
after applying the first Affine Transform we obtain:
![image](images/Warp_Affine_Tutorial_Result_Warp.jpg)
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)

View File

@@ -0,0 +1,236 @@
More Morphology Transformations {#tutorial_opening_closing_hats}
===============================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::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:
- 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.
\f[dst = open( src, element) = dilate( erode( src, element ) )\f]
- 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)
### Closing
- It is obtained by the dilation of an image followed by an erosion.
\f[dst = close( src, element ) = erode( dilate( src, element ) )\f]
- Useful to remove small holes (dark regions).
![image](images/Morphology_2_Tutorial_Theory_Closing.png)
### Morphological Gradient
- It is the difference between the dilation and the erosion of an image.
\f[dst = morph_{grad}( src, element ) = dilate( src, element ) - erode( src, element )\f]
- It is useful for finding the outline of an object as can be seen below:
![image](images/Morphology_2_Tutorial_Theory_Gradient.png)
### Top Hat
- It is the difference between an input image and its opening.
\f[dst = tophat( src, element ) = src - open( src, element )\f]
![image](images/Morphology_2_Tutorial_Theory_TopHat.png)
### Black Hat
- It is the difference between the closing and its input image
\f[dst = blackhat( src, element ) = close( src, element ) - src\f]
![image](images/Morphology_2_Tutorial_Theory_BlackHat.png)
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{.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 );
}
@endcode
Explanation
-----------
1. 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{.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 );
@endcode
- The second trackbar **"Element"** returns **morph_elem**, which indicates what kind of
structure our kernel is:
@code{.cpp}
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
&morph_elem, max_elem,
Morphology_Operations );
@endcode
- The final trackbar **"Kernel Size"** returns the size of the kernel to be used
(**morph_size**)
@code{.cpp}
createTrackbar( "Kernel size:\n 2n +1", window_name,
&morph_size, max_kernel_size,
Morphology_Operations );
@endcode
- 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{.cpp}
/*
* @function Morphology_Operations
*/
@endcode
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 @ref
cv::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{.cpp}
int operation = morph_operator + 2;
@endcode
- **element**: The kernel to be used. We use the function @ref cv::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)
- 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_Cover.jpg)

View File

@@ -0,0 +1,230 @@
Image Pyramids {#tutorial_pyramids}
==============
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV functions @ref cv::pyrUp and @ref cv::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:
1. *Upsize* the image (zoom in) or
2. *Downsize* it (zoom out).
- Although there is a *geometric transformation* function in OpenCV that -literally- resize an
image (@ref cv::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
- 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)
- Every layer is numbered from bottom to top, so layer \f$(i+1)\f$ (denoted as \f$G_{i+1}\f$ is smaller
than layer \f$i\f$ (\f$G_{i}\f$).
- To produce layer \f$(i+1)\f$ in the Gaussian pyramid, we do the following:
- Convolve \f$G_{i}\f$ with a Gaussian kernel:
\f[\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}\f]
- 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 \f$G_{0}\f$ (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 (\f$0\f$)
- 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 @ref cv::pyrUp and @ref cv::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{.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;
}
@endcode
Explanation
-----------
1. 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{.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; }
@endcode
- Create a Mat object to store the result of the operations (*dst*) and one to save temporal
results (*tmp*).
@code{.cpp}
Mat src, dst, tmp;
/* ... */
tmp = src;
dst = tmp;
@endcode
- Create a window to display the result
@code{.cpp}
namedWindow( window_name, WINDOW_AUTOSIZE );
imshow( window_name, dst );
@endcode
- Perform an infinite loop waiting for user input.
@code{.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;
}
@endcode
Our program exits if the user presses *ESC*. Besides, it has two options:
- **Perform upsampling (after pressing 'u')**
@code{.cpp}
pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 )
@endcode
We use the function @ref cv::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,
@ref cv::pyrUp expects a size double than the input image (in this case *tmp*).
- **Perform downsampling (after pressing 'd')**
@code{.cpp}
pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 )
@endcode
Similarly as with @ref cv::pyrUp , we use the function @ref cv::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,
@ref cv::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{.cpp}
tmp = dst;
@endcode
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 \f$512 \times 512\f$,
hence a downsample won't generate any error (\f$512 = 2^{9}\f$). The original image is shown below:
![image](images/Pyramids_Tutorial_Original_Image.jpg)
- First we apply two successive @ref cv::pyrDown operations by pressing 'd'. Our output is:
![image](images/Pyramids_Tutorial_PyrDown_Result.jpg)
- 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 @ref cv::pyrUp twice (by pressing 'u'). Our output
is now:
![image](images/Pyramids_Tutorial_PyrUp_Result.jpg)

View File

@@ -0,0 +1,112 @@
Creating Bounding boxes and circles for contours {#tutorial_bounding_rects_circles}
================================================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::boundingRect
- Use the OpenCV function @ref cv::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{.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 );
}
@endcode
Explanation
-----------
Result
------
1. Here it is:
---------- ----------
|BRC_0| |BRC_1|
---------- ----------

View File

@@ -0,0 +1,114 @@
Creating Bounding rotated boxes and ellipses for contours {#tutorial_bounding_rotated_ellipses}
=========================================================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::minAreaRect
- Use the OpenCV function @ref cv::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{.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 );
}
@endcode
Explanation
-----------
Result
------
1. Here it is:
---------- ----------
|BRE_0| |BRE_1|
---------- ----------

View File

@@ -0,0 +1,97 @@
Finding contours in your image {#tutorial_find_contours}
==============================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::findContours
- Use the OpenCV function @ref cv::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{.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 );
}
@endcode
Explanation
-----------
Result
------
1. Here it is:
-------------- --------------
|contour_0| |contour_1|
-------------- --------------

View File

@@ -0,0 +1,93 @@
Convex Hull {#tutorial_hull}
===========
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::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{.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* );
@endcode
/\* @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
------
1. Here it is:
----------- -----------
|Hull_0| |Hull_1|
----------- -----------

View File

@@ -0,0 +1,119 @@
Image Moments {#tutorial_moments}
=============
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::moments
- Use the OpenCV function @ref cv::contourArea
- Use the OpenCV function @ref cv::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{.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 );
}
}
@endcode
Explanation
-----------
Result
------
1. Here it is:
--------- --------- ---------
|MU_0| |MU_1| |MU_2|
--------- --------- ---------

View File

@@ -0,0 +1,106 @@
Point Polygon Test {#tutorial_point_polygon_test}
==================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::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{.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);
}
@endcode
Explanation
-----------
Result
------
1. Here it is:
---------- ----------
|PPT_0| |PPT_1|
---------- ----------

View File

@@ -0,0 +1,204 @@
Image Processing (imgproc module) {#tutorial_table_of_content_imgproc}
=================================
In this section you will learn about the image processing (manipulation) functions inside OpenCV.
- @subpage tutorial_gausian_median_blur_bilateral_filter
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Let's take a look at some basic linear filters!
- @subpage tutorial_erosion_dilatation
*Compatibility:* \> OpenCV 2.0
Author: Ana Huamán
Let's *change* the shape of objects!
- @subpage tutorial_opening_closing_hats
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Here we investigate different morphology operators
- @subpage tutorial_pyramids
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
What if I need a bigger/smaller image?
- @subpage tutorial_threshold
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
After so much processing, it is time to decide which pixels stay!
- @subpage tutorial_filter_2d
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn to design our own filters by using OpenCV functions
- @subpage tutorial_copyMakeBorder
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to pad our images!
- @subpage tutorial_sobel_derivatives
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to calculate gradients and use them to detect edges!
- @subpage tutorial_laplace_operator
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn about the *Laplace* operator and how to detect edges with it.
- @subpage tutorial_canny_detector
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn a sophisticated alternative to detect edges.
- @subpage tutorial_hough_lines
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to detect lines
- @subpage tutorial_hough_circle
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to detect circles
- @subpage tutorial_remap
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to manipulate pixels locations
- @subpage tutorial_warp_affine
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to rotate, translate and scale our images
- @subpage tutorial_histogram_equalization
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to improve the contrast in our images
- @subpage tutorial_histogram_calculation
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to create and generate histograms
- @subpage tutorial_histogram_comparison
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn to calculate metrics between histograms
- @subpage tutorial_back_projection
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to use histograms to find similar objects in images
- @subpage tutorial_template_matching
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to match templates in an image
- @subpage tutorial_find_contours
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to find contours of objects in our image
- @subpage tutorial_hull
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to get hull contours and draw them!
- @subpage tutorial_bounding_rects_circles
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to obtain bounding boxes and circles for our contours.
- @subpage tutorial_bounding_rotated_ellipses
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to obtain rotated bounding boxes and ellipses for our contours.
- @subpage tutorial_moments
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn to calculate the moments of an image
- @subpage tutorial_point_polygon_test
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we learn how to calculate distances from the image to contours

View File

@@ -0,0 +1,263 @@
Basic Thresholding Operations {#tutorial_threshold}
=============================
Goal
----
In this tutorial you will learn how to:
- Perform basic thresholding operations using OpenCV function @ref cv::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 \f$0\f$ (black), \f$255\f$ (white) or any value that
suits your needs).
![image](images/Threshold_Tutorial_Theory_Example.jpg)
### Types of Thresholding
- OpenCV offers the function @ref cv::threshold to perform thresholding operations.
- We can effectuate \f$5\f$ 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 \f$src(x,y)\f$. The plot below depicts this. The horizontal blue
line represents the threshold \f$thresh\f$ (fixed).
![image](images/Threshold_Tutorial_Theory_Base_Figure.png)
#### Threshold Binary
- This thresholding operation can be expressed as:
\f[\texttt{dst} (x,y) = \fork{\texttt{maxVal}}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{0}{otherwise}\f]
- So, if the intensity of the pixel \f$src(x,y)\f$ is higher than \f$thresh\f$, then the new pixel
intensity is set to a \f$MaxVal\f$. Otherwise, the pixels are set to \f$0\f$.
![image](images/Threshold_Tutorial_Theory_Binary.png)
#### Threshold Binary, Inverted
- This thresholding operation can be expressed as:
\f[\texttt{dst} (x,y) = \fork{0}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{maxVal}}{otherwise}\f]
- If the intensity of the pixel \f$src(x,y)\f$ is higher than \f$thresh\f$, then the new pixel intensity
is set to a \f$0\f$. Otherwise, it is set to \f$MaxVal\f$.
![image](images/Threshold_Tutorial_Theory_Binary_Inverted.png)
#### Truncate
- This thresholding operation can be expressed as:
\f[\texttt{dst} (x,y) = \fork{\texttt{threshold}}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{src}(x,y)}{otherwise}\f]
- The maximum intensity value for the pixels is \f$thresh\f$, if \f$src(x,y)\f$ is greater, then its value
is *truncated*. See figure below:
![image](images/Threshold_Tutorial_Theory_Truncate.png)
#### Threshold to Zero
- This operation can be expressed as:
\f[\texttt{dst} (x,y) = \fork{\texttt{src}(x,y)}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{0}{otherwise}\f]
- If \f$src(x,y)\f$ is lower than \f$thresh\f$, the new pixel value will be set to \f$0\f$.
![image](images/Threshold_Tutorial_Theory_Zero.png)
#### Threshold to Zero, Inverted
- This operation can be expressed as:
\f[\texttt{dst} (x,y) = \fork{0}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{src}(x,y)}{otherwise}\f]
- If \f$src(x,y)\f$ is greater than \f$thresh\f$, the new pixel value will be set to \f$0\f$.
![image](images/Threshold_Tutorial_Theory_Zero_Inverted.png)
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{.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 );
}
@endcode
Explanation
-----------
1. 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 @ref cv::cvtColor :
@code{.cpp}
src = imread( argv[1], 1 );
/// Convert the image to Gray
cvtColor( src, src_gray, COLOR_RGB2GRAY );
@endcode
- Create a window to display the result
@code{.cpp}
namedWindow( window_name, WINDOW_AUTOSIZE );
@endcode
- Create \f$2\f$ trackbars for the user to enter user input:
- **Type of thresholding**: Binary, To Zero, etc...
- **Threshold value**
@code{.cpp}
createTrackbar( trackbar_type,
window_name, &threshold_type,
max_type, Threshold_Demo );
createTrackbar( trackbar_value,
window_name, &threshold_value,
max_value, Threshold_Demo );
@endcode
- 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{.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 );
}
@endcode
As you can see, the function @ref cv::threshold is invoked. We give \f$5\f$ parameters:
- *src_gray*: Our input image
- *dst*: Destination (output) image
- *threshold_value*: The \f$thresh\f$ 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 \f$5\f$ thresholding operations. They are listed in the
comment section of the function above.
Results
-------
1. 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)
2. First, we try to threshold our image with a *binary threhold inverted*. We expect that the
pixels brighter than the \f$thresh\f$ 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)
3. 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)