Commit 8375182e authored by Maksim Shabunin's avatar Maksim Shabunin

Doxygen tutorials: basic structure

parent 220f6716
Camera calibration with square chessboard {#tutorial_camera_calibration_square_chess}
=========================================
The goal of this tutorial is to learn how to calibrate a camera given a set of chessboard images.
*Test data*: use images in your data/chess folder.
- Compile opencv with samples by setting BUILD_EXAMPLES to ON in cmake configuration.
- Go to bin folder and use imagelist_creator to create an XML/YAML list of your images.
- Then, run calibration sample to get camera parameters. Use square size equal to 3cm.
Pose estimation
---------------
Now, let us write a code that detects a chessboard in a new image and finds its distance from the
camera. You can apply the same method to any object with known 3D geometry that you can detect in an
image.
*Test data*: use chess_test\*.jpg images from your data folder.
- Create an empty console project. Load a test image: :
Mat img = imread(argv[1], IMREAD_GRAYSCALE);
- Detect a chessboard in this image using findChessboard function. :
bool found = findChessboardCorners( img, boardSize, ptvec, CALIB_CB_ADAPTIVE_THRESH );
- Now, write a function that generates a vector\<Point3f\> array of 3d coordinates of a chessboard
in any coordinate system. For simplicity, let us choose a system such that one of the chessboard
corners is in the origin and the board is in the plane *z = 0*.
- Read camera parameters from XML/YAML file: :
FileStorage fs(filename, FileStorage::READ);
Mat intrinsics, distortion;
fs["camera_matrix"] >> intrinsics;
fs["distortion_coefficients"] >> distortion;
- Now we are ready to find chessboard pose by running \`solvePnP\`: :
vector<Point3f> boardPoints;
// fill the array
...
solvePnP(Mat(boardPoints), Mat(foundBoardCorners), cameraMatrix,
distCoeffs, rvec, tvec, false);
- Calculate reprojection error like it is done in calibration sample (see
opencv/samples/cpp/calibration.cpp, function computeReprojectionErrors).
Question: how to calculate the distance from the camera origin to any of the corners?
This diff is collapsed.
Camera calibration and 3D reconstruction (calib3d module) {#tutorial_table_of_content_calib3d}
==========================================================
Although we got most of our images in a 2D format they do come from a 3D world. Here you will learn
how to find out from the 2D images information about the 3D world.
- @subpage tutorial_camera_calibration_square_chess
*Compatibility:* \> OpenCV 2.0
*Author:* Victor Eruhimov
You will use some chessboard images to calibrate your camera.
- @subpage tutorial_camera_calibration
*Compatibility:* \> OpenCV 2.0
*Author:* Bernát Gábor
Camera calibration by using either the chessboard, circle or the asymmetrical circle
pattern. Get the images either from a camera attached, a video file or from an image
collection.
- @subpage tutorial_real_time_pose
*Compatibility:* \> OpenCV 2.0
*Author:* Edgar Riba
Real time pose estimation of a textured object using ORB features, FlannBased matcher, PnP
approach plus Ransac and Linear Kalman Filter to reject possible bad poses.
Adding (blending) two images using OpenCV {#tutorial_adding_images}
=========================================
Goal
----
In this tutorial you will learn:
- what is *linear blending* and why it is useful;
- how to add two images using @ref cv::addWeighted
Theory
------
@note
The explanation below belongs to the book [Computer Vision: Algorithms and
Applications](http://szeliski.org/Book/) by Richard Szeliski
From our previous tutorial, we know already a bit of *Pixel operators*. An interesting dyadic
(two-input) operator is the *linear blend operator*:
\f[g(x) = (1 - \alpha)f_{0}(x) + \alpha f_{1}(x)\f]
By varying \f$\alpha\f$ from \f$0 \rightarrow 1\f$ this operator can be used to perform a temporal
*cross-disolve* between two images or videos, as seen in slide shows and film productions (cool,
eh?)
Code
----
As usual, after the not-so-lengthy explanation, let's go to the code:
@code{.cpp}
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
int main( int argc, char** argv )
{
double alpha = 0.5; double beta; double input;
Mat src1, src2, dst;
/// Ask the user enter alpha
std::cout<<" Simple Linear Blender "<<std::endl;
std::cout<<"-----------------------"<<std::endl;
std::cout<<"* Enter alpha [0-1]: ";
std::cin>>input;
/// We use the alpha provided by the user if it is between 0 and 1
if( input >= 0.0 && input <= 1.0 )
{ alpha = input; }
/// Read image ( same size, same type )
src1 = imread("../../images/LinuxLogo.jpg");
src2 = imread("../../images/WindowsLogo.jpg");
if( !src1.data ) { printf("Error loading src1 \n"); return -1; }
if( !src2.data ) { printf("Error loading src2 \n"); return -1; }
/// Create Windows
namedWindow("Linear Blend", 1);
beta = ( 1.0 - alpha );
addWeighted( src1, alpha, src2, beta, 0.0, dst);
imshow( "Linear Blend", dst );
waitKey(0);
return 0;
}
@endcode
Explanation
-----------
1. Since we are going to perform:
\f[g(x) = (1 - \alpha)f_{0}(x) + \alpha f_{1}(x)\f]
We need two source images (\f$f_{0}(x)\f$ and \f$f_{1}(x)\f$). So, we load them in the usual way:
@code{.cpp}
src1 = imread("../../images/LinuxLogo.jpg");
src2 = imread("../../images/WindowsLogo.jpg");
@endcode
**warning**
Since we are *adding* *src1* and *src2*, they both have to be of the same size (width and
height) and type.
2. Now we need to generate the @ref cv::g(x)\` image. For this, the function
add_weighted:addWeighted comes quite handy:
.. code-block:: cpp
beta = ( 1.0 - alpha );
addWeighted( src1, alpha, src2, beta, 0.0, dst);
since @ref cv::addWeighted produces:
.. math::
dst = \\alpha \\cdot src1 + \\beta \\cdot src2 + \\gamma
In this case, :math:gamma\` is the argument \f$0.0\f$ in the code above.
3. Create windows, show the images and wait for the user to end the program.
Result
------
![image](images/Adding_Images_Tutorial_Result_0.jpg)
Basic Drawing {#tutorial_basic_geometric_drawing}
=============
Goals
-----
In this tutorial you will learn how to:
- Use @ref cv::Point to define 2D points in an image.
- Use @ref cv::Scalar and why it is useful
- Draw a **line** by using the OpenCV function @ref cv::line
- Draw an **ellipse** by using the OpenCV function @ref cv::ellipse
- Draw a **rectangle** by using the OpenCV function @ref cv::rectangle
- Draw a **circle** by using the OpenCV function @ref cv::circle
- Draw a **filled polygon** by using the OpenCV function @ref cv::fillPoly
OpenCV Theory
-------------
For this tutorial, we will heavily use two structures: @ref cv::Point and @ref cv::Scalar :
### Point
It represents a 2D point, specified by its image coordinates \f$x\f$ and \f$y\f$. We can define it as:
@code{.cpp}
Point pt;
pt.x = 10;
pt.y = 8;
@endcode
or
@code{.cpp}
Point pt = Point(10, 8);
@endcode
### Scalar
- Represents a 4-element vector. The type Scalar is widely used in OpenCV for passing pixel
values.
- In this tutorial, we will use it extensively to represent RGB color values (3 parameters). It is
not necessary to define the last argument if it is not going to be used.
- Let's see an example, if we are asked for a color argument and we give:
@code{.cpp}
Scalar( a, b, c )
@endcode
We would be defining a RGB color such as: *Red = c*, *Green = b* and *Blue = a*
Code
----
- This code is in your OpenCV sample folder. Otherwise you can grab it from
[here](https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/core/Matrix/Drawing_1.cpp)
Explanation
-----------
1. Since we plan to draw two examples (an atom and a rook), we have to create 02 images and two
windows to display them.
@code{.cpp}
/// Windows names
char atom_window[] = "Drawing 1: Atom";
char rook_window[] = "Drawing 2: Rook";
/// Create black empty images
Mat atom_image = Mat::zeros( w, w, CV_8UC3 );
Mat rook_image = Mat::zeros( w, w, CV_8UC3 );
@endcode
2. We created functions to draw different geometric shapes. For instance, to draw the atom we used
*MyEllipse* and *MyFilledCircle*:
@code{.cpp}
/// 1. Draw a simple atom:
/// 1.a. Creating ellipses
MyEllipse( atom_image, 90 );
MyEllipse( atom_image, 0 );
MyEllipse( atom_image, 45 );
MyEllipse( atom_image, -45 );
/// 1.b. Creating circles
MyFilledCircle( atom_image, Point( w/2.0, w/2.0) );
@endcode
3. And to draw the rook we employed *MyLine*, *rectangle* and a *MyPolygon*:
@code{.cpp}
/// 2. Draw a rook
/// 2.a. Create a convex polygon
MyPolygon( rook_image );
/// 2.b. Creating rectangles
rectangle( rook_image,
Point( 0, 7*w/8.0 ),
Point( w, w),
Scalar( 0, 255, 255 ),
-1,
8 );
/// 2.c. Create a few lines
MyLine( rook_image, Point( 0, 15*w/16 ), Point( w, 15*w/16 ) );
MyLine( rook_image, Point( w/4, 7*w/8 ), Point( w/4, w ) );
MyLine( rook_image, Point( w/2, 7*w/8 ), Point( w/2, w ) );
MyLine( rook_image, Point( 3*w/4, 7*w/8 ), Point( 3*w/4, w ) );
@endcode
4. Let's check what is inside each of these functions:
- *MyLine*
@code{.cpp}
void MyLine( Mat img, Point start, Point end )
{
int thickness = 2;
int lineType = 8;
line( img, start, end,
Scalar( 0, 0, 0 ),
thickness,
lineType );
}
@endcode
As we can see, *MyLine* just call the function @ref cv::line , which does the following:
- Draw a line from Point **start** to Point **end**
- The line is displayed in the image **img**
- The line color is defined by **Scalar( 0, 0, 0)** which is the RGB value correspondent
to **Black**
- The line thickness is set to **thickness** (in this case 2)
- The line is a 8-connected one (**lineType** = 8)
- *MyEllipse*
@code{.cpp}
void MyEllipse( Mat img, double angle )
{
int thickness = 2;
int lineType = 8;
ellipse( img,
Point( w/2.0, w/2.0 ),
Size( w/4.0, w/16.0 ),
angle,
0,
360,
Scalar( 255, 0, 0 ),
thickness,
lineType );
}
@endcode
From the code above, we can observe that the function @ref cv::ellipse draws an ellipse such
that:
- The ellipse is displayed in the image **img**
- The ellipse center is located in the point **(w/2.0, w/2.0)** and is enclosed in a box
of size **(w/4.0, w/16.0)**
- The ellipse is rotated **angle** degrees
- The ellipse extends an arc between **0** and **360** degrees
- The color of the figure will be **Scalar( 255, 255, 0)** which means blue in RGB value.
- The ellipse's **thickness** is 2.
- *MyFilledCircle*
@code{.cpp}
void MyFilledCircle( Mat img, Point center )
{
int thickness = -1;
int lineType = 8;
circle( img,
center,
w/32.0,
Scalar( 0, 0, 255 ),
thickness,
lineType );
}
@endcode
Similar to the ellipse function, we can observe that *circle* receives as arguments:
- The image where the circle will be displayed (**img**)
- The center of the circle denoted as the Point **center**
- The radius of the circle: **w/32.0**
- The color of the circle: **Scalar(0, 0, 255)** which means *Red* in BGR
- Since **thickness** = -1, the circle will be drawn filled.
- *MyPolygon*
@code{.cpp}
void MyPolygon( Mat img )
{
int lineType = 8;
/* Create some points */
Point rook_points[1][20];
rook_points[0][0] = Point( w/4.0, 7*w/8.0 );
rook_points[0][1] = Point( 3*w/4.0, 7*w/8.0 );
rook_points[0][2] = Point( 3*w/4.0, 13*w/16.0 );
rook_points[0][3] = Point( 11*w/16.0, 13*w/16.0 );
rook_points[0][4] = Point( 19*w/32.0, 3*w/8.0 );
rook_points[0][5] = Point( 3*w/4.0, 3*w/8.0 );
rook_points[0][6] = Point( 3*w/4.0, w/8.0 );
rook_points[0][7] = Point( 26*w/40.0, w/8.0 );
rook_points[0][8] = Point( 26*w/40.0, w/4.0 );
rook_points[0][9] = Point( 22*w/40.0, w/4.0 );
rook_points[0][10] = Point( 22*w/40.0, w/8.0 );
rook_points[0][11] = Point( 18*w/40.0, w/8.0 );
rook_points[0][12] = Point( 18*w/40.0, w/4.0 );
rook_points[0][13] = Point( 14*w/40.0, w/4.0 );
rook_points[0][14] = Point( 14*w/40.0, w/8.0 );
rook_points[0][15] = Point( w/4.0, w/8.0 );
rook_points[0][16] = Point( w/4.0, 3*w/8.0 );
rook_points[0][17] = Point( 13*w/32.0, 3*w/8.0 );
rook_points[0][18] = Point( 5*w/16.0, 13*w/16.0 );
rook_points[0][19] = Point( w/4.0, 13*w/16.0) ;
const Point* ppt[1] = { rook_points[0] };
int npt[] = { 20 };
fillPoly( img,
ppt,
npt,
1,
Scalar( 255, 255, 255 ),
lineType );
}
@endcode
To draw a filled polygon we use the function @ref cv::fillPoly . We note that:
- The polygon will be drawn on **img**
- The vertices of the polygon are the set of points in **ppt**
- The total number of vertices to be drawn are **npt**
- The number of polygons to be drawn is only **1**
- The color of the polygon is defined by **Scalar( 255, 255, 255)**, which is the BGR
value for *white*
- *rectangle*
@code{.cpp}
rectangle( rook_image,
Point( 0, 7*w/8.0 ),
Point( w, w),
Scalar( 0, 255, 255 ),
-1, 8 );
@endcode
Finally we have the @ref cv::rectangle function (we did not create a special function for
this guy). We note that:
- The rectangle will be drawn on **rook_image**
- Two opposite vertices of the rectangle are defined by *\* Point( 0, 7*w/8.0 )*\*
andPoint( w, w)*\*
- The color of the rectangle is given by **Scalar(0, 255, 255)** which is the BGR value
for *yellow*
- Since the thickness value is given by **-1**, the rectangle will be filled.
Result
------
Compiling and running your program should give you a result like this:
![image](images/Drawing_1_Tutorial_Result_0.png)
Changing the contrast and brightness of an image! {#tutorial_basic_linear_transform}
=================================================
Goal
----
In this tutorial you will learn how to:
- Access pixel values
- Initialize a matrix with zeros
- Learn what @ref cv::saturate_cast does and why it is useful
- Get some cool info about pixel transformations
Theory
------
@note
The explanation below belongs to the book [Computer Vision: Algorithms and
Applications](http://szeliski.org/Book/) by Richard Szeliski
### Image Processing
- A general image processing operator is a function that takes one or more input images and
produces an output image.
- Image transforms can be seen as:
- Point operators (pixel transforms)
- Neighborhood (area-based) operators
### Pixel Transforms
- In this kind of image processing transform, each output pixel's value depends on only the
corresponding input pixel value (plus, potentially, some globally collected information or
parameters).
- Examples of such operators include *brightness and contrast adjustments* as well as color
correction and transformations.
### Brightness and contrast adjustments
- Two commonly used point processes are *multiplication* and *addition* with a constant:
\f[g(x) = \alpha f(x) + \beta\f]
- The parameters \f$\alpha > 0\f$ and \f$\beta\f$ are often called the *gain* and *bias* parameters;
sometimes these parameters are said to control *contrast* and *brightness* respectively.
- You can think of \f$f(x)\f$ as the source image pixels and \f$g(x)\f$ as the output image pixels. Then,
more conveniently we can write the expression as:
\f[g(i,j) = \alpha \cdot f(i,j) + \beta\f]
where \f$i\f$ and \f$j\f$ indicates that the pixel is located in the *i-th* row and *j-th* column.
Code
----
- The following code performs the operation \f$g(i,j) = \alpha \cdot f(i,j) + \beta\f$ :
@code{.cpp}
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
double alpha; /*< Simple contrast control */
int beta; /*< Simple brightness control */
int main( int argc, char** argv )
{
/// Read image given by user
Mat image = imread( argv[1] );
Mat new_image = Mat::zeros( image.size(), image.type() );
/// Initialize values
std::cout<<" Basic Linear Transforms "<<std::endl;
std::cout<<"-------------------------"<<std::endl;
std::cout<<"* Enter the alpha value [1.0-3.0]: ";std::cin>>alpha;
std::cout<<"* Enter the beta value [0-100]: "; std::cin>>beta;
/// Do the operation new_image(i,j) = alpha*image(i,j) + beta
for( int y = 0; y < image.rows; y++ ) {
for( int x = 0; x < image.cols; x++ ) {
for( int c = 0; c < 3; c++ ) {
new_image.at<Vec3b>(y,x)[c] =
saturate_cast<uchar>( alpha*( image.at<Vec3b>(y,x)[c] ) + beta );
}
}
}
/// Create Windows
namedWindow("Original Image", 1);
namedWindow("New Image", 1);
/// Show stuff
imshow("Original Image", image);
imshow("New Image", new_image);
/// Wait until user press some key
waitKey();
return 0;
}
@endcode
Explanation
-----------
1. We begin by creating parameters to save \f$\alpha\f$ and \f$\beta\f$ to be entered by the user:
@code{.cpp}
double alpha;
int beta;
@endcode
2. We load an image using @ref cv::imread and save it in a Mat object:
@code{.cpp}
Mat image = imread( argv[1] );
@endcode
3. Now, since we will make some transformations to this image, we need a new Mat object to store
it. Also, we want this to have the following features:
- Initial pixel values equal to zero
- Same size and type as the original image
@code{.cpp}
Mat new_image = Mat::zeros( image.size(), image.type() );
@endcode
We observe that @ref cv::Mat::zeros returns a Matlab-style zero initializer based on
*image.size()* and *image.type()*
4. Now, to perform the operation \f$g(i,j) = \alpha \cdot f(i,j) + \beta\f$ we will access to each
pixel in image. Since we are operating with RGB images, we will have three values per pixel (R,
G and B), so we will also access them separately. Here is the piece of code:
@code{.cpp}
for( int y = 0; y < image.rows; y++ ) {
for( int x = 0; x < image.cols; x++ ) {
for( int c = 0; c < 3; c++ ) {
new_image.at<Vec3b>(y,x)[c] =
saturate_cast<uchar>( alpha*( image.at<Vec3b>(y,x)[c] ) + beta );
}
}
}
@endcode
Notice the following:
- To access each pixel in the images we are using this syntax: *image.at\<Vec3b\>(y,x)[c]*
where *y* is the row, *x* is the column and *c* is R, G or B (0, 1 or 2).
- Since the operation @ref cv::alpha cdot p(i,j) + beta\` can give values out of range or not
integers (if \f$\alpha\f$ is float), we use :saturate_cast:\`saturate_cast to make sure the
values are valid.
5. Finally, we create windows and show the images, the usual way.
@code{.cpp}
namedWindow("Original Image", 1);
namedWindow("New Image", 1);
imshow("Original Image", image);
imshow("New Image", new_image);
waitKey(0);
@endcode
@note
Instead of using the **for** loops to access each pixel, we could have simply used this command:
@code{.cpp}
image.convertTo(new_image, -1, alpha, beta);
@endcode
where @ref cv::convertTo would effectively perform *new_image = a*image + beta\*. However, we
wanted to show you how to access each pixel. In any case, both methods give the same result but
convertTo is more optimized and works a lot faster.
Result
------
- Running our code and using \f$\alpha = 2.2\f$ and \f$\beta = 50\f$
@code{.bash}
\f$ ./BasicLinearTransforms lena.jpg
Basic Linear Transforms
-------------------------
* Enter the alpha value [1.0-3.0]: 2.2
* Enter the beta value [0-100]: 50
@endcode
- We get this:
![image](images/Basic_Linear_Transform_Tutorial_Result_0.jpg)
Discrete Fourier Transform {#tutorial_discrete_fourier_transform}
==========================
Goal
----
We'll seek answers for the following questions:
- What is a Fourier transform and why use it?
- How to do it in OpenCV?
- Usage of functions such as: @ref cv::copyMakeBorder() , @ref cv::merge() , @ref cv::dft() , @ref
cv::getOptimalDFTSize() , @ref cv::log() and @ref cv::normalize() .
Source code
-----------
You can [download this from here
](samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp) or
find it in the
`samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp` of the
OpenCV source code library.
Here's a sample usage of @ref cv::dft() :
@includelineno cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp
lines
1-4, 6, 20-21, 24-79
Explanation
-----------
The Fourier Transform will decompose an image into its sinus and cosines components. In other words,
it will transform an image from its spatial domain to its frequency domain. The idea is that any
function may be approximated exactly with the sum of infinite sinus and cosines functions. The
Fourier Transform is a way how to do this. Mathematically a two dimensional images Fourier transform
is:
\f[F(k,l) = \displaystyle\sum\limits_{i=0}^{N-1}\sum\limits_{j=0}^{N-1} f(i,j)e^{-i2\pi(\frac{ki}{N}+\frac{lj}{N})}\f]\f[e^{ix} = \cos{x} + i\sin {x}\f]
Here f is the image value in its spatial domain and F in its frequency domain. The result of the
transformation is complex numbers. Displaying this is possible either via a *real* image and a
*complex* image or via a *magnitude* and a *phase* image. However, throughout the image processing
algorithms only the *magnitude* image is interesting as this contains all the information we need
about the images geometric structure. Nevertheless, if you intend to make some modifications of the
image in these forms and then you need to retransform it you'll need to preserve both of these.
In this sample I'll show how to calculate and show the *magnitude* image of a Fourier Transform. In
case of digital images are discrete. This means they may take up a value from a given domain value.
For example in a basic gray scale image values usually are between zero and 255. Therefore the
Fourier Transform too needs to be of a discrete type resulting in a Discrete Fourier Transform
(*DFT*). You'll want to use this whenever you need to determine the structure of an image from a
geometrical point of view. Here are the steps to follow (in case of a gray scale input image *I*):
1. **Expand the image to an optimal size**. The performance of a DFT is dependent of the image
size. It tends to be the fastest for image sizes that are multiple of the numbers two, three and
five. Therefore, to achieve maximal performance it is generally a good idea to pad border values
to the image to get a size with such traits. The @ref cv::getOptimalDFTSize() returns this
optimal size and we can use the @ref cv::copyMakeBorder() function to expand the borders of an
image:
@code{.cpp}
Mat padded; //expand input image to optimal size
int m = getOptimalDFTSize( I.rows );
int n = getOptimalDFTSize( I.cols ); // on the border add zero pixels
copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
@endcode
The appended pixels are initialized with zero.
2. **Make place for both the complex and the real values**. The result of a Fourier Transform is
complex. This implies that for each image value the result is two image values (one per
component). Moreover, the frequency domains range is much larger than its spatial counterpart.
Therefore, we store these usually at least in a *float* format. Therefore we'll convert our
input image to this type and expand it with another channel to hold the complex values:
@code{.cpp}
Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
Mat complexI;
merge(planes, 2, complexI); // Add to the expanded another plane with zeros
@endcode
3. **Make the Discrete Fourier Transform**. It's possible an in-place calculation (same input as
output):
@code{.cpp}
dft(complexI, complexI); // this way the result may fit in the source matrix
@endcode
4. **Transform the real and complex values to magnitude**. A complex number has a real (*Re*) and a
complex (imaginary - *Im*) part. The results of a DFT are complex numbers. The magnitude of a
DFT is:
\f[M = \sqrt[2]{ {Re(DFT(I))}^2 + {Im(DFT(I))}^2}\f]
Translated to OpenCV code:
@code{.cpp}
split(complexI, planes); // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude
Mat magI = planes[0];
@endcode
5. **Switch to a logarithmic scale**. It turns out that the dynamic range of the Fourier
coefficients is too large to be displayed on the screen. We have some small and some high
changing values that we can't observe like this. Therefore the high values will all turn out as
white points, while the small ones as black. To use the gray scale values to for visualization
we can transform our linear scale to a logarithmic one:
\f[M_1 = \log{(1 + M)}\f]
Translated to OpenCV code:
@code{.cpp}
magI += Scalar::all(1); // switch to logarithmic scale
log(magI, magI);
@endcode
6. **Crop and rearrange**. Remember, that at the first step, we expanded the image? Well, it's time
to throw away the newly introduced values. For visualization purposes we may also rearrange the
quadrants of the result, so that the origin (zero, zero) corresponds with the image center.
@code{.cpp}
magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));
int cx = magI.cols/2;
int cy = magI.rows/2;
Mat q0(magI, Rect(0, 0, cx, cy)); // Top-Left - Create a ROI per quadrant
Mat q1(magI, Rect(cx, 0, cx, cy)); // Top-Right
Mat q2(magI, Rect(0, cy, cx, cy)); // Bottom-Left
Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right
Mat tmp; // swap quadrants (Top-Left with Bottom-Right)
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp); // swap quadrant (Top-Right with Bottom-Left)
q2.copyTo(q1);
tmp.copyTo(q2);
@endcode
7. **Normalize**. This is done again for visualization purposes. We now have the magnitudes,
however this are still out of our image display range of zero to one. We normalize our values to
this range using the @ref cv::normalize() function.
@code{.cpp}
normalize(magI, magI, 0, 1, NORM_MINMAX); // Transform the matrix with float values into a
// viewable image form (float between values 0 and 1).
@endcode
Result
------
An application idea would be to determine the geometrical orientation present in the image. For
example, let us find out if a text is horizontal or not? Looking at some text you'll notice that the
text lines sort of form also horizontal lines and the letters form sort of vertical lines. These two
main components of a text snippet may be also seen in case of the Fourier transform. Let us use
[this horizontal ](samples/data/imageTextN.png) and [this rotated](samples/data/imageTextR.png)
image about a text.
In case of the horizontal text:
![image](images/result_normal.jpg)
In case of a rotated text:
![image](images/result_rotated.jpg)
You can see that the most influential components of the frequency domain (brightest dots on the
magnitude image) follow the geometric rotation of objects on the image. From this we may calculate
the offset and perform an image rotation to correct eventual miss alignments.
File Input and Output using XML and YAML files {#tutorial_file_input_output_with_xml_yml}
==============================================
Goal
----
You'll find answers for the following questions:
- How to print and read text entries to a file and OpenCV using YAML or XML files?
- How to do the same for OpenCV data structures?
- How to do this for your data structures?
- Usage of OpenCV data structures such as @ref cv::FileStorage , @ref cv::FileNode or @ref
cv::FileNodeIterator .
Source code
-----------
You can [download this from here
](samples/cpp/tutorial_code/core/file_input_output/file_input_output.cpp) or find it in the
`samples/cpp/tutorial_code/core/file_input_output/file_input_output.cpp` of the OpenCV source code
library.
Here's a sample code of how to achieve all the stuff enumerated at the goal list.
@includelineno cpp/tutorial_code/core/file_input_output/file_input_output.cpp
lines
1-7, 21-154
Explanation
-----------
Here we talk only about XML and YAML file inputs. Your output (and its respective input) file may
have only one of these extensions and the structure coming from this. They are two kinds of data
structures you may serialize: *mappings* (like the STL map) and *element sequence* (like the STL
vector). The difference between these is that in a map every element has a unique name through what
you may access it. For sequences you need to go through them to query a specific item.
1. **XML/YAML File Open and Close.** Before you write any content to such file you need to open it
and at the end to close it. The XML/YAML data structure in OpenCV is @ref cv::FileStorage . To
specify that this structure to which file binds on your hard drive you can use either its
constructor or the *open()* function of this:
@code{.cpp}
string filename = "I.xml";
FileStorage fs(filename, FileStorage::WRITE);
//...
fs.open(filename, FileStorage::READ);
@endcode
Either one of this you use the second argument is a constant specifying the type of operations
you'll be able to on them: WRITE, READ or APPEND. The extension specified in the file name also
determinates the output format that will be used. The output may be even compressed if you
specify an extension such as *.xml.gz*.
The file automatically closes when the @ref cv::FileStorage objects is destroyed. However, you
may explicitly call for this by using the *release* function:
@code{.cpp}
fs.release(); // explicit close
@endcode
2. **Input and Output of text and numbers.** The data structure uses the same \<\< output operator
that the STL library. For outputting any type of data structure we need first to specify its
name. We do this by just simply printing out the name of this. For basic types you may follow
this with the print of the value :
@code{.cpp}
fs << "iterationNr" << 100;
@endcode
Reading in is a simple addressing (via the [] operator) and casting operation or a read via
the \>\> operator :
@code{.cpp}
int itNr;
fs["iterationNr"] >> itNr;
itNr = (int) fs["iterationNr"];
@endcode
3. **Input/Output of OpenCV Data structures.** Well these behave exactly just as the basic C++
types:
@code{.cpp}
Mat R = Mat_<uchar >::eye (3, 3),
T = Mat_<double>::zeros(3, 1);
fs << "R" << R; // Write cv::Mat
fs << "T" << T;
fs["R"] >> R; // Read cv::Mat
fs["T"] >> T;
@endcode
4. **Input/Output of vectors (arrays) and associative maps.** As I mentioned beforehand, we can
output maps and sequences (array, vector) too. Again we first print the name of the variable and
then we have to specify if our output is either a sequence or map.
For sequence before the first element print the "[" character and after the last one the "]"
character:
@code{.cpp}
fs << "strings" << "["; // text - string sequence
fs << "image1.jpg" << "Awesomeness" << "baboon.jpg";
fs << "]"; // close sequence
@endcode
For maps the drill is the same however now we use the "{" and "}" delimiter characters:
@code{.cpp}
fs << "Mapping"; // text - mapping
fs << "{" << "One" << 1;
fs << "Two" << 2 << "}";
@endcode
To read from these we use the @ref cv::FileNode and the @ref cv::FileNodeIterator data
structures. The [] operator of the @ref cv::FileStorage class returns a @ref cv::FileNode data
type. If the node is sequential we can use the @ref cv::FileNodeIterator to iterate through the
items:
@code{.cpp}
FileNode n = fs["strings"]; // Read string sequence - Get node
if (n.type() != FileNode::SEQ)
{
cerr << "strings is not a sequence! FAIL" << endl;
return 1;
}
FileNodeIterator it = n.begin(), it_end = n.end(); // Go through the node
for (; it != it_end; ++it)
cout << (string)*it << endl;
@endcode
For maps you can use the [] operator again to acces the given item (or the \>\> operator too):
@code{.cpp}
n = fs["Mapping"]; // Read mappings from a sequence
cout << "Two " << (int)(n["Two"]) << "; ";
cout << "One " << (int)(n["One"]) << endl << endl;
@endcode
5. **Read and write your own data structures.** Suppose you have a data structure such as:
@code{.cpp}
class MyData
{
public:
MyData() : A(0), X(0), id() {}
public: // Data Members
int A;
double X;
string id;
};
@endcode
It's possible to serialize this through the OpenCV I/O XML/YAML interface (just as in case of
the OpenCV data structures) by adding a read and a write function inside and outside of your
class. For the inside part:
@code{.cpp}
void write(FileStorage& fs) const //Write serialization for this class
{
fs << "{" << "A" << A << "X" << X << "id" << id << "}";
}
void read(const FileNode& node) //Read serialization for this class
{
A = (int)node["A"];
X = (double)node["X"];
id = (string)node["id"];
}
@endcode
Then you need to add the following functions definitions outside the class:
@code{.cpp}
void write(FileStorage& fs, const std::string&, const MyData& x)
{
x.write(fs);
}
void read(const FileNode& node, MyData& x, const MyData& default_value = MyData())
{
if(node.empty())
x = default_value;
else
x.read(node);
}
@endcode
Here you can observe that in the read section we defined what happens if the user tries to read
a non-existing node. In this case we just return the default initialization value, however a
more verbose solution would be to return for instance a minus one value for an object ID.
Once you added these four functions use the \>\> operator for write and the \<\< operator for
read:
@code{.cpp}
MyData m(1);
fs << "MyData" << m; // your own data structures
fs["MyData"] >> m; // Read your own structure_
@endcode
Or to try out reading a non-existing read:
@code{.cpp}
fs["NonExisting"] >> m; // Do not add a fs << "NonExisting" << m command for this to work
cout << endl << "NonExisting = " << endl << m << endl;
@endcode
Result
------
Well mostly we just print out the defined numbers. On the screen of your console you could see:
@code{.bash}
Write Done.
Reading:
100image1.jpg
Awesomeness
baboon.jpg
Two 2; One 1
R = [1, 0, 0;
0, 1, 0;
0, 0, 1]
T = [0; 0; 0]
MyData =
{ id = mydata1234, X = 3.14159, A = 97}
Attempt to read NonExisting (should initialize the data structure with its default).
NonExisting =
{ id = , X = 0, A = 0}
Tip: Open up output.xml with a text editor to see the serialized data.
@endcode
Nevertheless, it's much more interesting what you may see in the output xml file:
@code{.xml}
<?xml version="1.0"?>
<opencv_storage>
<iterationNr>100</iterationNr>
<strings>
image1.jpg Awesomeness baboon.jpg</strings>
<Mapping>
<One>1</One>
<Two>2</Two></Mapping>
<R type_id="opencv-matrix">
<rows>3</rows>
<cols>3</cols>
<dt>u</dt>
<data>
1 0 0 0 1 0 0 0 1</data></R>
<T type_id="opencv-matrix">
<rows>3</rows>
<cols>1</cols>
<dt>d</dt>
<data>
0. 0. 0.</data></T>
<MyData>
<A>97</A>
<X>3.1415926535897931e+000</X>
<id>mydata1234</id></MyData>
</opencv_storage>
@endcode
Or the YAML file:
@code{.yaml}
%YAML:1.0
iterationNr: 100
strings:
- "image1.jpg"
- Awesomeness
- "baboon.jpg"
Mapping:
One: 1
Two: 2
R: !!opencv-matrix
rows: 3
cols: 3
dt: u
data: [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]
T: !!opencv-matrix
rows: 3
cols: 1
dt: d
data: [ 0., 0., 0. ]
MyData:
A: 97
X: 3.1415926535897931e+000
id: mydata1234
@endcode
You may observe a runtime instance of this on the [YouTube
here](https://www.youtube.com/watch?v=A4yqVnByMMM) .
\htmlonly
<div align="center">
<iframe title="File Input and Output using XML and YAML files in OpenCV" width="560" height="349" src="http://www.youtube.com/embed/A4yqVnByMMM?rel=0&loop=1" frameborder="0" allowfullscreen align="middle"></iframe>
</div>
\endhtmlonly
Intel® IPP Asynchronous C/C++ library in OpenCV {#tutorial_how_to_use_ippa_conversion}
===============================================
Goal
----
The tutorial demonstrates the [Intel® IPP Asynchronous
C/C++](http://software.intel.com/en-us/intel-ipp-preview) library usage with OpenCV. The code
example below illustrates implementation of the Sobel operation, accelerated with Intel® IPP
Asynchronous C/C++ functions. In this code example, @ref cv::hpp::getMat and @ref cv::hpp::getHpp
functions are used for data conversion between
[hppiMatrix](http://software.intel.com/en-us/node/501660) and Mat matrices.
Code
----
You may also find the source code in the
`samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp` file of the OpenCV source library or
download it from here
\<../../../../samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp\>.
@includelineno cpp/tutorial_code/core/ippasync/ippasync_sample.cpp
Explanation
-----------
1. Create parameters for OpenCV:
@code{.cpp}
VideoCapture cap;
Mat image, gray, result;
@endcode
and IPP Async:
@code{.cpp}
hppiMatrix* src,* dst;
hppAccel accel = 0;
hppAccelType accelType;
hppStatus sts;
hppiVirtualMatrix * virtMatrix;
@endcode
2. Load input image or video. How to open and read video stream you can see in the @ref
videoInputPSNRMSSIM tutorial.
@code{.cpp}
if( useCamera )
{
printf("used camera\n");
cap.open(0);
}
else
{
printf("used image %s\n", file.c_str());
cap.open(file.c_str());
}
if( !cap.isOpened() )
{
printf("can not open camera or video file\n");
return -1;
}
@endcode
3. Create accelerator instance using
[hppCreateInstance](http://software.intel.com/en-us/node/501686):
@code{.cpp}
accelType = sAccel == "cpu" ? HPP_ACCEL_TYPE_CPU:
sAccel == "gpu" ? HPP_ACCEL_TYPE_GPU:
HPP_ACCEL_TYPE_ANY;
//Create accelerator instance
sts = hppCreateInstance(accelType, 0, &accel);
CHECK_STATUS(sts, "hppCreateInstance");
@endcode
4. Create an array of virtual matrices using
[hppiCreateVirtualMatrices](http://software.intel.com/en-us/node/501700) function.
@code{.cpp}
virtMatrix = hppiCreateVirtualMatrices(accel, 1);
@endcode
5. Prepare a matrix for input and output data:
@code{.cpp}
cap >> image;
if(image.empty())
break;
cvtColor( image, gray, COLOR_BGR2GRAY );
result.create( image.rows, image.cols, CV_8U);
@endcode
6. Convert Mat to [hppiMatrix](http://software.intel.com/en-us/node/501660) using @ref cv::getHpp
and call [hppiSobel](http://software.intel.com/en-us/node/474701) function.
@code{.cpp}
//convert Mat to hppiMatrix
src = getHpp(gray, accel);
dst = getHpp(result, accel);
sts = hppiSobel(accel,src, HPP_MASK_SIZE_3X3,HPP_NORM_L1,virtMatrix[0]);
CHECK_STATUS(sts,"hppiSobel");
sts = hppiConvert(accel, virtMatrix[0], 0, HPP_RND_MODE_NEAR, dst, HPP_DATA_TYPE_8U);
CHECK_STATUS(sts,"hppiConvert");
// Wait for tasks to complete
sts = hppWait(accel, HPP_TIME_OUT_INFINITE);
CHECK_STATUS(sts, "hppWait");
@endcode
We use [hppiConvert](http://software.intel.com/en-us/node/501746) because
[hppiSobel](http://software.intel.com/en-us/node/474701) returns destination matrix with
HPP_DATA_TYPE_16S data type for source matrix with HPP_DATA_TYPE_8U type. You should check
hppStatus after each call IPP Async function.
7. Create windows and show the images, the usual way.
@code{.cpp}
imshow("image", image);
imshow("rez", result);
waitKey(15);
@endcode
8. Delete hpp matrices.
@code{.cpp}
sts = hppiFreeMatrix(src);
CHECK_DEL_STATUS(sts,"hppiFreeMatrix");
sts = hppiFreeMatrix(dst);
CHECK_DEL_STATUS(sts,"hppiFreeMatrix");
@endcode
9. Delete virtual matrices and accelerator instance.
@code{.cpp}
if (virtMatrix)
{
sts = hppiDeleteVirtualMatrices(accel, virtMatrix);
CHECK_DEL_STATUS(sts,"hppiDeleteVirtualMatrices");
}
if (accel)
{
sts = hppDeleteInstance(accel);
CHECK_DEL_STATUS(sts, "hppDeleteInstance");
}
@endcode
Result
------
After compiling the code above we can execute it giving an image or video path and accelerator type
as an argument. For this tutorial we use baboon.png image as input. The result is below.
![image](images/How_To_Use_IPPA_Result.jpg)
Interoperability with OpenCV 1 {#tutorial_interoperability_with_OpenCV_1}
==============================
Goal
----
For the OpenCV developer team it's important to constantly improve the library. We are constantly
thinking about methods that will ease your work process, while still maintain the libraries
flexibility. The new C++ interface is a development of us that serves this goal. Nevertheless,
backward compatibility remains important. We do not want to break your code written for earlier
version of the OpenCV library. Therefore, we made sure that we add some functions that deal with
this. In the following you'll learn:
- What changed with the version 2 of OpenCV in the way you use the library compared to its first
version
- How to add some Gaussian noise to an image
- What are lookup tables and why use them?
General
-------
When making the switch you first need to learn some about the new data structure for images: @ref
matTheBasicImageContainer, this replaces the old *CvMat* and *IplImage* ones. Switching to the new
functions is easier. You just need to remember a couple of new things.
OpenCV 2 received reorganization. No longer are all the functions crammed into a single library. We
have many modules, each of them containing data structures and functions relevant to certain tasks.
This way you do not need to ship a large library if you use just a subset of OpenCV. This means that
you should also include only those headers you will use. For example:
@code{.cpp}
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
@endcode
All the OpenCV related stuff is put into the *cv* namespace to avoid name conflicts with other
libraries data structures and functions. Therefore, either you need to prepend the *cv::* keyword
before everything that comes from OpenCV or after the includes, you just add a directive to use
this:
@code{.cpp}
using namespace cv; // The new C++ interface API is inside this namespace. Import it.
@endcode
Because the functions are already in a namespace there is no need for them to contain the *cv*
prefix in their name. As such all the new C++ compatible functions don't have this and they follow
the camel case naming rule. This means the first letter is small (unless it's a name, like Canny)
and the subsequent words start with a capital letter (like *copyMakeBorder*).
Now, remember that you need to link to your application all the modules you use, and in case you are
on Windows using the *DLL* system you will need to add, again, to the path all the binaries. For
more in-depth information if you're on Windows read @ref Windows_Visual_Studio_How_To and for
Linux an example usage is explained in @ref Linux_Eclipse_Usage.
Now for converting the *Mat* object you can use either the *IplImage* or the *CvMat* operators.
While in the C interface you used to work with pointers here it's no longer the case. In the C++
interface we have mostly *Mat* objects. These objects may be freely converted to both *IplImage* and
*CvMat* with simple assignment. For example:
@code{.cpp}
Mat I;
IplImage pI = I;
CvMat mI = I;
@endcode
Now if you want pointers the conversion gets just a little more complicated. The compilers can no
longer automatically determinate what you want and as you need to explicitly specify your goal. This
is to call the *IplImage* and *CvMat* operators and then get their pointers. For getting the pointer
we use the & sign:
@code{.cpp}
Mat I;
IplImage* pI = &I.operator IplImage();
CvMat* mI = &I.operator CvMat();
@endcode
One of the biggest complaints of the C interface is that it leaves all the memory management to you.
You need to figure out when it is safe to release your unused objects and make sure you do so before
the program finishes or you could have troublesome memory leeks. To work around this issue in OpenCV
there is introduced a sort of smart pointer. This will automatically release the object when it's no
longer in use. To use this declare the pointers as a specialization of the *Ptr* :
@code{.cpp}
Ptr<IplImage> piI = &I.operator IplImage();
@endcode
Converting from the C data structures to the *Mat* is done by passing these inside its constructor.
For example:
@code{.cpp}
Mat K(piL), L;
L = Mat(pI);
@endcode
A case study
------------
Now that you have the basics done [here's
](samples/cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp)
an example that mixes the usage of the C interface with the C++ one. You will also find it in the
sample directory of the OpenCV source code library at the
`samples/cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp` .
To further help on seeing the difference the programs supports two modes: one mixed C and C++ and
one pure C++. If you define the *DEMO_MIXED_API_USE* you'll end up using the first. The program
separates the color planes, does some modifications on them and in the end merge them back together.
@includelineno
cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp
lines
1-10, 23-26, 29-46
Here you can observe that with the new structure we have no pointer problems, although it is
possible to use the old functions and in the end just transform the result to a *Mat* object.
@includelineno
cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp
lines
48-53
Because, we want to mess around with the images luma component we first convert from the default RGB
to the YUV color space and then split the result up into separate planes. Here the program splits:
in the first example it processes each plane using one of the three major image scanning algorithms
in OpenCV (C [] operator, iterator, individual element access). In a second variant we add to the
image some Gaussian noise and then mix together the channels according to some formula.
The scanning version looks like:
@includelineno
cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp
lines
57-77
Here you can observe that we may go through all the pixels of an image in three fashions: an
iterator, a C pointer and an individual element access style. You can read a more in-depth
description of these in the @ref howToScanImagesOpenCV tutorial. Converting from the old function
names is easy. Just remove the cv prefix and use the new *Mat* data structure. Here's an example of
this by using the weighted addition function:
@includelineno
cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp
lines
81-113
As you may observe the *planes* variable is of type *Mat*. However, converting from *Mat* to
*IplImage* is easy and made automatically with a simple assignment operator.
@includelineno
cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp
lines
117-129
The new *imshow* highgui function accepts both the *Mat* and *IplImage* data structures. Compile and
run the program and if the first image below is your input you may get either the first or second as
output:
![image](images/outputInteropOpenCV1.jpg)
You may observe a runtime instance of this on the [YouTube
here](https://www.youtube.com/watch?v=qckm-zvo31w) and you can [download the source code from here
](samples/cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp)
or find it in the
`samples/cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp`
of the OpenCV source code library.
\htmlonly
<div align="center">
<iframe title="Interoperability with OpenCV 1" width="560" height="349" src="http://www.youtube.com/embed/qckm-zvo31w?rel=0&loop=1" frameborder="0" allowfullscreen align="middle"></iframe>
</div>
\endhtmlonly
Mask operations on matrices {#tutorial_mat_mask_operations}
===========================
Mask operations on matrices are quite simple. The idea is that we recalculate each pixels value in
an image according to a mask matrix (also known as kernel). This mask holds values that will adjust
how much influence neighboring pixels (and the current pixel) have on the new pixel value. From a
mathematical point of view we make a weighted average, with our specified values.
Our test case
-------------
Let us consider the issue of an image contrast enhancement method. Basically we want to apply for
every pixel of the image the following formula:
\f[I(i,j) = 5*I(i,j) - [ I(i-1,j) + I(i+1,j) + I(i,j-1) + I(i,j+1)]\f]\f[\iff I(i,j)*M, \text{where }
M = \bordermatrix{ _i\backslash ^j & -1 & 0 & +1 \cr
-1 & 0 & -1 & 0 \cr
0 & -1 & 5 & -1 \cr
+1 & 0 & -1 & 0 \cr
}\f]
The first notation is by using a formula, while the second is a compacted version of the first by
using a mask. You use the mask by putting the center of the mask matrix (in the upper case noted by
the zero-zero index) on the pixel you want to calculate and sum up the pixel values multiplied with
the overlapped matrix values. It's the same thing, however in case of large matrices the latter
notation is a lot easier to look over.
Now let us see how we can make this happen by using the basic pixel access method or by using the
@ref cv::filter2D function.
The Basic Method
----------------
Here's a function that will do this:
@code{.cpp}
void Sharpen(const Mat& myImage, Mat& Result)
{
CV_Assert(myImage.depth() == CV_8U); // accept only uchar images
Result.create(myImage.size(), myImage.type());
const int nChannels = myImage.channels();
for(int j = 1; j < myImage.rows - 1; ++j)
{
const uchar* previous = myImage.ptr<uchar>(j - 1);
const uchar* current = myImage.ptr<uchar>(j );
const uchar* next = myImage.ptr<uchar>(j + 1);
uchar* output = Result.ptr<uchar>(j);
for(int i = nChannels; i < nChannels * (myImage.cols - 1); ++i)
{
*output++ = saturate_cast<uchar>(5 * current[i]
-current[i - nChannels] - current[i + nChannels] - previous[i] - next[i]);
}
}
Result.row(0).setTo(Scalar(0));
Result.row(Result.rows - 1).setTo(Scalar(0));
Result.col(0).setTo(Scalar(0));
Result.col(Result.cols - 1).setTo(Scalar(0));
}
@endcode
At first we make sure that the input images data is in unsigned char format. For this we use the
@ref cv::CV_Assert function that throws an error when the expression inside it is false.
@code{.cpp}
CV_Assert(myImage.depth() == CV_8U); // accept only uchar images
@endcode
We create an output image with the same size and the same type as our input. As you can see in the
@ref How_Image_Stored_Memory section, depending on the number of channels we may have one or more
subcolumns. We will iterate through them via pointers so the total number of elements depends from
this number.
@code{.cpp}
Result.create(myImage.size(), myImage.type());
const int nChannels = myImage.channels();
@endcode
We'll use the plain C [] operator to access pixels. Because we need to access multiple rows at the
same time we'll acquire the pointers for each of them (a previous, a current and a next line). We
need another pointer to where we're going to save the calculation. Then simply access the right
items with the [] operator. For moving the output pointer ahead we simply increase this (with one
byte) after each operation:
@code{.cpp}
for(int j = 1; j < myImage.rows - 1; ++j)
{
const uchar* previous = myImage.ptr<uchar>(j - 1);
const uchar* current = myImage.ptr<uchar>(j );
const uchar* next = myImage.ptr<uchar>(j + 1);
uchar* output = Result.ptr<uchar>(j);
for(int i = nChannels; i < nChannels * (myImage.cols - 1); ++i)
{
*output++ = saturate_cast<uchar>(5 * current[i]
-current[i - nChannels] - current[i + nChannels] - previous[i] - next[i]);
}
}
@endcode
On the borders of the image the upper notation results inexistent pixel locations (like minus one -
minus one). In these points our formula is undefined. A simple solution is to not apply the kernel
in these points and, for example, set the pixels on the borders to zeros:
@code{.cpp}
Result.row(0).setTo(Scalar(0)); // The top row
Result.row(Result.rows - 1).setTo(Scalar(0)); // The bottom row
Result.col(0).setTo(Scalar(0)); // The left column
Result.col(Result.cols - 1).setTo(Scalar(0)); // The right column
@endcode
The filter2D function
---------------------
Applying such filters are so common in image processing that in OpenCV there exist a function that
will take care of applying the mask (also called a kernel in some places). For this you first need
to define a *Mat* object that holds the mask:
@code{.cpp}
Mat kern = (Mat_<char>(3,3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
@endcode
Then call the @ref cv::filter2D function specifying the input, the output image and the kernell to
use:
@code{.cpp}
filter2D(I, K, I.depth(), kern);
@endcode
The function even has a fifth optional argument to specify the center of the kernel, and a sixth one
for determining what to do in the regions where the operation is undefined (borders). Using this
function has the advantage that it's shorter, less verbose and because there are some optimization
techniques implemented it is usually faster than the *hand-coded method*. For example in my test
while the second one took only 13 milliseconds the first took around 31 milliseconds. Quite some
difference.
For example:
![image](images/resultMatMaskFilter2D.png)
You can download this source code from [here
](samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp) or look in the
OpenCV source code libraries sample directory at
`samples/cpp/tutorial_code/core/mat_mask_operations/mat_mask_operations.cpp`.
Check out an instance of running the program on our [YouTube
channel](http://www.youtube.com/watch?v=7PF1tAU9se4) .
\htmlonly
<div align="center">
<iframe width="560" height="349" src="https://www.youtube.com/embed/7PF1tAU9se4?hd=1" frameborder="0" allowfullscreen></iframe>
</div>
\endhtmlonly
Random generator and text with OpenCV {#tutorial_random_generator_and_text}
=====================================
Goals
-----
In this tutorial you will learn how to:
- Use the *Random Number generator class* (@ref cv::RNG ) and how to get a random number from a
uniform distribution.
- Display text on an OpenCV window by using the function @ref cv::putText
Code
----
- In the previous tutorial (@ref Drawing_1) we drew diverse geometric figures, giving as input
parameters such as coordinates (in the form of @ref cv::Points ), color, thickness, etc. You
might have noticed that we gave specific values for these arguments.
- In this tutorial, we intend to use *random* values for the drawing parameters. Also, we intend
to populate our image with a big number of geometric figures. Since we will be initializing them
in a random fashion, this process will be automatic and made by using *loops* .
- This code is in your OpenCV sample folder. Otherwise you can grab it from
[here](http://code.opencv.org/projects/opencv/repository/revisions/master/raw/samples/cpp/tutorial_code/core/Matrix/Drawing_2.cpp)
.
Explanation
-----------
1. Let's start by checking out the *main* function. We observe that first thing we do is creating a
*Random Number Generator* object (RNG):
@code{.cpp}
RNG rng( 0xFFFFFFFF );
@endcode
RNG implements a random number generator. In this example, *rng* is a RNG element initialized
with the value *0xFFFFFFFF*
2. Then we create a matrix initialized to *zeros* (which means that it will appear as black),
specifying its height, width and its type:
@code{.cpp}
/// Initialize a matrix filled with zeros
Mat image = Mat::zeros( window_height, window_width, CV_8UC3 );
/// Show it in a window during DELAY ms
imshow( window_name, image );
@endcode
3. Then we proceed to draw crazy stuff. After taking a look at the code, you can see that it is
mainly divided in 8 sections, defined as functions:
@code{.cpp}
/// Now, let's draw some lines
c = Drawing_Random_Lines(image, window_name, rng);
if( c != 0 ) return 0;
/// Go on drawing, this time nice rectangles
c = Drawing_Random_Rectangles(image, window_name, rng);
if( c != 0 ) return 0;
/// Draw some ellipses
c = Drawing_Random_Ellipses( image, window_name, rng );
if( c != 0 ) return 0;
/// Now some polylines
c = Drawing_Random_Polylines( image, window_name, rng );
if( c != 0 ) return 0;
/// Draw filled polygons
c = Drawing_Random_Filled_Polygons( image, window_name, rng );
if( c != 0 ) return 0;
/// Draw circles
c = Drawing_Random_Circles( image, window_name, rng );
if( c != 0 ) return 0;
/// Display text in random positions
c = Displaying_Random_Text( image, window_name, rng );
if( c != 0 ) return 0;
/// Displaying the big end!
c = Displaying_Big_End( image, window_name, rng );
@endcode
All of these functions follow the same pattern, so we will analyze only a couple of them, since
the same explanation applies for all.
4. Checking out the function **Drawing_Random_Lines**:
@code{.cpp}
int Drawing_Random_Lines( Mat image, char* window_name, RNG rng )
{
int lineType = 8;
Point pt1, pt2;
for( int i = 0; i < NUMBER; i++ )
{
pt1.x = rng.uniform( x_1, x_2 );
pt1.y = rng.uniform( y_1, y_2 );
pt2.x = rng.uniform( x_1, x_2 );
pt2.y = rng.uniform( y_1, y_2 );
line( image, pt1, pt2, randomColor(rng), rng.uniform(1, 10), 8 );
imshow( window_name, image );
if( waitKey( DELAY ) >= 0 )
{ return -1; }
}
return 0;
}
@endcode
We can observe the following:
- The *for* loop will repeat **NUMBER** times. Since the function @ref cv::line is inside this
loop, that means that **NUMBER** lines will be generated.
- The line extremes are given by *pt1* and *pt2*. For *pt1* we can see that:
@code{.cpp}
pt1.x = rng.uniform( x_1, x_2 );
pt1.y = rng.uniform( y_1, y_2 );
@endcode
- We know that **rng** is a *Random number generator* object. In the code above we are
calling **rng.uniform(a,b)**. This generates a radombly uniformed distribution between
the values **a** and **b** (inclusive in **a**, exclusive in **b**).
- From the explanation above, we deduce that the extremes *pt1* and *pt2* will be random
values, so the lines positions will be quite impredictable, giving a nice visual effect
(check out the Result section below).
- As another observation, we notice that in the @ref cv::line arguments, for the *color*
input we enter:
@code{.cpp}
randomColor(rng)
@endcode
Let's check the function implementation:
@code{.cpp}
static Scalar randomColor( RNG& rng )
{
int icolor = (unsigned) rng;
return Scalar( icolor&255, (icolor>>8)&255, (icolor>>16)&255 );
}
@endcode
As we can see, the return value is an *Scalar* with 3 randomly initialized values, which
are used as the *R*, *G* and *B* parameters for the line color. Hence, the color of the
lines will be random too!
5. The explanation above applies for the other functions generating circles, ellipses, polygones,
etc. The parameters such as *center* and *vertices* are also generated randomly.
6. Before finishing, we also should take a look at the functions *Display_Random_Text* and
*Displaying_Big_End*, since they both have a few interesting features:
7. **Display_Random_Text:**
@code{.cpp}
int Displaying_Random_Text( Mat image, char* window_name, RNG rng )
{
int lineType = 8;
for ( int i = 1; i < NUMBER; i++ )
{
Point org;
org.x = rng.uniform(x_1, x_2);
org.y = rng.uniform(y_1, y_2);
putText( image, "Testing text rendering", org, rng.uniform(0,8),
rng.uniform(0,100)*0.05+0.1, randomColor(rng), rng.uniform(1, 10), lineType);
imshow( window_name, image );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}
return 0;
}
@endcode
Everything looks familiar but the expression:
@code{.cpp}
putText( image, "Testing text rendering", org, rng.uniform(0,8),
rng.uniform(0,100)*0.05+0.1, randomColor(rng), rng.uniform(1, 10), lineType);
@endcode
So, what does the function @ref cv::putText do? In our example:
- Draws the text **"Testing text rendering"** in **image**
- The bottom-left corner of the text will be located in the Point **org**
- The font type is a random integer value in the range: \f$[0, 8>\f$.
- The scale of the font is denoted by the expression **rng.uniform(0, 100)x0.05 + 0.1**
(meaning its range is: \f$[0.1, 5.1>\f$)
- The text color is random (denoted by **randomColor(rng)**)
- The text thickness ranges between 1 and 10, as specified by **rng.uniform(1,10)**
As a result, we will get (analagously to the other drawing functions) **NUMBER** texts over our
image, in random locations.
8. **Displaying_Big_End**
@code{.cpp}
int Displaying_Big_End( Mat image, char* window_name, RNG rng )
{
Size textsize = getTextSize("OpenCV forever!", FONT_HERSHEY_COMPLEX, 3, 5, 0);
Point org((window_width - textsize.width)/2, (window_height - textsize.height)/2);
int lineType = 8;
Mat image2;
for( int i = 0; i < 255; i += 2 )
{
image2 = image - Scalar::all(i);
putText( image2, "OpenCV forever!", org, FONT_HERSHEY_COMPLEX, 3,
Scalar(i, i, 255), 5, lineType );
imshow( window_name, image2 );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}
return 0;
}
@endcode
Besides the function **getTextSize** (which gets the size of the argument text), the new
operation we can observe is inside the *foor* loop:
@code{.cpp}
image2 = image - Scalar::all(i)
@endcode
So, **image2** is the substraction of **image** and **Scalar::all(i)**. In fact, what happens
here is that every pixel of **image2** will be the result of substracting every pixel of
**image** minus the value of **i** (remember that for each pixel we are considering three values
such as R, G and B, so each of them will be affected)
Also remember that the substraction operation *always* performs internally a **saturate**
operation, which means that the result obtained will always be inside the allowed range (no
negative and between 0 and 255 for our example).
Result
------
As you just saw in the Code section, the program will sequentially execute diverse drawing
functions, which will produce:
1. First a random set of *NUMBER* lines will appear on screen such as it can be seen in this
screenshot:
![image](images/Drawing_2_Tutorial_Result_0.jpg)
2. Then, a new set of figures, these time *rectangles* will follow.
3. Now some ellipses will appear, each of them with random position, size, thickness and arc
length:
![image](images/Drawing_2_Tutorial_Result_2.jpg)
4. Now, *polylines* with 03 segments will appear on screen, again in random configurations.
![image](images/Drawing_2_Tutorial_Result_3.jpg)
5. Filled polygons (in this example triangles) will follow.
6. The last geometric figure to appear: circles!
![image](images/Drawing_2_Tutorial_Result_5.jpg)
7. Near the end, the text *"Testing Text Rendering"* will appear in a variety of fonts, sizes,
colors and positions.
8. And the big end (which by the way expresses a big truth too):
![image](images/Drawing_2_Tutorial_Result_7.jpg)
The Core Functionality (core module) {#tutorial_table_of_content_core}
=====================================
Here you will learn the about the basic building blocks of the library. A must read and know for
understanding how to manipulate the images on a pixel level.
- @subpage tutorial_mat_the_basic_image_container
*Compatibility:* \> OpenCV 2.0
*Author:* Bernát Gábor
You will learn how to store images in the memory and how to print out their content to the
console.
- @subpage tutorial_how_to_scan_images
*Compatibility:* \> OpenCV 2.0
*Author:* Bernát Gábor
You'll find out how to scan images (go through each of the image pixels) with OpenCV.
Bonus: time measurement with OpenCV.
- @subpage tutorial_mat_mask_operations
*Compatibility:* \> OpenCV 2.0
*Author:* Bernát Gábor
You'll find out how to scan images with neighbor access and use the @ref cv::filter2D
function to apply kernel filters on images.
- @subpage tutorial_adding_images
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
We will learn how to blend two images!
- @subpage tutorial_basic_linear_transform
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
We will learn how to change our image appearance!
- @subpage tutorial_basic_geometric_drawing
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
We will learn how to draw simple geometry with OpenCV!
- @subpage tutorial_random_generator_and_text
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
We will draw some *fancy-looking* stuff using OpenCV!
- @subpage tutorial_discrete_fourier_transform
*Compatibility:* \> OpenCV 2.0
*Author:* Bernát Gábor
You will see how and why use the Discrete Fourier transformation with OpenCV.
- @subpage tutorial_file_input_output_with_xml_yml
*Compatibility:* \> OpenCV 2.0
*Author:* Bernát Gábor
You will see how to use the @ref cv::FileStorage data structure of OpenCV to write and read
data to XML or YAML file format.
- @subpage tutorial_interoperability_with_OpenCV_1
*Compatibility:* \> OpenCV 2.0
*Author:* Bernát Gábor
Did you used OpenCV before its 2.0 version? Do you wanna know what happened with your library
with 2.0? Don't you know how to convert your old OpenCV programs to the new C++ interface?
Look here to shed light on all this questions.
- @subpage tutorial_how_to_use_ippa_conversion
*Compatibility:* \> OpenCV 2.0
*Author:* Elena Gvozdeva
You will see how to use the IPP Async with OpenCV.
AKAZE local features matching {#tutorial_akaze_matching}
=============================
Introduction
------------
In this tutorial we will learn how to use [AKAZE]_ local features to detect and match keypoints on
two images.
We will find keypoints on a pair of images with given homography matrix, match them and count the
number of inliers (i. e. matches that fit in the given homography).
You can find expanded version of this example here:
<https://github.com/pablofdezalc/test_kaze_akaze_opencv>
Data
----
We are going to use images 1 and 3 from *Graffity* sequence of Oxford dataset.
![image](images/graf.png)
Homography is given by a 3 by 3 matrix:
@code{.none}
7.6285898e-01 -2.9922929e-01 2.2567123e+02
3.3443473e-01 1.0143901e+00 -7.6999973e+01
3.4663091e-04 -1.4364524e-05 1.0000000e+00
@endcode
You can find the images (*graf1.png*, *graf3.png*) and homography (*H1to3p.xml*) in
*opencv/samples/cpp*.
### Source Code
@includelineno cpp/tutorial_code/features2D/AKAZE_match.cpp
### Explanation
1. **Load images and homography**
@code{.cpp}
Mat img1 = imread("graf1.png", IMREAD_GRAYSCALE);
Mat img2 = imread("graf3.png", IMREAD_GRAYSCALE);
Mat homography;
FileStorage fs("H1to3p.xml", FileStorage::READ);
fs.getFirstTopLevelNode() >> homography;
@endcode
We are loading grayscale images here. Homography is stored in the xml created with FileStorage.
1. **Detect keypoints and compute descriptors using AKAZE**
@code{.cpp}
vector<KeyPoint> kpts1, kpts2;
Mat desc1, desc2;
AKAZE akaze;
akaze(img1, noArray(), kpts1, desc1);
akaze(img2, noArray(), kpts2, desc2);
@endcode
We create AKAZE object and use it's *operator()* functionality. Since we don't need the *mask*
parameter, *noArray()* is used.
1. **Use brute-force matcher to find 2-nn matches**
@code{.cpp}
BFMatcher matcher(NORM_HAMMING);
vector< vector<DMatch> > nn_matches;
matcher.knnMatch(desc1, desc2, nn_matches, 2);
@endcode
We use Hamming distance, because AKAZE uses binary descriptor by default.
1. **Use 2-nn matches to find correct keypoint matches**
@code{.cpp}
for(size_t i = 0; i < nn_matches.size(); i++) {
DMatch first = nn_matches[i][0];
float dist1 = nn_matches[i][0].distance;
float dist2 = nn_matches[i][1].distance;
if(dist1 < nn_match_ratio * dist2) {
matched1.push_back(kpts1[first.queryIdx]);
matched2.push_back(kpts2[first.trainIdx]);
}
}
@endcode
If the closest match is *ratio* closer than the second closest one, then the match is correct.
1. **Check if our matches fit in the homography model**
@code{.cpp}
for(int i = 0; i < matched1.size(); i++) {
Mat col = Mat::ones(3, 1, CV_64F);
col.at<double>(0) = matched1[i].pt.x;
col.at<double>(1) = matched1[i].pt.y;
col = homography * col;
col /= col.at<double>(2);
float dist = sqrt( pow(col.at<double>(0) - matched2[i].pt.x, 2) +
pow(col.at<double>(1) - matched2[i].pt.y, 2));
if(dist < inlier_threshold) {
int new_i = inliers1.size();
inliers1.push_back(matched1[i]);
inliers2.push_back(matched2[i]);
good_matches.push_back(DMatch(new_i, new_i, 0));
}
}
@endcode
If the distance from first keypoint's projection to the second keypoint is less than threshold,
then it it fits in the homography.
We create a new set of matches for the inliers, because it is required by the drawing function.
1. **Output results**
@code{.cpp}
Mat res;
drawMatches(img1, inliers1, img2, inliers2, good_matches, res);
imwrite("res.png", res);
...
@endcode
Here we save the resulting image and print some statistics.
### Results
Found matches
-------------
![image](images/res.png)
A-KAZE Matching Results
-----------------------
@code{.none}
Keypoints 1: 2943
Keypoints 2: 3511
Matches: 447
Inliers: 308
Inlier Ratio: 0.689038}
@endcode
AKAZE and ORB planar tracking {#tutorial_akaze_tracking}
=============================
Introduction
------------
In this tutorial we will compare *AKAZE* and *ORB* local features using them to find matches between
video frames and track object movements.
The algorithm is as follows:
- Detect and describe keypoints on the first frame, manually set object boundaries
- For every next frame:
1. Detect and describe keypoints
2. Match them using bruteforce matcher
3. Estimate homography transformation using RANSAC
4. Filter inliers from all the matches
5. Apply homography transformation to the bounding box to find the object
6. Draw bounding box and inliers, compute inlier ratio as evaluation metric
![image](images/frame.png)
### Data
To do the tracking we need a video and object position on the first frame.
You can download our example video and data from
[here](https://docs.google.com/file/d/0B72G7D4snftJandBb0taLVJHMFk).
To run the code you have to specify input and output video path and object bounding box.
@code{.none}
./planar_tracking blais.mp4 result.avi blais_bb.xml.gz
@endcode
### Source Code
@includelineno cpp/tutorial_code/features2D/AKAZE_tracking/planar_tracking.cpp
### Explanation
Tracker class
-------------
This class implements algorithm described abobve using given feature detector and descriptor
matcher.
- **Setting up the first frame**
@code{.cpp}
void Tracker::setFirstFrame(const Mat frame, vector<Point2f> bb, string title, Stats& stats)
{
first_frame = frame.clone();
(*detector)(first_frame, noArray(), first_kp, first_desc);
stats.keypoints = (int)first_kp.size();
drawBoundingBox(first_frame, bb);
putText(first_frame, title, Point(0, 60), FONT_HERSHEY_PLAIN, 5, Scalar::all(0), 4);
object_bb = bb;
}
@endcode
We compute and store keypoints and descriptors from the first frame and prepare it for the
output.
We need to save number of detected keypoints to make sure both detectors locate roughly the same
number of those.
- **Processing frames**
1. Locate keypoints and compute descriptors
@code{.cpp}
(*detector)(frame, noArray(), kp, desc);
@endcode
To find matches between frames we have to locate the keypoints first.
In this tutorial detectors are set up to find about 1000 keypoints on each frame.
1. Use 2-nn matcher to find correspondences
@code{.cpp}
matcher->knnMatch(first_desc, desc, matches, 2);
for(unsigned i = 0; i < matches.size(); i++) {
if(matches[i][0].distance < nn_match_ratio * matches[i][1].distance) {
matched1.push_back(first_kp[matches[i][0].queryIdx]);
matched2.push_back( kp[matches[i][0].trainIdx]);
}
}
@endcode
If the closest match is *nn_match_ratio* closer than the second closest one, then it's a
match.
2. Use *RANSAC* to estimate homography transformation
@code{.cpp}
homography = findHomography(Points(matched1), Points(matched2),
RANSAC, ransac_thresh, inlier_mask);
@endcode
If there are at least 4 matches we can use random sample consensus to estimate image
transformation.
3. Save the inliers
@code{.cpp}
for(unsigned i = 0; i < matched1.size(); i++) {
if(inlier_mask.at<uchar>(i)) {
int new_i = static_cast<int>(inliers1.size());
inliers1.push_back(matched1[i]);
inliers2.push_back(matched2[i]);
inlier_matches.push_back(DMatch(new_i, new_i, 0));
}
}
@endcode
Since *findHomography* computes the inliers we only have to save the chosen points and
matches.
4. Project object bounding box
@code{.cpp}
perspectiveTransform(object_bb, new_bb, homography);
@endcode
If there is a reasonable number of inliers we can use estimated transformation to locate the
object.
### Results
You can watch the resulting [video on youtube](http://www.youtube.com/watch?v=LWY-w8AGGhE).
*AKAZE* statistics:
@code{.none}
Matches 626
Inliers 410
Inlier ratio 0.58
Keypoints 1117
@endcode
*ORB* statistics:
@code{.none}
Matches 504
Inliers 319
Inlier ratio 0.56
Keypoints 1112
@endcode
Detection of planar objects {#tutorial_detection_of_planar_objects}
===========================
The goal of this tutorial is to learn how to use *features2d* and *calib3d* modules for detecting
known planar objects in scenes.
*Test data*: use images in your data folder, for instance, box.png and box_in_scene.png.
- Create a new console project. Read two input images. :
Mat img1 = imread(argv[1], IMREAD_GRAYSCALE);
Mat img2 = imread(argv[2], IMREAD_GRAYSCALE);
- Detect keypoints in both images and compute descriptors for each of the keypoints. :
// detecting keypoints
Ptr<Feature2D> surf = SURF::create();
vector<KeyPoint> keypoints1;
Mat descriptors1;
surf->detectAndCompute(img1, Mat(), keypoints1, descriptors1);
... // do the same for the second image
- Now, find the closest matches between descriptors from the first image to the second: :
// matching descriptors
BruteForceMatcher<L2<float> > matcher;
vector<DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
- Visualize the results: :
// drawing the results
namedWindow("matches", 1);
Mat img_matches;
drawMatches(img1, keypoints1, img2, keypoints2, matches, img_matches);
imshow("matches", img_matches);
waitKey(0);
- Find the homography transformation between two sets of points: :
vector<Point2f> points1, points2;
// fill the arrays with the points
....
Mat H = findHomography(Mat(points1), Mat(points2), RANSAC, ransacReprojThreshold);
- Create a set of inlier matches and draw them. Use perspectiveTransform function to map points
with homography:
Mat points1Projected; perspectiveTransform(Mat(points1), points1Projected, H);
- Use drawMatches for drawing inliers.
Feature Description {#tutorial_feature_description}
===================
Goal
----
In this tutorial you will learn how to:
- Use the @ref cv::DescriptorExtractor interface in order to find the feature vector correspondent
to the keypoints. Specifically:
- Use @ref cv::SurfDescriptorExtractor and its function @ref cv::compute to perform the
required calculations.
- Use a @ref cv::BFMatcher to match the features vector
- Use the function @ref cv::drawMatches to draw the detected matches.
Theory
------
Code
----
This tutorial code's is shown lines below.
@code{.cpp}
#include <stdio.h>
#include <iostream>
#include "opencv2/core.hpp"
#include "opencv2/features2d.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/xfeatures2d.hpp"
using namespace cv;
using namespace cv::xfeatures2d;
void readme();
/* @function main */
int main( int argc, char** argv )
{
if( argc != 3 )
{ return -1; }
Mat img_1 = imread( argv[1], IMREAD_GRAYSCALE );
Mat img_2 = imread( argv[2], IMREAD_GRAYSCALE );
if( !img_1.data || !img_2.data )
{ return -1; }
//-- Step 1: Detect the keypoints using SURF Detector, compute the descriptors
int minHessian = 400;
Ptr<SURF> detector = SURF::create();
detector->setMinHessian(minHessian);
std::vector<KeyPoint> keypoints_1, keypoints_2;
Mat descriptors_1, descriptors_2;
detector->detectAndCompute( img_1, keypoints_1, descriptors_1 );
detector->detectAndCompute( img_2, keypoints_2, descriptors_2 );
//-- Step 2: Matching descriptor vectors with a brute force matcher
BFMatcher matcher(NORM_L2);
std::vector< DMatch > matches;
matcher.match( descriptors_1, descriptors_2, matches );
//-- Draw matches
Mat img_matches;
drawMatches( img_1, keypoints_1, img_2, keypoints_2, matches, img_matches );
//-- Show detected matches
imshow("Matches", img_matches );
waitKey(0);
return 0;
}
/* @function readme */
void readme()
{ std::cout << " Usage: ./SURF_descriptor <img1> <img2>" << std::endl; }
@endcode
Explanation
-----------
Result
------
1. Here is the result after applying the BruteForce matcher between the two original images:
![image](images/Feature_Description_BruteForce_Result.jpg)
Feature Detection {#tutorial_feature_detection}
=================
Goal
----
In this tutorial you will learn how to:
- Use the @ref cv::FeatureDetector interface in order to find interest points. Specifically:
- Use the @ref cv::SurfFeatureDetector and its function @ref cv::detect to perform the
detection process
- Use the function @ref cv::drawKeypoints to draw the detected keypoints
Theory
------
Code
----
This tutorial code's is shown lines below.
@code{.cpp}
#include <stdio.h>
#include <iostream>
#include "opencv2/core.hpp"
#include "opencv2/features2d.hpp"
#include "opencv2/xfeatures2d.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
using namespace cv::xfeatures2d;
void readme();
/* @function main */
int main( int argc, char** argv )
{
if( argc != 3 )
{ readme(); return -1; }
Mat img_1 = imread( argv[1], IMREAD_GRAYSCALE );
Mat img_2 = imread( argv[2], IMREAD_GRAYSCALE );
if( !img_1.data || !img_2.data )
{ std::cout<< " --(!) Error reading images " << std::endl; return -1; }
//-- Step 1: Detect the keypoints using SURF Detector
int minHessian = 400;
Ptr<SURF> detector = SURF::create( minHessian );
std::vector<KeyPoint> keypoints_1, keypoints_2;
detector->detect( img_1, keypoints_1 );
detector->detect( img_2, keypoints_2 );
//-- Draw keypoints
Mat img_keypoints_1; Mat img_keypoints_2;
drawKeypoints( img_1, keypoints_1, img_keypoints_1, Scalar::all(-1), DrawMatchesFlags::DEFAULT );
drawKeypoints( img_2, keypoints_2, img_keypoints_2, Scalar::all(-1), DrawMatchesFlags::DEFAULT );
//-- Show detected (drawn) keypoints
imshow("Keypoints 1", img_keypoints_1 );
imshow("Keypoints 2", img_keypoints_2 );
waitKey(0);
return 0;
}
/* @function readme */
void readme()
{ std::cout << " Usage: ./SURF_detector <img1> <img2>" << std::endl; }
@endcode
Explanation
-----------
Result
------
1. Here is the result of the feature detection applied to the first image:
![image](images/Feature_Detection_Result_a.jpg)
2. And here is the result for the second image:
![image](images/Feature_Detection_Result_b.jpg)
Feature Matching with FLANN {#tutorial_feature_flann_matcher}
===========================
Goal
----
In this tutorial you will learn how to:
- Use the @ref cv::FlannBasedMatcher interface in order to perform a quick and efficient matching
by using the @ref cv::FLANN ( *Fast Approximate Nearest Neighbor Search Library* )
Theory
------
Code
----
This tutorial code's is shown lines below.
@code{.cpp}
/*
* @file SURF_FlannMatcher
* @brief SURF detector + descriptor + FLANN Matcher
* @author A. Huaman
*/
#include <stdio.h>
#include <iostream>
#include <stdio.h>
#include <iostream>
#include "opencv2/core.hpp"
#include "opencv2/features2d.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/xfeatures2d.hpp"
using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;
void readme();
/*
* @function main
* @brief Main function
*/
int main( int argc, char** argv )
{
if( argc != 3 )
{ readme(); return -1; }
Mat img_1 = imread( argv[1], IMREAD_GRAYSCALE );
Mat img_2 = imread( argv[2], IMREAD_GRAYSCALE );
if( !img_1.data || !img_2.data )
{ std::cout<< " --(!) Error reading images " << std::endl; return -1; }
//-- Step 1: Detect the keypoints using SURF Detector
int minHessian = 400;
SurfFeatureDetector detector( minHessian );
std::vector<KeyPoint> keypoints_1, keypoints_2;
detector.detect( img_1, keypoints_1 );
detector.detect( img_2, keypoints_2 );
//-- Step 2: Calculate descriptors (feature vectors)
SurfDescriptorExtractor extractor;
Mat descriptors_1, descriptors_2;
extractor.compute( img_1, keypoints_1, descriptors_1 );
extractor.compute( img_2, keypoints_2, descriptors_2 );
//-- Step 3: Matching descriptor vectors using FLANN matcher
FlannBasedMatcher matcher;
std::vector< DMatch > matches;
matcher.match( descriptors_1, descriptors_2, matches );
double max_dist = 0; double min_dist = 100;
//-- Quick calculation of max and min distances between keypoints
for( int i = 0; i < descriptors_1.rows; i++ )
{ double dist = matches[i].distance;
if( dist < min_dist ) min_dist = dist;
if( dist > max_dist ) max_dist = dist;
}
printf("-- Max dist : %f \n", max_dist );
printf("-- Min dist : %f \n", min_dist );
//-- Draw only "good" matches (i.e. whose distance is less than 2*min_dist,
//-- or a small arbitary value ( 0.02 ) in the event that min_dist is very
//-- small)
//-- PS.- radiusMatch can also be used here.
std::vector< DMatch > good_matches;
for( int i = 0; i < descriptors_1.rows; i++ )
{ if( matches[i].distance <= max(2*min_dist, 0.02) )
{ good_matches.push_back( matches[i]); }
}
//-- Draw only "good" matches
Mat img_matches;
drawMatches( img_1, keypoints_1, img_2, keypoints_2,
good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
//-- Show detected matches
imshow( "Good Matches", img_matches );
for( int i = 0; i < (int)good_matches.size(); i++ )
{ printf( "-- Good Match [%d] Keypoint 1: %d -- Keypoint 2: %d \n", i, good_matches[i].queryIdx, good_matches[i].trainIdx ); }
waitKey(0);
return 0;
}
/*
* @function readme
*/
void readme()
{ std::cout << " Usage: ./SURF_FlannMatcher <img1> <img2>" << std::endl; }
@endcode
Explanation
-----------
Result
------
1. Here is the result of the feature detection applied to the first image:
![image](images/Featur_FlannMatcher_Result.jpg)
2. Additionally, we get as console output the keypoints filtered:
![image](images/Feature_FlannMatcher_Keypoints_Result.jpg)
Features2D + Homography to find a known object {#tutorial_feature_homography}
==============================================
Goal
----
In this tutorial you will learn how to:
- Use the function @ref cv::findHomography to find the transform between matched keypoints.
- Use the function @ref cv::perspectiveTransform to map the points.
Theory
------
Code
----
This tutorial code's is shown lines below.
@code{.cpp}
#include <stdio.h>
#include <iostream>
#include "opencv2/core.hpp"
#include "opencv2/features2d.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/calib3d.hpp"
#include "opencv2/xfeatures2d.hpp"
using namespace cv;
using namespace cv::xfeatures2d;
void readme();
/* @function main */
int main( int argc, char** argv )
{
if( argc != 3 )
{ readme(); return -1; }
Mat img_object = imread( argv[1], IMREAD_GRAYSCALE );
Mat img_scene = imread( argv[2], IMREAD_GRAYSCALE );
if( !img_object.data || !img_scene.data )
{ std::cout<< " --(!) Error reading images " << std::endl; return -1; }
//-- Step 1: Detect the keypoints using SURF Detector
int minHessian = 400;
SurfFeatureDetector detector( minHessian );
std::vector<KeyPoint> keypoints_object, keypoints_scene;
detector.detect( img_object, keypoints_object );
detector.detect( img_scene, keypoints_scene );
//-- Step 2: Calculate descriptors (feature vectors)
SurfDescriptorExtractor extractor;
Mat descriptors_object, descriptors_scene;
extractor.compute( img_object, keypoints_object, descriptors_object );
extractor.compute( img_scene, keypoints_scene, descriptors_scene );
//-- Step 3: Matching descriptor vectors using FLANN matcher
FlannBasedMatcher matcher;
std::vector< DMatch > matches;
matcher.match( descriptors_object, descriptors_scene, matches );
double max_dist = 0; double min_dist = 100;
//-- Quick calculation of max and min distances between keypoints
for( int i = 0; i < descriptors_object.rows; i++ )
{ double dist = matches[i].distance;
if( dist < min_dist ) min_dist = dist;
if( dist > max_dist ) max_dist = dist;
}
printf("-- Max dist : %f \n", max_dist );
printf("-- Min dist : %f \n", min_dist );
//-- Draw only "good" matches (i.e. whose distance is less than 3*min_dist )
std::vector< DMatch > good_matches;
for( int i = 0; i < descriptors_object.rows; i++ )
{ if( matches[i].distance < 3*min_dist )
{ good_matches.push_back( matches[i]); }
}
Mat img_matches;
drawMatches( img_object, keypoints_object, img_scene, keypoints_scene,
good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
//-- Localize the object
std::vector<Point2f> obj;
std::vector<Point2f> scene;
for( int i = 0; i < good_matches.size(); i++ )
{
//-- Get the keypoints from the good matches
obj.push_back( keypoints_object[ good_matches[i].queryIdx ].pt );
scene.push_back( keypoints_scene[ good_matches[i].trainIdx ].pt );
}
Mat H = findHomography( obj, scene, RANSAC );
//-- Get the corners from the image_1 ( the object to be "detected" )
std::vector<Point2f> obj_corners(4);
obj_corners[0] = cvPoint(0,0); obj_corners[1] = cvPoint( img_object.cols, 0 );
obj_corners[2] = cvPoint( img_object.cols, img_object.rows ); obj_corners[3] = cvPoint( 0, img_object.rows );
std::vector<Point2f> scene_corners(4);
perspectiveTransform( obj_corners, scene_corners, H);
//-- Draw lines between the corners (the mapped object in the scene - image_2 )
line( img_matches, scene_corners[0] + Point2f( img_object.cols, 0), scene_corners[1] + Point2f( img_object.cols, 0), Scalar(0, 255, 0), 4 );
line( img_matches, scene_corners[1] + Point2f( img_object.cols, 0), scene_corners[2] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );
line( img_matches, scene_corners[2] + Point2f( img_object.cols, 0), scene_corners[3] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );
line( img_matches, scene_corners[3] + Point2f( img_object.cols, 0), scene_corners[0] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );
//-- Show detected matches
imshow( "Good Matches & Object detection", img_matches );
waitKey(0);
return 0;
}
/* @function readme */
void readme()
{ std::cout << " Usage: ./SURF_descriptor <img1> <img2>" << std::endl; }
@endcode
Explanation
-----------
Result
------
1. And here is the result for the detected object (highlighted in green)
![image](images/Feature_Homography_Result.jpg)
2D Features framework (feature2d module) {#tutorial_table_of_content_features2d}
=========================================
Learn about how to use the feature points detectors, descriptors and matching framework found inside
OpenCV.
- @subpage tutorial_harris_detector
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Why is it a good idea to track corners? We learn to use the Harris method to detect
corners
- @subpage tutorial_good_features_to_track
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Where we use an improved method to detect corners more accuratelyI
- @subpage tutorial_generic_corner_detector
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Here you will learn how to use OpenCV functions to make your personalized corner detector!
- @subpage tutorial_corner_subpixeles
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Is pixel resolution enough? Here we learn a simple method to improve our accuracy.
- @subpage tutorial_feature_detection
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
In this tutorial, you will use *features2d* to detect interest points.
- @subpage tutorial_feature_description
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
In this tutorial, you will use *features2d* to calculate feature vectors.
- @subpage tutorial_feature_flann_matcher
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
In this tutorial, you will use the FLANN library to make a fast matching.
- @subpage tutorial_feature_homography
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
In this tutorial, you will use *features2d* and *calib3d* to detect an object in a scene.
- @subpage tutorial_detection_of_planar_objects
*Compatibility:* \> OpenCV 2.0
*Author:* Victor Eruhimov
You will use *features2d* and *calib3d* modules for detecting known planar objects in
scenes.
- @subpage tutorial_akaze_matching
*Compatibility:* \> OpenCV 3.0
*Author:* Fedor Morozov
Using *AKAZE* local features to find correspondence between two images.
- @subpage tutorial_akaze_tracking
*Compatibility:* \> OpenCV 3.0
*Author:* Fedor Morozov
Using *AKAZE* and *ORB* for planar object tracking.
Detecting corners location in subpixeles {#tutorial_corner_subpixeles}
========================================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::cornerSubPix to find more exact corner positions (more exact
than integer pixels).
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/TrackingMotion/cornerSubPix_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;
/// Global variables
Mat src, src_gray;
int maxCorners = 10;
int maxTrackbar = 25;
RNG rng(12345);
char* source_window = "Image";
/// Function header
void goodFeaturesToTrack_Demo( int, void* );
/* @function main */
int main( int argc, char** argv )
{
/// Load source image and convert it to gray
src = imread( argv[1], 1 );
cvtColor( src, src_gray, COLOR_BGR2GRAY );
/// Create Window
namedWindow( source_window, WINDOW_AUTOSIZE );
/// Create Trackbar to set the number of corners
createTrackbar( "Max corners:", source_window, &maxCorners, maxTrackbar, goodFeaturesToTrack_Demo);
imshow( source_window, src );
goodFeaturesToTrack_Demo( 0, 0 );
waitKey(0);
return(0);
}
/*
* @function goodFeaturesToTrack_Demo.cpp
* @brief Apply Shi-Tomasi corner detector
*/
void goodFeaturesToTrack_Demo( int, void* )
{
if( maxCorners < 1 ) { maxCorners = 1; }
/// Parameters for Shi-Tomasi algorithm
vector<Point2f> corners;
double qualityLevel = 0.01;
double minDistance = 10;
int blockSize = 3;
bool useHarrisDetector = false;
double k = 0.04;
/// Copy the source image
Mat copy;
copy = src.clone();
/// Apply corner detection
goodFeaturesToTrack( src_gray,
corners,
maxCorners,
qualityLevel,
minDistance,
Mat(),
blockSize,
useHarrisDetector,
k );
/// Draw corners detected
cout<<"** Number of corners detected: "<<corners.size()<<endl;
int r = 4;
for( int i = 0; i < corners.size(); i++ )
{ circle( copy, corners[i], r, Scalar(rng.uniform(0,255), rng.uniform(0,255),
rng.uniform(0,255)), -1, 8, 0 ); }
/// Show what you got
namedWindow( source_window, WINDOW_AUTOSIZE );
imshow( source_window, copy );
/// Set the neeed parameters to find the refined corners
Size winSize = Size( 5, 5 );
Size zeroZone = Size( -1, -1 );
TermCriteria criteria = TermCriteria( TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001 );
/// Calculate the refined corner locations
cornerSubPix( src_gray, corners, winSize, zeroZone, criteria );
/// Write them down
for( int i = 0; i < corners.size(); i++ )
{ cout<<" -- Refined Corner ["<<i<<"] ("<<corners[i].x<<","<<corners[i].y<<")"<<endl; }
}
@endcode
Explanation
-----------
Result
------
![image](images/Corner_Subpixeles_Original_Image.jpg)
Here is the result:
![image](images/Corner_Subpixeles_Result.jpg)
Creating yor own corner detector {#tutorial_generic_corner_detector}
================================
Goal
----
In this tutorial you will learn how to:
- Use the OpenCV function @ref cv::cornerEigenValsAndVecs to find the eigenvalues and eigenvectors
to determine if a pixel is a corner.
- Use the OpenCV function @ref cv::cornerMinEigenVal to find the minimum eigenvalues for corner
detection.
- To implement our own version of the Harris detector as well as the Shi-Tomasi detector, by using
the two functions above.
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/TrackingMotion/cornerDetector_Demo.cpp)
@includelineno cpp/tutorial_code/TrackingMotion/cornerDetector_Demo.cpp
Explanation
-----------
Result
------
![image](images/My_Harris_corner_detector_Result.jpg)
![image](images/My_Shi_Tomasi_corner_detector_Result.jpg)
Shi-Tomasi corner detector {#tutorial_good_features_to_track}
==========================
Goal
----
In this tutorial you will learn how to:
- Use the function @ref cv::goodFeaturesToTrack to detect corners using the Shi-Tomasi method.
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/TrackingMotion/goodFeaturesToTrack_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;
/// Global variables
Mat src, src_gray;
int maxCorners = 23;
int maxTrackbar = 100;
RNG rng(12345);
char* source_window = "Image";
/// Function header
void goodFeaturesToTrack_Demo( int, void* );
/*
* @function main
*/
int main( int argc, char** argv )
{
/// Load source image and convert it to gray
src = imread( argv[1], 1 );
cvtColor( src, src_gray, COLOR_BGR2GRAY );
/// Create Window
namedWindow( source_window, WINDOW_AUTOSIZE );
/// Create Trackbar to set the number of corners
createTrackbar( "Max corners:", source_window, &maxCorners, maxTrackbar, goodFeaturesToTrack_Demo );
imshow( source_window, src );
goodFeaturesToTrack_Demo( 0, 0 );
waitKey(0);
return(0);
}
/*
* @function goodFeaturesToTrack_Demo.cpp
* @brief Apply Shi-Tomasi corner detector
*/
void goodFeaturesToTrack_Demo( int, void* )
{
if( maxCorners < 1 ) { maxCorners = 1; }
/// Parameters for Shi-Tomasi algorithm
vector<Point2f> corners;
double qualityLevel = 0.01;
double minDistance = 10;
int blockSize = 3;
bool useHarrisDetector = false;
double k = 0.04;
/// Copy the source image
Mat copy;
copy = src.clone();
/// Apply corner detection
goodFeaturesToTrack( src_gray,
corners,
maxCorners,
qualityLevel,
minDistance,
Mat(),
blockSize,
useHarrisDetector,
k );
/// Draw corners detected
cout<<"** Number of corners detected: "<<corners.size()<<endl;
int r = 4;
for( int i = 0; i < corners.size(); i++ )
{ circle( copy, corners[i], r, Scalar(rng.uniform(0,255), rng.uniform(0,255),
rng.uniform(0,255)), -1, 8, 0 ); }
/// Show what you got
namedWindow( source_window, WINDOW_AUTOSIZE );
imshow( source_window, copy );
}
@endcode
Explanation
-----------
Result
------
![image](images/Feature_Detection_Result_a.jpg)
General tutorials {#tutorial_table_of_content_general}
=================
These tutorials are the bottom of the iceberg as they link together multiple of the modules
presented above in order to solve complex problems.
GPU-Accelerated Computer Vision (cuda module) {#tutorial_table_of_content_gpu}
=============================================
Squeeze out every little computation power from your system by using the power of your video card to
run the OpenCV algorithms.
- @subpage tutorial_gpu_basics_similarity
*Compatibility:* \> OpenCV 2.0
*Author:* Bernát Gábor
This will give a good grasp on how to approach coding on the GPU module, once you already know
how to handle the other modules. As a test case it will port the similarity methods from the
tutorial @ref tutorial_video_input_psnr_ssim to the GPU.
This diff is collapsed.
High Level GUI and Media (highgui module) {#tutorial_table_of_content_highgui}
=========================================
This section contains valuable tutorials about how to read/save your image/video files and how to
use the built-in graphical user interface of the library.
- @subpage tutorial_trackbar
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
We will learn how to add a Trackbar to our applications
- @subpage tutorial_video_input_psnr_ssim
*Compatibility:* \> OpenCV 2.0
*Author:* Bernát Gábor
You will learn how to read video streams, and how to calculate similarity values such as PSNR
or SSIM.
- @subpage tutorial_video_write
*Compatibility:* \> OpenCV 2.0
*Author:* Bernát Gábor
Whenever you work with video feeds you may eventually want to save your image processing
result in a form of a new video file. Here's how to do it.
- @subpage tutorial_raster_io_gdal
*Compatibility:* \> OpenCV 2.0
*Author:* Marvin Smith
Read common GIS Raster and DEM files to display and manipulate geographic data.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Object Detection (objdetect module) {#tutorial_table_of_content_objdetect}
===================================
Ever wondered how your digital camera detects peoples and faces? Look here to find out!
- @subpage tutorial_cascade_classifier
*Compatibility:* \> OpenCV 2.0
*Author:* Ana Huamán
Here we learn how to use *objdetect* to find objects in our images or videos
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment