Removed Sphinx documentation files
This commit is contained in:
@@ -1,305 +0,0 @@
|
||||
.. _back_projection:
|
||||
|
||||
Back Projection
|
||||
****************
|
||||
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
In this tutorial you will learn:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* What is Back Projection and why it is useful
|
||||
|
||||
* How to use the OpenCV function :calc_back_project:`calcBackProject <>` to calculate Back Projection
|
||||
|
||||
* How to mix different channels of an image by using the OpenCV function :mix_channels:`mixChannels <>`
|
||||
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
What is Back Projection?
|
||||
---------------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Back Projection is a way of recording how well the pixels of a given image fit the distribution of pixels in a histogram model.
|
||||
|
||||
* To make it simpler: For Back Projection, you calculate the histogram model of a feature and then use it to find this feature in an image.
|
||||
|
||||
* Application example: If you have a histogram of flesh color (say, a Hue-Saturation histogram ), then you can use it to find flesh color areas in an image:
|
||||
|
||||
|
||||
How does it work?
|
||||
------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* We explain this by using the skin example:
|
||||
|
||||
* Let's say you have gotten a skin histogram (Hue-Saturation) based on the image below. The histogram besides is going to be our *model histogram* (which we know represents a sample of skin tonality). You applied some mask to capture only the histogram of the skin area:
|
||||
|
||||
====== ======
|
||||
|T0| |T1|
|
||||
====== ======
|
||||
|
||||
.. |T0| image:: images/Back_Projection_Theory0.jpg
|
||||
:align: middle
|
||||
|
||||
.. |T1| image:: images/Back_Projection_Theory1.jpg
|
||||
:align: middle
|
||||
|
||||
|
||||
* Now, let's imagine that you get another hand image (Test Image) like the one below: (with its respective histogram):
|
||||
|
||||
====== ======
|
||||
|T2| |T3|
|
||||
====== ======
|
||||
|
||||
.. |T2| image:: images/Back_Projection_Theory2.jpg
|
||||
:align: middle
|
||||
|
||||
.. |T3| image:: images/Back_Projection_Theory3.jpg
|
||||
:align: middle
|
||||
|
||||
|
||||
* What we want to do is to use our *model histogram* (that we know represents a skin tonality) to detect skin areas in our Test Image. Here are the steps
|
||||
|
||||
a. In each pixel of our Test Image (i.e. :math:`p(i,j)` ), collect the data and find the correspondent bin location for that pixel (i.e. :math:`( h_{i,j}, s_{i,j} )` ).
|
||||
|
||||
b. Lookup the *model histogram* in the correspondent bin - :math:`( h_{i,j}, s_{i,j} )` - and read the bin value.
|
||||
|
||||
c. Store this bin value in a new image (*BackProjection*). Also, you may consider to normalize the *model histogram* first, so the output for the Test Image can be visible for you.
|
||||
|
||||
d. Applying the steps above, we get the following BackProjection image for our Test Image:
|
||||
|
||||
.. image:: images/Back_Projection_Theory4.jpg
|
||||
:align: center
|
||||
|
||||
e. In terms of statistics, the values stored in *BackProjection* represent the *probability* that a pixel in *Test Image* belongs to a skin area, based on the *model histogram* that we use. For instance in our Test image, the brighter areas are more probable to be skin area (as they actually are), whereas the darker areas have less probability (notice that these "dark" areas belong to surfaces that have some shadow on it, which in turns affects the detection).
|
||||
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* **What does this program do?**
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Loads an image
|
||||
* Convert the original to HSV format and separate only *Hue* channel to be used for the Histogram (using the OpenCV function :mix_channels:`mixChannels <>`)
|
||||
* Let the user to enter the number of bins to be used in the calculation of the histogram.
|
||||
* Calculate the histogram (and update it if the bins change) and the backprojection of the same image.
|
||||
* Display the backprojection and the histogram in windows.
|
||||
|
||||
* **Downloadable code**:
|
||||
|
||||
a. Click `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching/calcBackProject_Demo1.cpp>`_ for the basic version (explained in this tutorial).
|
||||
b. For stuff slightly fancier (using H-S histograms and floodFill to define a mask for the skin area) you can check the `improved demo <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching/calcBackProject_Demo2.cpp>`_
|
||||
c. ...or you can always check out the classical `camshiftdemo <https://github.com/Itseez/opencv/tree/master/samples/cpp/camshiftdemo.cpp>`_ in samples.
|
||||
|
||||
* **Code at glance:**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
/// Global Variables
|
||||
Mat src; Mat hsv; Mat hue;
|
||||
int bins = 25;
|
||||
|
||||
/// Function Headers
|
||||
void Hist_and_Backproj(int, void* );
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Read the image
|
||||
src = imread( argv[1], 1 );
|
||||
/// Transform it to HSV
|
||||
cvtColor( src, hsv, COLOR_BGR2HSV );
|
||||
|
||||
/// Use only the Hue value
|
||||
hue.create( hsv.size(), hsv.depth() );
|
||||
int ch[] = { 0, 0 };
|
||||
mixChannels( &hsv, 1, &hue, 1, ch, 1 );
|
||||
|
||||
/// Create Trackbar to enter the number of bins
|
||||
char* window_image = "Source image";
|
||||
namedWindow( window_image, WINDOW_AUTOSIZE );
|
||||
createTrackbar("* Hue bins: ", window_image, &bins, 180, Hist_and_Backproj );
|
||||
Hist_and_Backproj(0, 0);
|
||||
|
||||
/// Show the image
|
||||
imshow( window_image, src );
|
||||
|
||||
/// Wait until user exits the program
|
||||
waitKey(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @function Hist_and_Backproj
|
||||
* @brief Callback to Trackbar
|
||||
*/
|
||||
void Hist_and_Backproj(int, void* )
|
||||
{
|
||||
MatND hist;
|
||||
int histSize = MAX( bins, 2 );
|
||||
float hue_range[] = { 0, 180 };
|
||||
const float* ranges = { hue_range };
|
||||
|
||||
/// Get the Histogram and normalize it
|
||||
calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
|
||||
normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
/// Get Backprojection
|
||||
MatND backproj;
|
||||
calcBackProject( &hue, 1, 0, hist, backproj, &ranges, 1, true );
|
||||
|
||||
/// Draw the backproj
|
||||
imshow( "BackProj", backproj );
|
||||
|
||||
/// Draw the histogram
|
||||
int w = 400; int h = 400;
|
||||
int bin_w = cvRound( (double) w / histSize );
|
||||
Mat histImg = Mat::zeros( w, h, CV_8UC3 );
|
||||
|
||||
for( int i = 0; i < bins; i ++ )
|
||||
{ rectangle( histImg, Point( i*bin_w, h ), Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ), Scalar( 0, 0, 255 ), -1 ); }
|
||||
|
||||
imshow( "Histogram", histImg );
|
||||
}
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
#. Declare the matrices to store our images and initialize the number of bins to be used by our histogram:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src; Mat hsv; Mat hue;
|
||||
int bins = 25;
|
||||
|
||||
#. Read the input image and transform it to HSV format:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1], 1 );
|
||||
cvtColor( src, hsv, COLOR_BGR2HSV );
|
||||
|
||||
#. For this tutorial, we will use only the Hue value for our 1-D histogram (check out the fancier code in the links above if you want to use the more standard H-S histogram, which yields better results):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
hue.create( hsv.size(), hsv.depth() );
|
||||
int ch[] = { 0, 0 };
|
||||
mixChannels( &hsv, 1, &hue, 1, ch, 1 );
|
||||
|
||||
as you see, we use the function :mix_channels:`mixChannels` to get only the channel 0 (Hue) from the hsv image. It gets the following parameters:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ **&hsv:** The source array from which the channels will be copied
|
||||
+ **1:** The number of source arrays
|
||||
+ **&hue:** The destination array of the copied channels
|
||||
+ **1:** The number of destination arrays
|
||||
+ **ch[] = {0,0}:** The array of index pairs indicating how the channels are copied. In this case, the Hue(0) channel of &hsv is being copied to the 0 channel of &hue (1-channel)
|
||||
+ **1:** Number of index pairs
|
||||
|
||||
#. Create a Trackbar for the user to enter the bin values. Any change on the Trackbar means a call to the **Hist_and_Backproj** callback function.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
char* window_image = "Source image";
|
||||
namedWindow( window_image, WINDOW_AUTOSIZE );
|
||||
createTrackbar("* Hue bins: ", window_image, &bins, 180, Hist_and_Backproj );
|
||||
Hist_and_Backproj(0, 0);
|
||||
|
||||
#. Show the image and wait for the user to exit the program:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
imshow( window_image, src );
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
|
||||
#. **Hist_and_Backproj function:** Initialize the arguments needed for :calc_hist:`calcHist <>`. The number of bins comes from the Trackbar:
|
||||
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
void Hist_and_Backproj(int, void* )
|
||||
{
|
||||
MatND hist;
|
||||
int histSize = MAX( bins, 2 );
|
||||
float hue_range[] = { 0, 180 };
|
||||
const float* ranges = { hue_range };
|
||||
|
||||
#. Calculate the Histogram and normalize it to the range :math:`[0,255]`
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
|
||||
normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
#. Get the Backprojection of the same image by calling the function :calc_back_project:`calcBackProject <>`
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
MatND backproj;
|
||||
calcBackProject( &hue, 1, 0, hist, backproj, &ranges, 1, true );
|
||||
|
||||
all the arguments are known (the same as used to calculate the histogram), only we add the backproj matrix, which will store the backprojection of the source image (&hue)
|
||||
|
||||
#. Display backproj:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
imshow( "BackProj", backproj );
|
||||
|
||||
#. Draw the 1-D Hue histogram of the image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
int w = 400; int h = 400;
|
||||
int bin_w = cvRound( (double) w / histSize );
|
||||
Mat histImg = Mat::zeros( w, h, CV_8UC3 );
|
||||
|
||||
for( int i = 0; i < bins; i ++ )
|
||||
{ rectangle( histImg, Point( i*bin_w, h ), Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ), Scalar( 0, 0, 255 ), -1 ); }
|
||||
|
||||
imshow( "Histogram", histImg );
|
||||
|
||||
|
||||
|
||||
Results
|
||||
=======
|
||||
|
||||
#. Here are the output by using a sample image ( guess what? Another hand ). You can play with the bin values and you will observe how it affects the results:
|
||||
|
||||
====== ====== ======
|
||||
|R0| |R1| |R2|
|
||||
====== ====== ======
|
||||
|
||||
.. |R0| image:: images/Back_Projection1_Source_Image.jpg
|
||||
:align: middle
|
||||
|
||||
.. |R1| image:: images/Back_Projection1_Histogram.jpg
|
||||
:align: middle
|
||||
|
||||
.. |R2| image:: images/Back_Projection1_BackProj.jpg
|
||||
:align: middle
|
@@ -1,331 +0,0 @@
|
||||
.. _histogram_calculation:
|
||||
|
||||
Histogram Calculation
|
||||
*********************
|
||||
|
||||
Goal
|
||||
=====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :split:`split <>` to divide an image into its correspondent planes.
|
||||
|
||||
* To calculate histograms of arrays of images by using the OpenCV function :calc_hist:`calcHist <>`
|
||||
|
||||
* To normalize an array by using the function :normalize:`normalize <>`
|
||||
|
||||
|
||||
.. note::
|
||||
In the last tutorial (:ref:`histogram_equalization`) we talked about a particular kind of histogram called *Image histogram*. Now we will considerate it in its more general concept. Read on!
|
||||
|
||||
|
||||
What are histograms?
|
||||
--------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Histograms are collected *counts* of data organized into a set of predefined *bins*
|
||||
|
||||
* When we say *data* we are not restricting it to be intensity values (as we saw in the previous Tutorial). The data collected can be whatever feature you find useful to describe your image.
|
||||
|
||||
* Let's see an example. Imagine that a Matrix contains information of an image (i.e. intensity in the range :math:`0-255`):
|
||||
|
||||
|
||||
.. image:: images/Histogram_Calculation_Theory_Hist0.jpg
|
||||
:align: center
|
||||
|
||||
* What happens if we want to *count* this data in an organized way? Since we know that the *range* of information value for this case is 256 values, we can segment our range in subparts (called **bins**) like:
|
||||
|
||||
.. math::
|
||||
\begin{array}{l}
|
||||
[0, 255] = { [0, 15] \cup [16, 31] \cup ....\cup [240,255] } \\
|
||||
range = { bin_{1} \cup bin_{2} \cup ....\cup bin_{n = 15} }
|
||||
\end{array}
|
||||
|
||||
and we can keep count of the number of pixels that fall in the range of each :math:`bin_{i}`. Applying this to the example above we get the image below ( axis x represents the bins and axis y the number of pixels in each of them).
|
||||
|
||||
|
||||
.. image:: images/Histogram_Calculation_Theory_Hist1.jpg
|
||||
:align: center
|
||||
|
||||
* This was just a simple example of how an histogram works and why it is useful. An histogram can keep count not only of color intensities, but of whatever image features that we want to measure (i.e. gradients, directions, etc).
|
||||
|
||||
* Let's identify some parts of the histogram:
|
||||
|
||||
a. **dims**: The number of parameters you want to collect data of. In our example, **dims = 1** because we are only counting the intensity values of each pixel (in a greyscale image).
|
||||
b. **bins**: It is the number of **subdivisions** in each dim. In our example, **bins = 16**
|
||||
c. **range**: The limits for the values to be measured. In this case: **range = [0,255]**
|
||||
|
||||
* What if you want to count two features? In this case your resulting histogram would be a 3D plot (in which x and y would be :math:`bin_{x}` and :math:`bin_{y}` for each feature and z would be the number of counts for each combination of :math:`(bin_{x}, bin_{y})`. The same would apply for more features (of course it gets trickier).
|
||||
|
||||
|
||||
What OpenCV offers you
|
||||
-----------------------
|
||||
|
||||
For simple purposes, OpenCV implements the function :calc_hist:`calcHist <>`, which calculates the histogram of a set of arrays (usually images or image planes). It can operate with up to 32 dimensions. We will see it in the code below!
|
||||
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* **What does this program do?**
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Loads an image
|
||||
* Splits the image into its R, G and B planes using the function :split:`split <>`
|
||||
* Calculate the Histogram of each 1-channel plane by calling the function :calc_hist:`calcHist <>`
|
||||
* Plot the three histograms in a window
|
||||
|
||||
* **Downloadable code**:
|
||||
Click `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching/calcHist_Demo.cpp>`_
|
||||
|
||||
* **Code at glance:**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
/*
|
||||
* @function main
|
||||
*/
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
Mat src, dst;
|
||||
|
||||
/// Load image
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
/// Separate the image in 3 places ( B, G and R )
|
||||
vector<Mat> bgr_planes;
|
||||
split( src, bgr_planes );
|
||||
|
||||
/// Establish the number of bins
|
||||
int histSize = 256;
|
||||
|
||||
/// Set the ranges ( for B,G,R) )
|
||||
float range[] = { 0, 256 } ;
|
||||
const float* histRange = { range };
|
||||
|
||||
bool uniform = true; bool accumulate = false;
|
||||
|
||||
Mat b_hist, g_hist, r_hist;
|
||||
|
||||
/// Compute the histograms:
|
||||
calcHist( &bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );
|
||||
calcHist( &bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate );
|
||||
calcHist( &bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate );
|
||||
|
||||
// Draw the histograms for B, G and R
|
||||
int hist_w = 512; int hist_h = 400;
|
||||
int bin_w = cvRound( (double) hist_w/histSize );
|
||||
|
||||
Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
|
||||
|
||||
/// Normalize the result to [ 0, histImage.rows ]
|
||||
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
|
||||
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
|
||||
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
/// Draw for each channel
|
||||
for( int i = 1; i < histSize; i++ )
|
||||
{
|
||||
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ) ,
|
||||
Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),
|
||||
Scalar( 255, 0, 0), 2, 8, 0 );
|
||||
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ) ,
|
||||
Point( bin_w*(i), hist_h - cvRound(g_hist.at<float>(i)) ),
|
||||
Scalar( 0, 255, 0), 2, 8, 0 );
|
||||
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ) ,
|
||||
Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),
|
||||
Scalar( 0, 0, 255), 2, 8, 0 );
|
||||
}
|
||||
|
||||
/// Display
|
||||
namedWindow("calcHist Demo", WINDOW_AUTOSIZE );
|
||||
imshow("calcHist Demo", histImage );
|
||||
|
||||
waitKey(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
#. Create the necessary matrices:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src, dst;
|
||||
|
||||
#. Load the source image
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
if( !src.data )
|
||||
{ return -1; }
|
||||
|
||||
#. Separate the source image in its three R,G and B planes. For this we use the OpenCV function :split:`split <>`:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
vector<Mat> bgr_planes;
|
||||
split( src, bgr_planes );
|
||||
|
||||
our input is the image to be divided (this case with three channels) and the output is a vector of Mat )
|
||||
|
||||
#. Now we are ready to start configuring the **histograms** for each plane. Since we are working with the B, G and R planes, we know that our values will range in the interval :math:`[0,255]`
|
||||
|
||||
a. Establish number of bins (5, 10...):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
int histSize = 256; //from 0 to 255
|
||||
|
||||
b. Set the range of values (as we said, between 0 and 255 )
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/// Set the ranges ( for B,G,R) )
|
||||
float range[] = { 0, 256 } ; //the upper boundary is exclusive
|
||||
const float* histRange = { range };
|
||||
|
||||
c. We want our bins to have the same size (uniform) and to clear the histograms in the beginning, so:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
bool uniform = true; bool accumulate = false;
|
||||
|
||||
d. Finally, we create the Mat objects to save our histograms. Creating 3 (one for each plane):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat b_hist, g_hist, r_hist;
|
||||
|
||||
e. We proceed to calculate the histograms by using the OpenCV function :calc_hist:`calcHist <>`:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/// Compute the histograms:
|
||||
calcHist( &bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );
|
||||
calcHist( &bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate );
|
||||
calcHist( &bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate );
|
||||
|
||||
where the arguments are:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ **&bgr_planes[0]:** The source array(s)
|
||||
+ **1**: The number of source arrays (in this case we are using 1. We can enter here also a list of arrays )
|
||||
+ **0**: The channel (*dim*) to be measured. In this case it is just the intensity (each array is single-channel) so we just write 0.
|
||||
+ **Mat()**: A mask to be used on the source array ( zeros indicating pixels to be ignored ). If not defined it is not used
|
||||
+ **b_hist**: The Mat object where the histogram will be stored
|
||||
+ **1**: The histogram dimensionality.
|
||||
+ **histSize:** The number of bins per each used dimension
|
||||
+ **histRange:** The range of values to be measured per each dimension
|
||||
+ **uniform** and **accumulate**: The bin sizes are the same and the histogram is cleared at the beginning.
|
||||
|
||||
|
||||
#. Create an image to display the histograms:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// Draw the histograms for R, G and B
|
||||
int hist_w = 512; int hist_h = 400;
|
||||
int bin_w = cvRound( (double) hist_w/histSize );
|
||||
|
||||
Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
|
||||
|
||||
#. Notice that before drawing, we first :normalize:`normalize <>` the histogram so its values fall in the range indicated by the parameters entered:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/// Normalize the result to [ 0, histImage.rows ]
|
||||
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
|
||||
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
|
||||
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
this function receives these arguments:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ **b_hist:** Input array
|
||||
+ **b_hist:** Output normalized array (can be the same)
|
||||
+ **0** and**histImage.rows**: For this example, they are the lower and upper limits to normalize the values of **r_hist**
|
||||
+ **NORM_MINMAX:** Argument that indicates the type of normalization (as described above, it adjusts the values between the two limits set before)
|
||||
+ **-1:** Implies that the output normalized array will be the same type as the input
|
||||
+ **Mat():** Optional mask
|
||||
|
||||
#. Finally, observe that to access the bin (in this case in this 1D-Histogram):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
/// Draw for each channel
|
||||
for( int i = 1; i < histSize; i++ )
|
||||
{
|
||||
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ) ,
|
||||
Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),
|
||||
Scalar( 255, 0, 0), 2, 8, 0 );
|
||||
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ) ,
|
||||
Point( bin_w*(i), hist_h - cvRound(g_hist.at<float>(i)) ),
|
||||
Scalar( 0, 255, 0), 2, 8, 0 );
|
||||
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ) ,
|
||||
Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),
|
||||
Scalar( 0, 0, 255), 2, 8, 0 );
|
||||
}
|
||||
|
||||
|
||||
we use the expression:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
b_hist.at<float>(i)
|
||||
|
||||
|
||||
where :math:`i` indicates the dimension. If it were a 2D-histogram we would use something like:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
b_hist.at<float>( i, j )
|
||||
|
||||
|
||||
#. Finally we display our histograms and wait for the user to exit:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow("calcHist Demo", WINDOW_AUTOSIZE );
|
||||
imshow("calcHist Demo", histImage );
|
||||
|
||||
waitKey(0);
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
Result
|
||||
======
|
||||
|
||||
#. Using as input argument an image like the shown below:
|
||||
|
||||
.. image:: images/Histogram_Calculation_Original_Image.jpg
|
||||
:align: center
|
||||
|
||||
#. Produces the following histogram:
|
||||
|
||||
.. image:: images/Histogram_Calculation_Result.jpg
|
||||
:align: center
|
@@ -1,221 +0,0 @@
|
||||
.. _histogram_comparison:
|
||||
|
||||
Histogram Comparison
|
||||
********************
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the function :compare_hist:`compareHist <>` to get a numerical parameter that express how well two histograms match with each other.
|
||||
* Use different metrics to compare histograms
|
||||
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* To compare two histograms ( :math:`H_{1}` and :math:`H_{2}` ), first we have to choose a *metric* (:math:`d(H_{1}, H_{2})`) to express how well both histograms match.
|
||||
|
||||
* OpenCV implements the function :compare_hist:`compareHist <>` to perform a comparison. It also offers 4 different metrics to compute the matching:
|
||||
|
||||
|
||||
a. **Correlation ( CV\_COMP\_CORREL )**
|
||||
|
||||
.. math::
|
||||
|
||||
d(H_1,H_2) = \frac{\sum_I (H_1(I) - \bar{H_1}) (H_2(I) - \bar{H_2})}{\sqrt{\sum_I(H_1(I) - \bar{H_1})^2 \sum_I(H_2(I) - \bar{H_2})^2}}
|
||||
|
||||
where
|
||||
|
||||
.. math::
|
||||
|
||||
\bar{H_k} = \frac{1}{N} \sum _J H_k(J)
|
||||
|
||||
|
||||
and :math:`N` is the total number of histogram bins.
|
||||
|
||||
|
||||
|
||||
b. **Chi-Square ( CV\_COMP\_CHISQR )**
|
||||
|
||||
.. math::
|
||||
|
||||
d(H_1,H_2) = \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)}
|
||||
|
||||
|
||||
c. **Intersection ( method=CV\_COMP\_INTERSECT )**
|
||||
|
||||
.. math::
|
||||
|
||||
d(H_1,H_2) = \sum _I \min (H_1(I), H_2(I))
|
||||
|
||||
|
||||
d. **Bhattacharyya distance ( CV\_COMP\_BHATTACHARYYA )**
|
||||
|
||||
.. math::
|
||||
|
||||
d(H_1,H_2) = \sqrt{1 - \frac{1}{\sqrt{\bar{H_1} \bar{H_2} N^2}} \sum_I \sqrt{H_1(I) \cdot H_2(I)}}
|
||||
|
||||
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* **What does this program do?**
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Loads a *base image* and 2 *test images* to be compared with it.
|
||||
* Generate 1 image that is the lower half of the *base image*
|
||||
* Convert the images to HSV format
|
||||
* Calculate the H-S histogram for all the images and normalize them in order to compare them.
|
||||
* Compare the histogram of the *base image* with respect to the 2 test histograms, the histogram of the lower half base image and with the same base image histogram.
|
||||
* Display the numerical matching parameters obtained.
|
||||
|
||||
* **Downloadable code**:
|
||||
Click `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp>`_
|
||||
|
||||
* **Code at glance:**
|
||||
|
||||
.. literalinclude:: ../../../../../samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp
|
||||
:language: cpp
|
||||
:tab-width: 4
|
||||
|
||||
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
#. Declare variables such as the matrices to store the base image and the two other images to compare ( RGB and HSV )
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src_base, hsv_base;
|
||||
Mat src_test1, hsv_test1;
|
||||
Mat src_test2, hsv_test2;
|
||||
Mat hsv_half_down;
|
||||
|
||||
#. Load the base image (src\_base) and the other two test images:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
if( argc < 4 )
|
||||
{ printf("** Error. Usage: ./compareHist_Demo <image_settings0> <image_setting1> <image_settings2>\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
src_base = imread( argv[1], 1 );
|
||||
src_test1 = imread( argv[2], 1 );
|
||||
src_test2 = imread( argv[3], 1 );
|
||||
|
||||
#. Convert them to HSV format:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
cvtColor( src_base, hsv_base, COLOR_BGR2HSV );
|
||||
cvtColor( src_test1, hsv_test1, COLOR_BGR2HSV );
|
||||
cvtColor( src_test2, hsv_test2, COLOR_BGR2HSV );
|
||||
|
||||
#. Also, create an image of half the base image (in HSV format):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
hsv_half_down = hsv_base( Range( hsv_base.rows/2, hsv_base.rows - 1 ), Range( 0, hsv_base.cols - 1 ) );
|
||||
|
||||
#. Initialize the arguments to calculate the histograms (bins, ranges and channels H and S ).
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
int h_bins = 50; int s_bins = 60;
|
||||
int histSize[] = { h_bins, s_bins };
|
||||
|
||||
float h_ranges[] = { 0, 180 };
|
||||
float s_ranges[] = { 0, 256 };
|
||||
|
||||
const float* ranges[] = { h_ranges, s_ranges };
|
||||
|
||||
int channels[] = { 0, 1 };
|
||||
|
||||
#. Create the MatND objects to store the histograms:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
MatND hist_base;
|
||||
MatND hist_half_down;
|
||||
MatND hist_test1;
|
||||
MatND hist_test2;
|
||||
|
||||
#. Calculate the Histograms for the base image, the 2 test images and the half-down base image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );
|
||||
normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
calcHist( &hsv_half_down, 1, channels, Mat(), hist_half_down, 2, histSize, ranges, true, false );
|
||||
normalize( hist_half_down, hist_half_down, 0, 1, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false );
|
||||
normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false );
|
||||
normalize( hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
|
||||
#. Apply sequentially the 4 comparison methods between the histogram of the base image (hist\_base) and the other histograms:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
for( int i = 0; i < 4; i++ )
|
||||
{ int compare_method = i;
|
||||
double base_base = compareHist( hist_base, hist_base, compare_method );
|
||||
double base_half = compareHist( hist_base, hist_half_down, compare_method );
|
||||
double base_test1 = compareHist( hist_base, hist_test1, compare_method );
|
||||
double base_test2 = compareHist( hist_base, hist_test2, compare_method );
|
||||
|
||||
printf( " Method [%d] Perfect, Base-Half, Base-Test(1), Base-Test(2) : %f, %f, %f, %f \n", i, base_base, base_half , base_test1, base_test2 );
|
||||
}
|
||||
|
||||
|
||||
Results
|
||||
========
|
||||
|
||||
#. We use as input the following images:
|
||||
|
||||
============ ============ ============
|
||||
|Base_0| |Test_1| |Test_2|
|
||||
============ ============ ============
|
||||
|
||||
.. |Base_0| image:: images/Histogram_Comparison_Source_0.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Test_1| image:: images/Histogram_Comparison_Source_1.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Test_2| image:: images/Histogram_Comparison_Source_2.jpg
|
||||
:align: middle
|
||||
|
||||
where the first one is the base (to be compared to the others), the other 2 are the test images. We will also compare the first image with respect to itself and with respect of half the base image.
|
||||
|
||||
#. We should expect a perfect match when we compare the base image histogram with itself. Also, compared with the histogram of half the base image, it should present a high match since both are from the same source. For the other two test images, we can observe that they have very different lighting conditions, so the matching should not be very good:
|
||||
|
||||
#. Here the numeric results:
|
||||
|
||||
=============== =============== =============== =============== ===============
|
||||
*Method* Base - Base Base - Half Base - Test 1 Base - Test 2
|
||||
=============== =============== =============== =============== ===============
|
||||
*Correlation* 1.000000 0.930766 0.182073 0.120447
|
||||
*Chi-square* 0.000000 4.940466 21.184536 49.273437
|
||||
*Intersection* 24.391548 14.959809 3.889029 5.775088
|
||||
*Bhattacharyya* 0.000000 0.222609 0.646576 0.801869
|
||||
=============== =============== =============== =============== ===============
|
||||
|
||||
|
||||
For the *Correlation* and *Intersection* methods, the higher the metric, the more accurate the match. As we can see, the match *base-base* is the highest of all as expected. Also we can observe that the match *base-half* is the second best match (as we predicted). For the other two metrics, the less the result, the better the match. We can observe that the matches between the test 1 and test 2 with respect to the base are worse, which again, was expected.
|
@@ -1,217 +0,0 @@
|
||||
.. _histogram_equalization:
|
||||
|
||||
Histogram Equalization
|
||||
**********************
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
In this tutorial you will learn:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* What an image histogram is and why it is useful
|
||||
|
||||
* To equalize histograms of images by using the OpenCV function:equalize_hist:`equalizeHist <>`
|
||||
|
||||
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
What is an Image Histogram?
|
||||
---------------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* It is a graphical representation of the intensity distribution of an image.
|
||||
|
||||
* It quantifies the number of pixels for each intensity value considered.
|
||||
|
||||
.. image:: images/Histogram_Equalization_Theory_0.jpg
|
||||
:align: center
|
||||
|
||||
|
||||
What is Histogram Equalization?
|
||||
-------------------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* It is a method that improves the contrast in an image, in order to stretch out the intensity range.
|
||||
|
||||
* To make it clearer, from the image above, you can see that the pixels seem clustered around the middle of the available range of intensities. What Histogram Equalization does is to *stretch out* this range. Take a look at the figure below: The green circles indicate the *underpopulated* intensities. After applying the equalization, we get an histogram like the figure in the center. The resulting image is shown in the picture at right.
|
||||
|
||||
.. image:: images/Histogram_Equalization_Theory_1.jpg
|
||||
:align: center
|
||||
|
||||
How does it work?
|
||||
-----------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Equalization implies *mapping* one distribution (the given histogram) to another distribution (a wider and more uniform distribution of intensity values) so the intensity values are spreaded over the whole range.
|
||||
|
||||
* To accomplish the equalization effect, the remapping should be the *cumulative distribution function (cdf)* (more details, refer to *Learning OpenCV*). For the histogram :math:`H(i)`, its *cumulative distribution* :math:`H^{'}(i)` is:
|
||||
|
||||
.. math::
|
||||
|
||||
H^{'}(i) = \sum_{0 \le j < i} H(j)
|
||||
|
||||
To use this as a remapping function, we have to normalize :math:`H^{'}(i)` such that the maximum value is 255 ( or the maximum value for the intensity of the image ). From the example above, the cumulative function is:
|
||||
|
||||
.. image:: images/Histogram_Equalization_Theory_2.jpg
|
||||
:align: center
|
||||
|
||||
* Finally, we use a simple remapping procedure to obtain the intensity values of the equalized image:
|
||||
|
||||
.. math::
|
||||
|
||||
equalized( x, y ) = H^{'}( src(x,y) )
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* **What does this program do?**
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Loads an image
|
||||
* Convert the original image to grayscale
|
||||
* Equalize the Histogram by using the OpenCV function :equalize_hist:`EqualizeHist <>`
|
||||
* Display the source and equalized images in a window.
|
||||
|
||||
* **Downloadable code**:
|
||||
Click `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching/EqualizeHist_Demo.cpp>`_
|
||||
|
||||
* **Code at glance:**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
Mat src, dst;
|
||||
|
||||
char* source_window = "Source image";
|
||||
char* equalized_window = "Equalized Image";
|
||||
|
||||
/// Load image
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
if( !src.data )
|
||||
{ cout<<"Usage: ./Histogram_Demo <path_to_image>"<<endl;
|
||||
return -1;}
|
||||
|
||||
/// Convert to grayscale
|
||||
cvtColor( src, src, COLOR_BGR2GRAY );
|
||||
|
||||
/// Apply Histogram Equalization
|
||||
equalizeHist( src, dst );
|
||||
|
||||
/// Display results
|
||||
namedWindow( source_window, WINDOW_AUTOSIZE );
|
||||
namedWindow( equalized_window, WINDOW_AUTOSIZE );
|
||||
|
||||
imshow( source_window, src );
|
||||
imshow( equalized_window, dst );
|
||||
|
||||
/// Wait until user exits the program
|
||||
waitKey(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
#. Declare the source and destination images as well as the windows names:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat src, dst;
|
||||
|
||||
char* source_window = "Source image";
|
||||
char* equalized_window = "Equalized Image";
|
||||
|
||||
#. Load the source image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src = imread( argv[1], 1 );
|
||||
|
||||
if( !src.data )
|
||||
{ cout<<"Usage: ./Histogram_Demo <path_to_image>"<<endl;
|
||||
return -1;}
|
||||
|
||||
#. Convert it to grayscale:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
cvtColor( src, src, COLOR_BGR2GRAY );
|
||||
|
||||
#. Apply histogram equalization with the function :equalize_hist:`equalizeHist <>` :
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
equalizeHist( src, dst );
|
||||
|
||||
As it can be easily seen, the only arguments are the original image and the output (equalized) image.
|
||||
|
||||
#. Display both images (original and equalized) :
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow( source_window, WINDOW_AUTOSIZE );
|
||||
namedWindow( equalized_window, WINDOW_AUTOSIZE );
|
||||
|
||||
imshow( source_window, src );
|
||||
imshow( equalized_window, dst );
|
||||
|
||||
#. Wait until user exists the program
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
|
||||
|
||||
Results
|
||||
=======
|
||||
|
||||
#. To appreciate better the results of equalization, let's introduce an image with not much contrast, such as:
|
||||
|
||||
.. image:: images/Histogram_Equalization_Original_Image.jpg
|
||||
:align: center
|
||||
|
||||
which, by the way, has this histogram:
|
||||
|
||||
.. image:: images/Histogram_Equalization_Original_Histogram.jpg
|
||||
:align: center
|
||||
|
||||
notice that the pixels are clustered around the center of the histogram.
|
||||
|
||||
#. After applying the equalization with our program, we get this result:
|
||||
|
||||
.. image:: images/Histogram_Equalization_Equalized_Image.jpg
|
||||
:align: center
|
||||
|
||||
this image has certainly more contrast. Check out its new histogram like this:
|
||||
|
||||
.. image:: images/Histogram_Equalization_Equalized_Histogram.jpg
|
||||
:align: center
|
||||
|
||||
Notice how the number of pixels is more distributed through the intensity range.
|
||||
|
||||
|
||||
.. note::
|
||||
Are you wondering how did we draw the Histogram figures shown above? Check out the following tutorial!
|
@@ -1,371 +0,0 @@
|
||||
.. _template_matching:
|
||||
|
||||
Template Matching
|
||||
*****************
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Use the OpenCV function :match_template:`matchTemplate <>` to search for matches between an image patch and an input image
|
||||
* Use the OpenCV function :min_max_loc:`minMaxLoc <>` to find the maximum and minimum values (as well as their positions) in a given array.
|
||||
|
||||
Theory
|
||||
======
|
||||
|
||||
What is template matching?
|
||||
--------------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
Template matching is a technique for finding areas of an image that match (are similar) to a template image (patch).
|
||||
|
||||
|
||||
How does it work?
|
||||
------------------
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* We need two primary components:
|
||||
|
||||
a. **Source image (I):** The image in which we expect to find a match to the template image
|
||||
b. **Template image (T):** The patch image which will be compared to the template image
|
||||
|
||||
our goal is to detect the highest matching area:
|
||||
|
||||
.. image:: images/Template_Matching_Template_Theory_Summary.jpg
|
||||
:align: center
|
||||
|
||||
* To identify the matching area, we have to *compare* the template image against the source image by sliding it:
|
||||
|
||||
.. image:: images/Template_Matching_Template_Theory_Sliding.jpg
|
||||
:align: center
|
||||
|
||||
* By **sliding**, we mean moving the patch one pixel at a time (left to right, up to down). At each location, a metric is calculated so it represents how "good" or "bad" the match at that location is (or how similar the patch is to that particular area of the source image).
|
||||
|
||||
* For each location of **T** over **I**, you *store* the metric in the *result matrix* **(R)**. Each location :math:`(x,y)` in **R** contains the match metric:
|
||||
|
||||
.. image:: images/Template_Matching_Template_Theory_Result.jpg
|
||||
:align: center
|
||||
|
||||
the image above is the result **R** of sliding the patch with a metric **TM_CCORR_NORMED**. The brightest locations indicate the highest matches. As you can see, the location marked by the red circle is probably the one with the highest value, so that location (the rectangle formed by that point as a corner and width and height equal to the patch image) is considered the match.
|
||||
|
||||
* In practice, we use the function :min_max_loc:`minMaxLoc <>` to locate the highest value (or lower, depending of the type of matching method) in the *R* matrix.
|
||||
|
||||
Which are the matching methods available in OpenCV?
|
||||
----------------------------------------------------
|
||||
|
||||
Good question. OpenCV implements Template matching in the function :match_template:`matchTemplate <>`. The available methods are 6:
|
||||
|
||||
a. **method=CV\_TM\_SQDIFF**
|
||||
|
||||
.. math::
|
||||
|
||||
R(x,y)= \sum _{x',y'} (T(x',y')-I(x+x',y+y'))^2
|
||||
|
||||
|
||||
b. **method=CV\_TM\_SQDIFF\_NORMED**
|
||||
|
||||
.. math::
|
||||
|
||||
R(x,y)= \frac{\sum_{x',y'} (T(x',y')-I(x+x',y+y'))^2}{\sqrt{\sum_{x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}}
|
||||
|
||||
|
||||
c. **method=CV\_TM\_CCORR**
|
||||
|
||||
.. math::
|
||||
|
||||
R(x,y)= \sum _{x',y'} (T(x',y') \cdot I(x+x',y+y'))
|
||||
|
||||
|
||||
d. **method=CV\_TM\_CCORR\_NORMED**
|
||||
|
||||
.. math::
|
||||
|
||||
R(x,y)= \frac{\sum_{x',y'} (T(x',y') \cdot I(x+x',y+y'))}{\sqrt{\sum_{x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}}
|
||||
|
||||
|
||||
e. **method=CV\_TM\_CCOEFF**
|
||||
|
||||
.. math::
|
||||
|
||||
R(x,y)= \sum _{x',y'} (T'(x',y') \cdot I(x+x',y+y'))
|
||||
|
||||
where
|
||||
|
||||
.. math::
|
||||
|
||||
\begin{array}{l} T'(x',y')=T(x',y') - 1/(w \cdot h) \cdot \sum _{x'',y''} T(x'',y'') \\ I'(x+x',y+y')=I(x+x',y+y') - 1/(w \cdot h) \cdot \sum _{x'',y''} I(x+x'',y+y'') \end{array}
|
||||
|
||||
|
||||
f. **method=CV\_TM\_CCOEFF\_NORMED**
|
||||
|
||||
.. math::
|
||||
|
||||
R(x,y)= \frac{ \sum_{x',y'} (T'(x',y') \cdot I'(x+x',y+y')) }{ \sqrt{\sum_{x',y'}T'(x',y')^2 \cdot \sum_{x',y'} I'(x+x',y+y')^2} }
|
||||
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* **What does this program do?**
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
* Loads an input image and a image patch (*template*)
|
||||
* Perform a template matching procedure by using the OpenCV function :match_template:`matchTemplate <>` with any of the 6 matching methods described before. The user can choose the method by entering its selection in the Trackbar.
|
||||
* Normalize the output of the matching procedure
|
||||
* Localize the location with higher matching probability
|
||||
* Draw a rectangle around the area corresponding to the highest match
|
||||
|
||||
* **Downloadable code**:
|
||||
Click `here <https://github.com/Itseez/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching/MatchTemplate_Demo.cpp>`_
|
||||
|
||||
* **Code at glance:**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
/// Global Variables
|
||||
Mat img; Mat templ; Mat result;
|
||||
char* image_window = "Source Image";
|
||||
char* result_window = "Result window";
|
||||
|
||||
int match_method;
|
||||
int max_Trackbar = 5;
|
||||
|
||||
/// Function Headers
|
||||
void MatchingMethod( int, void* );
|
||||
|
||||
/* @function main */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// Load image and template
|
||||
img = imread( argv[1], 1 );
|
||||
templ = imread( argv[2], 1 );
|
||||
|
||||
/// Create windows
|
||||
namedWindow( image_window, WINDOW_AUTOSIZE );
|
||||
namedWindow( result_window, WINDOW_AUTOSIZE );
|
||||
|
||||
/// Create Trackbar
|
||||
char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED";
|
||||
createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod );
|
||||
|
||||
MatchingMethod( 0, 0 );
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @function MatchingMethod
|
||||
* @brief Trackbar callback
|
||||
*/
|
||||
void MatchingMethod( int, void* )
|
||||
{
|
||||
/// Source image to display
|
||||
Mat img_display;
|
||||
img.copyTo( img_display );
|
||||
|
||||
/// Create the result matrix
|
||||
int result_cols = img.cols - templ.cols + 1;
|
||||
int result_rows = img.rows - templ.rows + 1;
|
||||
|
||||
result.create( result_cols, result_rows, CV_32FC1 );
|
||||
|
||||
/// Do the Matching and Normalize
|
||||
matchTemplate( img, templ, result, match_method );
|
||||
normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
/// Localizing the best match with minMaxLoc
|
||||
double minVal; double maxVal; Point minLoc; Point maxLoc;
|
||||
Point matchLoc;
|
||||
|
||||
minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );
|
||||
|
||||
/// For SQDIFF and SQDIFF_NORMED, the best matches are lower values. For all the other methods, the higher the better
|
||||
if( match_method == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED )
|
||||
{ matchLoc = minLoc; }
|
||||
else
|
||||
{ matchLoc = maxLoc; }
|
||||
|
||||
/// Show me what you got
|
||||
rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
|
||||
rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
|
||||
|
||||
imshow( image_window, img_display );
|
||||
imshow( result_window, result );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
#. Declare some global variables, such as the image, template and result matrices, as well as the match method and the window names:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat img; Mat templ; Mat result;
|
||||
char* image_window = "Source Image";
|
||||
char* result_window = "Result window";
|
||||
|
||||
int match_method;
|
||||
int max_Trackbar = 5;
|
||||
|
||||
|
||||
#. Load the source image and template:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
img = imread( argv[1], 1 );
|
||||
templ = imread( argv[2], 1 );
|
||||
|
||||
#. Create the windows to show the results:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow( image_window, WINDOW_AUTOSIZE );
|
||||
namedWindow( result_window, WINDOW_AUTOSIZE );
|
||||
|
||||
#. Create the Trackbar to enter the kind of matching method to be used. When a change is detected the callback function **MatchingMethod** is called.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED";
|
||||
createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod );
|
||||
|
||||
#. Wait until user exits the program.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
waitKey(0);
|
||||
return 0;
|
||||
|
||||
#. Let's check out the callback function. First, it makes a copy of the source image:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat img_display;
|
||||
img.copyTo( img_display );
|
||||
|
||||
|
||||
#. Next, it creates the result matrix that will store the matching results for each template location. Observe in detail the size of the result matrix (which matches all possible locations for it)
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
int result_cols = img.cols - templ.cols + 1;
|
||||
int result_rows = img.rows - templ.rows + 1;
|
||||
|
||||
result.create( result_cols, result_rows, CV_32FC1 );
|
||||
|
||||
#. Perform the template matching operation:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
matchTemplate( img, templ, result, match_method );
|
||||
|
||||
the arguments are naturally the input image **I**, the template **T**, the result **R** and the match_method (given by the Trackbar)
|
||||
|
||||
#. We normalize the results:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );
|
||||
|
||||
#. We localize the minimum and maximum values in the result matrix **R** by using :min_max_loc:`minMaxLoc <>`.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
double minVal; double maxVal; Point minLoc; Point maxLoc;
|
||||
Point matchLoc;
|
||||
|
||||
minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );
|
||||
|
||||
the function calls as arguments:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ **result:** The source array
|
||||
+ **&minVal** and **&maxVal:** Variables to save the minimum and maximum values in **result**
|
||||
+ **&minLoc** and **&maxLoc:** The Point locations of the minimum and maximum values in the array.
|
||||
+ **Mat():** Optional mask
|
||||
|
||||
|
||||
#. For the first two methods ( TM\_SQDIFF and MT\_SQDIFF\_NORMED ) the best match are the lowest values. For all the others, higher values represent better matches. So, we save the corresponding value in the **matchLoc** variable:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
if( match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED )
|
||||
{ matchLoc = minLoc; }
|
||||
else
|
||||
{ matchLoc = maxLoc; }
|
||||
|
||||
#. Display the source image and the result matrix. Draw a rectangle around the highest possible matching area:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
|
||||
rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 );
|
||||
|
||||
imshow( image_window, img_display );
|
||||
imshow( result_window, result );
|
||||
|
||||
|
||||
Results
|
||||
=======
|
||||
|
||||
#. Testing our program with an input image such as:
|
||||
|
||||
.. image:: images/Template_Matching_Original_Image.jpg
|
||||
:align: center
|
||||
|
||||
and a template image:
|
||||
|
||||
.. image:: images/Template_Matching_Template_Image.jpg
|
||||
:align: center
|
||||
|
||||
#. Generate the following result matrices (first row are the standard methods SQDIFF, CCORR and CCOEFF, second row are the same methods in its normalized version). In the first column, the darkest is the better match, for the other two columns, the brighter a location, the higher the match.
|
||||
|
||||
============ ============ ============
|
||||
|Result_0| |Result_2| |Result_4|
|
||||
============ ============ ============
|
||||
|Result_1| |Result_3| |Result_5|
|
||||
============ ============ ============
|
||||
|
||||
.. |Result_0| image:: images/Template_Matching_Correl_Result_0.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Result_1| image:: images/Template_Matching_Correl_Result_1.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Result_2| image:: images/Template_Matching_Correl_Result_2.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Result_3| image:: images/Template_Matching_Correl_Result_3.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Result_4| image:: images/Template_Matching_Correl_Result_4.jpg
|
||||
:align: middle
|
||||
|
||||
.. |Result_5| image:: images/Template_Matching_Correl_Result_5.jpg
|
||||
:align: middle
|
||||
|
||||
#. The right match is shown below (black rectangle around the face of the guy at the right). Notice that CCORR and CCDEFF gave erroneous best matches, however their normalized version did it right, this may be due to the fact that we are only considering the "highest match" and not the other possible high matches.
|
||||
|
||||
.. image:: images/Template_Matching_Image_Result.jpg
|
||||
:align: center
|
Reference in New Issue
Block a user