mirror of
https://github.com/opencv/opencv.git
synced 2026-07-29 23:33:05 +04:00
Removed Sphinx documentation files
This commit is contained in:
@@ -1,113 +0,0 @@
|
||||
.. _Raster_IO_GDAL:
|
||||
|
||||
|
||||
Reading Geospatial Raster files with GDAL
|
||||
*****************************************
|
||||
|
||||
Geospatial raster data is a heavily used product in Geographic Information
|
||||
Systems and Photogrammetry. Raster data typically can represent imagery
|
||||
and Digital Elevation Models (DEM). The standard library for loading
|
||||
GIS imagery is the Geographic Data Abstraction Library (GDAL). In this example, we
|
||||
will show techniques for loading GIS raster formats using native OpenCV functions.
|
||||
In addition, we will show some an example of how OpenCV can use this data for
|
||||
novel and interesting purposes.
|
||||
|
||||
Goals
|
||||
=====
|
||||
|
||||
The primary objectives for this tutorial:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ How to use OpenCV imread to load satellite imagery.
|
||||
+ How to use OpenCV imread to load SRTM Digital Elevation Models
|
||||
+ Given the corner coordinates of both the image and DEM, correllate the elevation data to the image to find elevations for each pixel.
|
||||
+ Show a basic, easy-to-implement example of a terrain heat map.
|
||||
+ Show a basic use of DEM data coupled with ortho-rectified imagery.
|
||||
|
||||
To implement these goals, the following code takes a Digital Elevation Model as well as a GeoTiff image of San Francisco as input.
|
||||
The image and DEM data is processed and generates a terrain heat map of the image as well as labels areas of the city which would
|
||||
be affected should the water level of the bay rise 10, 50, and 100 meters.
|
||||
|
||||
Code
|
||||
====
|
||||
|
||||
.. literalinclude:: ../../../../samples/cpp/tutorial_code/HighGUI/GDAL_IO/gdal-image.cpp
|
||||
:language: cpp
|
||||
:linenos:
|
||||
:tab-width: 4
|
||||
|
||||
|
||||
How to Read Raster Data using GDAL
|
||||
======================================
|
||||
|
||||
This demonstration uses the default OpenCV :ocv:func:`imread` function. The primary difference is that in order to force GDAL to load the
|
||||
image, you must use the appropriate flag.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
cv::Mat image = cv::imread( argv[1], cv::IMREAD_LOAD_GDAL );
|
||||
|
||||
When loading digital elevation models, the actual numeric value of each pixel is essential
|
||||
and cannot be scaled or truncated. For example, with image data a pixel represented as a double with a value of 1 has
|
||||
an equal appearance to a pixel which is represented as an unsigned character with a value of 255.
|
||||
With terrain data, the pixel value represents the elevation in meters. In order to ensure that OpenCV preserves the native value,
|
||||
use the GDAL flag in imread with the ANYDEPTH flag.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
cv::Mat dem = cv::imread( argv[2], cv::IMREAD_LOAD_GDAL | cv::IMREAD_ANYDEPTH );
|
||||
|
||||
|
||||
If you know beforehand the type of DEM model you are loading, then it may be a safe bet to test the ``Mat::type()`` or ``Mat::depth()``
|
||||
using an assert or other mechanism. NASA or DOD specification documents can provide the input types for various
|
||||
elevation models. The major types, SRTM and DTED, are both signed shorts.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
Lat/Lon (Geodetic) Coordinates should normally be avoided
|
||||
---------------------------------------------------------
|
||||
|
||||
The Geodetic Coordinate System is a spherical coordinate system, meaning that using them with Cartesian mathematics is technically incorrect. This
|
||||
demo uses them to increase the readability and is accurate enough to make the point. A better coordinate system would be Universal Transverse Mercator.
|
||||
|
||||
Finding the corner coordinates
|
||||
------------------------------
|
||||
|
||||
One easy method to find the corner coordinates of an image is to use the command-line tool ``gdalinfo``. For imagery which is ortho-rectified and contains
|
||||
the projection information, you can use the `USGS EarthExplorer <http://http://earthexplorer.usgs.gov>`_.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$> gdalinfo N37W123.hgt
|
||||
|
||||
Driver: SRTMHGT/SRTMHGT File Format
|
||||
Files: N37W123.hgt
|
||||
Size is 3601, 3601
|
||||
Coordinate System is:
|
||||
GEOGCS["WGS 84",
|
||||
DATUM["WGS_1984",
|
||||
|
||||
... more output ...
|
||||
|
||||
Corner Coordinates:
|
||||
Upper Left (-123.0001389, 38.0001389) (123d 0' 0.50"W, 38d 0' 0.50"N)
|
||||
Lower Left (-123.0001389, 36.9998611) (123d 0' 0.50"W, 36d59'59.50"N)
|
||||
Upper Right (-121.9998611, 38.0001389) (121d59'59.50"W, 38d 0' 0.50"N)
|
||||
Lower Right (-121.9998611, 36.9998611) (121d59'59.50"W, 36d59'59.50"N)
|
||||
Center (-122.5000000, 37.5000000) (122d30' 0.00"W, 37d30' 0.00"N)
|
||||
|
||||
... more output ...
|
||||
|
||||
|
||||
Results
|
||||
=======
|
||||
|
||||
Below is the output of the program. Use the first image as the input. For the DEM model, download the SRTM file located at the USGS here. `http://dds.cr.usgs.gov/srtm/version2_1/SRTM1/Region_04/N37W123.hgt.zip <http://dds.cr.usgs.gov/srtm/version2_1/SRTM1/Region_04/N37W123.hgt.zip>`_
|
||||
|
||||
.. image:: images/gdal_output.jpg
|
||||
|
||||
.. image:: images/gdal_heat-map.jpg
|
||||
|
||||
.. image:: images/gdal_flood-zone.jpg
|
||||
@@ -1,98 +0,0 @@
|
||||
.. _Table-Of-Content-HighGui:
|
||||
|
||||
*highgui* module. High Level GUI and Media
|
||||
------------------------------------------
|
||||
|
||||
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.
|
||||
|
||||
.. include:: ../../definitions/tocDefinitions.rst
|
||||
|
||||
+
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
=============== ======================================================
|
||||
|Beginners_5| *Title:* :ref:`Adding_Trackbars`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_AnaH|
|
||||
|
||||
We will learn how to add a Trackbar to our applications
|
||||
|
||||
=============== ======================================================
|
||||
|
||||
.. |Beginners_5| image:: images/Adding_Trackbars_Tutorial_Cover.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
=============== ======================================================
|
||||
|hVideoInput| *Title:* :ref:`videoInputPSNRMSSIM`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_BernatG|
|
||||
|
||||
You will learn how to read video streams, and how to calculate similarity values such as PSNR or SSIM.
|
||||
|
||||
=============== ======================================================
|
||||
|
||||
.. |hVideoInput| image:: images/video-input-psnr-ssim.png
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
=============== ======================================================
|
||||
|hVideoWrite| *Title:* :ref:`videoWriteHighGui`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_BernatG|
|
||||
|
||||
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.
|
||||
|
||||
=============== ======================================================
|
||||
|
||||
.. |hVideoWrite| image:: images/video-write.png
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
+
|
||||
.. tabularcolumns:: m{100pt} m{300pt}
|
||||
.. cssclass:: toctableopencv
|
||||
|
||||
=============== ======================================================
|
||||
|hGDAL_IO| *Title:* :ref:`Raster_IO_GDAL`
|
||||
|
||||
*Compatibility:* > OpenCV 2.0
|
||||
|
||||
*Author:* |Author_MarvinS|
|
||||
|
||||
Read common GIS Raster and DEM files to display and manipulate geographic data.
|
||||
|
||||
=============== ======================================================
|
||||
|
||||
.. |hGDAL_IO| image:: images/gdal-io.jpg
|
||||
:height: 90pt
|
||||
:width: 90pt
|
||||
|
||||
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\pagebreak
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
../trackbar/trackbar
|
||||
../video-input-psnr-ssim/video-input-psnr-ssim
|
||||
../video-write/video-write
|
||||
../raster-gdal/raster_io_gdal
|
||||
@@ -1,154 +0,0 @@
|
||||
.. _Adding_Trackbars:
|
||||
|
||||
Adding a Trackbar to our applications!
|
||||
***************************************
|
||||
|
||||
* In the previous tutorials (about *linear blending* and the *brightness and contrast adjustments*) you might have noted that we needed to give some **input** to our programs, such as :math:`\alpha` and :math:`beta`. We accomplished that by entering this data using the Terminal
|
||||
|
||||
* Well, it is time to use some fancy GUI tools. OpenCV provides some GUI utilities (*highgui.h*) for you. An example of this is a **Trackbar**
|
||||
|
||||
.. image:: images/Adding_Trackbars_Tutorial_Trackbar.png
|
||||
:alt: Trackbar example
|
||||
:align: center
|
||||
|
||||
* In this tutorial we will just modify our two previous programs so that they get the input information from the trackbar.
|
||||
|
||||
|
||||
Goals
|
||||
======
|
||||
|
||||
In this tutorial you will learn how to:
|
||||
|
||||
* Add a Trackbar in an OpenCV window by using :create_trackbar:`createTrackbar <>`
|
||||
|
||||
Code
|
||||
=====
|
||||
|
||||
Let's modify the program made in the tutorial :ref:`Adding_Images`. We will let the user enter the :math:`\alpha` value by using the Trackbar.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
using namespace cv;
|
||||
|
||||
/// Global Variables
|
||||
const int alpha_slider_max = 100;
|
||||
int alpha_slider;
|
||||
double alpha;
|
||||
double beta;
|
||||
|
||||
/// Matrices to store images
|
||||
Mat src1;
|
||||
Mat src2;
|
||||
Mat dst;
|
||||
|
||||
/*
|
||||
* @function on_trackbar
|
||||
* @brief Callback for trackbar
|
||||
*/
|
||||
void on_trackbar( int, void* )
|
||||
{
|
||||
alpha = (double) alpha_slider/alpha_slider_max ;
|
||||
beta = ( 1.0 - alpha );
|
||||
|
||||
addWeighted( src1, alpha, src2, beta, 0.0, dst);
|
||||
|
||||
imshow( "Linear Blend", dst );
|
||||
}
|
||||
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
/// 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; }
|
||||
|
||||
/// Initialize values
|
||||
alpha_slider = 0;
|
||||
|
||||
/// Create Windows
|
||||
namedWindow("Linear Blend", 1);
|
||||
|
||||
/// Create Trackbars
|
||||
char TrackbarName[50];
|
||||
sprintf( TrackbarName, "Alpha x %d", alpha_slider_max );
|
||||
|
||||
createTrackbar( TrackbarName, "Linear Blend", &alpha_slider, alpha_slider_max, on_trackbar );
|
||||
|
||||
/// Show some stuff
|
||||
on_trackbar( alpha_slider, 0 );
|
||||
|
||||
/// Wait until user press some key
|
||||
waitKey(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Explanation
|
||||
============
|
||||
|
||||
We only analyze the code that is related to Trackbar:
|
||||
|
||||
#. First, we load 02 images, which are going to be blended.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
src1 = imread("../../images/LinuxLogo.jpg");
|
||||
src2 = imread("../../images/WindowsLogo.jpg");
|
||||
|
||||
#. To create a trackbar, first we have to create the window in which it is going to be located. So:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namedWindow("Linear Blend", 1);
|
||||
|
||||
#. Now we can create the Trackbar:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
createTrackbar( TrackbarName, "Linear Blend", &alpha_slider, alpha_slider_max, on_trackbar );
|
||||
|
||||
Note the following:
|
||||
|
||||
* Our Trackbar has a label **TrackbarName**
|
||||
* The Trackbar is located in the window named **"Linear Blend"**
|
||||
* The Trackbar values will be in the range from :math:`0` to **alpha_slider_max** (the minimum limit is always **zero**).
|
||||
* The numerical value of Trackbar is stored in **alpha_slider**
|
||||
* Whenever the user moves the Trackbar, the callback function **on_trackbar** is called
|
||||
|
||||
#. Finally, we have to define the callback function **on_trackbar**
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
void on_trackbar( int, void* )
|
||||
{
|
||||
alpha = (double) alpha_slider/alpha_slider_max ;
|
||||
beta = ( 1.0 - alpha );
|
||||
|
||||
addWeighted( src1, alpha, src2, beta, 0.0, dst);
|
||||
|
||||
imshow( "Linear Blend", dst );
|
||||
}
|
||||
|
||||
Note that:
|
||||
|
||||
* We use the value of **alpha_slider** (integer) to get a double value for **alpha**.
|
||||
* **alpha_slider** is updated each time the trackbar is displaced by the user.
|
||||
* We define *src1*, *src2*, *dist*, *alpha*, *alpha_slider* and *beta* as global variables, so they can be used everywhere.
|
||||
|
||||
Result
|
||||
=======
|
||||
|
||||
* Our program produces the following output:
|
||||
|
||||
.. image:: images/Adding_Trackbars_Tutorial_Result_0.jpg
|
||||
:alt: Adding Trackbars - Windows Linux
|
||||
:align: center
|
||||
|
||||
* As a manner of practice, you can also add 02 trackbars for the program made in :ref:`Basic_Linear_Transform`. One trackbar to set :math:`\alpha` and another for :math:`\beta`. The output might look like:
|
||||
|
||||
.. image:: images/Adding_Trackbars_Tutorial_Result_1.jpg
|
||||
:alt: Adding Trackbars - Lena
|
||||
:align: center
|
||||
@@ -1,216 +0,0 @@
|
||||
.. _videoInputPSNRMSSIM:
|
||||
|
||||
Video Input with OpenCV and similarity measurement
|
||||
**************************************************
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
Today it is common to have a digital video recording system at your disposal. Therefore, you will eventually come to the situation that you no longer process a batch of images, but video streams. These may be of two kinds: real-time image feed (in the case of a webcam) or prerecorded and hard disk drive stored files. Luckily OpenCV threats these two in the same manner, with the same C++ class. So here's what you'll learn in this tutorial:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ How to open and read video streams
|
||||
+ Two ways for checking image similarity: PSNR and SSIM
|
||||
|
||||
The source code
|
||||
===============
|
||||
|
||||
As a test case where to show off these using OpenCV I've created a small program that reads in two video files and performs a similarity check between them. This is something you could use to check just how well a new video compressing algorithms works. Let there be a reference (original) video like :download:`this small Megamind clip <../../../../samples/cpp/tutorial_code/HighGUI/video-input-psnr-ssim/video/Megamind.avi>` and :download:`a compressed version of it <../../../../samples/cpp/tutorial_code/HighGUI/video-input-psnr-ssim/video/Megamind_bugy.avi>`. You may also find the source code and these video file in the :file:`samples/cpp/tutorial_code/HighGUI/video-input-psnr-ssim/` folder of the OpenCV source library.
|
||||
|
||||
.. literalinclude:: ../../../../samples/cpp/tutorial_code/HighGUI/video-input-psnr-ssim/video-input-psnr-ssim.cpp
|
||||
:language: cpp
|
||||
:linenos:
|
||||
:tab-width: 4
|
||||
:lines: 1-15, 29-31, 33-208
|
||||
|
||||
How to read a video stream (online-camera or offline-file)?
|
||||
===========================================================
|
||||
|
||||
Essentially, all the functionalities required for video manipulation is integrated in the :hgvideo:`VideoCapture <videocapture>` C++ class. This on itself builds on the FFmpeg open source library. This is a basic dependency of OpenCV so you shouldn't need to worry about this. A video is composed of a succession of images, we refer to these in the literature as frames. In case of a video file there is a *frame rate* specifying just how long is between two frames. While for the video cameras usually there is a limit of just how many frames they can digitalize per second, this property is less important as at any time the camera sees the current snapshot of the world.
|
||||
|
||||
The first task you need to do is to assign to a :hgvideo:`VideoCapture <videocapture>` class its source. You can do this either via the :hgvideo:`constructor <videocapture-videocapture>` or its :hgvideo:`open <videocapture-open>` function. If this argument is an integer then you will bind the class to a camera, a device. The number passed here is the ID of the device, assigned by the operating system. If you have a single camera attached to your system its ID will probably be zero and further ones increasing from there. If the parameter passed to these is a string it will refer to a video file, and the string points to the location and name of the file. For example, to the upper source code a valid command line is:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
video/Megamind.avi video/Megamind_bug.avi 35 10
|
||||
|
||||
We do a similarity check. This requires a reference and a test case video file. The first two arguments refer to this. Here we use a relative address. This means that the application will look into its current working directory and open the video folder and try to find inside this the *Megamind.avi* and the *Megamind_bug.avi*.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
const string sourceReference = argv[1],sourceCompareWith = argv[2];
|
||||
|
||||
VideoCapture captRefrnc(sourceReference);
|
||||
// or
|
||||
VideoCapture captUndTst;
|
||||
captUndTst.open(sourceCompareWith);
|
||||
|
||||
To check if the binding of the class to a video source was successful or not use the :hgvideo:`isOpened <video-isopened>` function:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
if ( !captRefrnc.isOpened())
|
||||
{
|
||||
cout << "Could not open reference " << sourceReference << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Closing the video is automatic when the objects destructor is called. However, if you want to close it before this you need to call its :hgvideo:`release <videocapture-release>` function. The frames of the video are just simple images. Therefore, we just need to extract them from the :hgvideo:`VideoCapture <videocapture>` object and put them inside a *Mat* one. The video streams are sequential. You may get the frames one after another by the :hgvideo:`read <videocapture-read>` or the overloaded >> operator:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Mat frameReference, frameUnderTest;
|
||||
captRefrnc >> frameReference;
|
||||
captUndTst.open(frameUnderTest);
|
||||
|
||||
The upper read operations will leave empty the *Mat* objects if no frame could be acquired (either cause the video stream was closed or you got to the end of the video file). We can check this with a simple if:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
if( frameReference.empty() || frameUnderTest.empty())
|
||||
{
|
||||
// exit the program
|
||||
}
|
||||
|
||||
A read method is made of a frame grab and a decoding applied on that. You may call explicitly these two by using the :hgvideo:`grab <videocapture-grab>` and then the :hgvideo:`retrieve <videocapture-retrieve>` functions.
|
||||
|
||||
Videos have many-many information attached to them besides the content of the frames. These are usually numbers, however in some case it may be short character sequences (4 bytes or less). Due to this to acquire these information there is a general function named :hgvideo:`get <videocapture-get>` that returns double values containing these properties. Use bitwise operations to decode the characters from a double type and conversions where valid values are only integers. Its single argument is the ID of the queried property. For example, here we get the size of the frames in the reference and test case video file; plus the number of frames inside the reference.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Size refS = Size((int) captRefrnc.get(CAP_PROP_FRAME_WIDTH),
|
||||
(int) captRefrnc.get(CAP_PROP_FRAME_HEIGHT)),
|
||||
|
||||
cout << "Reference frame resolution: Width=" << refS.width << " Height=" << refS.height
|
||||
<< " of nr#: " << captRefrnc.get(CAP_PROP_FRAME_COUNT) << endl;
|
||||
|
||||
When you are working with videos you may often want to control these values yourself. To do this there is a :hgvideo:`set <videocapture-set>` function. Its first argument remains the name of the property you want to change and there is a second of double type containing the value to be set. It will return true if it succeeds and false otherwise. Good examples for this is seeking in a video file to a given time or frame:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
captRefrnc.set(CAP_PROP_POS_MSEC, 1.2); // go to the 1.2 second in the video
|
||||
captRefrnc.set(CAP_PROP_POS_FRAMES, 10); // go to the 10th frame of the video
|
||||
// now a read operation would read the frame at the set position
|
||||
|
||||
For properties you can read and change look into the documentation of the :hgvideo:`get <videocapture-get>` and :hgvideo:`set <videocapture-set>` functions.
|
||||
|
||||
|
||||
Image similarity - PSNR and SSIM
|
||||
================================
|
||||
|
||||
We want to check just how imperceptible our video converting operation went, therefore we need a system to check frame by frame the similarity or differences. The most common algorithm used for this is the PSNR (aka **Peak signal-to-noise ratio**). The simplest definition of this starts out from the *mean squad error*. Let there be two images: I1 and I2; with a two dimensional size i and j, composed of c number of channels.
|
||||
|
||||
.. math::
|
||||
|
||||
MSE = \frac{1}{c*i*j} \sum{(I_1-I_2)^2}
|
||||
|
||||
Then the PSNR is expressed as:
|
||||
|
||||
.. math::
|
||||
|
||||
PSNR = 10 \cdot \log_{10} \left( \frac{MAX_I^2}{MSE} \right)
|
||||
|
||||
Here the :math:`MAX_I^2` is the maximum valid value for a pixel. In case of the simple single byte image per pixel per channel this is 255. When two images are the same the MSE will give zero, resulting in an invalid divide by zero operation in the PSNR formula. In this case the PSNR is undefined and as we'll need to handle this case separately. The transition to a logarithmic scale is made because the pixel values have a very wide dynamic range. All this translated to OpenCV and a C++ function looks like:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
double getPSNR(const Mat& I1, const Mat& I2)
|
||||
{
|
||||
Mat s1;
|
||||
absdiff(I1, I2, s1); // |I1 - I2|
|
||||
s1.convertTo(s1, CV_32F); // cannot make a square on 8 bits
|
||||
s1 = s1.mul(s1); // |I1 - I2|^2
|
||||
|
||||
Scalar s = sum(s1); // sum elements per channel
|
||||
|
||||
double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels
|
||||
|
||||
if( sse <= 1e-10) // for small values return zero
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
double mse =sse /(double)(I1.channels() * I1.total());
|
||||
double psnr = 10.0*log10((255*255)/mse);
|
||||
return psnr;
|
||||
}
|
||||
}
|
||||
|
||||
Typically result values are anywhere between 30 and 50 for video compression, where higher is better. If the images significantly differ you'll get much lower ones like 15 and so. This similarity check is easy and fast to calculate, however in practice it may turn out somewhat inconsistent with human eye perception. The **structural similarity** algorithm aims to correct this.
|
||||
|
||||
Describing the methods goes well beyond the purpose of this tutorial. For that I invite you to read the article introducing it. Nevertheless, you can get a good image of it by looking at the OpenCV implementation below.
|
||||
|
||||
.. seealso::
|
||||
|
||||
SSIM is described more in-depth in the: "Z. Wang, A. C. Bovik, H. R. Sheikh and E. P. Simoncelli, "Image quality assessment: From error visibility to structural similarity," IEEE Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004." article.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
Scalar getMSSIM( const Mat& i1, const Mat& i2)
|
||||
{
|
||||
const double C1 = 6.5025, C2 = 58.5225;
|
||||
/***************************** INITS **********************************/
|
||||
int d = CV_32F;
|
||||
|
||||
Mat I1, I2;
|
||||
i1.convertTo(I1, d); // cannot calculate on one byte large values
|
||||
i2.convertTo(I2, d);
|
||||
|
||||
Mat I2_2 = I2.mul(I2); // I2^2
|
||||
Mat I1_2 = I1.mul(I1); // I1^2
|
||||
Mat I1_I2 = I1.mul(I2); // I1 * I2
|
||||
|
||||
/***********************PRELIMINARY COMPUTING ******************************/
|
||||
|
||||
Mat mu1, mu2; //
|
||||
GaussianBlur(I1, mu1, Size(11, 11), 1.5);
|
||||
GaussianBlur(I2, mu2, Size(11, 11), 1.5);
|
||||
|
||||
Mat mu1_2 = mu1.mul(mu1);
|
||||
Mat mu2_2 = mu2.mul(mu2);
|
||||
Mat mu1_mu2 = mu1.mul(mu2);
|
||||
|
||||
Mat sigma1_2, sigma2_2, sigma12;
|
||||
|
||||
GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
|
||||
sigma1_2 -= mu1_2;
|
||||
|
||||
GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
|
||||
sigma2_2 -= mu2_2;
|
||||
|
||||
GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);
|
||||
sigma12 -= mu1_mu2;
|
||||
|
||||
///////////////////////////////// FORMULA ////////////////////////////////
|
||||
Mat t1, t2, t3;
|
||||
|
||||
t1 = 2 * mu1_mu2 + C1;
|
||||
t2 = 2 * sigma12 + C2;
|
||||
t3 = t1.mul(t2); // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))
|
||||
|
||||
t1 = mu1_2 + mu2_2 + C1;
|
||||
t2 = sigma1_2 + sigma2_2 + C2;
|
||||
t1 = t1.mul(t2); // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))
|
||||
|
||||
Mat ssim_map;
|
||||
divide(t3, t1, ssim_map); // ssim_map = t3./t1;
|
||||
|
||||
Scalar mssim = mean( ssim_map ); // mssim = average of ssim map
|
||||
return mssim;
|
||||
}
|
||||
|
||||
This will return a similarity index for each channel of the image. This value is between zero and one, where one corresponds to perfect fit. Unfortunately, the many Gaussian blurring is quite costly, so while the PSNR may work in a real time like environment (24 frame per second) this will take significantly more than to accomplish similar performance results.
|
||||
|
||||
Therefore, the source code presented at the start of the tutorial will perform the PSNR measurement for each frame, and the SSIM only for the frames where the PSNR falls below an input value. For visualization purpose we show both images in an OpenCV window and print the PSNR and MSSIM values to the console. Expect to see something like:
|
||||
|
||||
.. image:: images/outputVideoInput.png
|
||||
:alt: A sample output
|
||||
:align: center
|
||||
|
||||
You may observe a runtime instance of this on the `YouTube here <https://www.youtube.com/watch?v=iOcNljutOgg>`_.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div align="center">
|
||||
<iframe title="Video Input with OpenCV (Plus PSNR and MSSIM)" width="560" height="349" src="http://www.youtube.com/embed/iOcNljutOgg?rel=0&loop=1" frameborder="0" allowfullscreen align="middle"></iframe>
|
||||
</div>
|
||||
@@ -1,136 +0,0 @@
|
||||
.. _videoWriteHighGui:
|
||||
|
||||
Creating a video with OpenCV
|
||||
****************************
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
Whenever you work with video feeds you may eventually want to save your image processing result in a form of a new video file. For simple video outputs you can use the OpenCV built-in :hgvideo:`VideoWriter <videowriter-videowriter>` class, designed for this.
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ How to create a video file with OpenCV
|
||||
+ What type of video files you can create with OpenCV
|
||||
+ How to extract a given color channel from a video
|
||||
|
||||
As a simple demonstration I'll just extract one of the RGB color channels of an input video file into a new video. You can control the flow of the application from its console line arguments:
|
||||
|
||||
.. container:: enumeratevisibleitemswithsquare
|
||||
|
||||
+ The first argument points to the video file to work on
|
||||
+ The second argument may be one of the characters: R G B. This will specify which of the channels to extract.
|
||||
+ The last argument is the character Y (Yes) or N (No). If this is no, the codec used for the input video file will be the same as for the output. Otherwise, a window will pop up and allow you to select yourself the codec to use.
|
||||
|
||||
For example, a valid command line would look like:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
video-write.exe video/Megamind.avi R Y
|
||||
|
||||
The source code
|
||||
===============
|
||||
You may also find the source code and these video file in the :file:`samples/cpp/tutorial_code/highgui/video-write/` folder of the OpenCV source library or :download:`download it from here <../../../../samples/cpp/tutorial_code/HighGUI/video-write/video-write.cpp>`.
|
||||
|
||||
.. literalinclude:: ../../../../samples/cpp/tutorial_code/HighGUI/video-write/video-write.cpp
|
||||
:language: cpp
|
||||
:linenos:
|
||||
:tab-width: 4
|
||||
|
||||
The structure of a video
|
||||
========================
|
||||
For start, you should have an idea of just how a video file looks. Every video file in itself is a container. The type of the container is expressed in the files extension (for example *avi*, *mov* or *mkv*). This contains multiple elements like: video feeds, audio feeds or other tracks (like for example subtitles). How these feeds are stored is determined by the codec used for each one of them. In case of the audio tracks commonly used codecs are *mp3* or *aac*. For the video files the list is somehow longer and includes names such as *XVID*, *DIVX*, *H264* or *LAGS* (*Lagarith Lossless Codec*). The full list of codecs you may use on a system depends on just what one you have installed.
|
||||
|
||||
.. image:: images/videoFileStructure.png
|
||||
:alt: The Structure of the video
|
||||
:align: center
|
||||
|
||||
As you can see things can get really complicated with videos. However, OpenCV is mainly a computer vision library, not a video stream, codec and write one. Therefore, the developers tried to keep this part as simple as possible. Due to this OpenCV for video containers supports only the *avi* extension, its first version. A direct limitation of this is that you cannot save a video file larger than 2 GB. Furthermore you can only create and expand a single video track inside the container. No audio or other track editing support here. Nevertheless, any video codec present on your system might work. If you encounter some of these limitations you will need to look into more specialized video writing libraries such as *FFMpeg* or codecs as *HuffYUV*, *CorePNG* and *LCL*. As an alternative, create the video track with OpenCV and expand it with sound tracks or convert it to other formats by using video manipulation programs such as *VirtualDub* or *AviSynth*.
|
||||
The *VideoWriter* class
|
||||
=======================
|
||||
The content written here builds on the assumption you already read the :ref:`videoInputPSNRMSSIM` tutorial and you know how to read video files.
|
||||
To create a video file you just need to create an instance of the :hgvideo:`VideoWriter <videowriter-videowriter>` class. You can specify its properties either via parameters in the constructor or later on via the :hgvideo:`open <videowriter-open>` function. Either way, the parameters are the same:
|
||||
1. The name of the output that contains the container type in its extension. At the moment only *avi* is supported. We construct this from the input file, add to this the name of the channel to use, and finish it off with the container extension.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
const string source = argv[1]; // the source file name
|
||||
string::size_type pAt = source.find_last_of('.'); // Find extension point
|
||||
const string NAME = source.substr(0, pAt) + argv[2][0] + ".avi"; // Form the new name with container
|
||||
|
||||
#. The codec to use for the video track. Now all the video codecs have a unique short name of maximum four characters. Hence, the *XVID*, *DIVX* or *H264* names. This is called a four character code. You may also ask this from an input video by using its *get* function. Because the *get* function is a general function it always returns double values. A double value is stored on 64 bits. Four characters are four bytes, meaning 32 bits. These four characters are coded in the lower 32 bits of the *double*. A simple way to throw away the upper 32 bits would be to just convert this value to *int*:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
VideoCapture inputVideo(source); // Open input
|
||||
int ex = static_cast<int>(inputVideo.get(CAP_PROP_FOURCC)); // Get Codec Type- Int form
|
||||
|
||||
OpenCV internally works with this integer type and expect this as its second parameter. Now to convert from the integer form to string we may use two methods: a bitwise operator and a union method. The first one extracting from an int the characters looks like (an "and" operation, some shifting and adding a 0 at the end to close the string):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
char EXT[] = {ex & 0XFF , (ex & 0XFF00) >> 8,(ex & 0XFF0000) >> 16,(ex & 0XFF000000) >> 24, 0};
|
||||
|
||||
You can do the same thing with the *union* as:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
union { int v; char c[5];} uEx ;
|
||||
uEx.v = ex; // From Int to char via union
|
||||
uEx.c[4]='\0';
|
||||
|
||||
The advantage of this is that the conversion is done automatically after assigning, while for the bitwise operator you need to do the operations whenever you change the codec type. In case you know the codecs four character code beforehand, you can use the *CV_FOURCC* macro to build the integer:
|
||||
|
||||
.. code-block::cpp
|
||||
|
||||
CV_FOURCC('P','I','M,'1') // this is an MPEG1 codec from the characters to integer
|
||||
|
||||
If you pass for this argument minus one than a window will pop up at runtime that contains all the codec installed on your system and ask you to select the one to use:
|
||||
|
||||
.. image:: images/videoCompressSelect.png
|
||||
:alt: Select the codec type to use
|
||||
:align: center
|
||||
|
||||
#. The frame per second for the output video. Again, here I keep the input videos frame per second by using the *get* function.
|
||||
#. The size of the frames for the output video. Here too I keep the input videos frame size per second by using the *get* function.
|
||||
#. The final argument is an optional one. By default is true and says that the output will be a colorful one (so for write you will send three channel images). To create a gray scale video pass a false parameter here.
|
||||
|
||||
Here it is, how I use it in the sample:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
VideoWriter outputVideo;
|
||||
Size S = Size((int) inputVideo.get(CAP_PROP_FRAME_WIDTH), //Acquire input size
|
||||
(int) inputVideo.get(CAP_PROP_FRAME_HEIGHT));
|
||||
outputVideo.open(NAME , ex, inputVideo.get(CAP_PROP_FPS),S, true);
|
||||
|
||||
Afterwards, you use the :hgvideo:`isOpened() <videowriter-isopened>` function to find out if the open operation succeeded or not. The video file automatically closes when the *VideoWriter* object is destroyed. After you open the object with success you can send the frames of the video in a sequential order by using the :hgvideo:`write<videowriter-write>` function of the class. Alternatively, you can use its overloaded operator << :
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
outputVideo.write(res); //or
|
||||
outputVideo << res;
|
||||
|
||||
Extracting a color channel from an RGB image means to set to zero the RGB values of the other channels. You can either do this with image scanning operations or by using the split and merge operations. You first split the channels up into different images, set the other channels to zero images of the same size and type and finally merge them back:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
split(src, spl); // process - extract only the correct channel
|
||||
for( int i =0; i < 3; ++i)
|
||||
if (i != channel)
|
||||
spl[i] = Mat::zeros(S, spl[0].type());
|
||||
merge(spl, res);
|
||||
|
||||
Put all this together and you'll get the upper source code, whose runtime result will show something around the idea:
|
||||
|
||||
.. image:: images/resultOutputWideoWrite.png
|
||||
:alt: A sample output
|
||||
:align: center
|
||||
|
||||
You may observe a runtime instance of this on the `YouTube here <https://www.youtube.com/watch?v=jpBwHxsl1_0>`_.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div align="center">
|
||||
<iframe title="Creating a video with OpenCV" width="560" height="349" src="http://www.youtube.com/embed/jpBwHxsl1_0?rel=0&loop=1" frameborder="0" allowfullscreen align="middle"></iframe>
|
||||
</div>
|
||||
Reference in New Issue
Block a user