init - 初始化项目
This commit is contained in:
203
doc/tutorials/app/video_input_psnr_ssim.markdown
Normal file
203
doc/tutorials/app/video_input_psnr_ssim.markdown
Normal file
@@ -0,0 +1,203 @@
|
||||
Video Input with OpenCV and similarity measurement {#tutorial_video_input_psnr_ssim}
|
||||
==================================================
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@prev_tutorial{tutorial_raster_io_gdal}
|
||||
@next_tutorial{tutorial_video_write}
|
||||
|
||||
| | |
|
||||
| -: | :- |
|
||||
| Original author | Bernát Gábor |
|
||||
| Compatibility | OpenCV >= 3.0 |
|
||||
|
||||
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 treats these two in the same manner, with the same C++
|
||||
class. So here's what you'll learn in this tutorial:
|
||||
|
||||
- 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 [this small Megamind clip
|
||||
](https://github.com/opencv/opencv/tree/master/samples/data/Megamind.avi) and [a compressed
|
||||
version of it ](https://github.com/opencv/opencv/tree/master/samples/data/Megamind_bugy.avi).
|
||||
You may also find the source code and these video file in the
|
||||
`samples/data` folder of the OpenCV source library.
|
||||
|
||||
@add_toggle_cpp
|
||||
@include cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@include samples/python/tutorial_code/videoio/video-input-psnr-ssim.py
|
||||
@end_toggle
|
||||
|
||||
How to read a video stream (online-camera or offline-file)?
|
||||
-----------------------------------------------------------
|
||||
|
||||
Essentially, all the functionalities required for video manipulation is integrated in the @ref cv::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 digitize 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 @ref cv::VideoCapture class its source. You can do
|
||||
this either via the @ref cv::VideoCapture::VideoCapture or its @ref cv::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{.bash}
|
||||
video/Megamind.avi video/Megamind_bug.avi 35 10
|
||||
@endcode
|
||||
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{.cpp}
|
||||
const string sourceReference = argv[1],sourceCompareWith = argv[2];
|
||||
|
||||
VideoCapture captRefrnc(sourceReference);
|
||||
// or
|
||||
VideoCapture captUndTst;
|
||||
captUndTst.open(sourceCompareWith);
|
||||
@endcode
|
||||
To check if the binding of the class to a video source was successful or not use the @ref cv::VideoCapture::isOpened
|
||||
function:
|
||||
@code{.cpp}
|
||||
if ( !captRefrnc.isOpened())
|
||||
{
|
||||
cout << "Could not open reference " << sourceReference << endl;
|
||||
return -1;
|
||||
}
|
||||
@endcode
|
||||
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 @ref cv::VideoCapture::release function. The frames of the video are just
|
||||
simple images. Therefore, we just need to extract them from the @ref cv::VideoCapture object and put
|
||||
them inside a *Mat* one. The video streams are sequential. You may get the frames one after another
|
||||
by the @ref cv::VideoCapture::read or the overloaded \>\> operator:
|
||||
@code{.cpp}
|
||||
Mat frameReference, frameUnderTest;
|
||||
captRefrnc >> frameReference;
|
||||
captUndTst.read(frameUnderTest);
|
||||
@endcode
|
||||
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{.cpp}
|
||||
if( frameReference.empty() || frameUnderTest.empty())
|
||||
{
|
||||
// exit the program
|
||||
}
|
||||
@endcode
|
||||
A read method is made of a frame grab and a decoding applied on that. You may call explicitly these
|
||||
two by using the @ref cv::VideoCapture::grab and then the @ref cv::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 @ref cv::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{.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;
|
||||
@endcode
|
||||
When you are working with videos you may often want to control these values yourself. To do this
|
||||
there is a @ref cv::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{.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
|
||||
@endcode
|
||||
For properties you can read and change look into the documentation of the @ref cv::VideoCapture::get and
|
||||
@ref cv::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 squared error*. Let there be two images: I1 and I2; with a two dimensional size i and
|
||||
j, composed of c number of channels.
|
||||
|
||||
\f[MSE = \frac{1}{c*i*j} \sum{(I_1-I_2)^2}\f]
|
||||
|
||||
Then the PSNR is expressed as:
|
||||
|
||||
\f[PSNR = 10 \cdot \log_{10} \left( \frac{MAX_I^2}{MSE} \right)\f]
|
||||
|
||||
Here the \f$MAX_I\f$ 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 function looks
|
||||
like:
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp get-psnr
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/videoio/video-input-psnr-ssim.py get-psnr
|
||||
@end_toggle
|
||||
|
||||
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.
|
||||
|
||||
@note
|
||||
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.
|
||||
|
||||
@add_toggle_cpp
|
||||
@snippet samples/cpp/tutorial_code/videoio/video-input-psnr-ssim/video-input-psnr-ssim.cpp get-mssim
|
||||
@end_toggle
|
||||
|
||||
@add_toggle_python
|
||||
@snippet samples/python/tutorial_code/videoio/video-input-psnr-ssim.py get-mssim
|
||||
@end_toggle
|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
You may observe a runtime instance of this on the [YouTube here](https://www.youtube.com/watch?v=iOcNljutOgg).
|
||||
|
||||
@youtube{iOcNljutOgg}
|
||||
Reference in New Issue
Block a user